feat(billing): end-to-end Stripe subscription signup with platform enforcement

Move core signup service from marketplace to billing module, add
automatic Stripe product/price sync for tiers, create loyalty-specific
signup wizard, and enforce that platform is always explicitly known
(no silent defaulting to primary/hardcoded ID).

Key changes:
- New billing SignupService with separated account/store creation steps
- Stripe auto-sync on tier create/update (new prices, archive old)
- Loyalty signup template (Plan → Account → Store → Payment)
- platform_code is now required throughout the signup flow
- Pricing/signup pages return 404 if platform not detected
- OMS-specific logic (Letzshop claiming) stays in marketplace module
- Bootstrap script: scripts/seed/sync_stripe_products.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 19:16:14 +01:00
parent 2078ce35b2
commit 9684747d08
12 changed files with 1723 additions and 689 deletions

View File

@@ -93,6 +93,38 @@ class StripeService:
)
return customer.id
def create_customer_for_merchant(
self,
merchant,
email: str,
name: str | None = None,
metadata: dict | None = None,
) -> str:
"""
Create a Stripe customer for a merchant (before store exists).
Used during signup when the store hasn't been created yet.
Returns the Stripe customer ID.
"""
self._check_configured()
customer_metadata = {
"merchant_id": str(merchant.id),
"merchant_name": merchant.name,
**(metadata or {}),
}
customer = stripe.Customer.create(
email=email,
name=name or merchant.name,
metadata=customer_metadata,
)
logger.info(
f"Created Stripe customer {customer.id} for merchant {merchant.name}"
)
return customer.id
def get_customer(self, customer_id: str) -> stripe.Customer:
"""Get a Stripe customer by ID."""
self._check_configured()