Skip to main content

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-session
    • POST /api/v1/billing/portal-session
    • POST /api/v1/billing/webhook (Stripe signature verified, no auth middleware)
    • GET /api/v1/billing/summary (safe summary for UI)

Store a single billing link per org (in its own collection or nested on org):

  • provider: "stripe"
  • stripeCustomerId
  • subscriptionId
  • status: active | past_due | canceled
  • planKey: 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, optionally locationId
  • billing.status, billing.planKey
  • customerConfig feature 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.completed
  • customer.subscription.created|updated|deleted
  • invoice.payment_succeeded
  • invoice.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 / Invoice
  • InvoiceLineItem
  • Payment (provider-agnostic)

Then choose one rollout path:

  1. Stripe Payment Links (fastest): generate a payment link for an invoice total; mark paid via webhook.
  2. 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 locationId field to invoice drafts/records (optional, default null)
  • Feature gate limits such as limits.trucking.maxLocations

Rollout Plan (Backwards Compatible)​

  1. Add billing domain + webhooks in monitor-only mode (no gating).
  2. Add feature gate service and surface a billing status UI (read-only).
  3. Require subscription for new orgs only.
  4. Pilot internal invoice drafts for trucking jobs (no payments yet).
  5. 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).