Billing
Enable a payment gateway and verify checkout, signed webhooks, credits, cancellation, and refunds.
Billing starts disabled
Flare includes Stripe, Creem, Dodo, and Waffo adapters. By default, product.billing.enabled is empty and all gateway product mappings are blank. Included source code does not mean a fresh checkout can accept payments. The public demo is not a real-payment acceptance environment.
Your application's offers live in packages/config/src/index.ts. They are independent of the MuseMVP website's template prices, product IDs, and checkout rules.
1. Create your offers
The catalog contains two examples:
| planId | amount / currency | Purchase type | credits |
|---|---|---|---|
pro-monthly | 1900 / USD | Recurring | 1000 |
lifetime | 9900 / USD | One-time | 10000 |
Amounts are integer minor units, not floating-point dollars. Change currency, recurrence, credits, provider products, and displayed copy together. Editing a price label alone does not change checkout.
Create matching gateway products and fill each plan's products mapping. Stripe expects a price_ price ID, Dodo a pdt_ product ID, Creem its product ID, and Waffo its gateway product identifier. Keep sandbox/live credentials, products, and webhooks in the same environment.
2. Enable a gateway and provide secrets
For example, replace the existing product initializer with:
export const product = productSchema.parse({
billing: { enabled: ['stripe'], defaultGateway: 'stripe' },
});Do not add a duplicate declaration at the end of the file. defaultGateway must belong to enabled. Multiple enabled gateways each need complete product mappings and credentials.
| Gateway | Server variables |
|---|---|
| Stripe | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET |
| Creem | CREEM_API_KEY, CREEM_WEBHOOK_SECRET |
| Dodo | DODO_API_KEY, DODO_WEBHOOK_SECRET |
| Waffo | WAFFO_MERCHANT_ID, WAFFO_PRIVATE_KEY |
Use apps/web/.dev.vars locally and root .env or encrypted CI variables for deployment. Store Waffo's private key on one line with escaped newlines. Never expose secrets through publicConfig, browser bundles, or public logs.
3. Register signed webhooks
Use https://your-domain.com/api/webhooks/<gateway>, for example /api/webhooks/stripe. Flare does not use the Next.js starter's billing webhook path.
Signature checks, event-ID deduplication, order mapping, ledger writes, and the outbox determine payment outcomes. A successful browser return URL does not prove settlement. Wait for the server to process the signed event, then inspect the order and credits in billing and usage.
Checkout accepts {planId,gateway,requestId} with a UUID requestId. Preserve that identifier when retrying the same purchase attempt; use a new one for a new purchase. The server requires a session and an allowed plan/gateway combination.
4. Verify customer operations
/app/billing submits portal or cancel for an owned order to POST /api/billing/manage. All four gateways retain their adapters, but account capabilities and errors must be handled according to the actual gateway response.
Refunding a charge and cancelling renewal are separate operations. Stripe, Creem, and Dodo support full refunds of the latest eligible payment. Waffo refund creation is performed in its dashboard and reconciled through signed events. Partial refunds do not automatically reverse credits proportionally. Do not implement an unrelated client-side balance adjustment.
Administration and retries
/app/admin exposes orders, webhooks, refunds, and audit records. Duplicate events must reuse their recorded result without granting credits again. For timeouts or unknown provider outcomes, inspect the provider and audit history before attempting another refund. Preserve leases, idempotent ledger boundaries, and outbox behavior.
A refund does not automatically stop the next renewal. Verify payment, refund, and subscription states separately.
Acceptance before launch
- Run
pnpm muse:doctor --target production --jsonand resolve enabled-gateway blockers. - Complete controlled checkout and compare amount, currency, recurrence, and product.
- Replay the same signed event and confirm a single entitlement/credit outcome.
- Exercise failed payment, cancellation, portal, refunds, and cross-user order isolation.
- Compare webhook history and ledger entries. A return page or local mock is not proof of a real payment.
For missing checkout controls, check enabled. For checkout failures, check product mappings and environment. For rejected events, verify signature configuration. For missing credits, inspect processing state before issuing a manual grant. See environment variables and deployment.