Automated Action 2c6298ca4b Implement fintech payment service backend with FastAPI and SQLite
- Set up project structure with FastAPI
- Implement user and account management
- Add send and receive money functionality
- Set up transaction processing system
- Add JWT authentication
- Configure SQLAlchemy with SQLite
- Set up Alembic for database migrations
- Create comprehensive API documentation
2025-06-17 11:53:41 +00:00

292 lines
11 KiB
Python

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