← back to Shopify Sample Shipping
theme/sample-shipping-cart-engine.liquid
200 lines
{%- comment -%}
============================================================================
TK-11333 — SAMPLE-SHIPPING CART POLICY ENGINE (Option A — DTD-committed)
============================================================================
STATUS: install on a DUPLICATE/DEV theme only. Steve publishes. Do NOT paste
into the LIVE theme. Go-live gated on proto-autoapply proving the /discount
permalink makes free shipping persist to checkout for an eligible customer.
ARCHITECTURE (verified live 2026-09-09 — see verification/band-truth-2026-09-09.json):
• The FREE-SAMPLE floor is a delivery-profile RATE, not this engine: a $0
"Free Shipping (No Tracking)" method on the dedicated profile
"Samples — Free Shipping (No Tracking)" (gid 96764067891), gated by
cart TOTAL_PRICE <= $45. Samples are $4.25, so ~10 samples ship free with
NO code and NO login. VERIFIED the band evaluates WHOLE-CART subtotal:
any roll (General/carrier profile) pushes the total over $45 and the band
switches off for the whole cart.
• Store is Advanced Shopify — NO Shopify Functions — so a shipping RATE
CANNOT be gated by customer tag, sample count, or product type.
• PROVEN LIMIT: a TOTAL_PRICE band cannot tell 6 wallcovering samples
($25.50) from 3 WC + 3 fabric ($25.50) — identical price. So the retail
per-type "5 and 5" rule CANNOT be a shipping charge below the cap. Below
$45 everyone is free regardless of type. Per-type "5 and 5" is therefore
GUIDANCE ONLY here (a true per-type charge needs Plus + Functions).
WHAT THIS ENGINE DOES (and only this):
1. DESIGNER EXTENSION: a logged-in designer (tag match) whose sample cart is
OVER the band cap (11–12 samples, subtotal > $45) gets TRADESHIP
auto-applied so their free shipping extends to 12. This is the ONLY case
a code is auto-applied — it never fires when the band already gives free
(no needless redirect) and SAMPLESHIP is NOT auto-applied at all (the band
already covers the retail free range; SAMPLESHIP stays a manual code).
2. HONEST MESSAGING: it shows a "why you're charged" message ONLY when a
charge will ACTUALLY be incurred (cart subtotal is over the band cap AND
no covering code applies). It NEVER claims a charge the live band won't
impose. When free, it says so; it never nags "charged" on a free cart.
3. SOFT CAP 20: at 21+ samples it disables the standard checkout button AND
hides accelerated wallet buttons + shows a reduce-to-20 message. Honestly
SOFT — the direct /checkout URL and some wallet flows can still slip past;
a hard cap needs Plus/Functions.
INSTALL:
1. Save as snippets/sample-shipping-cart-engine.liquid on a DEV theme.
2. Render near the cart checkout button: {%- render 'sample-shipping-cart-engine' -%}
3. Confirm TRADESHIP exists (create-freeship-codes.mjs --apply) before relying on the extension.
4. Tune CONFIG (bandCap, codes, caps, checkout selectors) to the theme.
============================================================================
{%- endcomment -%}
<div id="dw-ship-policy" data-dw-ship-policy hidden></div>
<script type="application/json" id="dw-ship-cart-data">
{
"isLoggedIn": {{ customer | default: false | json }},
"tags": {{ customer.tags | default: '' | json }},
"cartSubtotalCents": {{ cart.total_price | default: 0 }},
"lines": [
{%- for item in cart.items -%}
{
"product_type": {{ item.product.type | default: '' | json }},
"variant_title": {{ item.variant.title | default: '' | json }},
"sku": {{ item.sku | default: '' | json }},
"quantity": {{ item.quantity }},
"line_price_cents": {{ item.final_line_price | default: 0 }}
}{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}
]
}
</script>
<script>
(function () {
// ---------------- CONFIG (tune to the store) ----------------
var CONFIG = {
designerCode: 'TRADESHIP', // segment-scoped free-ship code, maxShip $30
bandCapCents: 4500, // live band: free while whole-cart subtotal <= $45
designerCap: 12, // total samples free for designers (via code)
retailWC: 5, // per-type GUIDANCE only (cannot be a charge below the band)
retailFabric: 5, // per-type GUIDANCE only
softCap: 20, // block standard checkout above this many samples
// customer tags that mean "gets designer free shipping" — mirror the DW Trade/Designers segment
designerTags: ['trade','trade_approved','sample-freeship','interior designer',
'interior design','interior','architect','contractor','commercial property owner',
'wallcovering installer','interior designer - residential','interior designer - commercial'],
// product.type values that count as Fabric (VERIFIED live: catalog uses "Fabric" and
// "Wallcovering"; toLowerCase makes "Fabric" -> "fabric"). Extra synonyms are harmless.
fabricTypes: ['fabric','fabrics','textile','textiles'],
// VERIFIED against carnegie-color-swatch sections/cart.liquid: the checkout button is
// <button class="cart-checkout button" name="checkout">. [name="checkout"] + .cart-checkout both match.
checkoutBtnSelector: '[name="checkout"], button[name="checkout"], .cart-checkout, .cart__checkout, #checkout, #cart-checkout',
// accelerated wallet / dynamic checkout buttons to hide when over the soft cap.
// VERIFIED: carnegie renders <div class="additional-checkout-buttons"> for content_for_additional_checkout_buttons.
walletSelector: '.additional-checkout-buttons, .shopify-payment-button, [data-shopify="payment-button"], .dynamic-checkout__content',
messageMountSelector: '#dw-ship-policy'
};
var APPLIED_KEY = 'dw_ship_code_applied'; // sessionStorage guard against redirect loops
function readData() {
try { return JSON.parse(document.getElementById('dw-ship-cart-data').textContent); }
catch (e) { return { isLoggedIn: false, tags: '', cartSubtotalCents: 0, lines: [] }; }
}
function isSample(l) {
return (l.variant_title || '').toLowerCase() === 'sample' || /-sample$/i.test(l.sku || '');
}
function isFabric(l) {
return CONFIG.fabricTypes.indexOf((l.product_type || '').toLowerCase()) !== -1;
}
function isDesigner(tags) {
var set = String(tags || '').toLowerCase().split(',').map(function (s) { return s.trim(); });
return CONFIG.designerTags.some(function (t) { return set.indexOf(t) !== -1; });
}
function evaluate(d) {
var samples = d.lines.filter(isSample);
var total = samples.reduce(function (n, l) { return n + l.quantity; }, 0);
var wc = samples.filter(function (l) { return !isFabric(l); }).reduce(function (n, l) { return n + l.quantity; }, 0);
var fab = samples.filter(isFabric).reduce(function (n, l) { return n + l.quantity; }, 0);
var hasNonSample = d.lines.some(function (l) { return !isSample(l); }); // e.g. a roll
var designer = !!d.isLoggedIn && isDesigner(d.tags);
var overSoftCap = total > CONFIG.softCap;
// The LIVE band already gives free shipping while whole-cart subtotal <= $45.
var bandCovers = d.cartSubtotalCents <= CONFIG.bandCapCents;
// A covering code closes the gap ONLY for a logged-in designer within the 12-sample cap.
var codeCovers = designer && total <= CONFIG.designerCap && total > 0;
// Will the customer ACTUALLY be charged shipping? (this is what the message must reflect)
var willBeFree = bandCovers || codeCovers;
var willBeCharged = total > 0 && !willBeFree;
// Auto-apply the designer code ONLY when it changes the outcome:
// designer, over the band cap, still within the 12 cap, not over the soft cap.
var shouldApplyCode = designer && !bandCovers && total <= CONFIG.designerCap && !overSoftCap;
// Honest reason (only populated when a charge is real, or as a soft note).
var reason = '';
if (overSoftCap) {
reason = 'Sample orders are limited to ' + CONFIG.softCap + ' per order. Please reduce to ' +
CONFIG.softCap + ' or fewer to check out.';
} else if (willBeCharged) {
if (designer && total > CONFIG.designerCap) {
reason = 'Free sample shipping covers up to ' + CONFIG.designerCap + ' samples for trade accounts. ' +
'You have ' + total + ' — shipping on the rest is at carrier cost.';
} else if (hasNonSample) {
reason = 'Free sample shipping covers sample-only orders up to $' + (CONFIG.bandCapCents/100).toFixed(0) +
'. Rolls and larger orders ship at standard carrier rates.';
} else {
reason = 'Free sample shipping covers orders up to $' + (CONFIG.bandCapCents/100).toFixed(0) +
' (about 10 samples). Your order is over that — shipping is at carrier cost.';
}
}
return { total: total, wc: wc, fab: fab, designer: designer, bandCovers: bandCovers,
willBeFree: willBeFree, willBeCharged: willBeCharged, shouldApplyCode: shouldApplyCode,
overSoftCap: overSoftCap, reason: reason };
}
function applyCode(code) {
// /discount/CODE?redirect=/cart sets the session discount cookie (this is a full-page nav).
// Guarded so we redirect at most once per cart signature (no loops).
var sig = code + ':' + (document.getElementById('dw-ship-cart-data').textContent.length);
if (sessionStorage.getItem(APPLIED_KEY) === sig) return;
sessionStorage.setItem(APPLIED_KEY, sig);
window.location.assign('/discount/' + encodeURIComponent(code) + '?redirect=/cart');
}
function render(state) {
var mount = document.querySelector(CONFIG.messageMountSelector);
if (mount) {
mount.hidden = false;
if (state.overSoftCap) {
mount.innerHTML = '<div class="dw-ship-msg dw-ship-msg--block" role="alert" style="padding:.75rem 1rem;border:1px solid #b00;border-radius:8px;margin:.5rem 0;color:#b00;">' +
state.reason + '</div>';
} else if (state.reason) { // real charge -> honest "why charged"
mount.innerHTML = '<div class="dw-ship-msg" role="status" style="padding:.75rem 1rem;border:1px solid #d8c9a8;border-radius:8px;margin:.5rem 0;background:#faf6ee;">' +
state.reason + '</div>';
} else if (state.total > 0 && state.willBeFree) {
mount.innerHTML = '<div class="dw-ship-msg dw-ship-msg--ok" role="status" style="padding:.5rem 1rem;color:#2e6b2e;">Free sample shipping applied.</div>';
} else { mount.innerHTML = ''; }
}
// soft cap: disable standard checkout + hide accelerated wallet buttons (still SOFT — /checkout URL can slip past)
var over = state.overSoftCap;
document.querySelectorAll(CONFIG.checkoutBtnSelector).forEach(function (b) {
if (over) { b.setAttribute('disabled','disabled'); b.setAttribute('aria-disabled','true'); b.style.opacity='0.5'; b.style.pointerEvents='none'; }
else { b.removeAttribute('disabled'); b.removeAttribute('aria-disabled'); b.style.opacity=''; b.style.pointerEvents=''; }
});
document.querySelectorAll(CONFIG.walletSelector).forEach(function (w) { w.style.display = over ? 'none' : ''; });
}
function run() {
var d = readData();
var state = evaluate(d);
render(state);
// auto-apply the designer code ONLY when it actually changes the outcome (11-12 designer samples)
if (state.shouldApplyCode) applyCode(CONFIG.designerCode);
}
if (document.readyState !== 'loading') run();
else document.addEventListener('DOMContentLoaded', run);
})();
</script>