Payment
Pay channels, price plans, Stripe catalog fetch, project bindings, and localized pricing copy.
Last updated Mar 21, 2026
Payments are handled by node3-pay-service with Stripe, Creem, Dodo Payments, Waffo Pancake, and configurable plans. The usual model is: create pay channels first, map each plan to the provider's product IDs in Price Config, then bind every project (app_key) to a channel so checkout and webhooks use the right credentials. Each app is bound to exactly one provider channel.


Recommended setup order
Pay Channels (
/pay-channels)
Add Stripe or Creem channels (keys, webhook secrets, etc.). One Stripe account can back one or many channel rows in Admin if you split by environment or brand.Price Config (
/price-config)
Create or edit plans: amount, billing interval, credits, feature flags. For Stripe, align each plan with Product ID and Price ID (below).Project ↔ channel binding (same Pay Channels page)
Bind each app_key to a Stripe or Creem channel. New projects stay non-functional for payments until this binding exists—the system must know which channel credentials to use.
This lets you ship new sites quickly and reuse one Stripe catalog across many projects instead of rebuilding a full store for every launch; add or split channels when you are ready.
Stripe Product ID and Price ID
In the Stripe Dashboard (Products), each price has a Price ID (price_...) under a Product ID (prod_...). Admin Price Config must store the correct IDs for checkout and webhooks to line up.
To avoid manual copy/paste mistakes, use Fetch products / prices from Stripe in the price editor:
- Calls the Stripe API using the selected pay channel’s secret key and lists products/prices you can pick and persist.
- Prerequisites: choose the app (
app_key) in the editor and ensure that app already has a Stripe channel binding in/pay-channels; otherwise fetch is blocked. - If you run multiple Stripe channels, you can choose which channel’s credentials to use for the catalog fetch (vs. the app’s default binding).
Fetch only wires IDs to Stripe’s catalog; customer-facing copy is still maintained in Admin (next section).
Localized pricing copy
Plans support per-locale display fields (e.g. title, description, badge, feature bullets), typically stored as structured display_i18n keyed by locale (en, zh, …).
- You author and maintain each locale’s text in the Admin UI (including any bulk / side-by-side editors). Stripe does not supply localized marketing strings for these cards.
- Where AI Gateway is enabled, optional helpers may fill missing locales; always review before publishing.
The user-facing app picks the locale from site or user settings and falls back when a locale is missing (behavior follows the storefront implementation).
Creem
Enable Creem under Pay Channels and add the API key; project binding to that channel is still required for the app to process Creem payments. Plan fields follow Creem’s model (not Stripe Product/Price IDs—use the Admin form and API as reference).
Dodo Payments
- In the Dodo dashboard, go to Developer → API Keys and create a key. Test keys only work against the test API; live keys only against live.
- In Admin Pay Channels, create a Dodo Payments channel with the API key and set Environment (
testorlive) — required, it selects the API host. - Add a webhook endpoint
https://<node3-host>/dodo/webhookin Dodo and copy its signing secret (whsec_…) into the channel's webhook secret field. Subscribe to:payment.succeeded,subscription.active,subscription.renewed,subscription.cancelled,subscription.expired,subscription.on_hold,subscription.updated,subscription.plan_changed,refund.succeeded,refund.failed,dispute.opened,dispute.lost. - In Price Config, set each plan's Dodo Product ID (
pdt_…). - Bind the channel to the app (
app_key).
Buyers manage Dodo subscriptions through the Dodo customer portal (/dodo/portal returns a session link). In-app plan change/cancel is Stripe-only.
Waffo Pancake
- In the Waffo dashboard, copy your Merchant ID (
MER_…) from API & Development — notstoreId. - Create an API key and download the private key (PEM). Create a Waffo Pancake channel in Admin Pay Channels with the private key and merchant ID; environment is informational.
- Create products in Waffo and publish them to production before live checkout. Put each product ID (
PROD_…) into Price Config. - Register the HTTP webhook
https://<node3-host>/waffo/webhookonce per environment (testMode true/false). Subscribe to:order.completed,subscription.activated,subscription.renewed,subscription.recovered,subscription.canceling,subscription.uncanceled,subscription.canceled,subscription.past_due,refund.succeeded,refund.failed. Webhooks are verified with the embedded Waffo platform public keys; you can optionally paste a webhook public key on the channel to override them. - Bind the channel to the app.
Notes: test card 4576750000000110; successUrl must be absolute https; there is no auto-redirect after payment (the buyer clicks Done); the customer portal is the shared Waffo consumer portal with magic-link login (/waffo/portal returns it). In-app plan change/cancel is Stripe-only.
Provider limitations
- ZShip coupons are not applied for Dodo or Waffo checkout (
couponsEnabledisfalsewhen the app's bound provider is dodo/waffo). - The payment simulator only covers Stripe and Creem.
- One provider per app — binding a second provider replaces the first.
After you create a project
Whenever you add a new project (app_key), open Pay Channels and confirm:
- A binding points that app to the correct Stripe, Creem, Dodo, or Waffo channel.
- Keys and webhook endpoints match production.
Without a binding, checkout or webhooks may not work for that site.
Admin routes
- Pay Channels (
/pay-channels) — channels, project bindings, webhook guidance - Price Config (
/price-config) — plans, Stripe fetch, localized copy, optional per-channel Stripe overrides
