Skip to content

ADR 020 Reservation Payment Obligation And Actor Snapshot

Legacy payment pages supplied reservation identifiers, payment kinds, methods, amounts, and mutable status facts. The previous migration path also completed a reservation and payment inside booking, before a provider-confirmed payment existed. Current family grants could prove access but could not identify who historically initiated a payment after a grant changed or was revoked.

Booking creates three distinct facts:

  1. a scheduled reservation;
  2. an immutable finalized charge snapshot; and
  3. a waiting reservation payment obligation bound to a payment product plan.

POST /reservation-payment-pre-registrations accepts only paymentId in the body. Supabase bearer identity supplies the actor and Idempotency-Key supplies replay identity. The backend loads booking, method, purpose, product plan, and amount from the waiting obligation. Browser-supplied financial facts are not accepted.

Before Kill Bill handoff, the backend resolves authority from:

  • the authenticated Supabase member profile;
  • SpiceDB pay_pre_reservation or finance-operator capability; and
  • for a family payer, the active reservation-scoped PaymentDelegation, exact payment scope, consumed revision, effective interval, and amount ceiling.

The accepted decision becomes one immutable ReservationPaymentActorSnapshot. It freezes actor and mother identities, actor kind, branch, delegation facts, payment purpose, product plan, amount, authority evidence, authorization time, and idempotency key. A service-role-only Supabase RPC validates current backend facts and atomically links that snapshot to the payment obligation. Snapshot rows cannot be updated or normally deleted. Mutable payment/delegation rows do not own the snapshot through reverse foreign keys; historical evidence remains independent after delegation cleanup or revocation.

Kill Bill receives only the payment id and backend-owned amount. Provider success is still a later payment workflow event; pre-registration does not mark the obligation paid and does not complete the reservation.

The command side authorizes and freezes one obligation. Query-side payment and settlement projections render the frozen result. Frontend code may display the returned amount, fee snapshot, and actor kind but cannot derive or override them.

On 2026-07-11 the bounded stage command pnpm supabase:smoke:stage:reservation-to-settlement-completion passed without Playwright. Reservation 92961f7e-7896-4441-af53-8fb8825d6fe2 proved:

  • scheduled booking plus waiting payment obligation;
  • caregiver denial before delegated authorization;
  • family grant/replay, SpiceDB convergence, exact amount ceiling, and immutable actor snapshot da2c3402-0d85-52a2-b6b4-bcf7f6c189bf;
  • pre-registration replay and conflicting-idempotency rejection;
  • grant revocation and cleanup without loss of historical actor evidence;
  • paid-payment workflow, service delivery, caregiver payout instruction, branch settlement, headquarters settlement, and final settled state.

Pinned implementation:

  • The runtime proof covers the customer-share deposit plan. Balance, pre-reservation conversion, change adjustment, refund recipient, and receipt visibility paths must consume the same actor-snapshot pattern.
  • Live Kill Bill provider-originated callbacks remain separate from the stage debug-gateway proof.
  • Payment-completion communication must always consume the frozen actor and recipient decision instead of rescanning currently active family grants.

Missing backend obligations or authorization evidence block payment UI entry. Reservation booking no longer implies payment completion. Family grant changes after handoff cannot rewrite historical payer identity or customer-visible amounts.

  • 2026-07-11: accepted backend-owned payment obligation and immutable actor snapshot boundary with bounded stage reservation-to-settlement proof.