diff --git a/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py b/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py index 38ac29e1..edb2df1f 100644 --- a/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py +++ b/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py @@ -154,6 +154,23 @@ def _createStripePrice(stripe, productId: str, unitAmountCHF: float, interval: s return price.id +def _validateStripeIdsExist(stripe, mapping: StripePlanPrice) -> bool: + """Quick check whether at least the stored product IDs still exist in Stripe. + Returns False when running against a different Stripe account or after DB copy.""" + try: + if mapping.stripeProductIdUsers: + stripe.Product.retrieve(mapping.stripeProductIdUsers) + if mapping.stripeProductIdInstances: + stripe.Product.retrieve(mapping.stripeProductIdInstances) + return True + except Exception as e: + code = getattr(e, "code", None) + if code == "resource_missing": + return False + logger.debug("Stripe validation check failed (non-critical): %s", e) + return False + + def bootstrapStripePrices() -> None: """Ensure all paid plans have separate Stripe Products for users and instances.""" try: @@ -183,30 +200,37 @@ def bootstrapStripePrices() -> None: hasAllPrices = mapping.stripePriceIdUsers and mapping.stripePriceIdInstances hasAllProducts = mapping.stripeProductIdUsers and mapping.stripeProductIdInstances if hasAllPrices and hasAllProducts: - changed = False - reconciledUsers = _reconcilePrice( - stripe, mapping.stripeProductIdUsers, mapping.stripePriceIdUsers, - plan.pricePerUserCHF, interval, f"{planKey} — Benutzer-Lizenz", - ) - if reconciledUsers != mapping.stripePriceIdUsers: - changed = True + if _validateStripeIdsExist(stripe, mapping): + changed = False + reconciledUsers = _reconcilePrice( + stripe, mapping.stripeProductIdUsers, mapping.stripePriceIdUsers, + plan.pricePerUserCHF, interval, f"{planKey} — Benutzer-Lizenz", + ) + if reconciledUsers != mapping.stripePriceIdUsers: + changed = True - reconciledInstances = _reconcilePrice( - stripe, mapping.stripeProductIdInstances, mapping.stripePriceIdInstances, - plan.pricePerFeatureInstanceCHF, interval, f"{planKey} — Feature-Instanz", - ) - if reconciledInstances != mapping.stripePriceIdInstances: - changed = True + reconciledInstances = _reconcilePrice( + stripe, mapping.stripeProductIdInstances, mapping.stripePriceIdInstances, + plan.pricePerFeatureInstanceCHF, interval, f"{planKey} — Feature-Instanz", + ) + if reconciledInstances != mapping.stripePriceIdInstances: + changed = True - if changed: - db.recordModify(StripePlanPrice, mapping.id, { - "stripePriceIdUsers": reconciledUsers, - "stripePriceIdInstances": reconciledInstances, - }) - logger.info("Reconciled Stripe prices for plan %s: users=%s, instances=%s", planKey, reconciledUsers, reconciledInstances) + if changed: + db.recordModify(StripePlanPrice, mapping.id, { + "stripePriceIdUsers": reconciledUsers, + "stripePriceIdInstances": reconciledInstances, + }) + logger.info("Reconciled Stripe prices for plan %s: users=%s, instances=%s", planKey, reconciledUsers, reconciledInstances) + else: + logger.debug("Stripe prices up-to-date for plan %s", planKey) + continue else: - logger.debug("Stripe prices up-to-date for plan %s", planKey) - continue + logger.warning( + "Stored Stripe IDs for plan %s reference unknown objects " + "(likely wrong Stripe account or copied DB) — re-provisioning.", + planKey, + ) productIdUsers = None productIdInstances = None