# Subscription & Billing System The platform provides a comprehensive subscription and billing system for managing vendor subscriptions, usage limits, and payments through Stripe. ## Overview The billing system enables: - **Subscription Tiers**: Database-driven tier definitions with configurable limits - **Usage Tracking**: Orders, products, and team member limits per tier - **Stripe Integration**: Checkout sessions, customer portal, and webhook handling - **Self-Service Billing**: Vendor-facing billing page for subscription management - **Add-ons**: Optional purchasable items (domains, SSL, email packages) ## Architecture ### Database Models All subscription models are defined in `models/database/subscription.py`: | Model | Purpose | |-------|---------| | `SubscriptionTier` | Tier definitions with limits and Stripe price IDs | | `VendorSubscription` | Per-vendor subscription status and usage | | `AddOnProduct` | Purchasable add-ons (domains, SSL, email) | | `VendorAddOn` | Add-ons purchased by each vendor | | `StripeWebhookEvent` | Idempotency tracking for webhooks | | `BillingHistory` | Invoice and payment history | ### Services | Service | Location | Purpose | |---------|----------|---------| | `BillingService` | `app/services/billing_service.py` | Subscription operations, checkout, portal | | `SubscriptionService` | `app/services/subscription_service.py` | Limit checks, usage tracking | | `StripeService` | `app/services/stripe_service.py` | Core Stripe API operations | | `StripeWebhookHandler` | `app/services/stripe_webhook_handler.py` | Webhook event processing | ### API Endpoints All billing endpoints are under `/api/v1/vendor/billing`: | Endpoint | Method | Purpose | |----------|--------|---------| | `/billing/subscription` | GET | Current subscription status & usage | | `/billing/tiers` | GET | Available tiers for upgrade | | `/billing/checkout` | POST | Create Stripe checkout session | | `/billing/portal` | POST | Create Stripe customer portal session | | `/billing/invoices` | GET | Invoice history | | `/billing/addons` | GET | Available add-on products | | `/billing/my-addons` | GET | Vendor's purchased add-ons | | `/billing/cancel` | POST | Cancel subscription | | `/billing/reactivate` | POST | Reactivate cancelled subscription | ## Subscription Tiers ### Default Tiers | Tier | Price | Products | Orders/mo | Team | |------|-------|----------|-----------|------| | Essential | €49/mo | 200 | 100 | 1 | | Professional | €99/mo | Unlimited | 500 | 3 | | Business | €199/mo | Unlimited | 2000 | 10 | | Enterprise | Custom | Unlimited | Unlimited | Unlimited | ### Tier Features Each tier includes specific features stored in the `features` JSON column: ```python tier.features = [ "basic_support", # Essential "priority_support", # Professional+ "analytics", # Business+ "api_access", # Business+ "white_label", # Enterprise "custom_integrations", # Enterprise ] ``` ## Limit Enforcement Limits are enforced at the service layer: ### Orders ```python # app/services/order_service.py subscription_service.check_order_limit(db, vendor_id) ``` ### Products ```python # app/api/v1/vendor/products.py subscription_service.check_product_limit(db, vendor_id) ``` ### Team Members ```python # app/services/vendor_team_service.py subscription_service.can_add_team_member(db, vendor_id) ``` ## Stripe Integration ### Configuration Required environment variables: ```bash STRIPE_SECRET_KEY=sk_test_... STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_TRIAL_DAYS=14 # Optional, default trial period ``` ### Webhook Events The system handles these Stripe events: | Event | Handler | |-------|---------| | `checkout.session.completed` | Activates subscription, links customer | | `customer.subscription.updated` | Updates tier, status, period | | `customer.subscription.deleted` | Marks subscription cancelled | | `invoice.paid` | Records payment, resets counters | | `invoice.payment_failed` | Marks past due, increments retry count | ### Webhook Endpoint Webhooks are received at `/api/v1/webhooks/stripe`: ```python # Uses signature verification for security event = stripe_service.construct_event(payload, stripe_signature) ``` ## Vendor Billing Page The vendor billing page is at `/vendor/{vendor_code}/billing`: ### Page Sections 1. **Current Plan**: Tier name, status, next billing date 2. **Usage Meters**: Products, orders, team members with limits 3. **Change Plan**: Upgrade/downgrade options 4. **Payment Method**: Link to Stripe portal 5. **Invoice History**: Recent invoices with PDF links ### JavaScript Component The billing page uses Alpine.js (`static/vendor/js/billing.js`): ```javascript function billingData() { return { subscription: null, tiers: [], invoices: [], async init() { await this.loadData(); }, async selectTier(tier) { const response = await this.apiPost('/billing/checkout', { tier_code: tier.code, is_annual: false }); window.location.href = response.checkout_url; }, async openPortal() { const response = await this.apiPost('/billing/portal', {}); window.location.href = response.portal_url; } }; } ``` ## Add-ons ### Available Add-ons | Code | Name | Category | Price | |------|------|----------|-------| | `domain` | Custom Domain | domain | €15/year | | `ssl_premium` | Premium SSL | ssl | €49/year | | `email_5` | 5 Email Addresses | email | €5/month | | `email_10` | 10 Email Addresses | email | €9/month | | `email_25` | 25 Email Addresses | email | €19/month | ### Purchase Flow 1. Vendor selects add-on on billing page 2. For domains: enter domain name, validate availability 3. Create Stripe checkout session with add-on price 4. On webhook success: create `VendorAddOn` record ## Exception Handling Custom exceptions for billing operations (`app/exceptions/billing.py`): | Exception | HTTP Status | Description | |-----------|-------------|-------------| | `PaymentSystemNotConfiguredException` | 503 | Stripe not configured | | `TierNotFoundException` | 404 | Invalid tier code | | `StripePriceNotConfiguredException` | 400 | No Stripe price for tier | | `NoActiveSubscriptionException` | 400 | Operation requires subscription | | `SubscriptionNotCancelledException` | 400 | Cannot reactivate active subscription | ## Testing Unit tests for the billing system: ```bash # Run billing service tests pytest tests/unit/services/test_billing_service.py -v # Run webhook handler tests pytest tests/unit/services/test_stripe_webhook_handler.py -v ``` ### Test Coverage - `BillingService`: Subscription queries, checkout, portal, cancellation - `StripeWebhookHandler`: Event idempotency, checkout completion, invoice handling ## Migration ### Creating Tiers Tiers are seeded via migration: ```python # alembic/versions/xxx_add_subscription_billing_tables.py def seed_subscription_tiers(op): op.bulk_insert(subscription_tiers_table, [ { "code": "essential", "name": "Essential", "price_monthly_cents": 4900, "orders_per_month": 100, "products_limit": 200, "team_members": 1, }, # ... more tiers ]) ``` ### Setting Up Stripe 1. Create products and prices in Stripe Dashboard 2. Update `SubscriptionTier` records with Stripe IDs: ```python tier.stripe_product_id = "prod_xxx" tier.stripe_price_monthly_id = "price_xxx" tier.stripe_price_annual_id = "price_yyy" ``` 3. Configure webhook endpoint in Stripe Dashboard: - URL: `https://yourdomain.com/api/v1/webhooks/stripe` - Events: `checkout.session.completed`, `customer.subscription.*`, `invoice.*` ## Security Considerations - Webhook signatures verified before processing - Idempotency keys prevent duplicate event processing - Customer portal links are session-based and expire - Stripe API key stored securely in environment variables