diff --git a/UI_V2/.env b/UI_V2/.env index 05a5b65..75146d8 100644 --- a/UI_V2/.env +++ b/UI_V2/.env @@ -1,3 +1,7 @@ -SUPERUSER_EMAIL=macamete.robert@gmail.com -SUPERUSER_PASSWORD=Inteligent1_eu -SUPERUSER_ROLE=admin \ No newline at end of file +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 \ No newline at end of file diff --git a/UI_V2/dbActions/netopia.py b/UI_V2/dbActions/netopia.py new file mode 100644 index 0000000..1ee5398 --- /dev/null +++ b/UI_V2/dbActions/netopia.py @@ -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 \ No newline at end of file diff --git a/UI_V2/helpers/netopia.py b/UI_V2/helpers/netopia.py new file mode 100644 index 0000000..d5432f3 --- /dev/null +++ b/UI_V2/helpers/netopia.py @@ -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) diff --git a/UI_V2/main.py b/UI_V2/main.py index c490d26..c20a80a 100644 --- a/UI_V2/main.py +++ b/UI_V2/main.py @@ -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")) diff --git a/UI_V2/pages/auth/register.py b/UI_V2/pages/auth/register.py index 061a384..acf0762 100644 --- a/UI_V2/pages/auth/register.py +++ b/UI_V2/pages/auth/register.py @@ -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( diff --git a/UI_V2/pages/profile/profilepage.py b/UI_V2/pages/profile/profilepage.py index 11024ca..709ecc6 100644 --- a/UI_V2/pages/profile/profilepage.py +++ b/UI_V2/pages/profile/profilepage.py @@ -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), diff --git a/UI_V2/pages/shopping_cart/cart.py b/UI_V2/pages/shopping_cart/cart.py index 2ab2388..1184a37 100644 --- a/UI_V2/pages/shopping_cart/cart.py +++ b/UI_V2/pages/shopping_cart/cart.py @@ -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( diff --git a/UI_V2/pages/shopping_cart/confirm_data.py b/UI_V2/pages/shopping_cart/confirm_data.py new file mode 100644 index 0000000..0aecddc --- /dev/null +++ b/UI_V2/pages/shopping_cart/confirm_data.py @@ -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") + \ No newline at end of file diff --git a/UI_V2/pages/shopping_cart/payment_redirect.py b/UI_V2/pages/shopping_cart/payment_redirect.py new file mode 100644 index 0000000..539f79e --- /dev/null +++ b/UI_V2/pages/shopping_cart/payment_redirect.py @@ -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 + ) + ) \ No newline at end of file diff --git a/UI_V2/requirements.txt b/UI_V2/requirements.txt index 38eee15..c32016c 100644 --- a/UI_V2/requirements.txt +++ b/UI_V2/requirements.txt @@ -1 +1,5 @@ -flet==0.28.3 \ No newline at end of file +flet==0.28.3 +netopia-sdk==2.1.1 +python-dotenv==1.2.0 +Flask==3.1.2 +flask-cors==6.0.1 \ No newline at end of file