diff --git a/modules/routes/routeBilling.py b/modules/routes/routeBilling.py index d899ad2a..5029e485 100644 --- a/modules/routes/routeBilling.py +++ b/modules/routes/routeBilling.py @@ -875,7 +875,8 @@ def confirmCheckoutSession( if not session: raise HTTPException(status_code=404, detail="Stripe Checkout Session not found") - session_dict = session.to_dict_recursive() if hasattr(session, "to_dict_recursive") else dict(session) + from modules.shared.stripeClient import stripeToDict + session_dict = stripeToDict(session) metadata = session_dict.get("metadata") or {} mandate_id = metadata.get("mandateId") user_id = metadata.get("userId") or None @@ -995,7 +996,8 @@ def _handleSubscriptionCheckoutCompleted(session, eventId: str) -> None: from datetime import datetime, timezone if not isinstance(session, dict): - session = dict(session) + from modules.shared.stripeClient import stripeToDict + session = stripeToDict(session) metadata = session.get("metadata") or {} subscriptionRecordId = metadata.get("subscriptionRecordId") @@ -1010,7 +1012,8 @@ def _handleSubscriptionCheckoutCompleted(session, eventId: str) -> None: try: from modules.shared.stripeClient import getStripeClient stripe = getStripeClient() - subObj = dict(stripe.Subscription.retrieve(stripeSub)) + from modules.shared.stripeClient import stripeToDict + subObj = stripeToDict(stripe.Subscription.retrieve(stripeSub)) metadata = subObj.get("metadata") or {} subscriptionRecordId = metadata.get("subscriptionRecordId") mandateId = metadata.get("mandateId") @@ -1042,7 +1045,8 @@ def _handleSubscriptionCheckoutCompleted(session, eventId: str) -> None: try: from modules.shared.stripeClient import getStripeClient stripe = getStripeClient() - stripeSub = dict(stripe.Subscription.retrieve(stripeSubId, expand=["items"])) + from modules.shared.stripeClient import stripeToDict + stripeSub = stripeToDict(stripe.Subscription.retrieve(stripeSubId, expand=["items"])) if stripeSub.get("current_period_start"): stripeData["currentPeriodStart"] = datetime.fromtimestamp( diff --git a/modules/routes/routeSubscription.py b/modules/routes/routeSubscription.py index 99bdb4e6..97a7f23b 100644 --- a/modules/routes/routeSubscription.py +++ b/modules/routes/routeSubscription.py @@ -282,10 +282,10 @@ def verifyCheckout( _assertMandateAdmin(context, mandateId) try: - from modules.shared.stripeClient import getStripeClient + from modules.shared.stripeClient import getStripeClient, stripeToDict stripe = getStripeClient() rawSession = stripe.checkout.Session.retrieve(data.sessionId) - session = dict(rawSession) + session = stripeToDict(rawSession) except Exception as e: logger.error("Failed to retrieve checkout session %s: %s", data.sessionId, e) raise HTTPException(status_code=400, detail="Invalid session ID") diff --git a/modules/serviceCenter/services/serviceSubscription/mainServiceSubscription.py b/modules/serviceCenter/services/serviceSubscription/mainServiceSubscription.py index 5d2249a0..9535a2da 100644 --- a/modules/serviceCenter/services/serviceSubscription/mainServiceSubscription.py +++ b/modules/serviceCenter/services/serviceSubscription/mainServiceSubscription.py @@ -425,7 +425,8 @@ class SubscriptionService: try: from modules.shared.stripeClient import getStripeClient stripe = getStripeClient() - stripeSub = dict(stripe.Subscription.modify(stripeSubId, cancel_at_period_end=True)) + from modules.shared.stripeClient import stripeToDict + stripeSub = stripeToDict(stripe.Subscription.modify(stripeSubId, cancel_at_period_end=True)) pUrl = (stripeSub.get("metadata") or {}).get("platformUrl", "") except Exception as e: logger.error("Failed to set cancel_at_period_end for %s: %s", stripeSubId, e) @@ -488,7 +489,8 @@ class SubscriptionService: try: from modules.shared.stripeClient import getStripeClient stripe = getStripeClient() - stripeSub = dict(stripe.Subscription.retrieve(stripeSubId)) + from modules.shared.stripeClient import stripeToDict + stripeSub = stripeToDict(stripe.Subscription.retrieve(stripeSubId)) pUrl = (stripeSub.get("metadata") or {}).get("platformUrl", "") stripe.Subscription.cancel(stripeSubId) except Exception as e: @@ -673,7 +675,8 @@ def _buildInvoiceSummaryHtml( stripe = getStripeClient() invoices = stripe.Invoice.list(subscription=stripeSubId, limit=1) if invoices.data: - inv = dict(invoices.data[0]) if not isinstance(invoices.data[0], dict) else invoices.data[0] + from modules.shared.stripeClient import stripeToDict + inv = stripeToDict(invoices.data[0]) hostedUrl = inv.get("hosted_invoice_url", "") if hostedUrl: invoiceLink = ( @@ -715,7 +718,8 @@ def _buildCancelSummaryHtml(subRecord: Dict[str, Any], platformUrl: str = "") -> stripe = getStripeClient() invoices = stripe.Invoice.list(subscription=stripeSubId, limit=1) if invoices.data: - inv = dict(invoices.data[0]) if not isinstance(invoices.data[0], dict) else invoices.data[0] + from modules.shared.stripeClient import stripeToDict + inv = stripeToDict(invoices.data[0]) hostedUrl = inv.get("hosted_invoice_url", "") if hostedUrl: parts.append( diff --git a/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py b/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py index 1e44217b..14e9424a 100644 --- a/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py +++ b/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py @@ -108,7 +108,8 @@ def _findExistingStripePrice(stripe, productId: str, unitAmount: int, interval: def _getStripePriceAmount(stripe, priceId: str) -> Optional[int]: """Retrieve the unit_amount (in Rappen) of an existing Stripe Price.""" try: - price = dict(stripe.Price.retrieve(priceId)) + from modules.shared.stripeClient import stripeToDict + price = stripeToDict(stripe.Price.retrieve(priceId)) return price.get("unit_amount") if price else None except Exception: return None diff --git a/modules/shared/stripeClient.py b/modules/shared/stripeClient.py index 9c7b4c67..3f7dd3a7 100644 --- a/modules/shared/stripeClient.py +++ b/modules/shared/stripeClient.py @@ -8,13 +8,28 @@ API key, API version, and fallback handling across billing and subscription flow """ import logging -from typing import Optional +import json +from typing import Any, Dict, Optional logger = logging.getLogger(__name__) _stripeInitialized = False +def stripeToDict(obj) -> Dict[str, Any]: + """Convert a Stripe object to a plain dict, compatible with all stripe-python versions.""" + if isinstance(obj, dict): + return obj + if hasattr(obj, "to_dict_recursive"): + return obj.to_dict_recursive() + if hasattr(obj, "to_dict"): + return obj.to_dict() + try: + return json.loads(str(obj)) + except (json.JSONDecodeError, TypeError): + return dict(obj) + + def getStripeClient(): """ Initialize and return the configured Stripe SDK module.