← back to Dw Checkout Guard

README.md

150 lines

# DW Checkout Guard

A Shopify **Cart & Checkout Validation Function** for the LIVE DW store
(`designer-laboratory-sandbox.myshopify.com`). It blocks the "Checkout" button
whenever a cart contains a line item that is either:

1. **$0.00** — the quote-only "$0 Standard variant" exposure (~1,744 Phillipe
   Romano / Fentucci Naturals products whose sellable variant is `$0` +
   `available:true` and is addable via `/cart/add.js` even though the PDP button
   is hidden), or
2. **discontinued** — a product carrying a discontinued tag (`Discontinued`,
   `Discontinued-Review`, `YB-Discontinued-2026-04`). Belt-and-suspenders: the 42
   known sellable-discontinued items were already drafted 2026-07-30.

## Why a Validation Function (and not per-variant `available:false`)

Canary-proven 2026-07-30: this store does **not** gate `/cart/add.js` on
inventory. Setting the $0 variant to `available:false` (deny + qty 0) still lets
the $0 line be added (tested at qty 1 and qty 5). Only a **checkout-layer
validation** reliably blocks the $0 *order* — and, unlike drafting, it does so
**without delisting the product**, so the intended `$4.25` sample flow keeps
working. (Discontinued items get drafted instead, since they should be fully off
the store.) See memory `dw-shopify-addjs-inventory-not-gated`.

**Cost:** $0 — Shopify Functions have no per-invocation charge; the custom app is
free.

---

## The two files that matter

Everything else here is boilerplate. The business logic lives in exactly two
files, and they are stable across CLI versions:

- `extensions/dw-checkout-guard/src/cart_validations_generate_run.graphql` — the
  input query (reads each line's per-unit `cost` + the product's discontinued tag).
- `extensions/dw-checkout-guard/src/cart_validations_generate_run.js` — the rule
  (`amount <= 0 || isDiscontinued` → add a blocking error scoped to that line).

---

## Deploy — RECOMMENDED path (version-proof)

Because the exact `api_version` / target string can drift between `shopify` CLI
versions, the safest path is to let the CLI scaffold the boilerplate, then paste
in the two logic files above:

```sh
cd ~/Projects/dw-checkout-guard

# 1. Log in to the DW Partner org (opens a browser)
shopify auth login

# 2. Link (or create) the app under the Partner org
shopify app config link          # fills client_id in shopify.app.toml

# 3. Generate a Cart & Checkout Validation function extension
shopify app generate extension
#   → pick:  Function  →  "Cart and checkout validation"
#   → language: JavaScript
#   → name it e.g. dw-checkout-guard

# 4. Overwrite the generated src/ logic with the two files from THIS repo:
#      extensions/dw-checkout-guard/src/cart_validations_generate_run.graphql
#      extensions/dw-checkout-guard/src/cart_validations_generate_run.js
#    (match the generated filenames/export name if the CLI used different ones —
#     the rule inside the run() function is what you're copying.)

# 5. Build + deploy
shopify app function typegen      # optional, regenerates types
shopify app deploy                # pushes the function version live
```

### ⚠️ MANDATORY post-deploy step — ACTIVATE the validation

`shopify app deploy` uploads the function but **does NOT enforce it** until you
turn it on. A deployed-but-inactive validation lets every $0 line straight
through — it looks done and isn't. So this is step 1, not a footnote:

**Admin → Settings → Checkout → Validations (a.k.a. "Manage checkout rules")**
→ add **DW Checkout Guard** → **toggle it ON**.

Then confirm it's actually enforcing with the smoke test in "Verify after deploy"
below — do not consider this shipped until a $0 line is provably blocked at the
Checkout step in a real browser.

### ⚠️ MANDATORY pre-deploy step — confirm the cost field name

The one field this function depends on is the per-unit line price. Before you
deploy, confirm the Function input schema actually exposes it (schemas differ
from the Storefront API and can shift between `api_version`s):

```sh
shopify app function typegen
# open the generated types for CartLineCost and confirm `amountPerQuantity`
# exists. If the field is named differently in your api_version, update
# src/cart_validations_generate_run.graphql to match.
```

If the field name is wrong, `shopify app function build` FAILS LOUDLY with an
"unknown field" error before anything reaches the store — so a bad field can't
silently ship. And even at runtime the JS **fails open** (an unreadable price is
skipped, never treated as $0), so the worst case is "the $0 block doesn't fire,"
never "all checkout blocked."

## Deploy — as-is path

If your `shopify` CLI matches `api_version = "2025-01"` and the
`cart.validations.generate.run` target, this repo is already complete — just:

```sh
cd ~/Projects/dw-checkout-guard
shopify auth login
shopify app config link
shopify app deploy
```

---

## Verify after deploy (no real order needed)

The exposure reproduces only through the cart endpoint, so test there:

```sh
# $0 quote-only variant should now be BLOCKED at checkout.
# Add it, then try to advance to checkout — the guard message should appear.
curl -s -X POST https://www.designerwallcoverings.com/cart/add.js \
  -H 'Content-Type: application/json' \
  --data '{"id":44494274199603,"quantity":1}'      # $0 "Standard" variant
# (add.js still succeeds — that's expected; the block is at the CHECKOUT step)
```

Then, in a browser cart, verify all three cases:
1. **$0 line alone** → click Checkout → blocked with the guard message. ✅
2. **Mixed cart** ($0 line + a $4.25 sample) → blocked while the $0 line is
   present; **remove the $0 line** → the sample alone checks out fine. ✅
   (This proves the guard doesn't collateral-block the sample flow.)
3. **A normal-priced product alone** → checks out fine (no false positive). ✅

**Scope caveat:** Cart & Checkout Validation functions run on the standard
browser checkout AND draft orders — which covers this exposure (the `/cart/add.js`
vector). They do **not** necessarily run on a fully headless/custom Storefront-API
checkout or a Subscriptions-app flow. DW doesn't use those today; revisit this
guard if that ever changes.

## Rollback

Turn the validation off in **Settings → Checkout**, or `shopify app deploy` a
version with the function removed. No catalog data is touched by this function.