Waffo Payments
Configure Waffo Pancake hosted checkout, the consumer portal, and Webhooks
Waffo Pancake Payment Integration
EasyStarter's web app includes Waffo Pancake payment support for web-only scenarios, including:
- Hosted subscription checkout (monthly / yearly)
- Hosted one-time lifetime purchase checkout
- Waffo product trials
- Waffo Consumer Portal
- Cancellation at the end of the billing period
- Webhook handling for orders, subscriptions, renewals, and refunds
Payment setup is split into two parts:
- Environment variables: Merchant ID, private key, and runtime environment in the server's
.dev.vars/.env.production - Pricing plans: Waffo Product IDs and pricing metadata in
packages/app-config/src/app-config.ts
The Waffo SDK is server-side only. Never expose
WAFFO_PRIVATE_KEYthrough web environment variables or frontend code, and never commit it to Git.
Required environment variables
WAFFO_MERCHANT_ID=
WAFFO_PRIVATE_KEY=
WAFFO_ENVIRONMENT=test| Variable | Description |
|---|---|
WAFFO_MERCHANT_ID | Merchant ID in the MER_xxx format; this is not a Store ID |
WAFFO_PRIVATE_KEY | RSA private key for the current environment, used only by the server SDK |
WAFFO_ENVIRONMENT | Webhook verification environment: test in development and prod in production |
Get the Merchant ID and test private key
- Sign in to the Waffo Pancake Merchant Dashboard
- Open Integration and select Test Mode
- Copy the Merchant ID at the top of the page (
MER_xxx) - Create a test key under Create API Key, then copy the private key or use Copy .env config
- Add the credentials to the local server environment:
WAFFO_MERCHANT_ID=MER_xxxxxxxxxxxxxxxxxxxxxxxx
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
WAFFO_ENVIRONMENT=testWaffo isolates test and production API keys. When switching the Dashboard environment, replace both the private key and
WAFFO_ENVIRONMENT; otherwise API requests or Webhook verification will fail.
Keep the PEM format copied from the Dashboard. For a single-line .env value, preserve the quotes and escaped \n line breaks; the Waffo SDK normalizes the PEM value automatically.
Create products in Waffo
EasyStarter defaults to three plans: Free, Pro (monthly + yearly), and Lifetime (one-time purchase). The Free plan does not require a Waffo product.
Open Products in the Merchant Dashboard and create these three products:
| EasyStarter price | Waffo product type | Waffo billing period |
|---|---|---|
| Pro Monthly | Subscription | Monthly |
| Pro Yearly | Subscription | Yearly |
| Lifetime | One-time | Not applicable |
After saving each product, copy its Product ID (PROD_xxx). EasyStarter stores Waffo Product IDs in the shared providerPriceId field.
If you offer a trial, configure its duration on the Waffo subscription product or product group and keep the next step's trialDays value in sync.
Configure pricing plans
Add the Waffo Product IDs to web.payments.plans in packages/app-config/src/app-config.ts:
web: {
payments: {
provider: "waffo",
plans: [
{
id: "free",
},
{
id: "pro",
prices: [
{
id: "monthly",
provider: "waffo",
test: {
providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Test monthly Product ID
},
prod: {
providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Production monthly Product ID
},
currency: "usd",
amountCents: 1000,
priceType: "subscription",
interval: "month",
trialDays: 7,
status: "active",
},
{
id: "yearly",
provider: "waffo",
test: {
providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Test yearly Product ID
},
prod: {
providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Production yearly Product ID
},
currency: "usd",
amountCents: 10000,
priceType: "subscription",
interval: "year",
trialDays: 7,
status: "active",
},
],
},
{
id: "lifetime",
prices: [
{
id: "lifetime",
provider: "waffo",
test: {
providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Test one-time Product ID
},
prod: {
providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Production one-time Product ID
},
currency: "usd",
amountCents: 20000,
priceType: "lifetime",
status: "active",
},
],
},
],
},
},Field reference:
| Field | Description |
|---|---|
provider | Set both the web default and every price provider to "waffo" |
test.providerPriceId | Waffo Product ID available in the test environment (PROD_xxx) |
prod.providerPriceId | Published Waffo Product ID available in production (PROD_xxx) |
amountCents | Amount displayed by EasyStarter in cents; it must match the Waffo product price |
priceType | "subscription" for recurring or "lifetime" for a one-time purchase |
interval | "month" or "year" for subscriptions; omit for Lifetime |
trialDays | A non-empty value asks checkout to enable a trial; Waffo controls the actual duration |
status | "active" to enable or "archived" to hide the price |
Waffo product APIs use display amounts such as
"10.00". EasyStarter's local catalog still uses cents, so$10.00isamountCents: 1000.
Configure Waffo Webhooks
Webhooks are required to grant entitlements after checkout and keep subscription state synchronized.
-
Open Settings → Webhooks in the Waffo Merchant Dashboard
-
Add an HTTP Webhook and select the test or production environment that matches the current credentials
-
Set the Endpoint URL:
- Local development:
https://your-ngrok-url/api/webhooks/waffo - Production:
https://your-server.workers.dev/api/webhooks/waffo
- Local development:
-
Subscribe to the events handled by EasyStarter:
Event EasyStarter behavior order.completedComplete a one-time purchase or credit package order subscription.activatedActivate the subscription subscription.payment_succeededSynchronize a successful renewal subscription.cancelingMark cancellation at period end and keep current access subscription.uncanceledRestore a subscription that was scheduled to cancel subscription.updatedSynchronize subscription changes subscription.canceledMark the subscription as terminated subscription.past_dueMark a failed renewal as past due refund.succeededRevoke the corresponding lifetime or credit entitlement refund.failedRecord delivery without changing entitlements
Waffo signs the raw request body with RSA-SHA256 and sends the signature in x-waffo-signature. EasyStarter reads the raw text and verifies it with the Waffo SDK, so no separate Webhook secret is required.
Use ngrok to forward the Server's
3001port during local development. Avoid tunnel services that strip custom request headers, because losingx-waffo-signaturemakes verification impossible.
ngrok http 3001Start the app and complete a sandbox checkout
Start the Web and Server apps:
pnpm dev:web+serverOpen the pricing page and complete a test checkout. Waffo checkout opens in a new tab so EasyStarter keeps the current merchant page state.
| Scenario | Test card number |
|---|---|
| Successful payment | 4576 7500 0000 0110 |
| Declined payment | 4576 7500 0000 0220 |
Use any future expiry date and any CVC. After payment, confirm the Server receives POST /api/webhooks/waffo with a 200 response, then check the subscription or lifetime entitlement at /settings/billing.
Production setup
Complete these checks before launch:
- Switch the Waffo Dashboard to Live Mode, then create and copy a separate production private key
- Publish the required products and confirm every
prod.providerPriceIdpoints to a Product ID available in production - Register
https://your-server.workers.dev/api/webhooks/waffoin the production environment - Add the production server variables:
WAFFO_MERCHANT_ID=MER_xxxxxxxxxxxxxxxxxxxxxxxx
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
WAFFO_ENVIRONMENT=prod- Follow Deploy Server to upload production secrets and deploy
Do not reuse the test private key in production, and do not configure a production Product ID before that product has been published.
Consumer Portal and current limitations
Users can open the Waffo Consumer Portal from /settings/billing. The current EasyStarter integration has these capability boundaries:
- Cancellation at the end of the current billing period is supported
- A buyer must undo a scheduled cancellation in the Waffo portal
- Waffo's Change Subscription Product endpoint is not implemented yet and currently always returns
501 Not Implemented, so EasyStarter cannot offer in-app subscription upgrades or downgrades until Waffo makes this platform capability available - Waffo Webhooks still synchronize uncancellations and subscription changes completed in the portal
For platform details, see the official Waffo SDK integration guide and Webhook guide.