import sqlite3 from datetime import datetime from pathlib import Path from typing import Optional, List import uuid import os PAYMENT_DATABASE_FILE = Path(os.environ.get("BUSGO_PAYMENT_DATABASE_FILE", "demo_payments.db")) def get_payment_connection(): conn = sqlite3.connect(PAYMENT_DATABASE_FILE) conn.row_factory = sqlite3.Row return conn def init_payment_database(): conn = get_payment_connection() cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS wallet_accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, provider TEXT NOT NULL, phone TEXT NOT NULL, balance INTEGER NOT NULL, pin TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(provider, phone) ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS payment_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, transaction_id TEXT UNIQUE NOT NULL, booking_id TEXT NOT NULL, provider TEXT NOT NULL, phone TEXT NOT NULL, amount INTEGER NOT NULL, status TEXT NOT NULL, transaction_type TEXT NOT NULL DEFAULT 'payment', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS refund_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, refund_id TEXT UNIQUE NOT NULL, booking_id TEXT NOT NULL, payment_transaction_id TEXT, provider TEXT, phone TEXT, amount INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'requested', requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, processed_at TEXT ) """) seed_wallets = [ ("bkash", "01309183295", 20000, "1234"), ("bkash", "01711111111", 15000, "1234"), ("nagad", "01309183295", 20000, "1234"), ("nagad", "01811111111", 15000, "1234"), ] cursor.executemany(""" INSERT OR IGNORE INTO wallet_accounts (provider, phone, balance, pin) VALUES (?, ?, ?, ?) """, seed_wallets) conn.commit() conn.close() print("Demo payment database initialized") init_payment_database() def normalize_provider(provider: str) -> str: return str(provider or "").strip().lower() def get_wallet(provider: str, phone: str) -> Optional[dict]: conn = get_payment_connection() cursor = conn.cursor() cursor.execute( "SELECT provider, phone, balance FROM wallet_accounts WHERE provider = ? AND phone = ?", (normalize_provider(provider), phone) ) row = cursor.fetchone() conn.close() return dict(row) if row else None def list_demo_wallets() -> List[dict]: conn = get_payment_connection() cursor = conn.cursor() cursor.execute("SELECT provider, phone, balance FROM wallet_accounts ORDER BY provider, phone") rows = [dict(row) for row in cursor.fetchall()] conn.close() return rows def list_refund_requests() -> List[dict]: conn = get_payment_connection() cursor = conn.cursor() cursor.execute(""" SELECT refund_id, booking_id, amount, status, requested_at, processed_at FROM refund_requests ORDER BY requested_at DESC """) rows = [dict(row) for row in cursor.fetchall()] conn.close() return rows def verify_and_deduct(provider: str, phone: str, amount: int, pin: str, booking_id: str): provider = normalize_provider(provider) conn = get_payment_connection() cursor = conn.cursor() cursor.execute( "SELECT * FROM wallet_accounts WHERE provider = ? AND phone = ?", (provider, phone) ) account = cursor.fetchone() if not account: conn.close() return None, "No demo wallet found for this provider and phone number." if str(account["pin"]) != str(pin): conn.close() return None, "Invalid demo wallet PIN." if int(account["balance"]) < int(amount): conn.close() return None, "Insufficient demo wallet balance." transaction_id = "TXN-DEMO-" + uuid.uuid4().hex[:10].upper() cursor.execute( "UPDATE wallet_accounts SET balance = balance - ? WHERE id = ?", (int(amount), account["id"]) ) cursor.execute(""" INSERT INTO payment_transactions ( transaction_id, booking_id, provider, phone, amount, status, transaction_type ) VALUES (?, ?, ?, ?, ?, 'paid', 'payment') """, (transaction_id, booking_id, provider, phone, int(amount))) conn.commit() conn.close() return { "transaction_id": transaction_id, "provider": provider, "phone": phone, "amount": int(amount), "status": "paid", }, None def get_payment_for_booking(booking_id: str) -> Optional[dict]: conn = get_payment_connection() cursor = conn.cursor() cursor.execute(""" SELECT * FROM payment_transactions WHERE booking_id = ? AND transaction_type = 'payment' AND status = 'paid' ORDER BY created_at DESC LIMIT 1 """, (booking_id,)) row = cursor.fetchone() conn.close() return dict(row) if row else None def get_refund_request_for_booking(booking_id: str) -> Optional[dict]: conn = get_payment_connection() cursor = conn.cursor() cursor.execute(""" SELECT refund_id, booking_id, amount, status, requested_at, processed_at FROM refund_requests WHERE booking_id = ? ORDER BY requested_at DESC LIMIT 1 """, (booking_id,)) row = cursor.fetchone() conn.close() return dict(row) if row else None def create_refund_request(booking_id: str, amount: int, payment: Optional[dict]): existing = get_refund_request_for_booking(booking_id) if existing and existing.get("status") in {"requested", "approved"}: return existing refund_id = "RF-DEMO-" + uuid.uuid4().hex[:10].upper() conn = get_payment_connection() cursor = conn.cursor() cursor.execute(""" INSERT INTO refund_requests ( refund_id, booking_id, payment_transaction_id, provider, phone, amount, status ) VALUES (?, ?, ?, ?, ?, ?, 'requested') """, ( refund_id, booking_id, payment.get("transaction_id") if payment else None, payment.get("provider") if payment else None, payment.get("phone") if payment else None, int(amount), )) conn.commit() conn.close() return { "refund_id": refund_id, "booking_id": booking_id, "amount": int(amount), "status": "requested", } def approve_refund(refund_id: str): conn = get_payment_connection() cursor = conn.cursor() cursor.execute("SELECT * FROM refund_requests WHERE refund_id = ?", (refund_id,)) refund = cursor.fetchone() if not refund: conn.close() return None, "Refund request not found." if refund["status"] != "requested": conn.close() return None, "Refund request is not pending." if not refund["provider"] or not refund["phone"]: conn.close() return None, "Refund wallet details are missing." cursor.execute(""" UPDATE wallet_accounts SET balance = balance + ? WHERE provider = ? AND phone = ? """, (int(refund["amount"]), refund["provider"], refund["phone"])) transaction_id = "RF-TXN-" + uuid.uuid4().hex[:10].upper() cursor.execute(""" INSERT INTO payment_transactions ( transaction_id, booking_id, provider, phone, amount, status, transaction_type ) VALUES (?, ?, ?, ?, ?, 'refunded', 'refund') """, ( transaction_id, refund["booking_id"], refund["provider"], refund["phone"], int(refund["amount"]), )) cursor.execute(""" UPDATE refund_requests SET status = 'approved', processed_at = ? WHERE refund_id = ? """, (datetime.now().isoformat(), refund_id)) conn.commit() conn.close() return {"refund_id": refund_id, "transaction_id": transaction_id, "status": "approved"}, None