from typing import Optional, List import random import string from datetime import datetime from sqlalchemy.orm import Session from sqlalchemy import or_ from app.models.transaction import Transaction, TransactionType, TransactionStatus from app.models.account import Account from app.crud.account import update_account_balance from app.schemas.transaction import ( TransferCreate, DepositCreate, WithdrawalCreate, ReceiveMoneyCreate, SendMoneyCreate, TransactionUpdate ) def generate_transaction_reference() -> str: """ Generate a random transaction reference """ # Generate a 12-character transaction reference chars = string.ascii_uppercase + string.digits return 'TX-' + ''.join(random.choices(chars, k=12)) def get_transaction_by_id(db: Session, id: int) -> Optional[Transaction]: """ Get a transaction by ID """ return db.query(Transaction).filter(Transaction.id == id).first() def get_transaction_by_reference(db: Session, reference: str) -> Optional[Transaction]: """ Get a transaction by reference """ return db.query(Transaction).filter(Transaction.transaction_reference == reference).first() def get_user_transactions(db: Session, user_id: int, skip: int = 0, limit: int = 100) -> List[Transaction]: """ Get all transactions for a user (sent or received) """ return db.query(Transaction).filter( or_( Transaction.sender_id == user_id, Transaction.receiver_id == user_id ) ).offset(skip).limit(limit).all() def get_account_transactions(db: Session, account_id: int, skip: int = 0, limit: int = 100) -> List[Transaction]: """ Get all transactions for an account (sent or received) """ return db.query(Transaction).filter( or_( Transaction.sender_account_id == account_id, Transaction.receiver_account_id == account_id ) ).offset(skip).limit(limit).all() def create_deposit(db: Session, deposit_in: DepositCreate, user_id: int) -> Transaction: """ Create a deposit transaction """ # Generate a unique transaction reference transaction_reference = generate_transaction_reference() while get_transaction_by_reference(db, transaction_reference): transaction_reference = generate_transaction_reference() # Create the transaction db_transaction = Transaction( transaction_reference=transaction_reference, receiver_id=user_id, receiver_account_id=deposit_in.account_id, amount=deposit_in.amount, currency=deposit_in.currency, transaction_type=TransactionType.DEPOSIT, status=TransactionStatus.PENDING, description=deposit_in.description, ) db.add(db_transaction) db.commit() db.refresh(db_transaction) return db_transaction def create_withdrawal(db: Session, withdrawal_in: WithdrawalCreate, user_id: int) -> Transaction: """ Create a withdrawal transaction """ # Generate a unique transaction reference transaction_reference = generate_transaction_reference() while get_transaction_by_reference(db, transaction_reference): transaction_reference = generate_transaction_reference() # Create the transaction db_transaction = Transaction( transaction_reference=transaction_reference, sender_id=user_id, sender_account_id=withdrawal_in.account_id, amount=withdrawal_in.amount, currency=withdrawal_in.currency, transaction_type=TransactionType.WITHDRAWAL, status=TransactionStatus.PENDING, description=withdrawal_in.description, ) db.add(db_transaction) db.commit() db.refresh(db_transaction) return db_transaction def create_transfer(db: Session, transfer_in: TransferCreate, user_id: int) -> Transaction: """ Create a transfer transaction """ # Generate a unique transaction reference transaction_reference = generate_transaction_reference() while get_transaction_by_reference(db, transaction_reference): transaction_reference = generate_transaction_reference() # Create the transaction db_transaction = Transaction( transaction_reference=transaction_reference, sender_id=user_id, sender_account_id=transfer_in.sender_account_id, receiver_account_id=transfer_in.receiver_account_id, amount=transfer_in.amount, currency=transfer_in.currency, transaction_type=TransactionType.TRANSFER, status=TransactionStatus.PENDING, description=transfer_in.description, ) # Get the receiver account to set the receiver_id receiver_account = db.query(Account).filter(Account.id == transfer_in.receiver_account_id).first() if receiver_account: db_transaction.receiver_id = receiver_account.owner_id db.add(db_transaction) db.commit() db.refresh(db_transaction) return db_transaction def create_receive_money(db: Session, receive_in: ReceiveMoneyCreate, user_id: int) -> Transaction: """ Create a transaction for receiving money from an external source """ # Generate a unique transaction reference transaction_reference = generate_transaction_reference() while get_transaction_by_reference(db, transaction_reference): transaction_reference = generate_transaction_reference() # Create the transaction db_transaction = Transaction( transaction_reference=transaction_reference, receiver_id=user_id, receiver_account_id=receive_in.receiver_account_id, amount=receive_in.amount, currency=receive_in.currency, transaction_type=TransactionType.DEPOSIT, status=TransactionStatus.PENDING, description=f"External deposit from: {receive_in.external_sender_info}. {receive_in.description or ''}", ) db.add(db_transaction) db.commit() db.refresh(db_transaction) return db_transaction def create_send_money(db: Session, send_in: SendMoneyCreate, user_id: int) -> Transaction: """ Create a transaction for sending money to an external destination """ # Generate a unique transaction reference transaction_reference = generate_transaction_reference() while get_transaction_by_reference(db, transaction_reference): transaction_reference = generate_transaction_reference() # Create the transaction db_transaction = Transaction( transaction_reference=transaction_reference, sender_id=user_id, sender_account_id=send_in.sender_account_id, amount=send_in.amount, currency=send_in.currency, transaction_type=TransactionType.WITHDRAWAL, status=TransactionStatus.PENDING, description=f"External withdrawal to: {send_in.external_receiver_info}. {send_in.description or ''}", ) db.add(db_transaction) db.commit() db.refresh(db_transaction) return db_transaction def update_transaction(db: Session, transaction: Transaction, transaction_in: TransactionUpdate) -> Transaction: """ Update a transaction """ update_data = transaction_in.dict(exclude_unset=True) # If status is changing to completed, set the completed_at timestamp if "status" in update_data and update_data["status"] == TransactionStatus.COMPLETED and transaction.status != TransactionStatus.COMPLETED: update_data["completed_at"] = datetime.utcnow() for field, value in update_data.items(): setattr(transaction, field, value) db.add(transaction) db.commit() db.refresh(transaction) return transaction def process_transaction(db: Session, transaction: Transaction) -> Transaction: """ Process a pending transaction """ # Only process pending transactions if transaction.status != TransactionStatus.PENDING: return transaction try: # Process based on transaction type if transaction.transaction_type == TransactionType.DEPOSIT: # Handle deposit if transaction.receiver_account_id: receiver_account = db.query(Account).filter(Account.id == transaction.receiver_account_id).first() if receiver_account: update_account_balance(db, receiver_account, transaction.amount) transaction.status = TransactionStatus.COMPLETED transaction.completed_at = datetime.utcnow() elif transaction.transaction_type == TransactionType.WITHDRAWAL: # Handle withdrawal if transaction.sender_account_id: sender_account = db.query(Account).filter(Account.id == transaction.sender_account_id).first() if sender_account: # Check if there's enough balance if sender_account.balance >= transaction.amount: update_account_balance(db, sender_account, -transaction.amount) transaction.status = TransactionStatus.COMPLETED transaction.completed_at = datetime.utcnow() else: transaction.status = TransactionStatus.FAILED transaction.description = transaction.description + " (Insufficient funds)" elif transaction.transaction_type == TransactionType.TRANSFER: # Handle transfer between accounts if transaction.sender_account_id and transaction.receiver_account_id: sender_account = db.query(Account).filter(Account.id == transaction.sender_account_id).first() receiver_account = db.query(Account).filter(Account.id == transaction.receiver_account_id).first() if sender_account and receiver_account: # Check if there's enough balance if sender_account.balance >= transaction.amount: update_account_balance(db, sender_account, -transaction.amount) update_account_balance(db, receiver_account, transaction.amount) transaction.status = TransactionStatus.COMPLETED transaction.completed_at = datetime.utcnow() else: transaction.status = TransactionStatus.FAILED transaction.description = transaction.description + " (Insufficient funds)" db.add(transaction) db.commit() db.refresh(transaction) return transaction except Exception as e: # If there's an error, mark the transaction as failed transaction.status = TransactionStatus.FAILED transaction.description = transaction.description + f" (Error: {str(e)})" db.add(transaction) db.commit() db.refresh(transaction) return transaction