Payments (pilot posture)
How deposits are configured, what the checkout hold does, why live charging stays disabled during the pilot, and what that means for each booking.
Payments in DineGlobe cover two separate things: where a venue configures the deposit it would charge for a booking, and how a paid booking moves money once a provider is connected. The machinery is fully built — holds, hosted checkout, verified webhooks, reconciliation and an append-only ledger all run as designed. What the live pilot does not have is a configured payment provider, so no charge has ever been taken, and every payment path is built to refuse visibly rather than pretend otherwise.
The pilot reality, stated plainly
DineGlobe runs as a free-booking pilot. The payment capability gates are closed, and the live environment deliberately has no payment provider configured — the provider registry names only sandbox transports, and recording a sandbox as production is refused by design (CAPABILITY_NOT_ACTIVATABLE).
What that means in practice:
- A
DEPOSITpolicy cannot be activated. Activation refuses with 422PAYMENT_NOT_READY, naming the exact block that stands closed (table below). - A guest who reaches a deposit offering cannot complete a paid booking online. The booking refuses with 422
PAID_BOOKING_UNAVAILABLE, and the booking screen says: “This service requires a deposit, which is not available online yet. Please book by phone.” - Staff payment panels answer 503
PAYMENT_PROVIDER_UNAVAILABLEfor anything that would call a provider. - No hosted-checkout URL is faked, no capability is invented, no success is staged. When a gate stands closed, the panel says which gate it is.
Single-market pilot evidence only: one approved country and its listed capabilities do not constitute global support; unlisted countries and capabilities stay disabled.
Until an operator activates a real provider under the documented launch gates, DineGlobe will not take a charge — and it will not pretend to.
Where a deposit is configured
Deposits are policy configuration, not a per-booking toggle. Each offering is governed by a published policy version (see the Booking policies & cancellation page, /docs/setup/policies/), and the money fields live there:
| Field | Values | Meaning |
|---|---|---|
paymentMode |
NONE or DEPOSIT |
Whether the offering charges anything at all. |
amountMode |
PER_BOOKING or PER_PERSON |
Deposit per booking or per guest. |
amountMinor |
integer, exactly 0 for NONE, at least 1 for DEPOSIT |
Exact integer minor units — never a decimal figure. |
currency |
three-letter code, must equal the venue currency | One venue never mixes currencies. |
| cancellation fields | freeCancelMinutes, lateRetentionBps, noShowRetentionBps, graceMinutes |
The same version quotes what a cancellation or no-show retains. |
Policy versions are immutable. You change terms by retiring a version and publishing a new one, and a booking stores the accepted policy snapshot at booking time, so its quoted terms never drift when you publish later.
A DEPOSIT policy activates only when the venue’s connected payment account is charge-ready. Otherwise activation refuses with PAYMENT_NOT_READY and one of these blocks in the paymentMode field error:
| Block | Meaning | What to do |
|---|---|---|
NO_ACCOUNT |
No payment account is connected. | An owner connects the account (see below). |
CHARGES_DISABLED |
The connected account cannot take charges right now. | Refresh its capability truth. |
CURRENCY_MISMATCH |
The connected account settles in a different currency than the venue. | Align the account’s settlement currency. |
COUNTRY_NOT_APPROVED |
Payments are not approved for the venue’s country. | Wait for the operator’s capability approval. |
When everything lines up, the capability view reports “PAID INTAKE READY — a DEPOSIT policy may now be activated.” In the pilot it reports blocked, and that is the honest answer.
How money is represented
- Every amount is an exact integer in minor units, stored in a bigint column and carried as a string through APIs and panels. Floating-point arithmetic never touches an amount, and there is no automatic currency conversion anywhere: an amount quoted in one currency is held, captured, refunded and reported in exactly that currency.
- One venue, one currency. The venue’s currency is fixed at setup, and a policy’s deposit currency must equal it.
- Rendering is separate from money. A fixed list of zero-decimal currencies (the Japanese yen among them) renders no decimals; a fixed three-decimal list (the Kuwaiti dinar among them) renders three; everything else renders two. Display formatting re-renders the stored integer — it never recomputes an amount.
- Finance views aggregate per currency and never combine currencies into one total (the views are covered in Refunds,
/docs/money/refunds/).
The checkout flow as designed
When the gates are open and a guest books a DEPOSIT offering, the flow is:
- Hold. The booking command creates the payment attempt and moves the reservation to
HELD; both commit before any provider call. The hold lasts 10 minutes. - Hosted checkout. Staff open the provider’s hosted checkout from the
Payment holds and checkoutpanel. The URL is the provider’s own page, shown once, and the state line always namesAWAITING_GUEST— a checkout session is never presented as a payment. - Verification, not the browser. The provider calls
POST /api/webhooks/payments. The signature is verified over the raw body before anything is parsed or stored, and a valid event is stored once in an idempotent inbox (a redelivery answers an idempotentDUPLICATE). A stored receipt is pending truth only — money effects belong to reconciliation, never to the receipt. - Reconciliation confirms. The reconciliation pass drains verified events into money truth: the capture is written to an append-only ledger, and only a live
HELDreservation is confirmed. A browser return or a provider session state can never confirm a booking — payment is confirmed only by provider reconciliation. - Expiry. If the guest never pays, the expiry sweep — run opportunistically inside every booking mutation and staff-fired from the panel — expires the attempt and the reservation, releases the held tables and queues a
booking.expiredevent (PAYMENT_HOLD_EXPIRED). A lapsed hold never blocks a replacement booking. - Late money never resurrects a booking. If a capture arrives after the hold died, reconciliation records it and schedules a full-amount compensation refund intent; the booking stays non-confirmed and the tables were already released.
A payment attempt moves CREATED → AWAITING_GUEST → EXPIRED, with the capture recorded in the ledger only when a verified provider event lands.
If the provider does not answer during checkout, the error is 503 PAYMENT_CHECKOUT_TIMEOUT and the remedy is to retry the same action: the stable idempotency key continues the SAME checkout, so a retry can never create a second charge.
The payment panels
All money surfaces live in the operator console at /app/panels (sign in at /app/login):
Payment account connection— owner/org-admin only. Begins the provider-hosted onboarding (credentials live with the provider; the URL is shown once and never stored) and refreshes capability truth — charges, payouts, settlement currency, country and route approval.Payment holds and checkout— start or recover a hosted checkout with the booking’s request key, and run the hold-expiry sweep. An empty sweep is a normal outcome.Provider event inbox— the webhook surface and its staff-side envelope view. Encrypted payloads never leave the database; the panel shows envelopes and checkpoints only.Payment reconciliation— drains pending verified events into money truth, sweeps a provider account window, and lists refund intents.Payment status and finance views— per-reservation money status and settled finance totals. Host and door sessions are refused here: money views belong to the money roles (see the Finance guide,/docs/roles/finance/).
Error codes
| Code | Meaning and remedy |
|---|---|
PAID_BOOKING_UNAVAILABLE |
The offering charges a deposit but the gates refuse right now. Book as a free or request booking if offered, or by phone. |
PAYMENT_NOT_READY |
A deposit policy cannot be activated; the field error names the block (NO_ACCOUNT, CHARGES_DISABLED, CURRENCY_MISMATCH, COUNTRY_NOT_APPROVED). Fix the named problem and retry. |
PAYMENT_PROVIDER_UNAVAILABLE |
No payment provider is configured for this deployment. Expected during the pilot; retry after a provider is connected. |
PAYMENT_COUNTRY_NOT_APPROVED |
The venue’s country has no approved payment capability. Waits on an operator decision. |
PAYMENT_ROUTE_NOT_APPROVED |
The approved capability names a different provider than the one in use. The operator aligns the capability row. |
PAYMENT_CHECKOUT_TIMEOUT |
The provider did not answer. Retry the same action — it recovers the same checkout, never a second charge. |
PAYMENT_HOLD_EXPIRED |
The hold expired; the reservation is EXPIRED and its tables were released. Rebook. |
PAYMENT_WEBHOOK_REJECTED |
Signature verification failed on the raw bytes, or the event names an account nobody owns. Nothing was stored; check provider configuration. |
Full code reference: Error codes & fixes. For what happens to money after a capture — refunds, thresholds and approvals — see Refunds.