Stripe Billing Foundation (SaaS Subscriptions now, Invoicing later)
Context / Goalsβ
AttuneLogic is a multi-tenant platform (initial focus: trucking). We want to:
- Implement SaaS subscriptions first (monthly, org pays, no trial).
- Add feature gating as a first-class concept (independent of UI).
- Later add job invoicing and other payments without breaking changes.
Non-goals (for this phase):
- Full job invoicing workflow
- Marketplace/payout flows (Stripe Connect) unless explicitly required
Principlesβ
- Backwards compatible: existing tenants should continue working while billing is rolled out.
- Webhook-driven truth: billing state is derived from Stripe webhooks, not front-end redirects.
- Provider-agnostic domain: internal models refer to
provider+providerIds(Stripe first). - Tenant-safe: billing actions are scoped to the authenticated org context (no client-supplied org IDs).
Proposed Architectureβ
1) Billing Domain (API)β
Add a billing bounded context to @attunelogic-api:
- Services
billing.service(orchestration + domain logic)stripe-billing.service(Stripe adapter)billing-webhook.service(webhook verification + idempotent processing)
- Routes
POST /api/v1/billing/checkout-sessionPOST /api/v1/billing/portal-sessionPOST /api/v1/billing/webhook(Stripe signature verified, no auth middleware)GET /api/v1/billing/summary(safe summary for UI)
2) Billing Link per Organizationβ
Store a single billing link per org (in its own collection or nested on org):
provider: "stripe"stripeCustomerIdsubscriptionIdstatus: active | past_due | canceledplanKey: founding_monthly(internal stable identifier)
Also store a small event log for debugging + idempotency:
processedStripeEventIds: string[](or a separate collection with unique index)
3) Feature Gate Serviceβ
Introduce a server-side feature gate evaluator:
FeatureGateService.can(featureKey, ctx) -> boolean (+ reason)
Inputs:
orgId,appType,userRole, optionallylocationIdbilling.status,billing.planKeycustomerConfigfeature flags and limits
This allows gradual enforcement:
- Monitor-only mode
- Gate new tenants first
- Gate specific features later
Stripe Subscription Flow (Phase 1)β
Checkoutβ
- Create a Stripe Checkout Session in subscription mode.
- Use a single βfoundingβ monthly price to start (
founding_monthly). - Redirect the user back to the app after payment setup.
Webhooks (source of truth)β
Handle at minimum:
checkout.session.completedcustomer.subscription.created|updated|deletedinvoice.payment_succeededinvoice.payment_failed
Update billing status in your DB from these events.
Invoicing & Payments (Phase 2+)β
Add internal invoice models without changing existing job logic:
InvoiceDraft/InvoiceInvoiceLineItemPayment(provider-agnostic)
Then choose one rollout path:
- Stripe Payment Links (fastest): generate a payment link for an invoice total; mark paid via webhook.
- Stripe Invoicing (full invoice lifecycle): create invoice items + finalize/send invoices in Stripe; sync status back.
Multi-location Considerationsβ
Multi-location should be supported by:
- Adding a
locationIdfield to invoice drafts/records (optional, default null) - Feature gate limits such as
limits.trucking.maxLocations
Rollout Plan (Backwards Compatible)β
- Add billing domain + webhooks in monitor-only mode (no gating).
- Add feature gate service and surface a billing status UI (read-only).
- Require subscription for new orgs only.
- Pilot internal invoice drafts for trucking jobs (no payments yet).
- Enable Stripe Payment Links or Stripe Invoicing for selected tenants.
Risks / Pitfallsβ
- Confusing Stripe Connect onboarding with Stripe Billing (subscriptions typically do not require Connect).
- Environment mismatch (test vs live keys/webhooks).
- Missing webhook idempotency causing duplicated state updates.
- Allowing client-supplied org identifiers for billing operations (tenant leakage risk).