Refunds
Request, approve and execute refunds: thresholds, the fresh-MFA rule, the second-person rule, disputes and pending-versus-settled refunds.
Refunds never happen quietly in DineGlobe. Every refund is requested under the venue’s own lock, bounded by the refundable balance, confirmed with fresh two-step verification and — unless the amount sits at or below the owner’s delegation threshold — approved by a second person who can never be the requester. Until the provider settles the money, a refund is displayed as pending, never as refunded. The pages that matter here are the Refund workflow and Payment status and finance views panels in the operator console at /app/panels.
Who may do what
| Act | Permission | Roles |
|---|---|---|
| Request a refund, read the money views | payments.refund.execute |
Owner, org admin, manager, finance |
| Approve or decline a refund, set the delegation threshold | payments.refund.approve |
Owner, org admin only |
| Anything with money | — | Host and door are refused; they see no money views |
Two further guards apply to the humans in this workflow:
- Fresh MFA. Both requesting and deciding demand that the live session was MFA-verified within the last 30 minutes. Without it the action answers 401
MFA_REQUIRED— complete the authenticator prompt and retry promptly; the window is deliberately short. - Approver ≠ requester. A decision by the person who requested the refund answers 403
PERMISSION_DENIED. An owner cannot approve their own refund.
Requesting a refund
A refund is requested against a payment attempt whose capture has already been reconciled, with the amount in exact minor units, a mandatory reason and an idempotency request key.
The request runs under the venue lock and is bounded by the refundable balance:
captured net − settled refunds − open disputes − other pending refund intents
If the requested amount exceeds that balance, the request refuses with 422 REFUND_EXCEEDS_AVAILABLE and names the exact remaining amount — refund at most that. Nothing is written.
The request key is idempotent: replaying the same key returns the stored result, and the same key with a different payload answers 409 CONFLICT — nothing is rewritten.
The delegation threshold
The owner sets a per-venue threshold in minor units. It decides who may act alone:
- At or below the threshold — the intent is born approved: the message reads “within delegated authority (approved at request)”.
- Above the threshold — the intent waits for a separate ownership-tier decision.
- No threshold configured (empty) — the fail-closed default: every human refund needs an approval. Clearing a threshold deliberately restores this state; the save confirms with “every human refund now needs owner approval (fail closed)”.
The decision is recorded as an immutable evidence row with its own mandatory reason. There is one decision per intent per approver and the history is append-only. A declined decision cancels the intent.
Intent states
| State | Meaning |
|---|---|
REQUESTED |
Created and bounded; awaiting a decision when the amount is above the threshold (or no threshold exists). |
PENDING_PROVIDER |
Released to the provider, not settled yet. Displayed everywhere as pending — never as “refunded”. |
SUCCEEDED |
The provider settled the refund; its ledger row is written exactly once. |
FAILED |
The provider permanently refused. Terminal — manual resolution with the provider. |
CANCELLED |
The decision came back declined, or the intent was cancelled. |
Execution and recovery
Approved intents (and system-generated ones, such as the compensation refund for money that arrived after a hold died) are executed by the worker sweep — staff can also fire it from the panel. Execution carries the stable idempotency key refund:{intentId} and is retrieval-first: if the create call times out, the next sweep retrieves the SAME provider object instead of issuing a second refund.
- 503
PAYMENT_REFUND_TIMEOUT— the provider did not answer and may have stored the refund. Retryable: the next sweep recovers the same refund. - 422
PAYMENT_REFUND_REJECTED— the provider permanently refused. Terminal; resolve manually. The system never double-collects to work around a refusal. - 422
REFUND_INVALID_TRANSITION— the intent is not in a state that accepts that action. Reload it; it may already be settled, failed or cancelled. - 409
CONFLICT— this key or this approver already recorded something. Nothing was rewritten.
Reconciling a single refund repairs truth from the provider: if the provider settled, the intent becomes SUCCEEDED with its ledger row (never duplicated); if it is still pending, it stays pending with a reconciliation stamp. This is also how a restored database is brought back in line with provider truth.
Disputes
The transaction ledger has a DISPUTE kind alongside captures and refunds. Disputed money is subtracted from the refundable balance — the chargeback process owns that money, so it can never be paid out a second time. V1 ships no dispute-intake screen; a dispute row reaches the ledger as evidence and immediately reduces what remains refundable on that attempt.
Reading the money views
- Reservation payment status — the reservation badge and the payment badge are separate lines; receipts carry the provider’s own effect ids; a refund pending at the provider reads
PENDING, never “Refunded”. - Finance transactions — settled ledger rows by transaction date and currency. Totals aggregate per currency and are never combined across currencies. The pending queue lists every unresolved refund demand: awaiting decisions, pending at the provider, and failed escalations.
- Refund intent queue —
REQUESTEDrows await the workflow; nothing in the queue calls a provider.
Aggregate per-currency money also feeds the operational reports — see Reports & metrics.
The pilot posture
No live payment provider is configured in the pilot environment, so the execution sweep has nothing real to run against: it answers 503
PAYMENT_PROVIDER_UNAVAILABLEand fabricates nothing. The request chain, the balance arithmetic, the approval evidence and the ledger are real and exercised; the provider leg waits for the capability gates described in Payments to open.
In other words: configure thresholds, rehearse approvals, audit decisions — and expect an honest refusal the moment a command would have to reach a real provider. The gate posture itself is described in Payments; the full code reference lives in Error codes & fixes.