add netopia payment process
This commit is contained in:
10
UI_V2/.env
10
UI_V2/.env
@@ -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
|
||||||
33
UI_V2/dbActions/netopia.py
Normal file
33
UI_V2/dbActions/netopia.py
Normal 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
284
UI_V2/helpers/netopia.py
Normal 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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# High‑level 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 redirect‑based 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)
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
9
UI_V2/pages/shopping_cart/confirm_data.py
Normal file
9
UI_V2/pages/shopping_cart/confirm_data.py
Normal 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")
|
||||||
|
|
||||||
35
UI_V2/pages/shopping_cart/payment_redirect.py
Normal file
35
UI_V2/pages/shopping_cart/payment_redirect.py
Normal 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
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user