Waffo Pancake
How to set up and use Waffo Pancake for payments and subscriptions
TanStarter uses Waffo Pancake for payment processing, supporting both one-time payments and subscriptions. Waffo Pancake is a Merchant of Record (MoR): it acts as the legal seller and handles global tax calculation, collection, compliance, and payouts on your behalf.
The integration uses the official @waffo/pancake-ts SDK with Waffo's hosted checkout and consumer portal, so no card data ever touches your server.
Setup
TanStarter template provides three pricing plans by default: a free plan, a pro subscription plan (monthly/yearly), and a lifetime plan (one-time payment). Follow these steps to set up:
Create Waffo Account
Sign up for a Waffo Pancake account at pancake.waffo.ai and create a store during onboarding. You can find more about the basics in the Waffo Quickstart.
Get API Keys
Get your Merchant ID and private key from the Waffo Dashboard:
- Go to Waffo Dashboard >
API & Developmentand clickCreate API Keyto generate a new key pair - Copy the Merchant ID (starts with
MER_) and the private key - Save them to your environment file as
WAFFO_MERCHANT_IDandWAFFO_PRIVATE_KEY
WAFFO_MERCHANT_ID is the Merchant ID — not a storeId and not a store identifier from a URL. API keys are bound to either test or production at creation time, so create a separate key for each environment.
The private key is server-only. When storing a PEM key in an environment variable, preserve the line breaks with escaped \n:
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----"You can find more about API Key authentication in the Waffo documentation.
Set Up Webhook
Set up a webhook and subscribe to the payment events:
- Go to Waffo Dashboard >
Settings>Webhooksand clickAdd Webhook - Choose the Raw payload format (only Raw carries the
X-Waffo-Signatureheader used for verification) - Enter Webhook URL:
https://YOUR-DOMAIN.com/api/webhooks/waffo - All events are subscribed by default. Keep at least the following:
order.completedsubscription.activatedsubscription.payment_succeededsubscription.updatedsubscription.cancelingsubscription.uncanceledsubscription.canceledsubscription.past_duerefund.succeededrefund.failed
Unlike Stripe and Creem, there is no webhook signing secret environment variable for Waffo. The @waffo/pancake-ts SDK embeds the verification public keys and auto-detects the environment, so verifyWebhook() handles signature verification for you.
You can find more about webhooks and webhook signature verification in the Waffo documentation.
Create Products and Pricing Plans
Create products in Waffo and set up pricing plans. Waffo uses Product IDs (starts with PROD_) for checkout, not price IDs:
- Go to Waffo Dashboard >
Productsand clickCreate Product - Create the Pro subscription plan products:
- Product type:
Subscription - Name:
Pro Plan - Billing period:
Monthly— save and copy the Product ID forVITE_WAFFO_PRODUCT_PRO_MONTHLY - Create a second subscription product with billing period
Yearly— save and copy the Product ID forVITE_WAFFO_PRODUCT_PRO_YEARLY
- Product type:
- Create the Lifetime plan product:
- Product type:
One-time - Name:
Lifetime Plan - Save and copy the Product ID for
VITE_WAFFO_PRODUCT_LIFETIME
- Product type:
Create separate subscription products for each billing period (monthly/yearly), as recommended in the Waffo subscriptions guide. New products start in test mode; publish them to production before you accept real payments — see Publish Product.
Add Environment Variables
Add the following environment variables:
# Payment provider
VITE_PAYMENT_PROVIDER=waffo
# Waffo API credentials (server-side only)
WAFFO_MERCHANT_ID=MER_...
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----"
# Optional: accept test-mode webhooks in a production build (smoke testing only)
# WAFFO_DEBUG=true
# Product IDs
VITE_WAFFO_PRODUCT_PRO_MONTHLY=PROD_...
VITE_WAFFO_PRODUCT_PRO_YEARLY=PROD_...
VITE_WAFFO_PRODUCT_LIFETIME=PROD_...Update Website Configuration
Update the payment section in src/config/website.ts to configure pricing plans — amounts, currencies, intervals, and plan metadata. The enable, provider, and priceId fields are automatically resolved from your environment variables (VITE_PAYMENT_PROVIDER and VITE_WAFFO_PRODUCT_*), so you don't need to hardcode them.
You must configure this section to match the products you created in Waffo:
payment: {
enable: isPaymentEnabled, // ← auto: true when VITE_PAYMENT_PROVIDER is set
provider: isPaymentEnabled ? paymentProvider : undefined, // ← auto: 'waffo'
price: {
plans: {
free: {
id: 'free',
prices: [],
isFree: true,
isLifetime: false,
},
pro: {
id: 'pro',
prices: [
{
type: 'subscription',
priceId: priceIds.proMonthly, // ← auto: from VITE_WAFFO_PRODUCT_PRO_MONTHLY
amount: 990, // amount in cents ($9.90)
currency: 'USD',
interval: 'month',
},
{
type: 'subscription',
priceId: priceIds.proYearly, // ← auto: from VITE_WAFFO_PRODUCT_PRO_YEARLY
amount: 9900, // amount in cents ($99.00)
currency: 'USD',
interval: 'year',
},
],
isFree: false,
isLifetime: false,
popular: true,
},
lifetime: {
id: 'lifetime',
prices: [
{
type: 'one_time',
priceId: priceIds.lifetime, // ← auto: from VITE_WAFFO_PRODUCT_LIFETIME
amount: 19900, // amount in cents ($199.00)
currency: 'USD',
allowPromotionCode: true,
},
],
isFree: false,
isLifetime: true,
},
},
},
},If you are setting up your environment, you can now go back to the Environment Configuration and continue. The rest of this document can be read later.
Environment Configuration
Set up environment variables
Core Features
- One-time payment for lifetime membership
- Recurring subscription payments (monthly/yearly)
- Free trial period support
- Hosted checkout with buyer attribution (authenticated checkout)
- Webhook handling for payment, subscription, and refund events
- Tax, compliance, and payouts handled by Waffo
- Consumer portal with Magic Link login
- Built-in pricing components (table, card, button)
- Server-side actions for secure payment operations
- Multiple pricing plan support (free, pro, lifetime)
Development
For local development, expose your server with an HTTPS tunnel so Waffo can reach your webhook endpoint. The official docs recommend ngrok; cloudflared also works. Do not use localtunnel — it strips custom HTTP headers such as X-Waffo-Signature:
ngrok http 3000
# or
cloudflared tunnel --url http://localhost:3000Then:
- Set the tunnel URL (e.g.
https://xxxx.ngrok-free.app/api/webhooks/waffo) as the test webhook URL in Waffo Dashboard >Settings>Webhooks. Never put a temporary tunnel URL into the production webhook. - Make a test purchase on the website and verify the event flow works as expected.
The template also ships a Waffo sandbox E2E suite:
pnpm e2e:waffoWaffo provides a complete test environment with test cards — no real charges. In the hosted sandbox checkout you can pick Credit/Debit Card and use the Quick Fill Success option to complete a payment instantly. See Test Mode in the Waffo documentation.
Production
- Complete the store review / KYB so production payments are enabled — see Account Reviews
- Publish your subscription and one-time products from test to production. Publishing is a one-way, first-publish-only operation — see Publish Product
- Create a production API key and use it for
WAFFO_MERCHANT_ID/WAFFO_PRIVATE_KEY - Add the production webhook URL
https://YOUR-DOMAIN.com/api/webhooks/waffoin Waffo Dashboard >Settings>Webhooks - Leave
WAFFO_DEBUGunset (or set tofalse) so test-mode events are rejected in production — a sandbox purchase must never grant real access
Customer Portal
Waffo provides a hosted consumer portal at https://pancake.waffo.ai/consumer/portal/login. Customers log in with the email used for their purchase and a one-time Magic Link — no password required. In the portal they can:
- View active subscriptions, next billing date, and payment history
- Cancel or reactivate subscriptions
- Download PDF invoices and receipts
- Update billing details
- Request refunds
The Billing page (/settings/billing) sends customers to this portal via the "Manage subscription" button. You can find more about the Customer Portal in the Waffo documentation.
Webhook Events
Waffo supports the following webhook events:
| Event | Description |
|---|---|
order.completed | One-time order payment succeeded |
subscription.activated | Subscription first payment succeeded |
subscription.payment_succeeded | Renewal payment succeeded (not the first) |
subscription.updated | Subscription product changed (upgrade/downgrade) |
subscription.canceling | Cancellation requested — active until period ends |
subscription.uncanceled | Cancellation withdrawn |
subscription.canceled | Subscription terminated (period ended) |
subscription.past_due | Renewal payment failed, Waffo is retrying |
refund.succeeded | Refund completed — access is revoked |
refund.failed | Refund failed |
Waffo delivers test and production events to the same endpoint, and every payload carries a mode field. The template rejects events whose mode does not match the running environment; in a production build, set WAFFO_DEBUG=true only when smoke-testing a deployed Worker with a sandbox merchant.
Test Cards
To test the Waffo integration, use Waffo's test mode with these test cards:
| Card | Type | Result |
|---|---|---|
4576 7500 0000 0110 | Visa Credit | Success |
2226 9000 0000 0110 | Mastercard Credit | Success |
4576 7500 0000 0220 | Visa Credit | Declined |
Any future expiry date and any 3-digit CVC work. You can find more about test mode and test cards in the Waffo documentation.
Best Practices
- Protect API keys: Never expose
WAFFO_PRIVATE_KEYor the Merchant ID in client-side code - Validate webhook signatures: Always verify the
X-Waffo-Signatureheader (the SDK'sverifyWebhook()does this) - Match environments: Use the test or production API key and webhook URL that correspond to the environment you are running
- Handle errors gracefully: Provide user-friendly error messages when payments fail
- Test webhooks thoroughly: Complete a full purchase in test mode before going live
References
Last updated on
