> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0445 — Restricted tenders (EBT, meal vouchers) and the terminal PIN step

> Proposed · 2026-10-27

> [!NOTE]
>
> 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](https://docs.stackra.figentra.com/adr/0357-payments-architecture-ports-gateway-drivers-a-platform-layer-and-a-closed-loop-wallet.md),
> [0377](https://docs.stackra.figentra.com/adr/0377-card-terminals-semi-integrated-over-a-local-link-geidea-network-international-paymob-and-neoleap.md)
> and
> [0378](https://docs.stackra.figentra.com/adr/0378-card-terminals-server-driven-and-tap-to-pay-stripe-terminal-square-and-adyen.md).

## Context

Grocery takes SNAP at the till and online at launch in `us` (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

1. **A tender kind.** `TenderKind` (`card`, `restricted`) travels on `TerminalRequest` and `CheckoutRequest` with the
   program code (`program`). A restricted request asks the provider for `PaymentCapability.RestrictedTender`, so only
   a processor that takes the program serves it.
2. **Programs are pack data.** `RestrictedTenderProgram` (`code`, `ruleSet`, `taxExempt`, `captureAtFulfilment`,
   `pinEntry`) is read through the `RestrictedTenderPrograms` port; the `market-pack` driver reads the pack key
   `payments.restricted-tenders` (live entries only, so a program waiting on its processor is listed `planned`).
   No program, Market or category is written in code.
3. **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). The `memory` driver serves tests.
4. **The split is one pure function.** `splitTender` gives 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.
5. **Capture at fulfilment.** `RestrictedTenderService` authorises only when the program says
   `captureAtFulfilment`; `captureAtFulfilment` re-splits the picked basket and captures the restricted part, never
   more than was authorised, and voids when nothing eligible is left. Everything goes through `PaymentService`, so the
   `unknown` state and one payment per reference hold.
6. **The PIN step.** `TerminalRequest.pinEntry` asks the terminal for a PIN (`PaymentCapability.TerminalPin`); the PIN
   never reaches the platform. A restricted program's `pinEntry` sets 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 `TenderEligibility` over 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.