diff --git a/apps/payment/backend/application/payment_hub.py b/apps/payment/backend/application/payment_hub.py index 91afa8a..9ad3766 100644 --- a/apps/payment/backend/application/payment_hub.py +++ b/apps/payment/backend/application/payment_hub.py @@ -84,3 +84,16 @@ class PaymentHub: return await self.stripe_manager.invoke_checkout_session_webhook( payload, stripe_signature ) + + async def handle_account_update( + self, + account_id: str, + details_submitted: bool, + payouts_enabled: bool, + charges_enabled: bool + ) -> bool: + return await self.payment_manager.update_stripe_account_status( + account_id, + setup=details_submitted, + verified=payouts_enabled and charges_enabled + ) diff --git a/apps/payment/backend/business/payment_manager.py b/apps/payment/backend/business/payment_manager.py index d7bc72c..6a5d6d3 100644 --- a/apps/payment/backend/business/payment_manager.py +++ b/apps/payment/backend/business/payment_manager.py @@ -1,11 +1,13 @@ from typing import Dict, Optional from backend.services.project.models import ProjectDoc from backend.services.payment.models import IncomeProfileDoc +from datetime import datetime +from common.log.module_logger import ModuleLogger class PaymentManager: def __init__(self) -> None: - pass + self.module_logger = ModuleLogger(sender_id=PaymentManager) async def fetch_wechat_qr_code(self, project_id: str) -> Optional[Dict[str, any]]: project = await ProjectDoc.get(project_id) @@ -26,3 +28,79 @@ class PaymentManager: 0 ].stripe_account_id return None + + async def update_stripe_account_status( + self, + stripe_account_id: str, + setup: bool, + verified: bool + ) -> bool: + try: + # Should use IncomeProfileDoc to be consistent with other methods + payment_profile = await IncomeProfileDoc.find_one( + {"bank_account.money_collecting_methods": { + "$elemMatch": { + "stripe_account_id": stripe_account_id + } + }} + ) + + if not payment_profile: + await self.module_logger.log_warning( + warning="No payment profile found for Stripe account", + properties={ + "stripe_account_id": stripe_account_id, + "action": "update_stripe_account_status" + } + ) + return False + + # Update the stripe method status + updated = False + # Need to check if money_collecting_methods exists and is not empty + if payment_profile.bank_account and payment_profile.bank_account.money_collecting_methods: + for method in payment_profile.bank_account.money_collecting_methods: + if method.stripe_account_id == stripe_account_id: + method.setup = setup + method.verified = verified + method.last_update_time = int(datetime.now().timestamp()) + updated = True + break # Exit loop once found and updated + + if updated: + await payment_profile.save() + await self.module_logger.log_info( + info="Successfully updated Stripe account status", + properties={ + "stripe_account_id": stripe_account_id, + "user_id": payment_profile.user_id, + "setup": setup, + "verified": verified + } + ) + return True + + # Log warning with more context + await self.module_logger.log_warning( + warning="Stripe account not found in payment methods", + properties={ + "stripe_account_id": stripe_account_id, + "user_id": payment_profile.user_id if payment_profile else None, + "has_bank_account": bool(payment_profile and payment_profile.bank_account), + "has_methods": bool(payment_profile and payment_profile.bank_account and payment_profile.bank_account.money_collecting_methods) + } + ) + return False + + except Exception as e: + await self.module_logger.log_exception( + exception=e, + info="Failed to update Stripe account status", + properties={ + "stripe_account_id": stripe_account_id, + "setup": setup, + "verified": verified, + "error": str(e) + } + ) + return False diff --git a/apps/payment/webapi/routes/payment/stripe_manager_controller.py b/apps/payment/webapi/routes/payment/stripe_manager_controller.py index 8a16ecc..963706b 100644 --- a/apps/payment/webapi/routes/payment/stripe_manager_controller.py +++ b/apps/payment/webapi/routes/payment/stripe_manager_controller.py @@ -1,9 +1,10 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Request, Header from backend.application.payment_hub import PaymentHub from typing import Dict, Optional, Tuple from decimal import Decimal from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder +import stripe router = APIRouter() payment_hub = PaymentHub() @@ -184,3 +185,36 @@ async def fetch_checkout_session_url(transaction_id: str) -> Optional[str]: ) async def invoke_checkout_session_webhook(payload: str, stripe_signature: str): return await payment_hub.invoke_checkout_session_webhook(payload, stripe_signature) + + +@router.post( + "/webhook/account", + operation_id="stripe_account_webhook", + summary="Handle Stripe account webhook events", +) +async def handle_account_webhook( + request: Request, + stripe_signature: str = Header(None) +): + payload = await request.body() + try: + event = stripe.Webhook.construct_event( + payload, + stripe_signature, + app_settings.STRIPE_WEBHOOK_SECRET + ) + + # Handle account.updated event + if event.type == 'account.updated': + account = event.data.object + return await payment_hub.handle_account_update( + account_id=account.id, + details_submitted=account.details_submitted, + payouts_enabled=account.payouts_enabled, + charges_enabled=account.charges_enabled + ) + + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + return JSONResponse(content={"status": "success"})