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
|
||||
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
|
||||
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.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"))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
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