Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

Stack card-linked offers with promo codes

Banks and BNPL providers often have card-linked offers (cashback, accelerated rewards, statement credits) that overlay on top of merchant promo codes. Stacking them — or deciding NOT to stack them when the merchant’s rules disallow it — is a real integration concern for bank, fintech, and brand-loyalty destinations.

This recipe shows the canonical pattern and the conflict checks to put in place.

​​ The two layers

Layer Owner Where it applies
Merchant promo codes The merchant — codes apply at the merchant’s checkout rules Inside Firmly’s Promotions API
Card-linked offers The bank or wallet — applied at the network or PSP layer Outside Firmly — calculated by the bank when the transaction settles

Because they live at different layers, both can apply simultaneously if the merchant permits it. Most merchants do; some explicitly exclude card-linked offers from stacking with codes.

  1. Build the cart as usual — discovery, add line items
  2. Apply merchant promo code first via Firmly’s add-promo-codes (the reference-page name for POST /cart/promo-codes)
  3. Re-read the cart and confirm the discount actually applied (200 doesn’t mean applied — see Common issues). Not every promo moves sub_total — free-shipping and order-level discounts land on total or a discount field instead, so check the field the promo actually affects.
  4. Calculate the card-linked offer separately on the destination side, against the post-merchant-promo cart total
  5. Surface BOTH to the user before checkout: "$5 off with code SAVE5, plus 4% back via your bank card — total saved: $9.20."
  6. Place the order with the chosen payment instrument
  7. Reconcile the card-linked offer separately when the bank’s settlement runs (this happens outside Firmly)

// 1-2. Add merchant promo (POST /cart/promo-codes)
const cartAfterPromo = await callFirmly('/cart/promo-codes', {
promo_codes: ['SAVE5'],
});
// 3. Did it apply? Compare totals before and after.
// NOTE: sub_total only moves for line-item discounts. Free-shipping and
// order-level promos land on `total` (or a discount field) instead — compare
// the field the promo actually affects, and/or inspect `coupons` /
// `cart_discount` on the cart response.
const subTotalBefore = cartBeforePromo.sub_total.value; // decimal dollars
const subTotalAfter = cartAfterPromo.sub_total.value; // decimal dollars
const totalBefore = cartBeforePromo.total.value;
const totalAfter = cartAfterPromo.total.value;
const promoApplied = subTotalAfter < subTotalBefore || totalAfter < totalBefore;
if (!promoApplied) {
// Check notices for the reason
const promoNotice = cartAfterPromo.notices?.find(
n => n.code === 'PROMO_EXPIRED' || n.code === 'PROMO_NOT_APPLICABLE'
);
agent.say(`The code didn't apply — ${promoNotice?.details?.description || 'check the merchant rules'}.`);
return;
}
// 4. Calculate card-linked offer on your side, against the post-promo total
const cardLinkedOffer = await bankApi.calculateOffer({
cardId: user.cardId,
merchantDomain: cart.shop_id,
cartTotal: cartAfterPromo.total.value,
});
// 5. Surface both to the user. All math is on `value` (decimal dollars),
// so no division is needed. estimatedSavings from the bank is also decimal.
const codeSavings = totalBefore - totalAfter;
agent.say(`
Cart: $${cartBeforePromo.total.value}
Code SAVE5: -$${codeSavings.toFixed(2)}
Card-linked offer: -$${cardLinkedOffer.estimatedSavings.toFixed(2)} (at settlement)
Total to charge today: $${cartAfterPromo.total.value}
Estimated savings: $${(codeSavings + cardLinkedOffer.estimatedSavings).toFixed(2)}
`);

​​ Conflict checks

Some merchants disallow stacking. Things to validate:

Check How
Does the merchant allow card-linked offers alongside promo codes? Merchant-specific; not exposed in the cart response. Talk to Firmly for the merchant’s policy
Does the user’s card actually carry the offer for this merchant? Bank-side check — call your bank’s offer API with the merchant domain
Will the offer settle on the card you’re charging? If the user’s chosen payment method isn’t the offer-bearing card, no card-linked offer applies — surface this

​​ What can go wrong

  • User expects card-linked savings on the wrong card. Make it visually clear which card needs to be charged for the offer to apply.
  • Merchant promo code silently rejects the cart. Always re-read and check cart.notices for PROMO_EXPIRED, PROMO_NOT_APPLICABLE, etc.
  • Card-linked offer settles weeks later. Don’t quote it as “savings now” — surface as “estimated, applied at settlement.”
  • Double-discounting at the merchant side. Some merchants are sensitive to perceived double-discounting and may reject orders. Confirm with the merchant’s terms.

​​ When NOT to stack

  • The merchant’s terms explicitly forbid stacking
  • The user has a fixed budget and the agent should pick the SINGLE best offer (often the larger of the two) rather than try both
  • The merchant’s order placement fails when both apply (test in sandbox first)