E-commerce / Case 01
Tiered discounts on a non-Plus Shopify store using a custom checkout app
A non-Plus Shopify store needed quantity-tier pricing that the theme could explain to the customer but could not authoritatively apply inside the protected checkout steps. A custom app hosted on Fly.io validated the cart, created a discounted Draft Order, and handed the customer back to Shopify's secure checkout.
Plain-English summary
What this solved for the business or user
The customer only saw a straightforward offer: buy more pairs and unlock a better price. The extra engineering existed because the store was not on Shopify Plus. Instead of trying to modify Shopify's protected checkout, the storefront sent the cart to a custom checkout app, the app prepared the correctly discounted order, and Shopify still handled the final payment experience.
01 / Real situation
What was happening
The promotion used four real quantity thresholds: buy 3 for 5% off, 6 for 10%, 10 for 15%, and 20 for 20%. The storefront needed to update the selected tier, total price, and savings immediately. But the final checkout price could not depend on JavaScript in the theme. The cart could also contain excluded products, free promotional items, or another promotion, so the discount had to be recalculated from the actual cart when checkout started.
02 / Constraint
Why the obvious solution was not enough
The store was not on Shopify Plus, so we could not build the required customization directly into Shopify's information, shipping, or payment checkout steps. Theme Liquid can display discount information, but a manual discount code is applied at checkout rather than being authoritatively applied by the theme cart itself. For this requirement, the theme therefore remained the customer-facing layer while a server-side application became the pricing and order-preparation layer.
03 / Implementation
How the solution works
- Keep the tier selector in the Shopify theme because that is where the customer chooses quantity and needs immediate feedback about price and savings.
- When the customer starts checkout, read the current cart rather than trusting a total that was calculated earlier in the browser.
- Send the cart to a custom checkout application hosted on Fly.io. The application acts as the bridge between the storefront and Shopify's Admin API; it does not replace Shopify's payment page.
- Normalize each cart line into the information the promotion actually needs: variant ID, quantity, price, eligibility, and promotional state.
- Exclude products or free lines that must not increase the paid quantity used to determine the discount tier.
- Calculate the highest valid threshold on the server. Seven eligible units receive the six-unit tier; ten eligible units receive the ten-unit tier.
- Validate the variant IDs, quantities, current prices, exclusions, and any free-item rules again before creating the order. The browser never supplies the final trusted discount percentage or order total.
- Use Shopify's Admin API to create a Draft Order containing the verified variants and quantities, then apply the calculated discount to that Draft Order.
- Return the Draft Order's secure invoice or checkout URL and redirect the customer there. Shopify continues to own payment, customer information, and order completion; the custom app only prepares the correct order state before checkout.
- Log the selected tier, qualifying lines, excluded lines, and Draft Order ID so pricing issues can be traced from the promotion decision to the resulting Shopify order.
// Storefront: send the current cart, not a trusted final price.
const cart = await getLiveCart();
const { checkoutUrl } = await customCheckoutApp.createCheckout({ cart });
// Fly.io app: server owns the final promotion calculation.
const lines = normalize(cart.items);
const eligibleQty = lines
.filter(isEligiblePaidLine)
.reduce((sum, line) => sum + line.quantity, 0);
const tier = getTier(eligibleQty, [
{ qty: 20, percent: 20 },
{ qty: 10, percent: 15 },
{ qty: 6, percent: 10 },
{ qty: 3, percent: 5 },
]);
const checkoutUrl = await createDiscountedDraftOrder({
lines,
tier,
});
return { checkoutUrl };04 / Release checks
What should be verified before shipping
- Test quantity edges immediately below and exactly at every tier: 2/3, 5/6, 9/10, and 19/20.
- Mix eligible and excluded products and confirm excluded quantity never increases the tier.
- Change quantity immediately before checkout and confirm the Fly.io app receives and prices the latest cart state.
- Test the tier discount together with BOGO or free-item rules and define which promotion wins when the same merchandise could qualify twice.
- Change a variant or price after the storefront has loaded and confirm the server does not blindly trust stale browser data.
- Force the custom app or Draft Order API call to fail and confirm the customer remains in a recoverable cart state instead of reaching a broken checkout.
- Confirm the Draft Order checkout contains the expected variants, quantities, discount, customer-facing total, and no duplicate promotional lines.
05 / Result
What changed
The store gained custom tiered pricing without rebuilding Shopify checkout or requiring Shopify Plus. Customers still completed payment through Shopify's secure checkout, while the custom application handled only the business rule Shopify theme code could not safely enforce as the final order price.
Reusable lessonUse each layer for the responsibility it can safely own: the theme explains and collects the customer's choice, the server validates the promotion and prepares the order, and Shopify remains responsible for checkout and payment.
Current note
How I would evaluate this today
This case documents the architecture used for the real non-Plus store. Shopify currently limits Checkout UI extensions on the information, shipping, and payment steps to Shopify Plus. Shopify's theme documentation also notes that manual discount codes are applied at checkout rather than exposed through the cart's discount applications. For a new project today, native automatic discounts or Shopify Discount Functions should be evaluated first when they can express the rule. A custom app plus Draft Order remains appropriate when the requirement needs a server-prepared order state that the theme alone cannot safely enforce.
References
