What You Can Do
- Create coupons: a percentage or a fixed amount off, limited to some products, a total number of uses and an expiry, with a code customers type at checkout
- Hand out more promotion codes for the same coupon, each with its own customer restrictions — eligible emails, uses per customer, a minimum amount — one per influencer or campaign channel, or a batch of single-use codes
- Let customers type a code on the checkout page, share a product link with a code already applied, or apply a coupon or pre-fill a promotion code from your server when you create a checkout session
Discounts apply to one-time products, new subscriptions and plan changes. On a subscription, the coupon’s duration decides how many billing periods are discounted — see Subscriptions. On a plan change, a new code replaces the subscription’s discount, and without one the discount carries over while it can — see Plan Changes.
Coupons and Promotion Codes
A coupon defines the discount and has its own code customers can type — set it when you create the coupon, or let Pancake generate one. A coupon only limits its expiry and total uses, so its own code works for any customer within those limits. A promotion code is another code for the same coupon that carries customer restrictions: a coupon can have many, and a customer must pass the promotion code’s restrictions as well as the coupon’s expiry and total uses. You can create promotion codes together with the coupon, in the same request, or add them later. A coupon you apply from your server withcouponId needs no code.
Coupons and their codes belong to one store and one environment: a code created in test mode does not work in live mode. Codes are case-insensitive — a customer can type
spring-20 for SPRING-20.
Creating Coupons
Via Dashboard
Go to Dashboard → Coupons to create a coupon, add promotion codes, generate a batch of codes, and see how many times each code has been used — see Coupons in the Dashboard guide.Via API or SDK
Create coupons and promotion codes from your server with the Coupon and Promotion Code endpoints:Common Campaigns
What to set up for the promotions merchants run most often. Each one is a coupon, plus promotion codes where customers need different rules — set up in the Dashboard or with the SDK.Launch sale: a public code for a limited time
Launch sale: a public code for a limited time
A 20% code everyone can use for two weeks, with a cap so the campaign cannot run away.
- Set up: one coupon — percentage 20, expiry in two weeks, redemption limit (for example 500), all products or just the launch product. Its own code is the public code, for example
LAUNCH-20. - Customers get it: they type the code on the checkout page — your product link shows the code entry field as soon as the coupon exists. For ads and emails, put the code in the link so nothing has to be typed:
…/product/{productId}?discount_code=LAUNCH-20. - Watch out: the expiry is checked at the moment of entry and again at order time, so a customer who opens the page before the deadline and pays after it is charged the full price. A coupon’s expiry also ends the discount on subscriptions it was applied to.
Influencers and channels: one code per partner
Influencers and channels: one code per partner
The same discount, but you want to know which partner each order came from and cap each of them separately.
- Set up: one coupon with the discount, then one promotion code per partner on it —
KOL-ALICE,KOL-BOB,PODCAST-10— each with its own total uses and, if the deal needs it, a minimum order amount. The coupon’s own code can stay unpublished. - Customers get it: the partner shares their code or a product link with
?discount_code=KOL-ALICE. - Track: the coupon details list uses per code; export the codes as CSV for your partner report. Deactivate a partner’s code on its own when the deal ends — the other codes keep working.
Win-back or VIP list: only certain customers
Win-back or VIP list: only certain customers
A discount only the customers on your list can use, even if the code leaks.
- Set up: a promotion code with Only specific customers can use it — paste the emails — and Each customer can use it up to 1 time.
- Customers get it: they type the code at checkout. The code is matched against the email they enter on the checkout page, so the page asks for the email first when the code is applied before one is typed; a customer who is not on the list sees that the code is for specific customers.
- Watch out: the match is on the exact email (case-insensitive). A customer who checks out with a different email than the one you have is refused — tell them which address the offer was sent to.
Loyalty tiers or partner pricing: you decide, no code shown
Loyalty tiers or partner pricing: you decide, no code shown
Your own system knows who gets the discount — a membership tier, a reseller, a quote you agreed on — and the customer should not see or share a code.
- Set up: a coupon per tier (
Gold 15%,Partner 25%), with no need to publish its code. - Customers get it: your server creates the checkout session with
couponIdand sends the customer to itscheckoutUrl. The checkout page shows the discount and the discounted total, no code and no code entry field; the customer cannot remove it. - Watch out:
couponIdneeds your API Key, so decide the eligibility in your server code — never from values the browser sends. With the Next.js SDK,createCheckoutActionstripscouponId; call@waffo/pancake-tsfrom your own server code instead. To let the customer stack their own code on top, use a promotion code withpromotionCodeplusallowPromotionCodes: trueinstead — a coupon cannot be combined with code entry.
Subscriptions: first months cheaper, or a reason to switch to annual
Subscriptions: first months cheaper, or a reason to switch to annual
“25% off for 3 months” on a monthly plan, or a code that only works on the annual plan to move customers over.
- Set up: for the first months, a coupon with duration Multiple billing cycles = 3. For the annual push, a coupon limited to the annual product, so the code is refused on the monthly plan with
PRODUCT_NOT_ELIGIBLE. - Customers get it: new subscribers type the code at checkout. Existing monthly subscribers switch from the Customer Portal: the plan change page shows the code entry field when a code can be used on the annual plan, or you create the plan change session yourself with
promotionCodeand send them the link. - Watch out: a free trial does not use the discount — it starts with the first paid period. A paid trial does, and counts as the first period. On a plan change, a new code replaces the current discount; without one the current discount carries over with the periods already used. See Subscriptions and Plan Changes.
Test first, then go live
Test first, then go live
Coupons belong to one environment, so a coupon built in test mode is not visible in live mode.
- Set up: build the coupon and its codes in test mode and run a test checkout — the test checkout accepts test cards.
- Go live: open the coupon and choose Copy to live (products must be published to live first). The copy starts with 0 uses; active, unexpired promotion codes come along if you tick the option. With the API, create the coupon again with your live API Key.
- Watch out: a code already in use on live stops the copy — deactivate it there first, or copy without codes.
Applying Discounts at Checkout
A discount reaches the checkout page in one of four ways. The customer always sees the discounted, tax-inclusive total before paying.Code Entry Field
Product links and your store page show the code entry field when the store has a code that can be used on the product: anactive coupon that covers the product, is not expired and is under its total usage limit, with its own code or a promotion code that is active, not expired and under its own usage limit. Rules that depend on the customer, such as eligible customers or a minimum amount, are checked only when the code is entered. A code that has just expired or reached its limit can keep the field showing for up to 5 minutes. Checkout sessions you create with your API Key show it only when you pass allowPromotionCodes: true:
Scenarios
How the checkout page decides, and what to pass, in common situations:Product Link with a Code
Adddiscount_code to a product link. The code is applied when the page opens, and the code entry field is hidden:
Coupon Set by Your Server
PasscouponId (the coupon’s CPN_ ID) when you create the checkout session. The customer sees the discount — its name, amount and duration — but not a code, even when the coupon has its own code; to show a code, pre-fill it with promotionCode instead. The coupon is checked right away: a coupon from another store or environment, a deactivated or expired coupon, a product the coupon does not cover, or a fixed amount with no value in the session currency is rejected with 409 — see Create Checkout Session.
couponId cannot be combined with allowPromotionCodes or promotionCode — sending both returns 400. couponId is accepted only with merchant credentials (API Key or JWT): a Store Slug request or a customer session token gets 403, and the Next.js SDK server action strips it, so customers cannot pick a coupon for themselves.
Promotion Code Set by Your Server
PasspromotionCode — the text of a coupon code or promotion code, case-insensitive — to apply it when the checkout page opens. It is checked right away against the same rules as couponId, and a code that cannot be used is rejected with 409. Whether the customer can change it is up to allowPromotionCodes:
couponId, promotionCode is accepted only with merchant credentials (API Key or JWT) — a Store Slug request or a customer session token gets 403, and the Next.js SDK server action strips it.
When a Code Cannot Be Used
Pancake checks the code when the customer enters it, and makes the final decision when they place the order. The check at entry is a preview, so a few rules are left to the order:- For a guest checkout, only paid orders count toward the code’s total uses: the code is refused at entry only once its paid uses reach the limit. Unpaid orders and the per-customer limit are checked when the order is placed — if other customers’ unpaid orders hold the remaining uses, the order is refused then.
- For a signed-in customer, their own unpaid order with the same code does not count against the use limits — except an unpaid plan change that still holds the use: one already confirmed and waiting for its switch date, or one the current discount is being carried over to.
- A customer who leaves the payment page without paying can come back and enter the same code again. When they place the new order, it takes over the use held by their unpaid order.
If a rule fails only when the order is placed — for example, someone else took the last use a moment earlier — no order is created, nothing is charged and the checkout session stays usable. What the customer can do next depends on how the code was applied:
A customer who placed an order with a code but has not paid can place a new order with the same code. The new order takes over the use held by the earlier one; the earlier order stays payable, but if it is paid as well it is flagged for review and not counted as another use.
An unpaid order holds its use for 72 hours. After that, the next order that uses the same coupon — with any of its promotion codes, placed by any customer — takes the use back. The unpaid order is not canceled: it keeps its status, and if it is paid later it is flagged for review and not counted as another use.
Subscriptions
New subscriptions take a discount the same ways as one-time products, with the same rules. Before paying, the customer sees the first charge and what each later period costs, with and without the discount.How Many Periods Are Discounted
Only a period whose price before the discount is above zero uses up the discount. A 100% discount still uses up its period, even though nothing is charged.
Trials
A promotion code’s minimum amount is checked against the first charge, before the discount and tax: the trial price for a paid trial, the regular price otherwise. Renewals are not checked again.
For example, at $10 a month with a 20% coupon for 3 periods (`repeating`), periods 1 to 3 charge $8 and period 4 onward $10. With a 7-day free trial and a `once` coupon, the trial costs $0, the first paid month is discounted and later months are full price.
When a Use Is Counted
- A use is counted once, when the first discounted charge succeeds: the first paid charge, or the trial charge for a paid trial. Renewals do not count again.
- Until then the subscription holds the use. If the subscription ends before that charge — canceled during a free trial, or its first payment never succeeds — the use is given back and the customer can use the code again. A subscription order left unpaid for more than 72 hours is not canceled, but it no longer holds the use: the next order that uses the same coupon, with any of its promotion codes, takes it back.
- If the coupon expires before that charge and the customer is charged the full price, the use is given back as well.
- A new order by the same customer with the same code takes over the use only from an earlier order that was never paid. An active subscription in its free trial keeps its hold: the new order does not take it over, and the held use counts toward the code’s limits.
When the Discount Ends
- When the duration runs out, renewals are charged the full price.
- The coupon’s expiry also ends discounts already on subscriptions: renewals after it are charged the full price, even with
forever. A promotion code’s own expiry only stops new uses. - Deactivating a coupon does not end discounts already on subscriptions until their next plan change — see Plan Changes.
- Canceling a subscription ends its discount for good. Undoing the cancellation before the period ends keeps the discount.
- A plan change ends the discount, unless it carries over to the new plan — see Plan Changes.
Plan Changes
A plan change replaces the subscription with a new one on the new plan. The plan change page shows a code entry field when the plan change session allows it, and you can setcouponId or promotionCode on a plan change session the same way as on a new purchase — see Create Checkout Session. A product link’s discount_code does not apply to plan changes. The plan change page does not ask for an email, so a promotion code’s eligible-customer list is matched against the original subscription’s customer email — both when the code is entered and when the plan change is submitted.
So deactivating a coupon ends the discounts on subscriptions at their next plan change.
- The first period. On a change that takes effect immediately, the new subscription’s first period costs the new plan’s price minus the credit for the unused part of the current period — or the amount you set with
changeAmountorchangeCreditAmount. When that is above zero, the discount applies to it and it uses up one period. The discount is worked out on the new plan’s full price, and then the credit is taken off: a percentage coupon takes its percentage of the new plan’s price, a fixed amount stays the same, and the discount is capped at the first period’s price, so the customer pays the first period’s price minus the discount, never below zero. A first period whose discount covers it in full still uses up one period. When it is zero — usually a downgrade whose credit covers the new plan — it takes no discount and uses no period, and the discount starts with the second period. A change that takes effect next period starts with a regular-price period, and a change that grants a trial follows the trial rules above. - The credit is based on what the customer pays. The credit for the unused part of the current period is worked out from what that period costs the customer: the discounted amount when the period is discounted, the list price otherwise.
- A promotion code’s minimum amount is checked against the new plan’s regular price, before the discount and tax. The credit is not taken off.
- When a use is counted. A new code is counted when the first discounted charge of the new subscription succeeds — the first period, or the second period when the first is zero. If the change does not go through — the customer does not finish it, the payment channel reports it failed, or the change scheduled for next period is canceled — the use is given back. A change that is started but never completes stops holding its use after 72 hours, and the next order that uses the same coupon, with any of its promotion codes, takes it back — the same goes for a use being carried over to it from a subscription that has since ended. When the current subscription’s own code is still waiting for its first discounted charge, a discount that carries over takes that held use along to the new subscription; a new code or no code gives it back.
repeating), 1 of them used, the customer upgrades to $10 a month halfway through the period without a code. The credit is $2 — half of the $4 they paid — so the first period is $8 before the discount and $7 to pay, the next period is $9, and later periods are $10. If they enter a new 20% `once` code instead, the discount is $2 — 20% of the $10 plan — so the first period is $8 before the discount and $6 to pay, and later periods are $10: the old discount’s remaining period is dropped.
Amounts on Subscription Payments
- The subscription order keeps the list price. Each payment records what Pancake expects for that period: the original price, the discount and the amount to pay. The
subscription.payment_succeededwebhook carries them inlistPrice(listTotal,discountAmount,total). - A charge that does not match the expected discount — the discount ended early, or the customer paid more or less — is reconciled by Pancake internally. The payment keeps the expected amounts; what was actually taken is in
chargedAmounton the webhook. - The first receipt — for the trial charge of a paid trial, or for the first charge without a trial — lists the original price, the discount, the subtotal, the tax and the amount paid when that charge is discounted. A free trial’s first receipt is for $0 and shows no breakdown.
- Each renewal receipt shows the same breakdown when its period is discounted. The first charge after a trial sends no renewal receipt — with a free trial, that is the first discounted period. The customer sees its expected amount in the upcoming-charge reminder.
- The next charge shown in emails includes the discount only when that period is still discounted.
- After a plan change, the new subscription’s first payment expects the first period’s amounts (see Plan Changes), but no receipt is sent for it. When the first period is zero, this first payment is a $0 payment; later payments expect the regular-period amounts. The customer gets a plan-changed email instead, which shows the new plan’s regular price before any discount, the next billing date, and the next charge amount — discounted when that period still gets the discount. It doesn’t show the first period’s amount or a discount breakdown. The payment record keeps the first period’s amounts. Later renewals get receipts as described above.
Amounts, Receipts and Usage
- The discount applies to the tax-inclusive price. A percentage discount is rounded down to the currency’s smallest unit; a fixed amount never takes the total below zero, and a 100% discount produces a free order.
- Orders, payments, receipts and the Dashboard show three amounts: the original price, the discount, and what the customer paid. Refunds are capped at what the customer paid.
- An order’s price snapshot keeps the list price. Its
totalis the tax-inclusive price before the discount, for one-time and subscription orders alike. The discounted amount of each charge is on the payment (snapshotAmountDetails.total). - A discounted receipt lists the original price, the discount, the subtotal (after the discount, excluding tax), the tax and the amount paid. The subtotal and tax lines appear only when tax applies.
- A use is counted when the payment succeeds — for a subscription, the first discounted charge (see Subscriptions). A canceled or expired order gives the use back, so the customer can use the code again. A refund does not give the use back.
Next Steps
Coupon Endpoints
Create, rename and deactivate coupons
Create Checkout Session
Apply a coupon or show the code entry field