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
SUPERUSER_PASSWORD=Inteligent1_eu
SUPERUSER_ROLE=admin
NETOPIA_API_KEY=yPYOnzSeU_qdMV5RaMEwvFCkBuQU6PUPijAWja8vY34JuG5SMTLtF7LkcXj8
NETOPIA_POS_SIGNATURE=351D-XRIS-7K5Y-WUZB-Y3IW
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.shopping_cart.cart import Cart
from pages.shopping_cart.peload_card import PreloadCard
from pages.shopping_cart.payment_redirect import PaymentRedirect
import os
os.environ["FLET_SECRET_KEY"] = os.urandom(12).hex()
from dotenv import load_dotenv
load_dotenv()
def main(page: ft.Page):
page.title = "Taina Gustului"
page.theme_mode = ft.ThemeMode.LIGHT
@@ -72,6 +76,12 @@ def main(page: ft.Page):
page.add(preload.build())
page.update()
return
if route == '/payment/redirect':
redirect = PaymentRedirect(page)
page.add(redirect.build())
page.update()
return
# 5) Fallback 404
page.add(ft.Text("404: Page not found"))

View File

@@ -92,12 +92,15 @@ class Register:
if self._check_repeat_password(password, repeat_password):
print("Password is valid!")
password_hash = self.users_manager.hash_password(password)
self.users_manager.register_user(email, password_hash)
self.error_message.value = "Inregistrarea a avut loc cu succes, va puteti autentifica!"
self.error_message.color = ft.Colors.GREEN
self.error_message.update()
time.sleep(3)
self.on_login_btn_click('')
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.color = ft.Colors.GREEN
self.error_message.update()
time.sleep(3)
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):
return ft.Column(

View File

@@ -9,15 +9,26 @@ class ProfilePage:
self.company_manager = Company()
self.user = self.page.session.get("user")
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.phone = ft.TextField(label="Telefon", value=self.user['phone'])
self.address = ft.TextField(
label="Adresa",
label="Strada si numar",
multiline=True,
min_lines=3,
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(
label="Denumire firma",
@@ -39,7 +50,7 @@ class ProfilePage:
value=self.company['address'] if self.company else '')
self.second_address_placeholder = ft.Column()
self.second_address = ft.TextField(
label="Adresa de livrare",
label="Adresa de livrare (str, nr, oras, judet)",
multiline=True,
min_lines=3,
max_lines=5,
@@ -75,7 +86,7 @@ class ProfilePage:
self.order_placeholder.controls.append(self.company_address)
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
if username is None or len(username)< 1:
found = True
@@ -83,6 +94,8 @@ class ProfilePage:
found = True
if address is None or len(address)< 1:
found = True
if city is None or len(city)< 1:
found = True
if found:
self.error_message.value = "Toate campurile sunt obligatori!"
self.error_message.color = ft.Colors.RED
@@ -115,14 +128,14 @@ class ProfilePage:
return found
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
address = self.address.value
address = self.city.value+"%"+self.address.value
if self.is_second_address:
if self.check_second_address_inserted(self.second_address.value):
return
address = self.address.value + '~' + self.second_address.value
if self.check_inserted_user_data(username, phone, address):
address = self.city.value+"%"+self.address.value + '~' + self.second_address.value
if self.check_inserted_user_data(username, phone, self.address.value, self.city.value):
return
self.user_manager.update_user_data(username, phone, address, self.user['id'])
@@ -150,12 +163,16 @@ class ProfilePage:
self.error_message.update()
self.user = self.user_manager.get(self.user['id'])
self.page.session.set('user',self.user)
self.user_name.value = self.user['name']
self.user_name.update()
self.first_name.value=self.user['name'].split('~')[0]
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.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.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_name.value=self.company['name'] if self.company else ''
@@ -191,10 +208,12 @@ class ProfilePage:
alignment=ft.MainAxisAlignment.END
),
ft.Icon(name=ft.Icons.ACCOUNT_CIRCLE, size=100),
self.user_name,
self.first_name,
self.last_name,
self.email,
self.phone,
self.address,
self.city,
ft.Divider(),
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),

View File

@@ -4,7 +4,9 @@ from dbActions.products import Products
from dbActions.company import Company
from dbActions.users import Users
from dbActions.fidelity_cards import FidelityCards
from dbActions.netopia import Netopia
from helpers.emails import send_gmail
from helpers.netopia import start_card_payment
import re
class Cart:
@@ -16,6 +18,7 @@ class Cart:
self.company_manager = Company()
self.user_manager = Users()
self.card_manager = FidelityCards()
self.netopia_manager = Netopia()
self.products = []
self.is_second_address = None
self.is_company = None
@@ -49,7 +52,7 @@ class Cart:
content=ft.Row(
[
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
@@ -83,10 +86,15 @@ class Cart:
)
self.user = self.page.session.get("user")
self.company = self.company_manager.get_company(self.user['id'])
self.user_name = ft.TextField(
label="Nume si Prenume",
value=self.user['name'] if "@default.com" not in self.user['email'] else None
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'] 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
)
self.address = ft.TextField(
label="Adresa",
label="Strada si numar",
multiline=True,
min_lines=3,
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(
label="Denumire firma",
value=self.company['name'] if self.company else ''
@@ -123,7 +136,7 @@ class Cart:
value=self.company['address'] if self.company else '')
self.second_address_placeholder = ft.Column()
self.second_address = ft.TextField(
label="Adresa de livrare",
label="Adresa de livrare (str, nr, oras, judet)",
multiline=True,
min_lines=3,
max_lines=5,
@@ -148,10 +161,12 @@ class Cart:
"Detaili de livrare",
weight=ft.FontWeight.BOLD
),
self.user_name,
self.first_name,
self.last_name,
self.email,
self.phone,
self.address,
self.city,
ft.Divider(),
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),
@@ -436,7 +451,7 @@ class Cart:
self.error_message.update()
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
if username is None or len(username)< 1:
print('Username not found')
@@ -447,6 +462,9 @@ class Cart:
if address is None or len(address)< 1:
print("Adress not found")
found = True
if city is None or len(city)< 1:
print("City not found")
found = True
if email is None or len(email)<1:
print("email not found")
found = True
@@ -469,17 +487,17 @@ class Cart:
return False
def create_update_user_details(self):
username = self.user_name.value
username = self.first_name.value + "~" + self.last_name.value
phone = self.phone.value
address = self.address.value
address = self.city.value+"%"+self.address.value
email = self.email.value
if self.is_second_address:
print("Second address has been selected (button click)")
if self.check_second_address_inserted(self.second_address.value):
return False
address = self.address.value + '~' + self.second_address.value
if self.check_inserted_user_data(username, phone, address, email):
address = self.city.value+"%"+self.address.value + '~' + self.second_address.value
if self.check_inserted_user_data(username, phone, self.address.value, email, self.city.value):
return False
if not self.check_email_is_valid(email):
@@ -521,7 +539,110 @@ class Cart:
'address': self.company_address.value
}
self.company_manager.add_company(company)
if self.payment.value == None:
self.error_message.value = "Va rugam selectati metoda de plata!"
self.error_message.color = ft.Colors.RED
self.error_message.update()
return False
return True
def create_history(self):
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.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 = self.create_history_list(buffer)
self.orders_manager_list.update()
def notify_admin_and_client(self):
users = self.user_manager.get_all()
admins = []
for user in users:
if user['role'] == 'admin':
admins.append(user)
for admin in admins:
send_gmail(
to_email=admin['email'],
subject="Comanda noua pe tainagustului.ro",
body=f'''
Ati primit o noua comanda de la {self.user['email']}.
Va rugam accesati wwww.tainagusutului.ro pentru detalii.
'''
)
send_gmail(
to_email=self.user['email'],
subject="Multumim pentru comanda!",
body=f'''
Buna ziua,
Comanda a fost primita si va fi livrata in cel mai scurt timp.
Va multumim,
Echipa tainagustului.ro
'''
)
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
@@ -531,7 +652,7 @@ class Cart:
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 = []
@@ -551,46 +672,18 @@ class Cart:
self.product_list.update()
#hiostory
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.all_orders[::-1]
self.orders_manager_list.controls.clear()
self.orders_manager_list.controls = self.create_history_list(self.all_orders)
self.orders_manager_list.update()
#notify admin
users = self.user_manager.get_all()
admins = []
for user in users:
if user['role'] == 'admin':
admins.append(user)
for admin in admins:
send_gmail(
to_email=admin['email'],
subject="Comanda noua pe tainagustului.ro",
body=f'''
Ati primit o noua comanda de la {self.user['email']}.
Va rugam accesati wwww.tainagusutului.ro pentru detalii.
'''
)
send_gmail(
to_email=self.user['email'],
subject="Multumim pentru comanda!",
body=f'''
Buna ziua,
Comanda a fost primita si va fi livrata in cel mai scurt timp.
Va multumim,
Echipa tainagustului.ro
'''
)
self.create_history()
self.error_message.value = "Comanda a fost trimisa cu success!"
self.error_message.color = ft.Colors.GREEN
self.error_message.update()
#online pay
self.online_pay(order_id)
#notify admin
self.notify_admin_and_client()
def build(self):
return ft.Container(
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