Coupon lifecycle

Events: coupon.*, with the cart's cart.updated, cart.calculating and cart.calculated.

Coupon lifecycle A code entered on a cart with lines is checked: the cart is priced with and without it. When it takes money off it is applied (cart.updated, then coupon.applied); when it takes nothing off, or cannot be checked (sdk.error first), it is denied (coupon.denied) and the cart does not change. A code entered on an empty cart, or before the campaign loads, is held until the cart is first priced, then checked. A code already on the cart, held or being checked is denied at once (duplicate). Removing an applied code sends cart.updated, then coupon.removed; clearing the cart sends cart.updated, cart.removed, then coupon.removed. Removing or clearing drops a held code, or one being checked, without events. entered on an empty cart entered on a cart with lines entered again: already on the cart, held or checking the cart is first priced takes money off cart.updated removed, or the cart cleared cart.updated no discount or check fails sdk.error held waits for the first price checking priced with and without it applied coupon.applied removed coupon.removed denied coupon.denied

The lifecycle is one code's on one cart: entered with next.actions.applyCoupon or a data-next-coupon form, checked against the cart, then on it or not. The Cart's coupons lists the codes on it.

StateMeans
heldEntered while the cart is empty or the campaign has not loaded; waits for the cart's first price. Kept across pages in this tab
checkingThe cart is priced with and without the code
appliedThe code takes money off and is on the cart
deniedThe code is not applied; the cart does not change
removedThe code was taken off the cart
FromWhat happensToEvents, in order
noneA code is entered, the cart has lines and the campaign has loadedcheckingnone
noneA code is entered while the cart is empty, or before the campaign loadsheldnone
noneThe code is already on the cart (a bundle card's own code too), held, or being checkeddeniedcoupon.denied (reason: 'duplicate')
heldThe cart is priced, with linescheckingnone, after cart.calculated
checkingThe code takes money off: a new discount, or a lower totalappliedcart.updated, coupon.applied (with amount and discounts), then cart.calculating, cart.calculated
checkingThe code takes nothing off this cartdeniedcoupon.denied (reason: 'no_discount')
checkingThe code cannot be checkeddeniedsdk.error (coupon.check), coupon.denied (reason: 'check_failed', with message)
checkingThe cart froze for an order before the check endednone; a held code stays heldnone; applyCoupon resolves false, or keeps waiting for a held code
held, checkingremoveCoupon, or the cart is clearednonenone; applyCoupon resolves false
appliedremoveCouponremovedcart.updated, coupon.removed (reason: 'shopper'), then cart.calculating, cart.calculated if the cart has lines
appliedThe cart is clearedremovedcart.updated, cart.removed if it had lines, coupon.removed (reason: 'cleared')

Checks

  • next.actions.applyCoupon resolves true once the code is applied, and false when it is denied or dropped.
  • Codes are trimmed and uppercased; an empty code resolves false with no event.
  • Codes are checked one at a time, each against the codes already on the cart, so two codes that do not stack are not both applied.
  • The price without the code is the cart's settled one; after a failed cart price the check prices the cart again.

Held codes

  • Kept across pages in this tab: a held code still waiting on a later page is checked there once the cart is priced.
  • Held codes are checked in the order entered, after each cart.calculated, until none is left.
  • applyCoupon for a held code resolves when it is checked, if that happens on the same page. The 0.4.x next.applyCoupon resolves true at once for a code it holds.

Duplicates

  • A code already on the cart, held, or being checked is denied at once, without a request; a bundle card's own code counts as on the cart.
  • removeCoupon for a code that is not on the cart, nor held, does nothing.

Frozen cart

  • While an order is being placed the cart is frozen: applyCoupon resolves false, removeCoupon does nothing, and no coupon event is sent.
  • A check that ends after the cart froze applies nothing and sends nothing; a held code stays held and is checked at the next cart.calculated once the cart unfreezes.
  • A paid order empties the cart without coupon events; an applyCoupon still waiting resolves false.

The cart

  • Applying or removing a code is a cart change: cart.updated, then the cart is priced again.
  • The cart's own discounts, or shipping_method.discounts for shipping, list the code with source coupon and the same code from the next cart.calculated, and follow the cart.
  • Applying or removing a code prices every selector's options again; see the selector lifecycle.

What each event carries

  • coupon.*: the code, uppercase.
  • coupon.applied: what the code takes off this cart, as amount and the discounts with its code; neither when the priced cart lists no discount with the code.
  • coupon.denied: the reason; with check_failed a message for developers and logs.
  • coupon.removed: reason shopper for removeCoupon, cleared for a cleared cart.

Errors

Each sends sdk.error with this action. The SDK lifecycle lists every action and code.

actionWhenWhat the SDK does next
coupon.checkA code cannot be checkedcoupon.denied with reason: 'check_failed'; applyCoupon resolves false
cart.calculateThe cart cannot be pricedHeld codes wait for the next cart.calculated; a check prices the cart without the code itself