Automated Action b78ac1f072 Create comprehensive gym membership management system
Features:
- User registration and authentication with JWT tokens
- Multi-level admin access (Admin and Super Admin)
- Gym management with membership plans
- Subscription management with payment integration
- Stripe and Paystack payment gateway support
- Role-based access control
- SQLite database with Alembic migrations
- Comprehensive API endpoints with FastAPI
- Database models for users, gyms, memberships, subscriptions, and transactions
- Admin endpoints for user management and financial reporting
- Health check and documentation endpoints

Core Components:
- FastAPI application with CORS support
- SQLAlchemy ORM with relationship mapping
- JWT-based authentication with bcrypt password hashing
- Payment service abstraction for multiple gateways
- Pydantic schemas for request/response validation
- Alembic database migration system
- Admin dashboard functionality
- Environment variable configuration
2025-06-20 09:08:21 +00:00

182 lines
5.9 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from app.db.session import get_db
from app.core.deps import get_current_active_user
from app.models.user import User
from app.models.membership import MembershipPlan
from app.models.subscription import Subscription, SubscriptionStatus
from app.models.transaction import Transaction, TransactionStatus
from app.schemas.transaction import PaymentRequest, Transaction as TransactionSchema
from app.services.payment import payment_service
router = APIRouter()
@router.post("/initialize")
def initialize_payment(
payment_request: PaymentRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user),
):
plan = (
db.query(MembershipPlan)
.filter(
MembershipPlan.id == payment_request.membership_plan_id,
MembershipPlan.is_active,
)
.first()
)
if not plan:
raise HTTPException(status_code=404, detail="Membership plan not found")
existing_active_subscription = (
db.query(Subscription)
.filter(
Subscription.user_id == current_user.id,
Subscription.membership_plan_id == payment_request.membership_plan_id,
Subscription.status == SubscriptionStatus.ACTIVE,
)
.first()
)
if existing_active_subscription:
raise HTTPException(
status_code=400, detail="Active subscription already exists for this plan"
)
# Create transaction record
transaction = Transaction(
user_id=current_user.id,
amount=plan.price,
payment_gateway=payment_request.payment_gateway,
description=f"Subscription to {plan.name}",
)
db.add(transaction)
db.commit()
db.refresh(transaction)
metadata = {
"user_id": str(current_user.id),
"transaction_id": str(transaction.id),
"plan_id": str(plan.id),
}
try:
payment_data = payment_service.initialize_payment(
gateway=payment_request.payment_gateway,
amount=plan.price,
currency="USD"
if payment_request.payment_gateway.value == "stripe"
else "NGN",
email=current_user.email,
metadata=metadata,
)
# Update transaction with gateway reference
if payment_request.payment_gateway.value == "stripe":
transaction.gateway_reference = payment_data.get("payment_intent_id")
else:
transaction.gateway_reference = payment_data.get("reference")
db.commit()
return {"transaction_id": transaction.id, "payment_data": payment_data}
except Exception as e:
transaction.status = TransactionStatus.FAILED
db.commit()
raise e
@router.post("/verify/{transaction_id}")
def verify_payment(
transaction_id: int,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user),
):
transaction = (
db.query(Transaction)
.filter(
Transaction.id == transaction_id, Transaction.user_id == current_user.id
)
.first()
)
if not transaction:
raise HTTPException(status_code=404, detail="Transaction not found")
if transaction.status == TransactionStatus.COMPLETED:
return {"message": "Payment already verified"}
try:
verification_data = payment_service.verify_payment(
gateway=transaction.payment_gateway, reference=transaction.gateway_reference
)
if verification_data["status"] in ["succeeded", "success"]:
transaction.status = TransactionStatus.COMPLETED
transaction.gateway_transaction_id = verification_data.get(
"reference"
) or verification_data.get("payment_intent_id")
# Create subscription
plan = (
db.query(MembershipPlan)
.filter(MembershipPlan.id == transaction.subscription_id)
.first()
)
if not plan:
# Find plan from metadata or transaction description
# This is a fallback - ideally we'd store plan_id in transaction
raise HTTPException(
status_code=500, detail="Cannot create subscription: plan not found"
)
start_date = datetime.utcnow()
end_date = start_date + timedelta(days=plan.duration_days)
subscription = Subscription(
user_id=current_user.id,
membership_plan_id=plan.id,
start_date=start_date,
end_date=end_date,
amount_paid=transaction.amount,
payment_reference=transaction.gateway_reference,
status=SubscriptionStatus.ACTIVE,
)
transaction.subscription_id = subscription.id
db.add(subscription)
db.commit()
return {"message": "Payment verified and subscription created successfully"}
else:
transaction.status = TransactionStatus.FAILED
db.commit()
raise HTTPException(status_code=400, detail="Payment verification failed")
except Exception as e:
transaction.status = TransactionStatus.FAILED
db.commit()
raise HTTPException(
status_code=400, detail=f"Payment verification error: {str(e)}"
)
@router.get("/transactions", response_model=list[TransactionSchema])
def get_user_transactions(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user),
):
transactions = (
db.query(Transaction)
.filter(Transaction.user_id == current_user.id)
.offset(skip)
.limit(limit)
.all()
)
return transactions