forklaunch 1.35.1

Launch faster with forklaunch
# {{{name}}}

Tenant-bound persistent billing with thirty plan, subscription, checkout session, payment link and billing portal operations, compliance and health. Internal routes use request HMAC; billing portal routes require an asymmetric JWT with the `admin` role. HTTP and Streamable HTTP MCP share the listener and authentication rules. MCP is served at `/mcp`; `MCP_ENABLED` (true or false) is required, and `MCP_PATH`, `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` are optional.

The crate follows the generated service layout. `config.rs` reads trusted settings, `registrations.rs` builds the store, provider client and `AppState`, and `api/routes/<resource>.rs` binds each billing contract to the controller in `api/controllers/<resource>.rs`. Controllers are the module's own code: each maps its request through the operation's contract, calls the audited billing service and replies in the module's wire format, so edit one to customize that operation. `forklaunch-billing-stripe` keeps the services, provider flows, webhooks, bound capabilities, audit and authorization. Routers added with `forklaunch init router` live in `api/routes/extensions`.

Apply `cargo run -p {{{name}}} -- --migrate` with a schema owner. Run the service with a separate role without SUPERUSER/BYPASSRLS: schema USAGE and SELECT/INSERT/UPDATE/DELETE on `fl_billing.records`, plus INSERT on `fl_billing.sensitive_read_audit`. Every read uses the configured tenant and audits the verified actor. Provider data and opaque session URIs use tenant-derived encryption. Retain previous keys with `LEGACY_ENCRYPTION_KEYS` during rotation.

Checkout/payment-link/portal records use a ten-minute PostgreSQL TTL cache. Set `BILLING_DATABASE_BACKUP=true` for durable backups. Provider session expiry remains separate from cache expiry. Plans and subscriptions are durable.

{{#stripe}}The managed gateway is optional. When `STRIPE_API_KEY` (or the existing native `STRIPE_SECRET_KEY` alias) is set, the service calls Stripe with that key, even if gateway settings are also present. Without a key, `PLATFORM_GATEWAY_URL`, `INSTANCE_ID` and `INSTANCE_HMAC_KEY` configured together send instance-signed requests through the platform gateway; `INSTANCE_HMAC_KEY` also verifies platform payment events in either mode. Signatures bind query filters and exact form bytes; each call uses a fresh nonce. Gateway URLs require HTTPS except explicit loopback fixtures. Incomplete managed settings fail startup.

The provider performs authenticated Stripe API requests pinned to the repository SDK version. `stripeFields` contains provider request fields and response objects. Provider calls precede local persistence; external effects can need reconciliation after transport or database failures. Plan replacement deletes the old provider plan first. Cancelled subscriptions cannot be resumed. Mutation retries are disabled.{{/stripe}}

Offline contract and OpenAPI exports do not require live database or provider settings. Add custom routes under `src/routes`. Compliance export/erasure require a `system`-role JWT. The current TS billing entities classify all fields as non-PII, so these endpoints report no classified data with HTTP 404. Provider payloads and session URLs remain encrypted in native storage.

{{#stripe}}Direct `POST /webhook` callbacks verify the exact raw JSON body against optional `STRIPE_WEBHOOK_SECRET`; absent credentials reject the callback. Managed `POST /platform-events/payments` callbacks verify the instance HMAC key, platform key ID, path, raw body and five-minute clock skew. Set `INSTANCE_HMAC_KEY` for this surface. Nonces are claimed atomically across replicas and retained for ten minutes to cover future-dated credentials. Grant SELECT/INSERT on `fl_billing.webhook_receipts` and SELECT/INSERT/DELETE on `fl_billing.platform_event_nonces` to the runtime role. Migration 4 also requires SELECT/INSERT/DELETE on `fl_billing.webhook_attempts`; the operator reconciliation role needs INSERT on `fl_billing.webhook_reconciliation_audit`.

Successful Stripe event receipts persist across restarts and are shared by direct and platform delivery. Concurrent duplicate processing returns 503 for retry. Intent is committed before callback effects. Failed, timed-out or interrupted attempts remain blocked with HTTP 503 until an operator checks provider/local state and resolves the attempt. Use `PostgresBillingStore::pending_webhooks(limit)` and `resolve_webhook(&attempt, WebhookResolution::Applied | RetryAllowed)` with an actor/correlation context. Applied suppresses future deliveries; RetryAllowed explicitly permits re-execution after verifying it is safe. Resolution is audited atomically and rejects stale attempts or active callbacks. Metadata contains no raw payloads or secrets. Provider effects and local writes remain separate transactions; the API records an operator decision and does not automatically repair them. Product updates visit bounded provider pages; unsupported event types are acknowledged and recorded. Portal-event expiry uses provider Unix seconds plus five minutes; configured statuses include SUCCESS/CREATED/UPDATED.{{/stripe}}

Migration 5 adds optional administrative Stripe identity bindings, without changing existing service IDs or provider routing. An operator explicitly provisions a tenant/account/live-or-test/deployment/kind-scoped public UUID alias for an existing durable record; storage keys and ciphertext stay unchanged. Cache-only ownership is rejected. Grant the operator SELECT/UPDATE on `fl_billing.records`, SELECT/INSERT/DELETE on `fl_billing.provider_bindings`, and SELECT/INSERT on `fl_billing.provider_binding_audit`, with schema USAGE and a nonblank actor/correlation context. Audit RLS allows only SELECT/INSERT for non-bypass roles. Revocation is generation-checked and must precede deletion of the durable record. These Rust store APIs make no provider calls, expose no HTTP route, and do not infer account/mode from API keys or `stripeFields`. No automatic backfill or cache-expiry ownership policy is enabled by this foundation. The explicit `postgres-identity` checkout read integrates these bindings as described below; existing trait methods retain their behavior.

{{#stripe}}The direct Stripe client also exposes `verify_context(expected_account, expected_livemode)` for trusted account/mode verification through authenticated account and balance GETs. Its sealed context expires five seconds after verification begins and is bound to one immutable client generation; it does not establish local ownership or gate the current wrappers automatically. Obtain fresh verification for each future integrated operation. Managed verification requires the companion signed JSON `/payments/context` and `/payments/context/checkout-read` protocol and its payment-binding-generation migration. It verifies account/mode, carries a short-lived credential/base-bound token and rechecks the exact mapping generation around pinned checkout GETs; old gateways fail closed. The raw proxy account/balance allowlist stays unchanged. Credential/base/account rotation invalidates outstanding contexts. The full five-second lifetime includes verification, network and database waits. With `postgres-identity`, `get_bound_checkout_session` additionally checks tenant-owned durable alias generation, audits encrypted-row reads and requires the provider customer to match the durable customer relation. It preserves storage keys/ciphertext and adds no HTTP route or financial mutation capability. `get_bound_payment_link` provides the analogous explicitly provisioned durable payment-link alias read for direct or managed clients, verifies provider object/ID/live mode, and preserves local amount/status. Managed payment-link reads require the dedicated signed `/payments/context/payment-link-read` endpoint and matching scope envelope; old gateways fail closed. Default service methods still use provider IDs. API key prefixes and instance IDs are never substituted for verified provider context.{{/stripe}}