LogoMkSaaS Docs
LogoMkSaaS Docs
HomepageIntroductionCodebaseVideo TutorialsGetting Started: Install and Run LocallyEnvironment Setup
Configuration

Integrations

DatabaseAuthenticationEmailNewsletterStorage
Payment
AIAnalyticsNotificationCaptchaChatboxAffiliates

Customization

MetadataFontsThemesImagesSaaS Internationalization with next-intlBlogDocsComponentsCustom PagesLanding PageUser ManagementAPI Key Management

Codebase

IDE SetupMCP ServerProject StructureFormatting & LintingAutomated TestingUpdating the Codebase
X (Twitter)

Waffo Pancake

How to set up and use Waffo Pancake for payments and subscriptions

MkSaaS supports Waffo Pancake for one-time payments and subscriptions. Waffo is a Merchant of Record (MoR), so it handles global tax calculation, collection, compliance, and payouts on your behalf.

The integration uses the official @waffo/pancake-ts SDK together with Waffo's hosted checkout and consumer portal. Card details do not pass through your application server.

Setup

MkSaaS includes a free plan, a Pro subscription plan with monthly and yearly intervals, and a Lifetime one-time plan by default.

Create a Waffo Account

Sign up at pancake.waffo.ai and create a store during onboarding. The Waffo Quickstart explains the basic account setup.

Get API Credentials

In Waffo Dashboard, open API & Development and click Create API Key. Copy the Merchant ID and private key, then save them as WAFFO_MERCHANT_ID and WAFFO_PRIVATE_KEY.

WAFFO_MERCHANT_ID is the Merchant ID, not a storeId or a store identifier copied from a URL. API keys are bound to the test or production environment when they are created, so create a separate key for each environment.

The private key is server-only. Preserve PEM line breaks with escaped \n characters when storing it in an environment variable:

WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----"

Configure the Webhook

In Waffo Dashboard > Settings > Webhooks, click Add Webhook and configure:

  • Payload format: Raw. This format includes the X-Waffo-Signature header required for verification.
  • URL: https://YOUR-DOMAIN.com/api/webhooks/waffo
  • Events: keep the payment, subscription, and refund events listed in the Webhook Events section.

Waffo does not require a webhook secret environment variable. The @waffo/pancake-ts SDK contains the verification keys and verifies the raw request body with verifyWebhook().

Create Products

Waffo checkout uses Product IDs beginning with PROD_, rather than Stripe-style price IDs:

  • Create a monthly Subscription product and save its ID as NEXT_PUBLIC_WAFFO_PRODUCT_PRO_MONTHLY.
  • Create a yearly Subscription product and save its ID as NEXT_PUBLIC_WAFFO_PRODUCT_PRO_YEARLY.
  • Create a One-time product and save its ID as NEXT_PUBLIC_WAFFO_PRODUCT_LIFETIME. New products start in test mode. Publish them to production before accepting real payments; publishing is a one-way operation. See the Waffo subscriptions guide and Publish Product.

Add Environment Variables

Set the provider and Waffo credentials in .env:

.env
NEXT_PUBLIC_PAYMENT_PROVIDER=waffo

# Server-side credentials
WAFFO_MERCHANT_ID=MER_...
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----"

# Optional: accept sandbox webhooks in a production build for smoke tests
# WAFFO_DEBUG=true

# Product IDs
NEXT_PUBLIC_WAFFO_PRODUCT_PRO_MONTHLY=PROD_...
NEXT_PUBLIC_WAFFO_PRODUCT_PRO_YEARLY=PROD_...
NEXT_PUBLIC_WAFFO_PRODUCT_LIFETIME=PROD_...

Configure Pricing

Keep the existing price.plans configuration in src/config/website.tsx, but make its priceId values resolve to the Waffo product IDs when Waffo is active. The amount, currency, interval, and plan metadata must match the products configured in Waffo.

src/config/website.tsx
price: {
  plans: {
    pro: {
      id: 'pro',
      prices: [
        {
          type: PaymentTypes.SUBSCRIPTION,
          priceId: process.env.NEXT_PUBLIC_WAFFO_PRODUCT_PRO_MONTHLY!,
          amount: 990,
          currency: 'USD',
          interval: PlanIntervals.MONTH,
        },
        {
          type: PaymentTypes.SUBSCRIPTION,
          priceId: process.env.NEXT_PUBLIC_WAFFO_PRODUCT_PRO_YEARLY!,
          amount: 9900,
          currency: 'USD',
          interval: PlanIntervals.YEAR,
        },
      ],
      isFree: false,
      isLifetime: false,
    },
  },
},

Core Features

  • Monthly and yearly recurring subscriptions
  • Lifetime one-time payments
  • Hosted checkout without handling card data on your server
  • Webhook handling for payments, subscriptions, and refunds
  • Waffo-managed tax, compliance, and payouts
  • Shared consumer portal with Magic Link login
  • Provider-agnostic pricing, checkout, and billing components

Development

Waffo must be able to reach your webhook endpoint during local development. Expose the local server through an HTTPS tunnel:

ngrok http 3000
# or
cloudflared tunnel --url http://localhost:3000

Register the tunnel URL as a test webhook, for example https://xxxx.ngrok-free.app/api/webhooks/waffo. Do not put a temporary tunnel URL in the production webhook. Avoid localtunnel because it can strip the X-Waffo-Signature header.

The template includes a Waffo sandbox E2E suite:

pnpm e2e:waffo

The sandbox checkout does not create real charges. Choose Credit/Debit Card and enter the sandbox card fields directly using one of the test cards listed below. The automated suite avoids Quick Fill because it can leave subscription confirmation pending. An ignored MkSaaS Template environment file can be reused by setting WAFFO_ENV_FILE when running the local E2E helper.

After payment, click Done on Waffo's hosted confirmation page. The browser returns through /payment?checkout_id=..., waits for the matching webhook-backed payment row, and then redirects to Billing.

For automated sandbox setup, server ports, environment-file precedence, and database prerequisites, see Automated Testing and tests/e2e/waffo/README.md in the template.

Production

  1. Complete Waffo store review / KYB so production payments are enabled.
  2. Publish the subscription and one-time products from test to production.
  3. Create production API credentials and set WAFFO_MERCHANT_ID and WAFFO_PRIVATE_KEY to those credentials.
  4. Register https://YOUR-DOMAIN.com/api/webhooks/waffo as the production webhook.
  5. Leave WAFFO_DEBUG unset or set it to false. Sandbox events must never grant access in a production build.

Customer Portal

Waffo provides the shared consumer portal at https://pancake.waffo.ai/consumer/portal/login. Customers sign in with the email used for their purchase and a one-time Magic Link. They can view subscriptions and invoices, cancel or reactivate subscriptions, update billing details, and request refunds.

Checkout Confirmation

Waffo does not return its CHK_* Checkout Session ID in webhook payloads. Before checkout, MkSaaS generates a unique payment reference and sends it as orderMerchantExternalId. Waffo carries that reference into the webhook, and MkSaaS stores it in payment.sessionId.

The payment result page polls only the row for that reference and the signed-in user, so an older paid order cannot complete a new checkout.

Webhook Events

Configure the following events for /api/webhooks/waffo:

EventDescription
order.completedOne-time order payment succeeded
subscription.activatedSubscription first payment succeeded
subscription.payment_succeededPayment receipt only; does not update lifecycle state
subscription.renewedRenewal completed; advances the current period and keeps access active
subscription.recoveredA previously failed subscription recovered and became active
subscription.plan_changedSubscription product changed; new product metadata takes precedence
subscription.cancelingCancellation requested; active until period end
subscription.uncanceledCancellation withdrawn
subscription.canceledSubscription terminated
subscription.past_dueRenewal payment failed and is being retried
refund.succeededRefund completed and access is revoked
refund.failedRefund failed

Waffo sends test and production events to the same endpoint and includes a mode field in every payload. MkSaaS rejects events whose mode does not match the running environment.

Webhook Responses and Retries

ResultHTTP status
Waffo is not the configured provider404
Missing payload or X-Waffo-Signature header400
Invalid signature, malformed header, or stale timestamp400
Successfully processed or safely duplicated200
Temporary provider or database failure500

Temporary failures return 500 so Waffo can retry. Handlers are idempotent, so repeated successful deliveries do not create another payment row.

Any successful refund.succeeded event revokes access. Waffo does not expose a reliable partial-versus-full refund marker, so a partial refund also revokes access and must be handled manually if that is not your policy.

Test Cards

Use Waffo test mode with any future expiry date and any three-digit CVC:

CardTypeResult
4576 7500 0000 0110Visa CreditSuccess
2226 9000 0000 0110Mastercard CreditSuccess
4576 7500 0000 0220Visa CreditDeclined

Best Practices

  1. Keep WAFFO_PRIVATE_KEY and WAFFO_MERCHANT_ID on the server only.
  2. Use the Raw webhook format so X-Waffo-Signature is preserved.
  3. Use test and production credentials in their matching environments.
  4. Complete a full sandbox checkout and webhook round trip before going live.

References

  • Waffo Pancake Documentation
  • Waffo Quickstart
  • API Key Authentication
  • TypeScript SDK
  • Webhooks Guide
  • Test Mode
  • Customer Portal

Creem

How to set up and use Creem for payments and subscriptions

AI

Configure Vercel AI Gateway and try AI demos

Table of Contents

SetupCreate a Waffo AccountGet API CredentialsConfigure the WebhookCreate ProductsAdd Environment VariablesConfigure PricingCore FeaturesDevelopmentProductionCustomer PortalCheckout ConfirmationWebhook EventsWebhook Responses and RetriesTest CardsBest PracticesReferences