Proposed (STA-922, batch stackra-32; STA-691). It carries out owner decision R3-SO-08 (merging
GRO-182 b, GRO-186 and the gift-card and promotion exclusions) and answers the Commerce STA-782
review:
@stackra/payments had no restricted-tender kind and TerminalRequest had no PIN step.
It reads with
0357,
0377
and
0378.Context
Grocery takes SNAP at the till and online at launch inus (GRO-182) and meal vouchers in eu-fr and eu-be
(GRO-186). Both are one mechanism: a tender that only certain lines may pay, the basket split by eligibility, no tax on
the part the tender pays where the law says so, and the rest on a second tender. Online SNAP adds one rule the card
flow lacks: the EBT amount is taken at delivery or pickup against the picked basket, never at checkout, and weight
changes and substitutions re-split it. EBT needs a PIN, entered on the terminal or on the processor’s hosted PIN pad.
Decision
- A tender kind.
TenderKind(card,restricted) travels onTerminalRequestandCheckoutRequestwith the program code (program). A restricted request asks the provider forPaymentCapability.RestrictedTender, so only a processor that takes the program serves it. - Programs are pack data.
RestrictedTenderProgram(code,ruleSet,taxExempt,captureAtFulfilment,pinEntry) is read through theRestrictedTenderProgramsport; themarket-packdriver reads the pack keypayments.restricted-tenders(live entries only, so a program waiting on its processor is listedplanned). No program, Market or category is written in code. - Eligibility is a rule set.
TenderEligibility.eligible(market, ruleSet, lines)returns one flag per line. The host binds it over@stackra/rules(rule-set:grocery:tender-eligibility, seeded per pack: fees, tips and delivery are never eligible). Thememorydriver serves tests. - The split is one pure function.
splitTendergives the restricted part (eligible lines, without their tax when the program is tax-exempt, capped by the tender’s balance), the waived tax (in proportion when the balance covers only part, rounded down) and the remainder; the three add up to the basket. The remainder is the product’s to take on an ordinary tender under its own reference. - Capture at fulfilment.
RestrictedTenderServiceauthorises only when the program sayscaptureAtFulfilment;captureAtFulfilmentre-splits the picked basket and captures the restricted part, never more than was authorised, and voids when nothing eligible is left. Everything goes throughPaymentService, so theunknownstate and one payment per reference hold. - The PIN step.
TerminalRequest.pinEntryasks the terminal for a PIN (PaymentCapability.TerminalPin); the PIN never reaches the platform. A restricted program’spinEntrysets it.
Consequences
- OS integrations writes the EBT processor connector (Forage or equivalent, named in
market-pack:us) and the meal-voucher connectors (Edenred, Swile;eu-fr) against@stackra/payments/contracts. They wait on the owner’s processor choice (owner:ebt-processor). - The host binds
TenderEligibilityover the rules engine; until it does, the host’s build fails on the missing port. - Terminal drivers that support PIN entry or a restricted program list the new capabilities.
Open
- The EBT and meal-voucher processor drivers (owner decision on the processor).
- Whether a program’s balance inquiry becomes its own gateway call; today the product passes the balance it read.