A discount that grows as the shopper answers
Goal: a bar above the quiz says “Answer 3 more questions to unlock 10% off”, fills as pages go by, shows the code when the quiz issues it, and on the finish the code is already on the cart.
Uses: Storefront JS API, page.viewed, discount.issued, quiz.finished, and Shopify’s /discount/CODE route. Nothing to install; no API key. Events fire on the Plus and Enterprise plans.
The counter
Section titled “The counter”The quiz does not tell your page how many pages it has, so the page states it. page.viewed carries page_index, the position of the page that landed.
<div id="reward"><div id="reward-bar"></div><span id="reward-text"></span></div><script> window.octaneai = window.octaneai || []; window.octaneai.push(function (octaneai) { var QUESTION_PAGES = 5; var bar = document.getElementById('reward-bar'); var text = document.getElementById('reward-text');
octaneai.on('page.viewed', function (event) { var page = event.data.object; var done = Math.min(page.page_index, QUESTION_PAGES); var left = QUESTION_PAGES - done; bar.style.width = Math.round(100 * done / QUESTION_PAGES) + '%'; if (left > 0) text.textContent = 'Answer ' + left + ' more question' + (left === 1 ? '' : 's') + ' to unlock 10% off'; });
octaneai.on('discount.issued', function (event) { var discount = event.data.object; bar.style.width = '100%'; text.textContent = 'Your code ' + discount.code + ' is unlocked' + (discount.ends_at ? ' until ' + new Date(discount.ends_at).toLocaleDateString() : ''); }); });</script><style> #reward { position: relative; height: 32px; background: #f3f4f6; border-radius: 16px; overflow: hidden; } #reward-bar { position: absolute; inset: 0 auto 0 0; width: 0; background: #bbf7d0; transition: width 300ms; } #reward-text { position: relative; line-height: 32px; padding: 0 16px; font: 14px system-ui, sans-serif; }</style>discount.issued fires once per discount when its code reaches the quiz; with a code minted at the start of the session that is right after mount, with one minted on the result page it is at the finish. The bar’s width is a display: the quiz issues one code per discount configured in the editor, whatever the page count says.
Apply the code on the finish
Section titled “Apply the code on the finish”quiz.finished carries discount_codes[], the same records discount.issued delivered. Shopify’s storefront applies a code to the current cart with a request to /discount/CODE; it sets the discount cookie the checkout reads. The quiz does the same when the shopper adds a product from a card with the discount setting on; this handler does it on the finish, so the code is on the cart even if the shopper walks to a product page and adds from there.
window.octaneai = window.octaneai || [];window.octaneai.push(function (octaneai) { octaneai.on('quiz.finished', function (event) { var root = (window.Shopify && window.Shopify.routes && window.Shopify.routes.root) || '/'; event.data.object.discount_codes.forEach(function (discount) { fetch(root + 'discount/' + encodeURIComponent(discount.code), { credentials: 'same-origin' }) .catch(function () {}); }); });});Errors to handle
Section titled “Errors to handle”/discount/CODEanswers a redirect to the cart or the home page with any status; the storefront ignores a code that does not exist, so there is nothing to read from the response. The.catchabove covers a network failure only.discount_codesis[]on a quiz without a discount, anddiscount.issuednever fires; the bar then keeps its last “Answer N more” text. Hide it onquiz.finishedwhendiscount_codes.length === 0if the quiz can run without one.
Things to know
Section titled “Things to know”- Tiers (“5% at page 3, 10% at page 5”) need real codes: the quiz issues each configured discount’s code once, so a growing rate means one discount whose code is issued on the result page and a bar that only says so earlier.
ends_atisnullfor a code without an expiry.- The code is the shopper’s own (minted per session when the discount is set up that way); the counter shows it only to them, and
discount.copiedfires when they copy it from the quiz’s coupon. - Accelerated checkouts (Buy now, Shop Pay from a product page) start a fresh checkout and may not carry the cart’s discount.
- Applying through
/discount/needs the page to be on the store’s own domain, wherewindow.Shopify.routes.rootis set; on another domain the request goes to a path that does not exist.
