Skip to content

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:

  1. 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 (same merchants.ownerId guard as the merchant wallet). Auth is username + password, not Keycloak.
  2. 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 into balance_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 (§Q in figma-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_Balance only reads; Request_Transaction only 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 against Check_Balance; the real provider-side crediting requires a new Mantarys integration (design gap #50). The deposits model gains a target (design gap #51). Ledgers stay independent — a platform top-up credit is not mirrored into balance_movements.

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 the apitae-sales REST 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.

  • 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, see TDR-0004). Visibility: merchant owners only — cashiers/supervisors have no wallet read access. Ownership mechanism (decided 2026-07-12): owner = the additive merchants.ownerId column (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-team manager who 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.

  • 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, pending deposits (A/B/C) are validated manually by the merchant (owner/manager) to flip them to available. 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.

  • The Wave-2 “two-wallets display” rule is superseded: TDR-0003 Decision 4 is amended (VB float internal-only; VAS fulfillment debits the merchant wallet via the TDR-0004 engine). The ledgers remain independent — VB movements are still never mirrored into balance_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-webservice is annotated as the Phase-2 model; Phase 1 uses the single backend-managed shared user.