ADR 026 Payment Cash-Receipt Handoff Snapshot
Context
Section titled “Context”Legacy KCP forms carried cash-receipt flags separately from payment-product, merchant-of-record, collection-channel, and receipt-issuer facts. Direct caregiver service balance and Sanmopia-collected customer share could therefore reach similar UI code despite different tax-evidence authority. Existing modern eligibility query was advisory and payment pre-registration could bypass it.
Decision
Section titled “Decision”PreRegisterReservationPaymentCommand requires one explicit
PaymentProviderCashReceiptIssueRequest. Public input contains payment id,
request state, purpose, and vault identity reference only. Browser cannot submit
reservation, amount, payment method, product plan, merchant, collection channel,
receipt issuer, eligibility, or provider routing.
Backend resolves one effective PaymentProductPlan, verifies obligation
coverage, quotes provider fee, authorizes actor, and creates
ReservationPaymentCashReceiptEligibilitySnapshot. Requested but ineligible
cash receipt stops before provider pre-registration. Exact replay requires same
actor, idempotency key, and cash-receipt request.
Reservation payment provider boundary receives one typed
ReservationPaymentProviderHandoffIntent containing frozen actor, route-fee,
and cash-receipt snapshots. Legacy (payment_id, amount) pre-registration stays
only for payment-change compatibility; reservation checkout cannot silently
drop cash-receipt policy before Kill Bill handoff.
Supabase RPC sanmopia_pre_register_payment_handoff calls existing actor, fee,
and cash-receipt pre-registration inside the same transaction, independently
validates the cash decision against the effective product catalog, validates the
backend-owned provider handoff session against frozen payment facts, then inserts
immutable eligibility and handoff snapshots. Product-policy or handoff forgery
rolls back all writes. Existing modern actor snapshots backfill as
not_requested; raw registration identity is never stored.
Reservation checkout receives a PaymentProviderHandoffSession descriptor whose
amount, selected method, allowed method set, provider route, return path,
expected payment revision, expiry, idempotency key, and fingerprint come from
backend facts. The browser never supplies provider payload fields or recomputes
the handoff from visible amount, product labels, or catalog state.
Evidence
Section titled “Evidence”Backend focused gate passed 54 tests plus Ruff and Tach dependency, interface,
and external-import gates. Contract layout, ESLint, TypeScript build, and 40
focused tests passed. Supabase stage applied migrations
20260712161500 and 20260712162500; schema lint, generated types, product
policy smoke, table lookup, and RPC lookup passed.
Transactional stage proof pre-registered payment
stage-payment-continuation-20260708084031, atomically returned actor snapshot
33333333-3333-5333-8333-333333333333 and cash snapshot
44444444-4444-5444-8444-444444444444, observed accepted provider issuance,
then rolled back. Separate proof submitted forged caregiver merchant policy for
same Sanmopia plan; DB rejected cash receipt snapshot contradicts payment product policy and retained no writes.
Callback ingestion parses provider cash fields once as
ProviderCashReceiptEventFacts. Cash-receipt evidence moves to
reconciliation_required without a frozen accepted decision, on purpose
mismatch, identity-reference mismatch, or partial approval identity. Accepted
payment workflow still continues; cash-evidence failure cannot trigger a
full-payment refund. Provider callback may supply approval, transaction, order,
and source-code evidence; stored request purpose and vault identity come only
from frozen pre-registration. Focused callback/handoff gate passed 98 tests plus
Ruff and Tach. Stage schema lint, type generation, and transactional
reconciliation-status proof passed.
Successful refund callback now reads latest approved cash-receipt evidence through
sanmopia_latest_approved_cash_receipt_evidence, then appends
provider_refund_reversal evidence only when provider supplies an explicit cash
reversal reference. Reversal row links original request/approval, refund intent,
payment-ledger revision, and immutable eligibility snapshot id. Missing source,
snapshot, reader, store, or reversal reference becomes
reconciliation_required; code never treats payment refund transaction id as
proof that tax evidence was reversed. Production composition now injects the
Supabase cash-evidence store and reader into Kill Bill ingestion; this binding
was previously absent.
Reversal-focused gate passed 58 tests, Ruff, Tach, main import, Supabase lint, generated types, and transactional stage proof for original-evidence lookup plus append-only reversal lineage. Stage data rolled back.
Receipt artifact and settlement tax-export projection now consume a backend-owned
cash-receipt evidence timeline. PaymentCashReceiptTaxEvidenceTimeline sorts all
append-only evidence for one payment by ledger revision and occurrence time,
selects the current customer-visible evidence for printable receipt artifacts,
and supplies all exportable approval/reversal rows for settlement tax datasets.
Supabase now exposes a payment-scoped evidence list reader, so UI/API callers no
longer choose a single evidence row and cannot expose a stale approval after a
refund reversal. Focused projection and adapter gates prove stale approval
replacement with provider_refund_reversal evidence and approval-plus-reversal
lineage in the settlement dataset.
Hosted customer receipt tax-evidence lookup now has an authenticated FastAPI
boundary: GET /customer-receipt-tax-evidence-artifacts/{reservationBookingId}.
The request supplies only paymentId and templateVersion; backend checks
Supabase Auth, rejects withdrawn sessions, checks SpiceDB view_receipt on the
reservation booking, loads all cash-receipt evidence through the Supabase
payment-scoped reader, and returns the current customer-safe artifact payload.
The response excludes provider payment/order/transaction references and raw
identity fields.
Hosted settlement tax-evidence dataset lookup now has an authenticated FastAPI
boundary: GET /settlement-tax-evidence-dataset-revisions/{datasetRevisionId}.
The request supplies branch or headquarters scope, payment id, and payment ledger
revision; backend checks Supabase Auth, rejects withdrawn sessions, checks
SpiceDB settle on the branch or review_hq_settlement on the headquarters
settlement data room before reading evidence, then returns backend-selected
approval and reversal lines with checksum and source evidence keys. The export
route does not allow UI row selection or provider reference exposure.
Stage runtime proof now exercises that hosted boundary with a real booking:
pnpm supabase:smoke:stage:settlement-tax-evidence-route created reservation
booking f6931f0c-1eaf-4cb8-a45a-7485c47934a4, seeded provider-neutral payment
stage-tax-evidence-payment-20260712122749-8c31df28, projected dataset
stage-tax-evidence-dataset-20260712122749-8c31df28, verified branch and
headquarters access each returned two approval/reversal lines, and reaped the
stage reservation batch with SpiceDB cleanup verification.
Stage customer receipt runtime proof now exercises the customer-safe hosted
boundary: pnpm supabase:smoke:stage:customer-receipt-tax-evidence-route
created reservation booking ba16063f-0b9c-4357-9fc4-b21a91399872, seeded
provider-neutral payment stage-customer-receipt-payment-20260712123232-51185685,
verified the mother actor could read the backend-selected reversal artifact
customer_receipt_tax_evidence:stage-customer-receipt-payment-20260712123232-51185685:ledger-4:stage-cash-receipt-reversal-20260712123232-51185685,
and reaped the stage reservation batch with SpiceDB cleanup verification.
Stage reservation checkout handoff proof now exercises the public
pre-registration route and persisted handoff row:
pnpm supabase:smoke:stage:reservation-payment-handoff-session created
reservation booking 113d0182-a79d-471d-a66b-d7183b053298, pre-registered
payment 113d0182a79d471da66bd7183b053298, verified the response included
backend-owned handoff
reservation-payment-handoff:113d0182-a79d-471d-a66b-d7183b053298:113d0182a79d471da66bd7183b053298:0,
verified exact replay kept the same handoff key and fingerprint, verified the
Supabase row persisted with provider_key=killbill, state=prepared, and
expected_payment_revision=0, and reaped the stage reservation batch with
SpiceDB cleanup verification.
Stage Kill Bill callback proof now exercises the hosted callback boundary with
the official ExtBus payment payload shape:
pnpm supabase:smoke:stage:killbill-payment-event-callback created reservation
booking 06bb3c50-aca5-4ab4-97e2-fd8a955729a6, pre-registered payment
06bb3c50aca54ab497e2fd8a955729a6, posted a signed PAYMENT_SUCCESS
callback, verified inbox event
killbill-event:sha256:3af8a2681cbb1d12985bf4eda20e0ede5e162efa50e9930b5e9d66d52165d08f,
verified exactly one sanmopia_payment_webhook_processing message 7,
verified replay returned duplicate without another queue message, dispatched
the internal payment_webhook_processing worker, verified the worker archived
message 7, verified Restate payment workflow
killbill:PAYMENT_SUCCESS:06bb3c50aca54ab497e2fd8a955729a6:stage-killbill-tx-20260712131444-9f9a82b5
completed, verified the payment record moved to approved with deposited_at,
and reaped the stage reservation batch with SpiceDB cleanup verification.
Stage callback-to-settlement proof now uses the callback-approved payment as the
same payment workflow for downstream settlement:
pnpm supabase:smoke:stage:killbill-callback-to-settlement-completion created
reservation booking b681a97a-3bb9-4e00-887d-c50d3799ef23, approved payment
b681a97a3bb94e00887dc50d3799ef23 through Kill Bill callback event
killbill-event:sha256:1aef72c067a736ae2bbbd3fcdb13e0efe2bdb1c35d3234093f4f397da22d001a,
continued the same payment workflow
killbill:PAYMENT_SUCCESS:b681a97a3bb94e00887dc50d3799ef23:stage-killbill-tx-20260712141353-8b4ed89b,
recorded service delivery, issued caregiver payout instruction
ec4f5565-8a01-57bf-b830-35e1e0a193b6, recorded branch payout deposit,
prepared branch and headquarters settlement, completed settlement, verified
customer account-disclosure readiness not_required, verified branch
settlement board line items, and reaped the stage reservation batch with
SpiceDB cleanup verification.
Consequences
Section titled “Consequences”Cash-receipt policy and provider checkout handoff become historical payment evidence, not UI or callback state. Catalog changes cannot rewrite completed payment presentation. Remaining proof: trusted Traefik/provider-originated Kill Bill callback deployment and live provider-originated reversal.