Payments Overview
Subscription billing and one-time purchases with Creem.
Overview
Wearlo uses Creem for payment processing, supporting both recurring subscriptions and one-time credit pack purchases.
Subscription Plans
Configured in constants/billing.ts:
| Plan | Price | Credits |
|---|---|---|
| Starter Monthly | $29/mo | 1,000/month |
| Starter Yearly | $290/yr | 12,000/year (1,000/month installments) |
| Pro Monthly | $99/mo | 10,000/month |
| Pro Yearly | $990/yr | 120,000/year (10,000/month installments) |
One-Time Credit Packs
| Pack | Price | Credits |
|---|---|---|
| 200 Credits | $5 | 200 |
Payment Flow
- User clicks buy → Frontend calls
/api/payments/creem/checkout - Durable intent created → The server freezes product, price and credits before calling Creem. A 200 response includes the checkout URL; a 202 response requires polling
/api/payments/creem/checkouts/{checkoutId}. - User completes payment → Creem sends webhook to
/api/payments/creem/webhook - Webhook receipt → Verify the raw-byte HMAC-SHA256 signature and commit an encrypted inbox event with its processing job before acknowledging it.
- Finance worker → Verify current provider objects and the canonical order, then commit payment, credit ledger, balance, subscription and installment changes together. Duplicate deliveries cannot grant twice. Notifications run in a separate job.
The browser's success return is not payment proof. Unknown checkout creation is retained for reconciliation; the server does not repeat the create request. Refunds, disputes and unproven historical orders enter review.
Annual Subscription Installments
Annual plans don't grant all credits at once. Instead, credits are distributed monthly:
- Year plan purchased → first month's credits granted immediately
- Remaining 11 months → credits scheduled in
subscriptionCreditScheduletable - Cron job (
/api/cron/subscription-grants) runs hourly to process scheduled grants
Testing
Use CREEM_MODE="test", matching test products and CREEM_API_BASE="https://test-api.creem.io". Configure an independent WARDROBE_PAYMENT_INBOX_KEY (64 hex characters) and an HTTPS application origin. CREEM_SIMULATE=true is rejected. Synthetic provider tests cover failures without real merchant requests; live test-mode payments remain a separate release check.
Start the finance worker only after the database migrations and configuration have been reviewed. The webhook receipt alone does not process grants or send notifications.