add netopia payment process

This commit is contained in:
2025-11-06 10:48:57 +02:00
parent 5a40af5434
commit 6c713171ed
10 changed files with 566 additions and 72 deletions

View File

@@ -1,3 +1,7 @@
SUPERUSER_EMAIL=macamete.robert@gmail.com NETOPIA_API_KEY=yPYOnzSeU_qdMV5RaMEwvFCkBuQU6PUPijAWja8vY34JuG5SMTLtF7LkcXj8
SUPERUSER_PASSWORD=Inteligent1_eu NETOPIA_POS_SIGNATURE=351D-XRIS-7K5Y-WUZB-Y3IW
SUPERUSER_ROLE=admin NETOPIA_PUBLIC_KEY=-----BEGIN CERTIFICATE-----\nMIIC3zCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCUk8xEjAQBgNVBAgTCUJ1Y2hhcmVzdDESMBAGA1UEBxMJQnVjaGFyZXN0MRAwDgYDVQQKEwdORVRPUElBMSEwHwYDVQQLExhORVRPUElBIERldmVsb3BtZW50IHRlYW0xHDAaBgNVBAMTE25ldG9waWEtcGF5bWVudHMucm8wHhcNMjUxMTA0MTUwNTI2WhcNMzUxMTAyMTUwNTI2WjCBiDELMAkGA1UEBhMCUk8xEjAQBgNVBAgTCUJ1Y2hhcmVzdDESMBAGA1UEBxMJQnVjaGFyZXN0MRAwDgYDVQQKEwdORVRPUElBMSEwHwYDVQQLExhORVRPUElBIERldmVsb3BtZW50IHRlYW0xHDAaBgNVBAMTE25ldG9waWEtcGF5bWVudHMucm8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALwh0/NhEpZFuKvghZ9N75CXba05MWNCh422kcfFKbqP5YViCUBg3Mc5ZYd1e0Xi9Ui1QI2Z/jvvchrDZGQwjarApr3S9bowHEkZH81ZolOoPHBZbYpA28BIyHYRcaTXjLtiBGvjpwuzljmXeBoVLinIaE0IUpMen9MLWG2fGMddAgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ9yXChMGxzUzQflmkXT1oyIBoetTANBgkqhkiG9w0BAQsFAAOBgQCSxX3hnoP+vWmdecz/Oustubp4Q89fV/bGMBztQy8QbnMjKghUKAba/0CGs2MbN2bWCN78mOak8Oi6qNB7/Z6/yKWTJdkwYjXl/C6UQhZ7k11XKINGk3LD9wTiyKsWt1iioNjNN6h8Fqeh4So5ikEzZG7LnvbAz+ct9YBjyHiCLw==\n-----END CERTIFICATE-----
NETOPIA_REDIRECT_URL=https://www.tainagustului.ro/payment/redirect
NETOPIA_NOTIFY_URL=https://www.tainagustului.ro/api/payments/ipn
NETOPIA_CANCEL_URL=https://www.tainagustului.ro/cos
NETOPIA_IS_LIVE=false

View File

@@ -0,0 +1,33 @@
import sqlite3
from typing import Optional
class Netopia:
def __init__(self, db_path="instance/app_database.db"):
self.db_path = db_path
self._create_netopia_table()
def _create_netopia_table(self):
with sqlite3.connect(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS netopia (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id TEXT,
netopia_id TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
""")
conn.commit()
def add_netopia_card(self, order_id, netopia_id):
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO netopia (order_id, netopia_id)
VALUES (?, ? )
""", (order_id, netopia_id))
conn.commit()
return True
except sqlite3.IntegrityError:
return False

284
UI_V2/helpers/netopia.py Normal file
View File

@@ -0,0 +1,284 @@
"""
NETOPIA Payments helper for Flet/Flask apps
-------------------------------------------
This module wraps the official NETOPIA Python SDK (API v2) and exposes:
• an easy `start_card_payment(...)` helper you can call from your app
• IPN verification and order status helpers
• an optional Flask Blueprint with REST endpoints you can plug into your
existing backend (or the small Flask service that often accompanies Flet apps)
Requirements:
pip install netopia-sdk flask
Environment variables expected (see README at bottom of file too):
NETOPIA_API_KEY API key from admin.netopia-payments.com
NETOPIA_POS_SIGNATURE POS signature
NETOPIA_PUBLIC_KEY RSA public key (PEM string)
NETOPIA_PRIVATE_KEY RSA private key (PEM string, optional)
NETOPIA_REDIRECT_URL your site URL where the buyer returns after payment
NETOPIA_NOTIFY_URL public URL that receives IPN callbacks
NETOPIA_CANCEL_URL optional cancel URL
NETOPIA_POS_SIGNATURE_SET optional, CSV list of allowed POS signatures
NETOPIA_IS_LIVE 'true' to use production; otherwise sandbox
Usage from Flet (example):
from helpers.netopia import start_card_payment
url_payload = start_card_payment(
order_id="TG-100045",
amount=159.90,
currency="RON",
description="Comandă #TG-100045",
customer={
"email": "client@example.com",
"phone": "0712345678",
"first_name": "Ion",
"last_name": "Popescu",
"city": "București",
"country": "642", # 642 = Romania
"address": "Str. Exemplu 10",
"zip": "010101",
"county": "B-IF",
"language": "ro",
},
products=[
{"name": "Mix nuci 500g", "code": "MX500", "category": "fructe-uscate", "price": 79.95, "vat": 0},
{"name": "Caju 500g", "code": "CJ500", "category": "fructe-uscate", "price": 79.95, "vat": 0},
],
installments=1,
)
# url_payload typically contains the redirect URL open it in a webview or browser
Notes:
• You must expose your IPN endpoint publicly (HTTPS) and configure it in Netopia admin.
• Always trust order status updates coming from IPN, not only the browser redirect.
"""
from __future__ import annotations
import os
from datetime import datetime, timezone
from dataclasses import dataclass
from typing import List, Optional, Dict, Any
# NETOPIA SDK imports
from netopia_sdk.config import Config
from netopia_sdk.client import PaymentClient
from netopia_sdk.payment import PaymentService
from netopia_sdk.requests.models import (
StartPaymentRequest,
ConfigData,
PaymentData,
PaymentOptions,
Instrument,
OrderData,
BillingData,
ProductsData,
ShippingData,
)
# ---------------------------
# Configuration & wiring
# ---------------------------
@dataclass
class NetopiaSettings:
api_key: str
pos_signature: str
public_key_str: str
notify_url: str
redirect_url: str
is_live: bool
pos_signature_set: List[str]
cancel_url: str | None = None
private_key_str: str | None = None
@classmethod
def from_env(cls) -> "NetopiaSettings":
print("API_KEY? ", os.getenv("NETOPIA_API_KEY") is not None)
is_live_str = os.getenv("NETOPIA_IS_LIVE", "false").strip().lower()
is_live = is_live_str in ("1", "true", "yes", "on")
pos_sig = os.environ.get("NETOPIA_POS_SIGNATURE", "").strip()
pos_sig_set_env = os.getenv("NETOPIA_POS_SIGNATURE_SET", pos_sig)
pos_sig_set = [s.strip() for s in pos_sig_set_env.split(",") if s.strip()]
return cls(
api_key=os.environ.get("NETOPIA_API_KEY", "").strip(),
pos_signature=pos_sig,
public_key_str=os.environ.get("NETOPIA_PUBLIC_KEY", "").strip(),
private_key_str=os.environ.get("NETOPIA_PRIVATE_KEY", "").strip(),
notify_url=os.environ.get("NETOPIA_NOTIFY_URL", "").strip(),
redirect_url=os.environ.get("NETOPIA_REDIRECT_URL", "").strip(),
is_live=is_live,
pos_signature_set=pos_sig_set,
cancel_url=os.environ.get("NETOPIA_CANCEL_URL", None),
)
def _build_payment_service(settings: Optional[NetopiaSettings] = None) -> PaymentService:
"""Create a PaymentService from settings/env."""
settings = settings or NetopiaSettings.from_env()
if not settings.api_key:
raise RuntimeError("NETOPIA_API_KEY is missing")
if not settings.pos_signature:
raise RuntimeError("NETOPIA_POS_SIGNATURE is missing")
if not settings.public_key_str:
raise RuntimeError("NETOPIA_PUBLIC_KEY is missing (PEM)")
if not settings.notify_url:
raise RuntimeError("NETOPIA_NOTIFY_URL is missing")
if not settings.redirect_url:
raise RuntimeError("NETOPIA_REDIRECT_URL is missing")
config = Config(
api_key=settings.api_key,
pos_signature=settings.pos_signature,
is_live=settings.is_live,
notify_url=settings.notify_url,
redirect_url=settings.redirect_url,
public_key_str=settings.public_key_str,
#private_key_str=settings.private_key_str,
pos_signature_set=settings.pos_signature_set,
)
client = PaymentClient(config)
return PaymentService(client)
# ---------------------------
# Helpers: BillingData from minimal fields (SDK-supported)
# ---------------------------
def _derive_billing_from_customer(customer: Dict[str, str]) -> BillingData:
"""Construct BillingData using only fields supported by the SDK example.
Accepts a single free-form address but does not send it (SDK BillingData
in v2 typically supports: email, phone, firstName, lastName, city, country).
We try to infer city from the first token before a comma if city is missing.
"""
city = (customer.get("city") or "").strip()
if not city:
addr = (customer.get("address") or "").strip()
if "," in addr:
candidate = addr.split(",", 1)[0].strip()
if 1 <= len(candidate) <= 64:
city = candidate
return BillingData(
email=customer.get("email", ""),
phone=customer.get("phone", ""),
firstName=customer.get("first_name", ""),
lastName=customer.get("last_name", ""),
city=city,
country=int(customer.get("country", "642") or 642),
countryName=customer.get("countryName", "Romania"),
state=customer.get("state", customer.get("county", "")),
postalCode=customer.get("zip", ""),
details=(customer.get("address") or "").strip(),
)
def _derive_shipping_from_customer(customer: Dict[str, str]) -> ShippingData:
city = (customer.get("ship_city") or customer.get("city") or "").strip()
if not city:
addr = (customer.get("ship_address") or customer.get("address") or "").strip()
if "," in addr:
cand = addr.split(",", 1)[0].strip()
if 1 <= len(cand) <= 64:
city = cand
return ShippingData(
email=customer.get("ship_email", customer.get("email", "")),
phone=customer.get("ship_phone", customer.get("phone", "")),
firstName=customer.get("ship_first_name", customer.get("first_name", "")),
lastName=customer.get("ship_last_name", customer.get("last_name", "")),
city=city,
country=int(customer.get("ship_country", customer.get("country", "642")) or 642),
countryName=customer.get("ship_countryName", customer.get("countryName", "Romania")),
state=customer.get("ship_state", customer.get("state", customer.get("county", ""))),
postalCode=customer.get("ship_zip", customer.get("zip", "")),
details=(customer.get("ship_address") or customer.get("address") or "").strip(),
)
# ---------------------------
# Highlevel helpers (call these from your app)
# ---------------------------
def start_card_payment(
*,
order_id: str,
amount: float,
currency: str,
description: str,
customer: Dict[str, str],
products: List[Dict[str, Any]],
installments: int = 1,
settings: Optional[NetopiaSettings] = None,
) -> Dict[str, Any]:
"""Create a redirectbased card payment and return the SDK response.
The response typically includes the URL you must redirect the buyer to.
You should persist the order locally before calling this.
"""
svc = _build_payment_service(settings)
billing = _derive_billing_from_customer(customer)
shipping = _derive_shipping_from_customer(customer)
prods: List[ProductsData] = []
for p in products:
prods.append(
ProductsData(
name=str(p["name"]),
code=str(p.get("code", p["name"]))[:32],
category=str(p.get("category", "")),
price=float(p.get("price", 0.0)),
vat=int(p.get("vat", 0)),
)
)
cfg = NetopiaSettings.from_env() if settings is None else settings
now_iso = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
req = StartPaymentRequest(
config=ConfigData(
emailTemplate=customer.get("email_template", "default"),
emailSubject=customer.get("email_subject", "Order Confirmation"),
cancelUrl=cfg.cancel_url or cfg.redirect_url,
notifyUrl=cfg.notify_url,
redirectUrl=cfg.redirect_url,
language=customer.get("language", "ro"),
),
payment=PaymentData(
options=PaymentOptions(installments=int(installments), bonus=0),
instrument=None,
data={},
),
order=OrderData(
ntpID=None,
posSignature=None,
dateTime=now_iso,
orderID=str(order_id),
amount=float(amount),
currency=str(currency),
description=str(description),
billing=billing,
shipping=shipping,
products=prods,
installments={"selected": int(installments) if installments else 0, "available": []},
data={},
),
)
return svc.start_payment(req)
def verify_ipn(raw_body: bytes, settings: Optional[NetopiaSettings] = None) -> Dict[str, Any]:
"""Verify an IPN payload coming from NETOPIA. Returns the decoded data.
Raise an exception if verification fails.
"""
svc = _build_payment_service(settings)
return svc.verify_ipn(raw_body)
def get_status(*, ntp_id: Optional[str] = None, order_id: Optional[str] = None, settings: Optional[NetopiaSettings] = None) -> Dict[str, Any]:
"""Query order status by ntpID and/or orderID."""
svc = _build_payment_service(settings)
return svc.get_status(ntpID=ntp_id, orderID=order_id)

View File

@@ -7,10 +7,14 @@ from pages.products.product import ProductPage
from pages.profile.profilepage import ProfilePage from pages.profile.profilepage import ProfilePage
from pages.shopping_cart.cart import Cart from pages.shopping_cart.cart import Cart
from pages.shopping_cart.peload_card import PreloadCard from pages.shopping_cart.peload_card import PreloadCard
from pages.shopping_cart.payment_redirect import PaymentRedirect
import os import os
os.environ["FLET_SECRET_KEY"] = os.urandom(12).hex() os.environ["FLET_SECRET_KEY"] = os.urandom(12).hex()
from dotenv import load_dotenv
load_dotenv()
def main(page: ft.Page): def main(page: ft.Page):
page.title = "Taina Gustului" page.title = "Taina Gustului"
page.theme_mode = ft.ThemeMode.LIGHT page.theme_mode = ft.ThemeMode.LIGHT
@@ -73,6 +77,12 @@ def main(page: ft.Page):
page.update() page.update()
return return
if route == '/payment/redirect':
redirect = PaymentRedirect(page)
page.add(redirect.build())
page.update()
return
# 5) Fallback 404 # 5) Fallback 404
page.add(ft.Text("404: Page not found")) page.add(ft.Text("404: Page not found"))
page.update() page.update()

View File

@@ -92,12 +92,15 @@ class Register:
if self._check_repeat_password(password, repeat_password): if self._check_repeat_password(password, repeat_password):
print("Password is valid!") print("Password is valid!")
password_hash = self.users_manager.hash_password(password) password_hash = self.users_manager.hash_password(password)
self.users_manager.register_user(email, password_hash) if self.users_manager.register_user(email, password_hash):
self.error_message.value = "Inregistrarea a avut loc cu succes, va puteti autentifica!" self.error_message.value = "Inregistrarea a avut loc cu succes, va puteti autentifica!"
self.error_message.color = ft.Colors.GREEN self.error_message.color = ft.Colors.GREEN
self.error_message.update() self.error_message.update()
time.sleep(3) time.sleep(3)
self.on_login_btn_click('') self.on_login_btn_click('')
else:
self.error_message.value = 'Exita deja un cont cu acet email. Daca ati fost invitat sau ati uitat prola accesati rubrica "Ai uitat parola?"'
self.error_message.update()
def build(self): def build(self):
return ft.Column( return ft.Column(

View File

@@ -9,15 +9,26 @@ class ProfilePage:
self.company_manager = Company() self.company_manager = Company()
self.user = self.page.session.get("user") self.user = self.page.session.get("user")
self.company = self.company_manager.get_company(self.user['id']) self.company = self.company_manager.get_company(self.user['id'])
self.user_name = ft.TextField(label="Nume si Prenume", value=self.user['name']) self.first_name = ft.TextField(
label="Prenume",
value=self.user['name'].split('~')[0] if "@default.com" not in self.user['email'] else None
)
self.last_name = ft.TextField(
label="Nume",
value=self.user['name'].split('~')[1] if "@default.com" not in self.user['email'] else None
)
self.email = ft.TextField(label="E-mail", value=self.user['email'], read_only=True) self.email = ft.TextField(label="E-mail", value=self.user['email'], read_only=True)
self.phone = ft.TextField(label="Telefon", value=self.user['phone']) self.phone = ft.TextField(label="Telefon", value=self.user['phone'])
self.address = ft.TextField( self.address = ft.TextField(
label="Adresa", label="Strada si numar",
multiline=True, multiline=True,
min_lines=3, min_lines=3,
max_lines=5, max_lines=5,
value = self.user['address'].split("~")[0] if self.user['address'] else '' value = self.user['address'].split("~")[0].split("%")[1] if self.user['address'] else ''
)
self.city = ft.TextField(
label="Oras",
value = self.user['address'].split("~")[0].split('%')[0] if self.user['address'] else ''
) )
self.company_name = ft.TextField( self.company_name = ft.TextField(
label="Denumire firma", label="Denumire firma",
@@ -39,7 +50,7 @@ class ProfilePage:
value=self.company['address'] if self.company else '') value=self.company['address'] if self.company else '')
self.second_address_placeholder = ft.Column() self.second_address_placeholder = ft.Column()
self.second_address = ft.TextField( self.second_address = ft.TextField(
label="Adresa de livrare", label="Adresa de livrare (str, nr, oras, judet)",
multiline=True, multiline=True,
min_lines=3, min_lines=3,
max_lines=5, max_lines=5,
@@ -75,7 +86,7 @@ class ProfilePage:
self.order_placeholder.controls.append(self.company_address) self.order_placeholder.controls.append(self.company_address)
self.order_placeholder.update() self.order_placeholder.update()
def check_inserted_user_data(self, username, phone, address): def check_inserted_user_data(self, username, phone, address, city):
found = False found = False
if username is None or len(username)< 1: if username is None or len(username)< 1:
found = True found = True
@@ -83,6 +94,8 @@ class ProfilePage:
found = True found = True
if address is None or len(address)< 1: if address is None or len(address)< 1:
found = True found = True
if city is None or len(city)< 1:
found = True
if found: if found:
self.error_message.value = "Toate campurile sunt obligatori!" self.error_message.value = "Toate campurile sunt obligatori!"
self.error_message.color = ft.Colors.RED self.error_message.color = ft.Colors.RED
@@ -115,14 +128,14 @@ class ProfilePage:
return found return found
def on_save_btn_click(self, e): def on_save_btn_click(self, e):
username = self.user_name.value username = self.first_name.value + "~" + self.last_name.value
phone = self.phone.value phone = self.phone.value
address = self.address.value address = self.city.value+"%"+self.address.value
if self.is_second_address: if self.is_second_address:
if self.check_second_address_inserted(self.second_address.value): if self.check_second_address_inserted(self.second_address.value):
return return
address = self.address.value + '~' + self.second_address.value address = self.city.value+"%"+self.address.value + '~' + self.second_address.value
if self.check_inserted_user_data(username, phone, address): if self.check_inserted_user_data(username, phone, self.address.value, self.city.value):
return return
self.user_manager.update_user_data(username, phone, address, self.user['id']) self.user_manager.update_user_data(username, phone, address, self.user['id'])
@@ -150,12 +163,16 @@ class ProfilePage:
self.error_message.update() self.error_message.update()
self.user = self.user_manager.get(self.user['id']) self.user = self.user_manager.get(self.user['id'])
self.page.session.set('user',self.user) self.page.session.set('user',self.user)
self.user_name.value = self.user['name'] self.first_name.value=self.user['name'].split('~')[0]
self.user_name.update() self.first_name.update()
self.last_name.value=self.user['name'].split('~')[1]
self.last_name.update()
self.phone.value = self.user['phone'] self.phone.value = self.user['phone']
self.phone.update() self.phone.update()
self.address.value = self.user['address'].split("~")[0] if self.user['address'] else '' self.address.value = self.user['address'].split("~")[0].split("%")[1] if self.user['address'] else ''
self.address.update() self.address.update()
self.city.value = self.user['address'].split("~")[0].split("%")[0] if self.user['address'] else ''
self.city.update()
self.company = self.company_manager.get_company(self.user['id']) self.company = self.company_manager.get_company(self.user['id'])
self.company_name.value=self.company['name'] if self.company else '' self.company_name.value=self.company['name'] if self.company else ''
@@ -191,10 +208,12 @@ class ProfilePage:
alignment=ft.MainAxisAlignment.END alignment=ft.MainAxisAlignment.END
), ),
ft.Icon(name=ft.Icons.ACCOUNT_CIRCLE, size=100), ft.Icon(name=ft.Icons.ACCOUNT_CIRCLE, size=100),
self.user_name, self.first_name,
self.last_name,
self.email, self.email,
self.phone, self.phone,
self.address, self.address,
self.city,
ft.Divider(), ft.Divider(),
ft.Text("Adresa de livrare difera de adresa de domiciliu?", text_align=ft.TextAlign.CENTER), ft.Text("Adresa de livrare difera de adresa de domiciliu?", text_align=ft.TextAlign.CENTER),
ft.Button("Adauga adresa livrare", width=400, on_click=self.on_second_address_btn_click), ft.Button("Adauga adresa livrare", width=400, on_click=self.on_second_address_btn_click),

View File

@@ -4,7 +4,9 @@ from dbActions.products import Products
from dbActions.company import Company from dbActions.company import Company
from dbActions.users import Users from dbActions.users import Users
from dbActions.fidelity_cards import FidelityCards from dbActions.fidelity_cards import FidelityCards
from dbActions.netopia import Netopia
from helpers.emails import send_gmail from helpers.emails import send_gmail
from helpers.netopia import start_card_payment
import re import re
class Cart: class Cart:
@@ -16,6 +18,7 @@ class Cart:
self.company_manager = Company() self.company_manager = Company()
self.user_manager = Users() self.user_manager = Users()
self.card_manager = FidelityCards() self.card_manager = FidelityCards()
self.netopia_manager = Netopia()
self.products = [] self.products = []
self.is_second_address = None self.is_second_address = None
self.is_company = None self.is_company = None
@@ -49,7 +52,7 @@ class Cart:
content=ft.Row( content=ft.Row(
[ [
ft.Radio(value="ramburs", label="Ramburs la curier"), ft.Radio(value="ramburs", label="Ramburs la curier"),
ft.Radio(value="plata_online_cu_cardul", label="Plata online cu cardul", disabled=True), ft.Radio(value="plata_online_cu_cardul", label="Plata online cu cardul"),
] ]
), ),
on_change=self.on_payment_value_change on_change=self.on_payment_value_change
@@ -83,10 +86,15 @@ class Cart:
) )
self.user = self.page.session.get("user") self.user = self.page.session.get("user")
self.company = self.company_manager.get_company(self.user['id']) self.company = self.company_manager.get_company(self.user['id'])
self.user_name = ft.TextField( self.first_name = ft.TextField(
label="Nume si Prenume", label="Prenume",
value=self.user['name'] if "@default.com" not in self.user['email'] else None value=self.user['name'].split('~')[0] if "@default.com" not in self.user['email'] else None
) )
self.last_name = ft.TextField(
label="Nume",
value=self.user['name'].split('~')[1] if "@default.com" not in self.user['email'] else None
)
self.email = ft.TextField( self.email = ft.TextField(
label="E-mail", label="E-mail",
value=self.user['email'] if "@default.com" not in self.user['email'] else None, value=self.user['email'] if "@default.com" not in self.user['email'] else None,
@@ -97,12 +105,17 @@ class Cart:
value=self.user['phone'] if "@default.com" not in self.user['email'] else None value=self.user['phone'] if "@default.com" not in self.user['email'] else None
) )
self.address = ft.TextField( self.address = ft.TextField(
label="Adresa", label="Strada si numar",
multiline=True, multiline=True,
min_lines=3, min_lines=3,
max_lines=5, max_lines=5,
value = self.user['address'].split("~")[0] if self.user['address'] else '' value = self.user['address'].split("~")[0].split("%")[1] if self.user['address'] else ''
) )
self.city = ft.TextField(
label="Oras",
value = self.user['address'].split("~")[0].split('%')[0] if self.user['address'] else ''
)
self.company_name = ft.TextField( self.company_name = ft.TextField(
label="Denumire firma", label="Denumire firma",
value=self.company['name'] if self.company else '' value=self.company['name'] if self.company else ''
@@ -123,7 +136,7 @@ class Cart:
value=self.company['address'] if self.company else '') value=self.company['address'] if self.company else '')
self.second_address_placeholder = ft.Column() self.second_address_placeholder = ft.Column()
self.second_address = ft.TextField( self.second_address = ft.TextField(
label="Adresa de livrare", label="Adresa de livrare (str, nr, oras, judet)",
multiline=True, multiline=True,
min_lines=3, min_lines=3,
max_lines=5, max_lines=5,
@@ -148,10 +161,12 @@ class Cart:
"Detaili de livrare", "Detaili de livrare",
weight=ft.FontWeight.BOLD weight=ft.FontWeight.BOLD
), ),
self.user_name, self.first_name,
self.last_name,
self.email, self.email,
self.phone, self.phone,
self.address, self.address,
self.city,
ft.Divider(), ft.Divider(),
ft.Text("Adresa de livrare difera de adresa de domiciliu?", text_align=ft.TextAlign.CENTER), ft.Text("Adresa de livrare difera de adresa de domiciliu?", text_align=ft.TextAlign.CENTER),
ft.Button("Adauga adresa livrare", width=500, on_click=self.on_second_address_btn_click), ft.Button("Adauga adresa livrare", width=500, on_click=self.on_second_address_btn_click),
@@ -436,7 +451,7 @@ class Cart:
self.error_message.update() self.error_message.update()
return found return found
def check_inserted_user_data(self, username, phone, address, email): def check_inserted_user_data(self, username, phone, address, email, city):
found = False found = False
if username is None or len(username)< 1: if username is None or len(username)< 1:
print('Username not found') print('Username not found')
@@ -447,6 +462,9 @@ class Cart:
if address is None or len(address)< 1: if address is None or len(address)< 1:
print("Adress not found") print("Adress not found")
found = True found = True
if city is None or len(city)< 1:
print("City not found")
found = True
if email is None or len(email)<1: if email is None or len(email)<1:
print("email not found") print("email not found")
found = True found = True
@@ -469,17 +487,17 @@ class Cart:
return False return False
def create_update_user_details(self): def create_update_user_details(self):
username = self.user_name.value username = self.first_name.value + "~" + self.last_name.value
phone = self.phone.value phone = self.phone.value
address = self.address.value address = self.city.value+"%"+self.address.value
email = self.email.value email = self.email.value
if self.is_second_address: if self.is_second_address:
print("Second address has been selected (button click)") print("Second address has been selected (button click)")
if self.check_second_address_inserted(self.second_address.value): if self.check_second_address_inserted(self.second_address.value):
return False return False
address = self.address.value + '~' + self.second_address.value address = self.city.value+"%"+self.address.value + '~' + self.second_address.value
if self.check_inserted_user_data(username, phone, address, email): if self.check_inserted_user_data(username, phone, self.address.value, email, self.city.value):
return False return False
if not self.check_email_is_valid(email): if not self.check_email_is_valid(email):
@@ -521,44 +539,26 @@ class Cart:
'address': self.company_address.value 'address': self.company_address.value
} }
self.company_manager.add_company(company) self.company_manager.add_company(company)
return True if self.payment.value == None:
self.error_message.value = "Va rugam selectati metoda de plata!"
def on_confim_btn_click(self, e):
self.error_message.color = ft.Colors.RED self.error_message.color = ft.Colors.RED
self.error_message.update() self.error_message.update()
#create / update user details: return False
print("Confirm button Selected ") return True
self.page.close(self.confirm_dialog)
if self.create_update_user_details():
print('User details updated')
self.orders_manager.update_order_status("new", self.on_hold_orders['id']) def create_history(self):
print('Order status is set to new')
self.products = []
self.on_hold_orders = self.orders_manager.get_on_hold_order(self.user['id'])
if self.on_hold_orders:
self.order_products = self.orders_manager.get_order_products(self.on_hold_orders['id'])
for product in self.order_products:
self.products.append(
{
'product':self.productsDB.get(product['prdouct_id']),
'quantity':product['quantity']
}
)
self.product_list.controls.clear()
self.product_list.controls = self.create_list(self.products, self.on_delete_product_click)
self.product_list.update()
#hiostory
if '@default.com' not in self.user['email']: if '@default.com' not in self.user['email']:
self.all_orders = self.orders_manager.get_orders_for_user(self.page.session.get('user')['id']) self.all_orders = self.orders_manager.get_orders_for_user(self.page.session.get('user')['id'])
self.all_orders = self.all_orders[::-1] self.all_orders = self.all_orders[::-1]
buffer = []
for order in self.all_orders:
if order['status'] != 'on_hold':
buffer.append(order)
self.orders_manager_list.controls.clear() self.orders_manager_list.controls.clear()
self.orders_manager_list.controls = self.create_history_list(self.all_orders) self.orders_manager_list.controls = self.create_history_list(buffer)
self.orders_manager_list.update() self.orders_manager_list.update()
#notify admin def notify_admin_and_client(self):
users = self.user_manager.get_all() users = self.user_manager.get_all()
admins = [] admins = []
for user in users: for user in users:
@@ -587,10 +587,103 @@ class Cart:
''' '''
) )
def save_order_ntp_id(self, order_id, netopia_id):
self.netopia_manager.add_netopia_card(order_id, netopia_id)
def online_pay(self, order_id):
if self.payment.value == "plata_online_cu_cardul":
print("The user seelected card payment")
order_products = []
products_ids = self.orders_manager.get_order_products(order_id)
for prod in products_ids:
p = self.productsDB.get(prod['prdouct_id'])
order_products.append(
{
'name':p['name'],
'code':p['id'],
'category':p['category_id'],
'price':p['price'],
'vat':0,
}
)
print(order_products)
response = start_card_payment(
order_id=order_id,
amount=self.pret_total.value.split(": ")[1],
currency='RON',
description="Comanda noua",
customer={
'email':self.email.value,
'phone':self.phone.value,
'firstName':self.first_name.value,
'lastName':self.last_name.value,
'city':self.city.value,
'country': 642,
'address':self.address.value,
'county':'',
'zipCode':''
},
products=order_products
)
print(type(response))
# Extract URL & ntpID from SDK response
payment_url = response.payment['paymentURL']
ntp_id = response.payment['ntpID']
# 1) Persist mapping (VERY IMPORTANT for IPN/status reconciliation)
if ntp_id:
self.save_order_ntp_id(order_id, ntp_id) # implement in your DB layer
# 2) Open hosted payment page
if payment_url:
self.page.launch_url(payment_url, web_window_name="_blank")
self.page.go("/payment/redirect") # your UX page
else:
self.page.snack_bar = ft.SnackBar(ft.Text("Nu am primit URL-ul de plată."))
self.page.snack_bar.open = True
self.page.update()
def on_confim_btn_click(self, e):
self.error_message.color = ft.Colors.RED
self.error_message.update()
#create / update user details:
print("Confirm button Selected ")
self.page.close(self.confirm_dialog)
if self.create_update_user_details():
print('User details updated')
order_id = self.on_hold_orders['id']
self.orders_manager.update_order_status("new", self.on_hold_orders['id'])
print('Order status is set to new')
self.products = []
self.on_hold_orders = self.orders_manager.get_on_hold_order(self.user['id'])
if self.on_hold_orders:
self.order_products = self.orders_manager.get_order_products(self.on_hold_orders['id'])
for product in self.order_products:
self.products.append(
{
'product':self.productsDB.get(product['prdouct_id']),
'quantity':product['quantity']
}
)
self.product_list.controls.clear()
self.product_list.controls = self.create_list(self.products, self.on_delete_product_click)
self.product_list.update()
#hiostory
self.create_history()
self.error_message.value = "Comanda a fost trimisa cu success!" self.error_message.value = "Comanda a fost trimisa cu success!"
self.error_message.color = ft.Colors.GREEN self.error_message.color = ft.Colors.GREEN
self.error_message.update() self.error_message.update()
#online pay
self.online_pay(order_id)
#notify admin
self.notify_admin_and_client()
def build(self): def build(self):
return ft.Container( return ft.Container(
content=ft.Column( content=ft.Column(

View File

@@ -0,0 +1,9 @@
import flet as ft
class ConfirmData:
def __init__(self):
self.first_name = ft.TextField(label="Prenume")
self.last_name = ft.TextField(label="Nume")
self.city = ft.TextField(label="Oras")
self.address = ft.TextField(label="Strada si numar")

View File

@@ -0,0 +1,35 @@
import flet as ft
class PaymentRedirect:
def __init__(self, page: ft.Page):
self.page = page
def build(self):
return ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Image(src='images/tainagustului.png', width=200)
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Text(
"Sunteți redirecționat către pagina de plată NETOPIA...",
size=20,
weight=ft.FontWeight.BOLD,
text_align=ft.TextAlign.CENTER,
),
ft.ProgressRing(width=40, height=40, color=ft.Colors.GREEN),
ft.Text(
"Vă rugăm să nu închideți această fereastră până la finalizarea plății.",
size=16,
color=ft.Colors.GREY_700,
text_align=ft.TextAlign.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=20
)
)

View File

@@ -1 +1,5 @@
flet==0.28.3 flet==0.28.3
netopia-sdk==2.1.1
python-dotenv==1.2.0
Flask==3.1.2
flask-cors==6.0.1