Creem

Creem checkout and webhook provider for the Next.js template.

Creem is the default provider. It implements the shared PaymentProvider interface in src/lib/payment/providers/creem.ts: it creates hosted checkouts, verifies webhook signatures, and handles a couple of lifecycle events.

Configuration

VariablePurpose
NEXT_PUBLIC_PAYMENT_PROVIDERSet to creem (the default) to select this provider.
CREEM_SERVER_IDXSelects the Creem server (sandbox vs. live). Defaults to 0.
CREEM_API_KEYServer key for creating checkouts.
CREEM_WEBHOOK_SECRETHMAC secret for verifying webhook signatures.
NEXT_PUBLIC_CREEM_PRODUCT_PRO_MONTHLY / _PRO_YEARLY / _LIFETIMEPublic Creem product IDs for the Pro monthly, Pro yearly, and Lifetime plans.

See Environment Variables.

Prices vs. product IDs

src/config/price-config.ts holds the display prices and plan metadata. Each plan's productId comes from a NEXT_PUBLIC_CREEM_PRODUCT_* variable and is what Creem actually charges for. The display amount and the Creem product are independent — keep them consistent yourself.

The create route inserts the payment row with the display amount from price-config.ts, while the real charge is determined by the Creem product. Reconcile the stored amount/currency with the Creem product if you rely on it.

Checkout

createCheckout builds a Creem checkout for the requested productId and returns its URL. The create route redirects the browser there, with success URL {origin}/pay-success?orderId={orderNo}.

Webhook

  • Path: POST /api/payment/notify/creem (served by the shared /api/payment/notify/[provider] route).
  • Signature: HMAC-SHA256 of the raw body using CREEM_WEBHOOK_SECRET, compared against the hex creem-signature header with a constant-time check. A missing/invalid signature makes the route answer 400.
  • Handled events:
    • checkout.completed — marks the matching payment row success and stores the Creem order/customer IDs.
    • Subscription lifecyclesubscription.active, subscription.paid, subscription.update, subscription.trialing, subscription.paused, subscription.past_due, subscription.scheduled_cancel, subscription.canceled, and subscription.expired upsert the user's subscription row to the matching status, period, and cancel timestamps.
    • refund.created — marks the matching payment row refunded.

Local testing

  • Use the Creem sandbox server (CREEM_SERVER_IDX) and sandbox product IDs.
  • Expose your local webhook with a tunnel (e.g. cloudflared / ngrok) and register https://<tunnel>/api/payment/notify/creem in Creem.
  • Confirm the payment row flips to success after checkout.completed.

Production notes

  • Switch to the live Creem server and live product IDs (rebuild for the public NEXT_PUBLIC_* IDs to take effect).
  • Webhooks can be retried; the handlers are not idempotent, so add event-id dedup if you extend them with non-terminal side effects.

Verify

  • Pricing CTA creates a checkout and redirects to Creem.
  • After paying in sandbox, the success page shows the order.
  • The webhook marks the payment row success.
  • A subscription product creates/updates the subscription row.

On this page