from flask import request, jsonify from models import db, User, Transaction, Alert, PurityMemory from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity from werkzeug.security import generate_password_hash, check_password_hash from ai_review import ai_review_transaction from datetime import datetime import random, string, uuid jwt = JWTManager() TIER_LIMITS = { 0: {"daily": 0, "max_balance": 0, "label": "Unverified"}, 1: {"daily": 50000, "max_balance": 300000, "label": "Tier 1 - NIN"}, 2: {"daily": 200000, "max_balance": 500000, "label": "Tier 2 - NIN+BVN"}, 3: {"daily": 5000000, "max_balance": float('inf'), "label": "Tier 3 - Full KYC"}, } def generate_account_number(): while True: number = "ACC" + "".join(random.choices(string.digits, k=10)) if not User.query.filter_by(account_number=number).first(): return number def create_alert(account_number, message): db.session.add(Alert(account_number=account_number, message=message)) def register(): data = request.get_json() if User.query.filter_by(email=data["email"]).first(): return jsonify({"message": "Email already exists"}), 400 user = User( name=data["name"], email=data["email"], password=generate_password_hash(data["password"]), account_number=generate_account_number(), phone=data.get("phone", ""), ) db.session.add(user) db.session.commit() create_alert(user.account_number, f"Welcome to dolor3v, {user.name}! Complete KYC to unlock transfers.") db.session.commit() return jsonify({"message": "User registered successfully", "account_number": user.account_number}) def login(): data = request.get_json() user = User.query.filter_by(email=data["email"]).first() if not user or not check_password_hash(user.password, data["password"]): return jsonify({"message": "Invalid credentials"}), 401 access_token = create_access_token(identity=user.account_number) return jsonify({ "access_token": access_token, "account_number": user.account_number, "name": user.name, "tier": user.tier, }) @jwt_required() def set_pin(): data = request.get_json() acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() pin = data.get("pin", "") if len(pin) != 4 or not pin.isdigit(): return jsonify({"message": "PIN must be 4 digits"}), 400 user.pin = generate_password_hash(pin) db.session.commit() return jsonify({"message": "PIN set successfully"}) @jwt_required() def verify_pin(): data = request.get_json() acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() if not user.pin: return jsonify({"valid": False, "message": "No PIN set"}), 400 valid = check_password_hash(user.pin, data.get("pin", "")) return jsonify({"valid": valid}) @jwt_required() def kyc_upgrade(): acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() data = request.get_json() tier = data.get("tier", 1) if tier == 1: if not data.get("nin"): return jsonify({"message": "NIN required for Tier 1"}), 400 user.nin = data["nin"] user.tier = 1 user.daily_limit = 50000 user.max_balance = 300000 create_alert(acc, "KYC Tier 1 approved! Daily limit: ₦50,000") elif tier == 2: if not data.get("bvn"): return jsonify({"message": "BVN required for Tier 2"}), 400 user.bvn = data["bvn"] user.tier = 2 user.daily_limit = 200000 user.max_balance = 500000 create_alert(acc, "KYC Tier 2 approved! Daily limit: ₦200,000") elif tier == 3: user.tier = 3 user.daily_limit = 5000000 user.max_balance = float('inf') create_alert(acc, "KYC Tier 3 approved! Daily limit: ₦5,000,000. Unlimited balance.") db.session.commit() return jsonify({"message": f"Tier {tier} upgrade successful", "tier": user.tier}) @jwt_required() def profile(): acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() limits = TIER_LIMITS.get(user.tier, TIER_LIMITS[0]) return jsonify({ "name": user.name, "email": user.email, "phone": user.phone, "account_number": user.account_number, "tier": user.tier, "tier_label": limits["label"], "daily_limit": limits["daily"], "max_balance": limits["max_balance"] if limits["max_balance"] != float('inf') else -1, "balance": user.balance, "pin_set": user.pin is not None, }) @jwt_required() def transfer(): data = request.get_json() sender_acc = get_jwt_identity() sender = User.query.filter_by(account_number=sender_acc).first() if sender.tier == 0: return jsonify({"message": "Complete KYC to make transfers"}), 403 if not sender.pin: return jsonify({"message": "Set a PIN before making transfers"}), 403 if not check_password_hash(sender.pin, data.get("pin", "")): return jsonify({"message": "Invalid PIN"}), 401 recipient = User.query.filter_by(account_number=data["recipient_account_number"]).first() if not sender or not recipient: return jsonify({"message": "Invalid account numbers"}), 404 if sender.account_number == recipient.account_number: return jsonify({"message": "Cannot transfer to same account"}), 400 amount = float(data["amount"]) limit = TIER_LIMITS[sender.tier]["daily"] if amount > limit: return jsonify({"message": f"Amount exceeds your daily limit of ₦{limit:,.0f}"}), 400 if sender.balance < amount: return jsonify({"message": "Insufficient funds"}), 400 decision, reason = ai_review_transaction(sender, recipient, amount, False) status = "pending" if decision == "hold" else "completed" if status == "completed": sender.balance -= amount recipient.balance += amount t = Transaction( sender_account_number=sender.account_number, recipient_account_number=recipient.account_number, amount=amount, status=status, is_external=False, reference=str(uuid.uuid4())[:8].upper(), ai_review=reason, ) db.session.add(t) create_alert(sender.account_number, f"Debit: ₦{amount:,.0f} to {recipient.name}. Ref: {t.reference}") create_alert(recipient.account_number, f"Credit: ₦{amount:,.0f} from {sender.name}. Ref: {t.reference}") db.session.commit() return jsonify({"message": f"Transfer {status}", "reference": t.reference, "ai_review": reason}) @jwt_required() def external_transfer(): data = request.get_json() sender_acc = get_jwt_identity() sender = User.query.filter_by(account_number=sender_acc).first() if sender.tier == 0: return jsonify({"message": "Complete KYC to make transfers"}), 403 if not sender.pin: return jsonify({"message": "Set a PIN before making transfers"}), 403 if not check_password_hash(sender.pin, data.get("pin", "")): return jsonify({"message": "Invalid PIN"}), 401 amount = float(data["amount"]) limit = TIER_LIMITS[sender.tier]["daily"] if amount > limit: return jsonify({"message": f"Exceeds daily limit of ₦{limit:,.0f}"}), 400 if sender.balance < amount: return jsonify({"message": "Insufficient funds"}), 400 bank_name = data.get("bank_name", "External Bank") recipient_name = data.get("recipient_name", "Unknown") decision, reason = ai_review_transaction(sender, recipient_name, amount, True) status = "pending" if decision == "hold" else "processing" if status == "processing": sender.balance -= amount ref = str(uuid.uuid4())[:8].upper() t = Transaction( sender_account_number=sender.account_number, recipient_account_number=f"EXT:{bank_name}:{data.get('recipient_account')}", amount=amount, status=status, is_external=True, reference=ref, ai_review=reason, ) db.session.add(t) create_alert(sender.account_number, f"External transfer: ₦{amount:,.0f} to {recipient_name} @ {bank_name}. Ref: {ref}") db.session.commit() return jsonify({"message": f"Transfer {status}", "reference": ref, "ai_review": reason}) @jwt_required() def buy_airtime(): data = request.get_json() acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() if not user.pin or not check_password_hash(user.pin, data.get("pin", "")): return jsonify({"message": "Invalid PIN"}), 401 amount = float(data["amount"]) phone = data.get("phone") network = data.get("network", "MTN") if user.balance < amount: return jsonify({"message": "Insufficient funds"}), 400 user.balance -= amount ref = "AIR" + str(uuid.uuid4())[:6].upper() t = Transaction( sender_account_number=user.account_number, recipient_account_number=f"AIRTIME:{network}:{phone}", amount=amount, status="completed", is_external=True, reference=ref, ai_review="Airtime purchase", transaction_type="airtime", ) db.session.add(t) create_alert(user.account_number, f"Airtime: ₦{amount:,.0f} {network} sent to {phone}. Ref: {ref}") db.session.commit() return jsonify({"message": f"₦{amount:,.0f} {network} airtime sent to {phone}", "reference": ref}) @jwt_required() def buy_data(): data = request.get_json() acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() if not user.pin or not check_password_hash(user.pin, data.get("pin", "")): return jsonify({"message": "Invalid PIN"}), 401 amount = float(data["amount"]) phone = data.get("phone") network = data.get("network", "MTN") plan = data.get("plan", "1GB") if user.balance < amount: return jsonify({"message": "Insufficient funds"}), 400 user.balance -= amount ref = "DATA" + str(uuid.uuid4())[:6].upper() t = Transaction( sender_account_number=user.account_number, recipient_account_number=f"DATA:{network}:{phone}", amount=amount, status="completed", is_external=True, reference=ref, ai_review="Data purchase", transaction_type="data", ) db.session.add(t) create_alert(user.account_number, f"Data: {plan} {network} sent to {phone}. Ref: {ref}") db.session.commit() return jsonify({"message": f"{plan} {network} data sent to {phone}", "reference": ref}) @jwt_required() def balance(): acc = get_jwt_identity() user = User.query.filter_by(account_number=acc).first() if not user: return jsonify({"message": "Invalid account number"}), 404 return jsonify({"balance": user.balance, "account_number": user.account_number, "name": user.name}) @jwt_required() def history(): acc = get_jwt_identity() txns = Transaction.query.filter( (Transaction.sender_account_number == acc) | (Transaction.recipient_account_number == acc) ).order_by(Transaction.timestamp.desc()).all() result = [] for t in txns: result.append({ "id": t.id, "type": "sent" if t.sender_account_number == acc else "received", "amount": t.amount, "sender": t.sender_account_number, "recipient": t.recipient_account_number, "status": t.status, "reference": t.reference, "is_external": t.is_external, "transaction_type": t.transaction_type, "ai_review": t.ai_review, "timestamp": t.timestamp.isoformat(), }) return jsonify({"transactions": result}) @jwt_required() def alerts(): acc = get_jwt_identity() unread = Alert.query.filter_by(account_number=acc, read=False).order_by(Alert.timestamp.desc()).all() result = [{"id": a.id, "message": a.message, "timestamp": a.timestamp.isoformat()} for a in unread] for a in unread: a.read = True db.session.commit() return jsonify({"alerts": result}) @jwt_required() def lookup(): acc_num = request.args.get("account_number") user = User.query.filter_by(account_number=acc_num).first() if not user: return jsonify({"message": "Account not found"}), 404 return jsonify({"name": user.name, "account_number": user.account_number, "bank": "dolor3v"}) @jwt_required() def purity_memory(): acc = get_jwt_identity() data = request.get_json() mem = PurityMemory(account_number=acc, memory=data.get("memory", "")) db.session.add(mem) db.session.commit() return jsonify({"message": "Memory saved"}) @jwt_required() def get_purity_memory(): acc = get_jwt_identity() mems = PurityMemory.query.filter_by(account_number=acc).order_by(PurityMemory.timestamp.desc()).limit(20).all() return jsonify({"memories": [m.memory for m in mems]})