Appearance
Form factors
formFactor is the load-bearing discriminator. It decides which machinery an order touches. Getting it wrong produces a plausible-looking failure rather than a loud one.
| Form factor | The instrument | Redemption | Ban surface |
|---|---|---|---|
bearer | The code is the value | Web checkout | Resale pattern |
account_bound | Credit inside an account | Login, then checkout | The identity |
account_bound_loyalty | As above, plus a points scheme | Login, then checkout | The identity |
in_store_barcode | A code scanned at a till | A human, at a register | Resale pattern |
credit_line | Borrowing capacity | A purchase made on the LP's card | The issuer |
The plain-language version, by how a card is spent (in store, online, or into an account), is Three kinds of gift card.
Why the distinction is not cosmetic
Bearer vs account-bound changes what failure means. A bearer card has no account to be locked out of, so a login wall is not a failure mode. An account-bound instrument can hit one. The correct response is to park the order for an operator to re-bank a session, not to retry and not to fail the buyer. Applying bearer handling to an account-bound instrument would retry into a lockout.
Ban surface changes who is harmed. A caught resale pattern lands on the behaviour. A caught account-bound pattern lands on a person's identity: their account, points and order history. Those deserve different risk appetites, and the field exists so the difference is explicit rather than implied.
in_store_barcode has no checkout to drive at all. This one most invites a wrong assumption. There is no web form to fill, only a cashier and a scanner. So its adapter's execute() refuses permanently and points at the reveal flow instead.
That refusal is the useful part. Without it, a future caller could wire the in-store channel into checkout automation. The result would be nothing at all or, worse, an attempted online redemption of a card a buyer is holding at a register.
requiresCheckoutAutomation('in_store_barcode') returns false, and the adapter is tested to agree with it. Two independent statements of the same fact. If they ever disagree, one is wrong and a buyer finds out at a till.
credit_line is the odd one
Every other form factor is an instrument we hold and redeem. credit_line is capacity we borrow against, and it settles over weeks rather than seconds.
That is why it maps to the deferred settlement model and why the refund daemon filters it out. "If the buyer has nothing by the deadline, refund" is right for a ticket and a gift card. It is wrong for something expected to be undelivered for a fortnight.
Barcode symbology, and an unverified claim
For in_store_barcode, the merchant's symbology (which format their scanner reads) is part of the profile.
A wrong symbology does not error. The scanner just does not beep while the buyer stands at a till and the reveal window burns. So the profile refuses to guess: an unknown merchant throws rather than defaulting.
The Best Buy entry is a claim, not a measurement
The profile declares Code 128. Nothing in this repo verifies that against a physical card: no capture, no photograph.
The reveal page does not assert it. Instead it renders the three plausible encodings for a 12–19 digit numeric retail PAN, labelled and ordered: Code 128 (declared), ITF (offered only for even-length all-digit payloads, the only thing it can encode), and Code 39. One scan at a register settles it permanently.
Target is the exception, and it was measured. Its eGift barcode is a PDF417 carrying the 26-digit number printed under it, so the profile asks the seller for that number and draws a PDF417 first, with the 1D codes as fallbacks. It is drawn on our server with bwip-js, and an independent decoder read a 26-digit payload back exactly. It has still never been scanned at a till.
The 1D encoders are written in-repo rather than pulled from a CDN, because a shop's wifi is not a dependency we get to have. Writing them also means the encoding is tested. The Code 128 table is 107 hand-transcribed patterns, and one wrong digit produces a barcode that renders perfectly and never scans. So the tests validate the table structurally: every data symbol exactly 11 modules, the stop 13, widths 1–4, no duplicates.