Skip to content

Cart and Checkout

WhiskerEnt edited this page Sep 9, 2026 · 1 revision

Cart & Checkout

The cart

Shoppers can reach the cart two ways.

The drawer slides in from the side when the cart button is pressed. It is for a glance: what is in the basket, quantities, and a way through to checkout.

The cart page at /cart is a real page. It can be linked to, shared, bookmarked and returned to with the back button. Abandoned-cart recovery emails send people here.

Both read the same cart. The drawer fetches /cart/data, which returns JSON. The page is rendered on the server.

What the cart page shows

  • Each line with its picture, name, chosen variant, unit price and line total
  • Quantity controls, and a remove link
  • The subtotal, with a note that delivery and tax are worked out at checkout
  • Progress towards free delivery, if a shipping zone sets a threshold
  • A few suggestions from the categories already in the basket

Suggestions never include something already in the basket or something out of stock.

Carts and customers

A guest's cart belongs to their browser session and lasts seven days.

Once somebody signs in, the cart belongs to them rather than to the browser. This means:

  • Adding something on a phone and then signing in on a laptop shows the same basket
  • Signing out does not discard it
  • Clearing cookies does not lose it

If a signed-in customer has a cart waiting from another device and also has something in the browser they are using, the two are combined. The same product on both sides becomes one line with a larger quantity rather than appearing twice.

A cart that has been merged is marked merged rather than abandoned, so recovery emails leave it alone.

Checkout

What is collected

Name, email, phone, delivery address, and optionally a billing address. Shops with pickup points enabled also offer collection instead of delivery.

Delivery notes are an optional box for anything the courier needs to know: a gate code, a safe place, when somebody is usually in. The note appears on the order in the admin, so whoever packs it can read it, and on the customer's own order page.

Terms acceptance appears only if the shop has published a page whose slug contains "terms". Shoppers tick to confirm they agree before the order is taken, and the time they did is recorded against the order. Shops with no terms page are not asked, because a tick box pointing at a page that does not exist is worse than no tick box. Publish one from Pages in the admin.

One order per attempt

Pressing Pay twice does not create two orders.

Each checkout form carries a token generated when the page was rendered. The order records that token under a unique database index. If a second submission arrives carrying the same token, whether from a double tap, a retried request or the back button, the shopper is shown the order that was already placed.

The database index is what enforces this, not a check in the code, so two requests arriving at the same moment cannot both succeed. The one that loses is handed the order that won.

The Pay button also disables itself on the first press and explains that the order is being placed. It comes back if the page is restored from browser history.

Email addresses

An address that cannot be parsed is rejected before the order is taken. Previously a mistyped address was quietly discarded and the order placed anyway, which left no way to send the confirmation.

Stock

Stock is deducted with a conditional update, so two people buying the last item at the same time cannot both succeed. Unpaid orders release their stock after fifteen minutes.

Reordering

A customer can put a past order back in the basket from the order page in their account.

Items are added at today's price and against today's stock, not the prices on the old order. Anything no longer for sale, or out of stock, is named in the message rather than quietly dropped.

Invoices

Customers can download the invoice for their own orders from the order page. It is the same document the shop sees in the admin. Requesting an order id belonging to somebody else returns a not-found page.

Clone this wiki locally