
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
154 lines
5.5 KiB
Python
154 lines
5.5 KiB
Python
import stripe
|
|
import requests
|
|
from typing import Dict, Any, Optional
|
|
from fastapi import HTTPException
|
|
|
|
from app.core.config import settings
|
|
from app.models.transaction import PaymentGateway
|
|
|
|
|
|
class StripeService:
|
|
def __init__(self):
|
|
if settings.STRIPE_SECRET_KEY:
|
|
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
|
|
def create_payment_intent(
|
|
self, amount: float, currency: str = "usd", metadata: Optional[Dict] = None
|
|
) -> Dict[str, Any]:
|
|
if not settings.STRIPE_SECRET_KEY:
|
|
raise HTTPException(status_code=500, detail="Stripe not configured")
|
|
|
|
try:
|
|
intent = stripe.PaymentIntent.create(
|
|
amount=int(amount * 100), # Stripe expects amount in cents
|
|
currency=currency.lower(),
|
|
metadata=metadata or {},
|
|
)
|
|
return {
|
|
"client_secret": intent.client_secret,
|
|
"payment_intent_id": intent.id,
|
|
"status": intent.status,
|
|
}
|
|
except stripe.error.StripeError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
def confirm_payment(self, payment_intent_id: str) -> Dict[str, Any]:
|
|
if not settings.STRIPE_SECRET_KEY:
|
|
raise HTTPException(status_code=500, detail="Stripe not configured")
|
|
|
|
try:
|
|
intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
|
return {
|
|
"payment_intent_id": intent.id,
|
|
"status": intent.status,
|
|
"amount": intent.amount / 100, # Convert back from cents
|
|
"currency": intent.currency,
|
|
}
|
|
except stripe.error.StripeError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
class PaystackService:
|
|
def __init__(self):
|
|
self.base_url = "https://api.paystack.co"
|
|
self.headers = {
|
|
"Authorization": f"Bearer {settings.PAYSTACK_SECRET_KEY}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
def initialize_transaction(
|
|
self,
|
|
email: str,
|
|
amount: float,
|
|
currency: str = "NGN",
|
|
metadata: Optional[Dict] = None,
|
|
) -> Dict[str, Any]:
|
|
if not settings.PAYSTACK_SECRET_KEY:
|
|
raise HTTPException(status_code=500, detail="Paystack not configured")
|
|
|
|
data = {
|
|
"email": email,
|
|
"amount": int(amount * 100), # Paystack expects amount in kobo
|
|
"currency": currency.upper(),
|
|
"metadata": metadata or {},
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{self.base_url}/transaction/initialize",
|
|
json=data,
|
|
headers=self.headers,
|
|
)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
|
|
if result["status"]:
|
|
return {
|
|
"authorization_url": result["data"]["authorization_url"],
|
|
"access_code": result["data"]["access_code"],
|
|
"reference": result["data"]["reference"],
|
|
}
|
|
else:
|
|
raise HTTPException(status_code=400, detail=result["message"])
|
|
except requests.RequestException as e:
|
|
raise HTTPException(status_code=500, detail=f"Paystack API error: {str(e)}")
|
|
|
|
def verify_transaction(self, reference: str) -> Dict[str, Any]:
|
|
if not settings.PAYSTACK_SECRET_KEY:
|
|
raise HTTPException(status_code=500, detail="Paystack not configured")
|
|
|
|
try:
|
|
response = requests.get(
|
|
f"{self.base_url}/transaction/verify/{reference}", headers=self.headers
|
|
)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
|
|
if result["status"]:
|
|
data = result["data"]
|
|
return {
|
|
"reference": data["reference"],
|
|
"status": data["status"],
|
|
"amount": data["amount"] / 100, # Convert back from kobo
|
|
"currency": data["currency"],
|
|
"gateway_response": data["gateway_response"],
|
|
}
|
|
else:
|
|
raise HTTPException(status_code=400, detail=result["message"])
|
|
except requests.RequestException as e:
|
|
raise HTTPException(status_code=500, detail=f"Paystack API error: {str(e)}")
|
|
|
|
|
|
class PaymentService:
|
|
def __init__(self):
|
|
self.stripe_service = StripeService()
|
|
self.paystack_service = PaystackService()
|
|
|
|
def initialize_payment(
|
|
self,
|
|
gateway: PaymentGateway,
|
|
amount: float,
|
|
currency: str,
|
|
email: str,
|
|
metadata: Optional[Dict] = None,
|
|
) -> Dict[str, Any]:
|
|
if gateway == PaymentGateway.STRIPE:
|
|
return self.stripe_service.create_payment_intent(amount, currency, metadata)
|
|
elif gateway == PaymentGateway.PAYSTACK:
|
|
return self.paystack_service.initialize_transaction(
|
|
email, amount, currency, metadata
|
|
)
|
|
else:
|
|
raise HTTPException(status_code=400, detail="Unsupported payment gateway")
|
|
|
|
def verify_payment(self, gateway: PaymentGateway, reference: str) -> Dict[str, Any]:
|
|
if gateway == PaymentGateway.STRIPE:
|
|
return self.stripe_service.confirm_payment(reference)
|
|
elif gateway == PaymentGateway.PAYSTACK:
|
|
return self.paystack_service.verify_transaction(reference)
|
|
else:
|
|
raise HTTPException(status_code=400, detail="Unsupported payment gateway")
|
|
|
|
|
|
payment_service = PaymentService()
|