Skip to main content
Copy this prompt to your AI code editor (Cursor, Copilot, etc.) to create checkout integration:
Add Waffo Pancake checkout to my project using the official TypeScript SDK.

npm install @waffo/pancake-ts

Create a WaffoPancake client with WAFFO_MERCHANT_ID and WAFFO_PRIVATE_KEY.
Use client.checkout.createSession({ productId, currency: "USD" })
to get a checkoutUrl, then redirect the user with res.redirect(checkoutUrl).

For webhooks, use verifyWebhook(rawBody, signature) from the SDK — it has embedded public keys,
no secret needed. Handle events: order.completed, subscription.activated, subscription.canceled.

Read https://waffo.mintlify.app/llms-full.txt for full API reference.

What You’ll Build

A checkout session is a dynamically generated payment page. Instead of sharing a static product link, you can:
  • Pre-fill customer email
  • Track orders with custom metadata
  • Set custom success URLs
  • Enable trial periods for subscriptions

Prerequisites

  • Waffo Pancake account
  • API key (Dashboard → Settings → Developers)
  • At least one product created

Basic Checkout Session

The simplest checkout session needs a product ID and currency.
npm install @waffo/pancake-ts
import { WaffoPancake } from "@waffo/pancake-ts";

const client = new WaffoPancake({
  merchantId: process.env.WAFFO_MERCHANT_ID!,
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
});

const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
});

// Redirect the user to the checkout page
res.redirect(session.checkoutUrl);

Using REST API Directly

curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
  -H "Content-Type: application/json" \
  -H "X-Store-Slug: your-store-slug" \
  -H "X-Environment: test" \
  -d '{
    "productId": "your-product-uuid",
    "currency": "USD"
  }'
Response:
{
  "data": {
    "sessionId": "cs_xxx",
    "checkoutUrl": "https://checkout.waffo.ai/your-store/checkout/cs_xxx",
    "expiresAt": "2024-01-22T12:00:00Z"
  }
}
Redirect your customer to checkoutUrl to complete payment.

Redirect Best Practices

Do not use window.open() to open the checkout page. Safari and many mobile browsers block popup windows opened from asynchronous callbacks (e.g., after an API call). This causes checkout to silently fail for your customers.
Recommended approaches:
MethodWhen to UseExample
Server-side redirectAPI route / server actionres.redirect(checkoutUrl)
Client-side redirectSPA / Reactwindow.location.href = checkoutUrl
Link elementStatic links<a href={checkoutUrl}>
// ✅ Recommended: server-side redirect
export async function POST(req: NextRequest) {
  const session = await client.checkout.createSession({ ... });
  return NextResponse.redirect(session.checkoutUrl);
}

// ✅ Also fine: client-side redirect
const { checkoutUrl } = await res.json();
window.location.href = checkoutUrl;

// ❌ Avoid: window.open — blocked by Safari and mobile browsers
window.open(checkoutUrl, "_blank"); // Will be blocked!
After payment, the customer is redirected back to your successUrl. Use the {SESSION_ID} placeholder to verify the payment on your success page.

Pre-fill Customer Email

Skip the email input step by pre-filling it:
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  buyerEmail: "user@example.com",
});
Use case: User is already logged into your app, so you know their email.

Track with Metadata

Link checkout sessions to your internal systems:
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  metadata: {
    user_id: "usr_abc123",
    campaign: "black_friday_2024",
    referrer: "twitter",
  },
  successUrl: "https://yoursite.com/success",
});
Metadata is stored on the checkout session and can be retrieved via the checkoutSession GraphQL query. Webhook passthrough for metadata is coming soon.

Subscription with Trial

Enable a trial period for subscription products:
const session = await client.checkout.createSession({
  productId: "prod_subscription",
  currency: "USD",
  withTrial: true,
  successUrl: "https://yoursite.com/welcome",
});
Discount codes and per-seat quantities are not yet supported via the API. Manage discounts through the Dashboard.

Subscription with Billing Detail

Pre-fill the consumer’s billing information for tax calculation:
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  billingDetail: {
    country: "US",
    isBusiness: false,
    state: "CA",
    postcode: "94105",
  },
});

Dynamic Success URL

Pass data to your success page:
const session = await client.checkout.createSession({
  productId: "prod_xxx",
  currency: "USD",
  successUrl: "https://yoursite.com/success?session_id={SESSION_ID}",
});
{SESSION_ID} will be replaced with the actual session ID, so you can verify the payment on your success page.

Complete Example: SaaS Upgrade Flow

Here’s a real-world example for upgrading a user’s subscription using the SDK:
// app/api/upgrade/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from "next/server";
import { WaffoPancake } from "@waffo/pancake-ts";

const client = new WaffoPancake({
  merchantId: process.env.WAFFO_MERCHANT_ID!,
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
});

export async function POST(req: NextRequest) {
  const { userId, planId, email } = await req.json();

  const session = await client.checkout.createSession({
    productId: planId,
    currency: "USD",
    buyerEmail: email,
    metadata: { user_id: userId, action: "upgrade" },
    successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?upgraded=true`,
  });

  // Server-side redirect — works in all browsers
  return NextResponse.redirect(session.checkoutUrl);
}
If you need to handle checkout on the client side (e.g., SPA):
async function handleUpgrade(planId: string) {
  const res = await fetch("/api/upgrade", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, planId, email: currentUser.email }),
  });
  const { checkoutUrl } = await res.json();

  // Redirect in same tab — Safari-safe
  window.location.href = checkoutUrl;
}

Parameters Reference

ParameterTypeRequiredDescription
productIdstringYesProduct UUID from Dashboard
currencystringYesISO 4217 currency code (e.g., "USD")
buyerEmailstringNoPre-fill consumer email
successUrlstringNoCustom success redirect URL
metadataobjectNoCustom key-value data stored on the session (webhook passthrough coming soon)
withTrialbooleanNoEnable trial period for subscription products

Next Steps

Handle Webhooks

Get notified when payments complete

Verify Payments

Securely verify webhook signatures