Native wallet contract
Native wallet contract
Polyflight uses the existing REST v1 wallet paths and response contract. Do not create separate credentials or Polyflight-only endpoints.
Request matrix
| Effect | Method and path | reason | Stable idempotency_key |
|---|---|---|---|
| Ticket purchase | POST /debit | mini_app_polyflight_purchase | mini_app_polyflight_purchase:{purchaseRequestId}:debit |
| Debit applied but no ticket admitted | POST /debit-reversal | mini_app_polyflight_purchase_compensation | mini_app_polyflight_purchase:{purchaseRequestId}:debit_reversal |
| Winning payout | POST /credit | mini_app_polyflight_win | mini_app_polyflight_ticket:{ticketId}:win |
| Cancelled-ticket refund | POST /credit | mini_app_polyflight_refund | mini_app_polyflight_ticket:{ticketId}:refund |
The endpoint determines direction; there is no direction field in the REST v1 mutation body. A losing ticket creates no zero-value credit request.
Request shape
{
"external_user_id": "operator-user-123",
"amount_minor": 10000,
"currency_code": "IDR",
"idempotency_key": "mini_app_polyflight_purchase:6f9f6d61-3ac7-47d2-aa42-6ab39e492931:debit",
"reason": "mini_app_polyflight_purchase",
"metadata": {
"product": "polyflight",
"operation": "purchase",
"walletTransport": "rest_v1",
"purchaseRequestId": "6f9f6d61-3ac7-47d2-aa42-6ab39e492931",
"flightId": "1df82a79-f8e2-4729-bced-07ca6387524f",
"flightCode": "PF-EXAMPLE",
"choiceKind": "fixed",
"choiceIndex": 2,
"currencyCode": "IDR",
"stakeMinor": 10000,
"feeMinor": 500,
"netStakeMinor": 9500,
"potentialPayoutMinor": 38000,
"opensAt": "2026-09-16T10:00:00.000Z",
"locksAt": "2026-09-16T10:50:00.000Z",
"landsAt": "2026-09-16T11:00:00.000Z",
"economyVersion": "polyflight-economy-v1"
}
}
Every Polyflight request carries the common native-ticket context below. Treat metadata fields as additive; do not reject an otherwise valid request because a later version adds a field.
| Metadata field | Meaning |
|---|---|
product | Always polyflight. |
operation | purchase, compensation, win, or refund. |
walletTransport | Immutable simulator or rest_v1 transport pinned under the live purchase claim before the debit is sent. |
purchaseRequestId | Stable purchase intent UUID. |
ticketId | Ticket UUID; present for admitted-ticket effects and absent when compensation occurs before admission. |
flightId, flightCode | Flight identifiers. |
choiceKind, choiceIndex | Selected option group and index. |
currencyCode | IDR. |
stakeMinor, feeMinor, netStakeMinor, potentialPayoutMinor | Immutable ticket quote amounts. |
opensAt, locksAt, landsAt | ISO-8601 flight times. |
economyVersion | Economy version committed to the ticket. |
Refund and compensation metadata both include originalDebitOperationId and originalDebitIdempotencyKey; win metadata does not. Polyflight metadata does not contain fake marketId or orderId values, an operator Credits balance, or a purchase fxRateSnapshotId.
The admitted ticket and every later win, refund, or compensation inherit the purchase's pinned walletTransport. Lease renewal or recovery cannot overwrite it with a different transport. This prevents an existing real-wallet obligation from being redirected through the simulator, or the reverse.
Amount by effect
- Purchase debit: exact
stakeMinor. - Win credit: exact stored
potentialPayoutMinor. - Cancelled-ticket refund credit: exact original
stakeMinor, including the ticket fee. - Unadmitted-purchase debit reversal: exact original purchase debit.
No request recalculates an amount from a current FX rate or current commercial terms.
Existing response contract
The response is unchanged from the existing debit/credit/reversal adapter:
{
"external_user_id": "operator-user-123",
"status": "succeeded",
"currency_code": "IDR",
"amount_minor": 10000,
"idempotency_key": "mini_app_polyflight_purchase:6f9f6d61-3ac7-47d2-aa42-6ab39e492931:debit",
"remote_reference": "wallet-op-123",
"balance_minor": 90000
}
status is required and must be succeeded, failed, or pending. remote_reference, balance_minor, echoed identity/key fields, and failure_reason are optional under the existing adapter contract. OpenPoly preserves the immutable request amount and currency; response echoes do not replace them.
X-Polynion-Request-Id identifies one HTTP attempt for tracing. It is not the financial idempotency key.
Idempotency and ambiguous outcomes
The operator wallet must enforce all of these rules:
- The same idempotency key and same parameters return the same effective result without applying money twice.
- Reusing a key with different user, amount, currency, reason, or material metadata is a conflict.
- Timeout, network failure, HTTP
408,429, or5xx,pending, and an invalid response are ambiguous outcomes. - OpenPoly does not blindly resend an ambiguous primary debit or credit.
- An ambiguous purchase is not admitted as a ticket until authoritative evidence proves the debit applied.
- A known applied debit that can no longer be admitted is returned through the dedicated debit-reversal key.
- A known non-applied debit can be resolved without inventing a compensating money movement.
Operators must retain ledger evidence keyed by idempotency_key. remote_reference is useful when returned but remains optional. Manual resolution requires authenticated operator-ledger evidence even when no remote reference was returned.
Durable lifecycle values
The purchase wallet lifecycle is separate from purchase/admission status:
planned → sent → succeeded or failed / unknown / manual_review; an applied debit that cannot produce a ticket moves through compensation_pending to compensated.
Winning/refund/compensation obligations use the separate payment states owed, in_flight, paid, or manual_review. Do not infer ticket outcome from payment state.
Purchase, result, and cancellation boundaries
A successful remote debit and an admitted ticket are two separate durable steps. If debit succeeds after the flight gate closes, OpenPoly schedules exact compensation rather than forcing a late ticket. If the ticket was admitted and the flight is later cancelled, OpenPoly uses /credit with the refund key instead of /debit-reversal.
A resolved winning ticket can show payout pending or manual review while the public result remains final. External wallet delay does not change the winner. A cancelled ticket can show refund owed before the wallet credit completes. Operator or user suspension blocks new admission but does not block money already owed.
