PDR 0003 — VAS provider connectivity phasing & wallet visibility
Purpose: record the Product Owner’s 2026-07-11 decisions on (a) how terminales connects to the external VAS providers across three phases, (b) which balances exist, who sees each, and what credits/debits them, and (c) the deposit channels that fund the merchant wallet. These decisions revise the Wave-2-era “two-wallets display” rule and drive the MerchantBalance track and the VAS Phase-1 integration.
- Status: Accepted
- Date: 2026-07-11
- Deciders: Product Owner (Wave 3 refinement session)
- Repos: Multi —
terminales-api,terminales-web,terminales - Related:
0004-merchant-wallet-dbos-integrity(the wallet engine),0003-vas-fulfillment-dbos(amended by this PDR), ./0001-mvp-golive-scope.md, ../business-rules/value-added-services.md,apitae-webservice
Amendment 2026-08-03 — external platform balances ARE shown and CAN fund VAS
Section titled “Amendment 2026-08-03 — external platform balances ARE shown and CAN fund VAS”Deciders: Product Owner (client meeting, 2026-08-03). Status: Accepted; amends Decision 1 (Phase 2) and Decision 2 of this PDR. Drives the Wave-3 “Plataformas externas de saldo” design module (Figma web flow W-14 / mobile M-9 — see
figma-wave3-external-platforms-prompts).The client decided terminales will support external balance platforms as a first-class product surface. This changes two things previously fixed here:
- External platform balances ARE displayed (reversing Decision 2’s “the VB webService balance is NEVER displayed — in no phase”). A merchant connects one or more external platforms (Mantarys and its instances VentaMóvil / Sureste, which are the same aggregator behind different backend URLs, plus a generic “Otra plataforma”), each with its own per-merchant username/password credential (the Phase-2 seam
resolveVbCredentials(merchantId), now brought forward as a product feature). The connected platforms and their balances are shown, owner-only (samemerchants.ownerIdguard as the merchant wallet). Auth is username + password, not Keycloak.- A connected platform’s balance is a funding source for VAS (extending Decision 3’s tender list). At checkout the operator may pick a connected platform whose balance funds the VAS portion of the sale — a money path distinct from and additional to the merchant-wallet debit (which remains the default). Insufficient platform balance → that platform is disabled at selection and, if none suffice, the VAS sale is blocked (no negatives). Online-only (no queue), idempotent by
clientId.Unchanged: the merchant wallet (
merchant_balances) and store cash keep their audiences and rules; the two ledgers stay independent (platform-balance debits are a separate ledger per connection, not mirrored intobalance_movements). Still-open (gaps #46–#49 in the design master map): the per-merchant connection data model + credential encryption + per-platform balance ledger; reversal/ timeout handling for platform-funded VAS; the “recargar saldo” destination; and mixed-cart (products + VAS) split behavior.
Amendment 2026-08-04 — adding balance to a platform (wave 3.1)
Section titled “Amendment 2026-08-04 — adding balance to a platform (wave 3.1)”Deciders: Product Owner. Status: Accepted; extends Decision 3 (deposit channels / crediting matrix). Drives the wave-3.1 “Agregar saldo + Depósitos” design (
§Qinfigma-wave3-external-platforms-prompts).Merchants can top up a connected platform balance (or terminales’ own wallet) from the handheld. This makes each per-platform ledger bidirectional (credits from top-ups/deposits + debits from VAS). Funding sources: (1) card on the handheld (channel-E style, immediate — reuses the AngelPay bridge); (2) transfer from the merchant wallet (immediate); (3) a deposit with a new
target(merchant wallet or a connected platform) — bank channels A–C stay PENDING → owner-validated, cash D immediate. This resolves the “recargar saldo” destination (design gap #48).Vendor dependency (not a design blocker): the Mantarys webservice has no inbound-credit method (
Check_Balanceonly reads;Request_Transactiononly debits). So the card-charge top-up is designed forward-looking: terminales records the credit against the per-platform ledger and reconciles the shown balance againstCheck_Balance; the real provider-side crediting requires a new Mantarys integration (design gap #50). Thedepositsmodel gains atarget(design gap #51). Ledgers stay independent — a platform top-up credit is not mirrored intobalance_movements.
Context
Section titled “Context”PDR-0001 put VAS + the financial domains in MVP scope, and
TDR-0003 fixed the fulfillment model (aggregator adapter +
DBOS durable workflows over the VB webService). Two questions stayed open: how credentials/
connectivity to the external system evolve, and which balance the product actually shows and
spends. The Wave-2-era docs assumed a per-tenant VB credential model and allowed displaying the
VB provider float (“two-wallets rule … display/low-float warning”). The PO has now decided both.
Decision 1 — provider connectivity in three phases
Section titled “Decision 1 — provider connectivity in three phases”- Phase 1 (MVP): terminales uses the client’s existing system (the VB webService + SQL
Server + provider connections that also back
apitae-sales) through a single shared webService user managed in the terminales backend. terminales makes purchases, deposits and recharges on behalf of all merchants with this one credential. Entry point is the VB webService directly (the aggregator adapter of TDR-0003) — terminales does not call theapitae-salesREST API. This is explicitly a temporary MVP solution. - Phase 2 (post-MVP): each merchant connects their own webService user, so merchants transact on their own behalf. The Phase-1 credential resolution must therefore be a seam (resolve credentials per call from config; MVP config returns the shared user).
- Phase 3 (post-MVP, future): direct integration with the providers themselves (TAE, services, …), requiring per-provider certification of the terminales solution.
Rationale: Phase 1 reuses infrastructure the client already operates and trusts, with zero provider-side certification effort — the fastest path to a working MVP. The per-call credential seam makes Phase 2 a configuration change, not a rewrite. Phase 3 is deferred because certification is long-lead and not needed to operate.
Alternatives rejected
Section titled “Alternatives rejected”- Through
apitae-sales(REST) → rejected; adds a hop and couples the MVP to the legacy API surface. The aggregator calls the VB methods directly (TDR-0003’s design stands). - Per-merchant webService users at MVP → deferred to Phase 2; onboarding every merchant with the upstream provider before launch would delay go-live for no day-one benefit.
Decision 2 — two balances, two audiences; the VB balance is never displayed
Section titled “Decision 2 — two balances, two audiences; the VB balance is never displayed”- Merchant wallet (
merchant_balances) — lives in and is fully managed by terminales. It is the balance the merchant owner sees in the web/mobile apps, the balance that funds VAS operations (purchases, deposits, recharges — terminales pays the provider on the merchant’s behalf in Phase 1), and the balance that is withdrawable to the merchant’s bank account (withdrawal flow itself is post-MVP — seam only, seeTDR-0004). Visibility: merchant owners only — cashiers/supervisors have no wallet read access. Ownership mechanism (decided 2026-07-12): owner = the additivemerchants.ownerIdcolumn (Appwrite Auth user id — the field roles-permissions-and-labels.md already assumed); the wallet guard (assertMerchantOwner) resolves against it and fails closed when unset. A store-teammanagerwho is not the owner is denied. - Store cash balance — the existing cash-control domain (shift open float, cash movements, Z-reports). This is what employees see and reconcile. It is not a new entity; it stays on the shipped shifts/cash_movements/shift_reports collections.
- The external VB webService balance (
Check_Balance) is NEVER displayed — in no phase. It may be read internally (ops metric / low-float alert for the platform) but never surfaces in any UI or in any API response consumed by a UI. — ⚠ AMENDED 2026-08-03 (see Amendment at top): connected external platform balances ARE now displayed, owner-only, in the Wave-3 “Plataformas externas de saldo” module. The internal-only rule no longer holds for platforms a merchant has explicitly connected with their own credential.
Rationale: the merchant wallet is the product’s own money surface; showing a supplier float merchants cannot act on adds confusion and support load. The owners-only rule keeps wallet data (revenue-shaped) away from store staff, matching the roles model.
Alternatives rejected
Section titled “Alternatives rejected”- Displaying the VB float read-only with a low-float warning (the Wave-2 rule in TDR-0003 Decision 4 / value-added-services.md) → superseded by this PDR; internal-only now.
- One balance for everything (wallet = store cash) → rejected; cash physically in the drawer and platform-held funds are different money with different audiences and controls.
Decision 3 — deposit channels and the crediting matrix
Section titled “Decision 3 — deposit channels and the crediting matrix”A Deposit is a “payment” made by a customer to the merchant (or a top-up by the merchant),
with statuses pending → declined | available → applied → canceling | canceled | refunded. A
deposit credits nothing until it is available.
| Channel | How | Initial status | Credits |
|---|---|---|---|
| A | Customer deposits at a bank branch to the merchant’s account; receipt optionally uploaded | pending |
Wallet, after manual validation → available |
| B | Customer bank-transfers from their own bank app; receipt optionally uploaded | pending |
Wallet, after manual validation → available |
| C | Customer deposits cash at a convenience store; receipt optionally uploaded | pending |
Wallet, after manual validation → available |
| D | Customer pays the merchant in cash, in person; merchant registers it and prints a receipt | available immediately |
Store cash (never the wallet — the merchant already holds the cash) |
| E | Customer pays by card on the handheld terminal; receipt printed | available immediately |
Wallet |
- Autopay top-up: the merchant or an employee can top up the merchant wallet by card on the handheld — mechanically a channel-E deposit without a cart; needs its own handheld menu/screen. Credits the wallet immediately.
- Validation: in MVP,
pendingdeposits (A/B/C) are validated manually by the merchant (owner/manager) to flip them toavailable. Bank-statement import for automatic validation is post-MVP (seam noted in the deposit-channels ticket). - Balance as a tender: customers can pay with cash, card, or balance; attempting to pay with balance when there is none → the sale is blocked (no negative balances, no credit line in MVP). — ⚠ EXTENDED 2026-08-03 (see Amendment at top): a connected external platform’s balance is an additional funding source for the VAS portion of a sale (distinct from the merchant-wallet debit); same “blocked when insufficient” rule applies.
Rationale: the matrix mirrors where the money physically lands — cash in the drawer belongs to store cash control; everything that reaches the merchant’s account through the platform rail becomes platform-held wallet balance once verified. Manual validation is the cheapest safe MVP control; statement import automates it later without changing the state machine.
Consequences
Section titled “Consequences”- The Wave-2 “two-wallets display” rule is superseded:
TDR-0003Decision 4 is amended (VB float internal-only; VAS fulfillment debits the merchant wallet via theTDR-0004engine). The ledgers remain independent — VB movements are still never mirrored intobalance_movements. - The W44–W47 wallet screens display only the merchant wallet (owners-only) — the VB-float panel is removed from the web financial-services draft.
- New MerchantBalance track (Track K /
manifests/wave3-10-merchant-balance.tsv): wallet schema- DBOS engine + deposit channels/validation + VAS debit wiring + handheld autopay top-up.
- Post-MVP catalog gains: Phase 2 (per-merchant webService user), Phase 3 (direct provider certification), bank-statement import validation, withdrawal/payout flow.
- The per-tenant credential text in
apitae-webserviceis annotated as the Phase-2 model; Phase 1 uses the single backend-managed shared user.
References
Section titled “References”0004-merchant-wallet-dbos-integrity— how the wallet stays correct under concurrency.0003-vas-fulfillment-dbos— fulfillment model this PDR amends.- ../business-rules/value-added-services.md ·
apitae-webservice. - ../product/user-stories.md — the BAL story area implements this PDR.