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)