sgit newsroom v0.1.29 · snapshot 2026-09-24

Reading room · store.sgit.ai

On this page

Reading room / store.sgit.ai · raw text · live ↗

From store.sgit.ai, the page as fetched on 2026-09-25 · open the live page ↗Everything on this sheet is the source site's own text; the newsroom's chrome is outside it.

Stripe

Six products priced and reconciling, three coupons live, and the store's side of the hand-off built. No payment link, no promotion code, no webhook. — The rail for everything bought from a screen, and the one that turns a buyer into a customer — including a buyer who pays nothing.

Why

The point of this rail is not the money. It is the customer record. A hundred-per-cent discount code taken on this rail produces a Stripe Customer with an email, a name, a country and a completed checkout against a named product at a stated price — for £0. That is the whole shape of what the event needs: hand somebody a code, they go through the real purchase, and at the end of it there is a person in a system rather than a business card in a pocket.

A payment link is an online payment, so no cross-border rule applies to it. That is the reason this store is links rather than a card reader: a United Kingdom card account cannot tap in Portugal, the provider declines on detecting a different country, and a reader bought for the event would have been a box that says no in public to the first person who tried it.

And a catalogue-backed checkout is the only version that can discount. A link that asks the buyer to type an amount has nothing to take a percentage of. Once the price is an object in Stripe, a coupon is a percentage of it, a promotion code is a string a human can be handed, and the arithmetic happens on the rail rather than in a browser where it is a demonstration.

What this rail is handed

The steps, in order

  1. Create the products and their prices — Done on 16 September. Six products, GBP, one price each, and the split into DEPOSIT and DELIVERY at the two upper levels was the project lead's and is better than the shape this plan first proposed: the pair sums to the price, so paying in full is adding both lines. Every price carries its code — ABP-T1, ABP-T3-DEPOSIT and the rest — in the Stripe price description field, because the dashboard's product view surfaces no lookup key. That is not the same object as Price.lookup_key, which exists on the API and is the field a webhook would use to map a line item back to a level. It works for a person reading the dashboard and it is not yet queryable, and that difference is written down here rather than found later by a handler that cannot tell what was bought.
  2. Turn on customer creation, and prove it on a £0 order — Set customer creation to always. Then run one hundred-per-cent order end to end and look for the Customer in the dashboard. If it is not there the rail is doing the wrong job silently, and it will look like it worked — the buyer still sees a confirmation, the session still completes, and nothing anywhere says the record was dropped.
  3. Create the promotion codes — and a fourth coupon for DOORSOPEN — The coupons are done — 25, 50 and 100 per cent, created 16 September. The promotion codes are the gap. A coupon carries the percentage; a promotion code is the string a person is actually handed, and it is what a link can carry pre-applied. Until one exists there is nothing to give anybody. There are eight strings in data/discounts.yml, not seven — DOORSOPEN was added after that count was written. Seven of the eight map onto the three existing coupons; five of those seven are at a hundred per cent and are kept separate so an order record says which one produced it.The eighth needs a coupon of its own, and this is the trap. DOORSOPEN is levels: [pack, vault] in the store's data, and that restriction cannot be expressed on a promotion code: a promotion code restricts by first transaction and by minimum amount, and scoping a discount to particular products is a property of the coupon, through applies_to.products. Attached to the shared hundred-per-cent coupon it would take the whole of a level-four deposit off — £300 of somebody's day, from a code published on a page on purpose. So: a fourth coupon, 100 per cent, applies_to the level-one and level-two products only, and DOORSOPEN attached to that one.No redemption caps, by ruling on 16 September — purchases are managed directly and what a hundred-per-cent code skips paying for is already published free. Expiries yes: every code carries an until: date in the store's data and the promotion code should carry the same one, or the two disagree and the store is the one that looks wrong.
  4. Switch promotion codes ON on every link — the store already prefills them — A link with the promotion-code field switched off refuses a valid code with no explanation, which is the worst failure available here: the buyer blames themselves. It is one checkbox per link and it is not optional, because the store's checkout now builds prefilled_promo_code onto every button when the buyer arrived with a code, and a prefill into a field that is switched off is silently dropped.One limitation, stated rather than hidden. The store keeps the discount record's id and never the code itself — what ships is a sha256, and a build check greps the whole tree to keep that true. So the plaintext is in hand for the life of one page and no longer. Open the checkout by a route that does not carry the code and the price shown is still discounted but the button cannot prefill; the page says so in as many words and tells the buyer to reopen from their link or type it at the provider. That warning is the only thing standing between a buyer and being charged a price they were not shown, which is why it is a callout and not a footnote.
  5. Create the four payment links, and set the return address — Four links, not six. ABP-T1 £10 · ABP-T2 £50 · ABP-T3-DEPOSIT £100 · ABP-T4-DEPOSIT £300. The two DELIVERY halves get no link, because the store's own copy says the balance is invoiced; adding them later is one line each in data/offers.yml.Per link: confirmation page set to redirect to https://store.sgit.ai/paid/↗<id>/?cs={CHECKOUT_SESSION_ID} — that placeholder is literal and the provider substitutes it; promotion codes on; adjustable quantity on for the two lower levels and off for the two deposits, because a deposit is not a thing anybody buys two of.The ?order= question is settled and the answer is that it cannot be done. A payment link's return address is configured once and is the same for every buyer, so the store's reference cannot ride back in it. It does not need to: the browser that built the order still holds it, and the page after paying renders from that. What travels the other way is client_reference_id, which the checkout already appends, and which is how the several links of one order find each other in the dashboard. What comes back is the provider's own session id, which is a string to search on and not a receipt — the page says you came back, and never that you paid.Then paste the four URLs into checkout_url in data/offers.yml. The gate pins each to buy.stripe.com or checkout.stripe.com, and the build refuses to run until the claim ledger's checkout-links-not-issued entry is corrected in the same commit.
  6. Turn the emails on — and understand which of them a £0 order will not send — Neither side is emailed today, and one of them cannot be.The buyer's receipt is a dashboard setting, not a property of the payment: Settings → Business → Customer emails → Successful payments. With it off the buyer completes a checkout and hears nothing, which on a store whose whole argument is that it collects nothing means the buyer has no record from anybody. Turn it on.The seller's notification is a per-person preference on the Stripe account (Profile → Notifications), plus push from their mobile app. It is not part of the link and it is not part of the product, so it has to be set by whoever is going to act on a sale rather than assumed.AND NEITHER FIRES ON A £0 ORDER, which is the half worth reading twice. A hundred-per-cent code takes the total to zero, and a zero-total checkout creates no charge — the session completes with nothing to pay. Both of those emails hang off a successful payment. So the exact journey this rail was built for — hand somebody a code at an event, watch them go through a real purchase, end up with a person in a system — is the journey that notifies nobody, by any route, and the only place it shows up is the dashboard. The webhook below is not a nicety for that journey. It is the only thing that can see it.A post-payment invoice PDF (on the link, at 0.4% capped near $2) is the other built-in way to email a buyer, and it carries more than a receipt does. Whether it is raised and sent for a zero-total order has not been checked here and should be, on the £0 walkthrough, before it is relied on either way.
  7. Stand up the webhook — This is the step that unblocks a first paid order at £50, £500 and £1,500. Their pages promise a person within 24 hours and nothing carries a sale from here to that person. A checkout.session.completed handler that emails the follow-up mailbox the level, the reference, the customer and — at £500 — the template chosen is the smallest thing that closes it. Listen to the session event, not to the payment. A hundred-per-cent order creates no charge, so a handler keyed on a succeeded payment sees every paid order and none of the free ones, which is precisely the set this rail exists to capture. Blocked on: It does not run on this site. This site is static and opens no connection; the handler is somebody else's process holding the signing secret, and it never appears in this repository.
  8. Take one real payment, for £5, and read the receipt — Against the live rail, with a real card. Then read what the receipt actually says, whether the reference is on it under a name the buyer would recognise, and what the fee was. Every sentence on this page about a receipt is currently unverified.
  9. Retire the browser-side discount arithmetic — A code is recognised in the browser today, which is a demonstration and the page says so. The moment a promotion code exists on the rail there are two implementations of one rule, and the browser one is the one that can be edited by the person it is discounting. Worse than editable: the two can simply disagree, and the buyer sees the browser's number and is charged the provider's. It comes out; the code field becomes a thing that carries a string to the provider rather than a thing that computes. Blocked on: Blocked on the promotion codes above.

Still open


This document is released under the Creative Commons Attribution 4.0 International licence (CC BY 4.0).