
- Implemented comprehensive multi-tenant data isolation using database-level security - Built JWT authentication system with role-based access control (Super Admin, Org Admin, User, Viewer) - Created RESTful API endpoints for user and organization operations - Added complete audit logging for all data modifications with IP tracking - Implemented API rate limiting and input validation with security middleware - Built webhook processing engine with async event handling and retry logic - Created external API call handlers with circuit breaker pattern and error handling - Implemented data synchronization between external services and internal data - Added integration health monitoring and status tracking - Created three mock external services (User Management, Payment, Communication) - Implemented idempotency for webhook processing to handle duplicates gracefully - Added comprehensive security headers and XSS/CSRF protection - Set up Alembic database migrations with proper SQLite configuration - Included extensive documentation and API examples Architecture features: - Multi-tenant isolation at database level - Circuit breaker pattern for external API resilience - Async background task processing - Complete audit trail with user context - Role-based permission system - Webhook signature verification - Request validation and sanitization - Health monitoring endpoints Co-Authored-By: Claude <noreply@anthropic.com>
156 lines
5.2 KiB
Python
156 lines
5.2 KiB
Python
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
import uuid
|
|
from enum import Enum
|
|
|
|
app = FastAPI(title="Mock Payment Service", version="1.0.0")
|
|
|
|
class SubscriptionStatus(str, Enum):
|
|
ACTIVE = "active"
|
|
CANCELLED = "cancelled"
|
|
EXPIRED = "expired"
|
|
|
|
class PaymentStatus(str, Enum):
|
|
PENDING = "pending"
|
|
SUCCEEDED = "succeeded"
|
|
FAILED = "failed"
|
|
|
|
class Subscription(BaseModel):
|
|
id: Optional[str] = None
|
|
organization_id: int
|
|
plan_name: str
|
|
status: SubscriptionStatus = SubscriptionStatus.ACTIVE
|
|
amount: float
|
|
currency: str = "USD"
|
|
billing_cycle: str = "monthly"
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Payment(BaseModel):
|
|
id: Optional[str] = None
|
|
organization_id: int
|
|
subscription_id: Optional[str] = None
|
|
amount: float
|
|
currency: str = "USD"
|
|
status: PaymentStatus = PaymentStatus.PENDING
|
|
description: Optional[str] = None
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
# Mock data storage
|
|
subscriptions_db: Dict[str, Dict[str, Any]] = {}
|
|
payments_db: Dict[str, Dict[str, Any]] = {}
|
|
organization_billing: Dict[int, List[str]] = {}
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "healthy", "service": "payment", "timestamp": datetime.utcnow()}
|
|
|
|
@app.post("/subscriptions", response_model=Subscription)
|
|
async def create_subscription(subscription: Subscription):
|
|
subscription_id = str(uuid.uuid4())
|
|
subscription.id = subscription_id
|
|
subscription.created_at = datetime.utcnow()
|
|
|
|
subscriptions_db[subscription_id] = subscription.dict()
|
|
|
|
# Add to organization billing
|
|
if subscription.organization_id not in organization_billing:
|
|
organization_billing[subscription.organization_id] = []
|
|
organization_billing[subscription.organization_id].append(subscription_id)
|
|
|
|
# Send webhook (simulated)
|
|
await send_webhook("subscription.created", subscription.dict())
|
|
|
|
return subscription
|
|
|
|
@app.get("/subscriptions/{subscription_id}", response_model=Subscription)
|
|
async def get_subscription(subscription_id: str):
|
|
if subscription_id not in subscriptions_db:
|
|
raise HTTPException(status_code=404, detail="Subscription not found")
|
|
return Subscription(**subscriptions_db[subscription_id])
|
|
|
|
@app.delete("/subscriptions/{subscription_id}")
|
|
async def cancel_subscription(subscription_id: str):
|
|
if subscription_id not in subscriptions_db:
|
|
raise HTTPException(status_code=404, detail="Subscription not found")
|
|
|
|
subscription_data = subscriptions_db[subscription_id].copy()
|
|
subscription_data["status"] = SubscriptionStatus.CANCELLED
|
|
subscription_data["updated_at"] = datetime.utcnow()
|
|
subscriptions_db[subscription_id] = subscription_data
|
|
|
|
# Send webhook (simulated)
|
|
await send_webhook("subscription.cancelled", subscription_data)
|
|
|
|
return {"message": "Subscription cancelled successfully"}
|
|
|
|
@app.post("/payments", response_model=Payment)
|
|
async def process_payment(payment: Payment):
|
|
payment_id = str(uuid.uuid4())
|
|
payment.id = payment_id
|
|
payment.created_at = datetime.utcnow()
|
|
|
|
# Simulate payment processing
|
|
import random
|
|
success_rate = 0.85 # 85% success rate
|
|
if random.random() < success_rate:
|
|
payment.status = PaymentStatus.SUCCEEDED
|
|
event_type = "payment.succeeded"
|
|
else:
|
|
payment.status = PaymentStatus.FAILED
|
|
event_type = "payment.failed"
|
|
|
|
payment.updated_at = datetime.utcnow()
|
|
payments_db[payment_id] = payment.dict()
|
|
|
|
# Add to organization billing
|
|
if payment.organization_id not in organization_billing:
|
|
organization_billing[payment.organization_id] = []
|
|
organization_billing[payment.organization_id].append(payment_id)
|
|
|
|
# Send webhook (simulated)
|
|
await send_webhook(event_type, payment.dict())
|
|
|
|
return payment
|
|
|
|
@app.get("/organizations/{organization_id}/billing")
|
|
async def get_billing_history(organization_id: int):
|
|
if organization_id not in organization_billing:
|
|
return {"subscriptions": [], "payments": []}
|
|
|
|
org_subscriptions = []
|
|
org_payments = []
|
|
|
|
for item_id in organization_billing[organization_id]:
|
|
if item_id in subscriptions_db:
|
|
org_subscriptions.append(subscriptions_db[item_id])
|
|
elif item_id in payments_db:
|
|
org_payments.append(payments_db[item_id])
|
|
|
|
return {
|
|
"subscriptions": org_subscriptions,
|
|
"payments": org_payments,
|
|
"total_subscriptions": len(org_subscriptions),
|
|
"total_payments": len(org_payments)
|
|
}
|
|
|
|
async def send_webhook(event_type: str, data: Dict[str, Any]):
|
|
"""Simulate sending webhook to main service"""
|
|
webhook_data = {
|
|
"event_id": str(uuid.uuid4()),
|
|
"event_type": event_type,
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"data": data
|
|
}
|
|
|
|
# In a real implementation, this would send HTTP request to main service
|
|
print(f"Webhook sent: {event_type} - {webhook_data['event_id']}")
|
|
|
|
return webhook_data
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8002) |