Skip to content

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 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.

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 () {});
});
});
});
  • /discount/CODE answers 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 .catch above covers a network failure only.
  • discount_codes is [] on a quiz without a discount, and discount.issued never fires; the bar then keeps its last “Answer N more” text. Hide it on quiz.finished when discount_codes.length === 0 if the quiz can run without one.
  • 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_at is null for 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.copied fires 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, where window.Shopify.routes.root is set; on another domain the request goes to a path that does not exist.