Square Catalog Runbook
Square Catalog Runbook
Purpose
This document explains how to configure, deploy, validate, and roll back the live Square catalog integration used by /books/.
What Lives Where
- Static site code:
/Users/q/src/hermeticus - Worker source:
integrations/square-catalog-worker/ - Public worker URL reference:
_config.ymlassquare_catalog_api_base - Worker secret:
SQUARE_ACCESS_TOKENin Cloudflare
Required Accounts and Inputs
- A Cloudflare account that can deploy Workers
- A Square seller account and developer app
- One Square access token with these permissions:
ITEMS_READINVENTORY_READORDERS_READORDERS_WRITEPAYMENTS_WRITE
- The Square location ID that should drive both inventory checks and checkout creation
Square Data Rules for V1
Each book intended for the website should:
- be active, not archived
- have exactly one sellable variation
- have a positive price on that variation
- have inventory tracking enabled at the chosen Square location
- have positive inventory if it should be visible online
Optional but recommended:
- a category
- one or more images
- a customer-facing description. If Square provides
description_html, the website displays that formatted HTML after client-side sanitization. - a seller-visible number custom attribute named exactly
Shipping weight (lb)when the product should use weight pricing or free shipping. A variation value overrides an item value. Leave it empty for normal per-item rates and use0.1for free-shipping products.
Items with multiple variations are skipped by v1.
Square’s native Dashboard Weight field is not exposed by the public Catalog API. Only the custom attribute affects this Worker.
Worker Configuration
integrations/square-catalog-worker/wrangler.toml stores non-secret defaults:
ALLOWED_ORIGINSaccount_idfor the Hermeticus Bookshop Cloudflare accountSQUARE_ENVCATALOG_TTL_SECONDSSQUARE_VERSIONSQUARE_LOCATION_ID
Before deploy, set these values as needed:
ALLOWED_ORIGINSshould includehttps://hermeticus.orgSQUARE_ENVshould beproductionunless testing against Square sandboxSQUARE_LOCATION_IDmust be set for the location used by the shopCATALOG_TTL_SECONDSdefaults to300
Shipping prices and thresholds live in integrations/square-catalog-worker/shipping-rates.json. Money values use US cents and weights use pounds. The Worker validates this file when its module loads.
Shipping Calculation
- Products without
Shipping weight (lb)use theperItemRatesCentsquantity table: $5 for one item, $6 for two, increasing by $1 per item through $28 for 24 items, then $30 for 25 or more. - Products from 0 through 0.1 lb are free-shipping products and are excluded from every item and weight total.
- Other weighted products cost $2 per pound. The Worker multiplies weight by quantity and rounds fractional cents up. A result below $1 becomes free, a result from $1 through $5 becomes $5, and a result above $5 is unchanged.
- A cart containing only null weights uses the quantity table. A cart containing only paid weights uses the weight result. A mixed cart uses the higher of the weight result and the quantity-table result for all non-free products.
- Missing, negative, or malformed weights become
null, which avoids accidental free shipping.
The secret token must never be committed:
npx wrangler secret put SQUARE_ACCESS_TOKEN \
--config integrations/square-catalog-worker/wrangler.toml
Local Setup
From the repo root:
npm install --cache "${TMPDIR}/hermeticus-npm-cache" \
--prefix integrations/square-catalog-worker
npm test --prefix integrations/square-catalog-worker
If the local npm cache is healthy, a plain npm install --prefix integrations/square-catalog-worker also works.
The Jekyll site still uses the Ruby toolchain described in AGENTS.md. On the machine used for this implementation, local bundle exec jekyll build is blocked by Ruby 2.6.10; GitHub Pages itself is still the production build target.
Deploy the Worker
- Authenticate Wrangler if needed:
npx wrangler login
- Set the Square token secret:
npx wrangler secret put SQUARE_ACCESS_TOKEN \
--config integrations/square-catalog-worker/wrangler.toml
-
Confirm the non-secret Cloudflare account ID and Square location ID before deploy. Review
shipping-rates.jsonwhen prices change, but never store the access token in the repo. -
Deploy:
npx wrangler deploy --config integrations/square-catalog-worker/wrangler.toml
- Copy the deployed worker base URL and set
_config.yml:
square_catalog_api_base: "https://<your-worker>.workers.dev"
- Commit and push the repo so the site points at the deployed worker URL.
Validate After Deploy
Worker API
Catalog:
curl -H 'Origin: https://hermeticus.org' \
'https://<your-worker>.workers.dev/catalog'
Expected result:
- HTTP
200 - JSON array
- only short keys:
i,v,n,p,d,c,m,w,q dcontains Squaredescription_htmlwhen available, otherwise Square plaintext description datawis a number of pounds from the configured Square custom attribute ornull
Checkout:
curl -X POST 'https://<your-worker>.workers.dev/checkout' \
-H 'Content-Type: application/json' \
-H 'Origin: https://hermeticus.org' \
--data '{"items":[{"v":"<variation-id>","q":1}]}'
Expected result:
- HTTP
200 - JSON containing
u - the
uvalue opens a Square-hosted checkout page - the checkout page adds the shipping amount calculated from the current catalog and
shipping-rates.json - the checkout page requires the buyer’s name, phone number, and shipping address, creating a
SHIPMENTfulfillment - the checkout page has no US-confirmation custom field
Shipping collection is driven entirely by the CreatePaymentLink request (checkout_options.ask_for_shipping_address), not by Square dashboard settings. After the buyer pays, the address is stored on the order in fulfillments[].shipment_details. The website and Worker do not store customer addresses.
Fulfill a Paid Order
Square Orders Manager is the source of truth for fulfillment:
- Open the paid order in Square Orders Manager. Do not ship from a website return message alone.
- Confirm the payment is completed and review the products, recipient, phone number, and shipping address. If the address is outside the United States, contact the buyer and refund the order instead of shipping it at the domestic rate.
- Pack and send the books using the shop’s normal mailing method.
- Record tracking information when available and complete the fulfillment manually in Square Orders Manager. Payment-link orders with fulfillments remain open until this step.
Square Checkout API links are single-use and are created separately for each cart. The links themselves are not managed like reusable Dashboard payment links.
Square’s hosted Checkout API does not provide a country allowlist. Checkout accepts any country without a custom confirmation field, and the shop verifies the country before fulfillment.
Public Site
After the repo is pushed:
/books/shows live books from Square- a buyer can add multiple distinct books to the cart
- clicking checkout redirects to Square
- the cart explains that shipping is calculated at checkout and that non-US orders may be canceled and refunded
- reducing stock to zero or archiving a book removes it after cache refresh
Operational Notes
GET /catalogis edge-cached to reduce load on Square.POST /checkoutalways validates against live Square data instead of using the cached public payload.POST /checkoutcalculates shipping only from the fresh server-side catalog; browser-supplied weights are never trusted.- Invalid shipping configuration prevents the Worker from starting, and an unsafe calculated amount fails closed without creating a payment link.
- The worker fails closed. If validation or Square access fails during checkout creation, no payment link is created.
Rollback
Worker rollback
- Redeploy the previous known-good worker version from the last good commit.
Site rollback
- Set
_config.ymlsquare_catalog_api_baseback to an empty string and push. - The
/books/page will stop trying to load the worker and will show the configuration message instead of broken checkout UI.
Secret rotation
If the Square token is rotated or revoked:
npx wrangler secret put SQUARE_ACCESS_TOKEN \
--config integrations/square-catalog-worker/wrangler.toml
No code change is required.