Bus-ticket-booking-application / backend /payment_database.py
Shoaib-33's picture
Upgrated the implementatio with testing
bf8b030
Raw
History Blame Contribute Delete
8.24 kB
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