Stripe Integration

Stripe payment integration covering customer creation, subscription management, webhook handling, billing portal, and test mode

Overview

The backend service handles all Stripe integration including customer management, subscription plans, checkout, invoicing, and webhook processing.

Customer Creation

# backend/api/infrastructure/externals/stripe_resolver.py
async def create_stripe_customer(email: str, name: str, tenant_id: str) -> str:
    customer = await stripe.Customer.create_async(
        email=email,
        name=name,
        metadata={"tenant_id": tenant_id}
    )
    return customer.id

Subscription Management

Trial Subscription

async def create_trial_subscription(tenant_id: str, price_id: str, customer_id: str):
    subscription = await stripe.Subscription.create_async(
        customer=customer_id,
        items=[{"price": price_id}],
        payment_behavior="default_incomplete",
        metadata={"tenant_id": tenant_id}
    )
    # Updates tenant with subscription_id, is_trial=True, trial_end_at

Checkout Session

curl -X POST http://localhost:8000/api/v1/checkout/tenant/subscription \
  -H "Authorization: Bearer <token>" \
  -d '{"price_id": "price_abc123"}'

Webhook Handling

Stripe sends webhooks to POST /api/v1/stripe/webhook. The service handles:

EventAction
checkout.session.completedActivate subscription
customer.subscription.updatedSync subscription status
customer.subscription.deletedDowngrade to free plan
customer.subscription.trial_will_endSend trial-ending notification
invoice.payment_succeededVerify active status
invoice.payment_failedHandle payment failure
@router.post("/stripe/webhook")
async def handle_stripe_webhook(request: Request):
    payload = await request.body()
    sig_header = request.headers.get("stripe-signature")
    event = stripe.Webhook.construct_event(payload, sig_header, WEBHOOK_SECRET)
    # Route to handler based on event.type

Dormant Tenant Detection

When a trial expires without payment:

  1. Celery task check_expired_trials runs hourly
  2. Stripe webhook customer.subscription.updated with status=incomplete_expired
  3. Tenant set to is_dormant=True, is_trial=False

Reactivation options:

  • Add payment method: Stripe SetupIntent flow
  • Downgrade to free: POST /api/v1/billing/subscription/downgrade-to-free

Test Mode

For local development, use Stripe test keys:

STRIPE_API_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_test_...

Use the fake-smtp container for email testing (Mailhog at port 1083).