💰 Loyalty Wallet Integration Guide
This guide explains how to award and redeem loyalty balance using the admin (server-to-server) endpoints and how to read a user’s balance and history with the public bearer on client surfaces.
TL;DR
- Admin flow (server-to-server): Use your backend bearer (Client Credentials) to credit/debit loyalty for a user after POS events.
- End-user flow (front end): Your app uses the public bearer (from the session code exchange) to show my balance and my transactions.
For more information about authentication and integration please refer the Authorization Flow guide for full details.
sequenceDiagram
autonumber
participant PartnerBE as Partner Backend / POS
participant FTAuth as Funtrips Auth (OAuth)
participant FTAPI as Funtrips API (Wallet)
participant App as Partner App / Webframe
participant User as End User
%% A) Server-to-server auth
PartnerBE->>FTAuth: POST /oauth/token (client_credentials)
FTAuth-->>PartnerBE: 200 { backend_bearer }
%% B) POS posts a wallet transaction
PartnerBE->>FTAPI: POST /v1/wallet/transactions Auth: backend_bearer
FTAPI-->>PartnerBE: 201 { transaction_id, new_balance }
Note over FTAPI: If wallet does not exist, auto-create on first transaction
%% C) Start session for user-facing access (code)
PartnerBE->>FTAPI: POST /v1/session Auth: backend_bearer
FTAPI-->>PartnerBE: 200 { session_code, frame_url?, pkce_required }
%% D) Webframe path (no PKCE; FT exchanges code)
PartnerBE-->>App: Provide frame_url with session_code
User->>FTAPI: Open frame_url?session_code=...
FTAPI-->>App: Issues public_bearer to the frame (server-side exchange)
%% E) User self-service with public bearer
App->>FTAPI: GET /v1/wallet/me/balance Auth: public_bearer
FTAPI-->>App: 200 { balance }
App->>FTAPI: GET /v1/wallet/me/transactions?limit=...&next_token=... Auth: public_bearer
FTAPI-->>App: 200 { data:[...], pagination }
%% F) Admin/audit lookups (backend only)
PartnerBE->>FTAPI: GET /v1/wallet/admin/users/{external_user_id} Auth: backend_bearer
FTAPI-->>PartnerBE: 200 { user, current_balance }
PartnerBE->>FTAPI: GET /v1/wallet/admin/transactions?external_user_id=...&limit=... Auth: backend_bearer
FTAPI-->>PartnerBE: 200 { data:[...], pagination }
%% G) Refund or redemption (debit)
PartnerBE->>FTAPI: POST /v1/wallet/transactions Auth: backend_bearer
FTAPI-->>PartnerBE: 201 { transaction_id, new_balance }
🧩 Core Concepts
- User identity: You reference users by your
external_user_id. In admin calls, you pass this identifier when needed; in public calls, identity comes from the public bearer itself. - Implicit user creation: If a wallet does not exist for a user, the first successful credit/debit can create it automatically (recommended), or you may choose to call an explicit admin “create user” endpoint if present.
- Idempotency: All transaction-creating endpoints accept
Idempotency-Key. Reuse the same key to safely retry without double crediting/debiting. - Atomic balance: Each transaction returns the
balance_aftersnapshot; you don’t need to re-fetch to confirm the result. - Types:
- credit = add to balance (earn).
- debit = subtract from balance (spend/refund-reversal).
🏪 POS → Loyalty: Award on Purchase (Credit)
When a POS sale completes and qualifies for loyalty:
- Calculate the loyalty amount in your system (rules, promos, rounding, currency).
- Create a credit transaction with your backend bearer. Include:
- external_user_id
- type = credit
- amount (Money)
- an idempotent reference (e.g., POS receipt/order ID)
- optional description (“POS purchase”, campaign code, etc.)
- Handle responses:
- 201/200 → success, store
transaction.idandbalance_after - 409 → conflicting duplicate or state; investigate your reference/idempotency usage
- 422/400 → validation error; correct and retry with same Idempotency-Key if safe
- 201/200 → success, store
See the admin create transaction for details
Why idempotency matters:
If your POS retries after a network blip, re-sending with the same Idempotency-Key guarantees no double credit.
↩️ POS Voids/Refunds: Reverse the Award (Debit)
If a sale is voided or refunded:
- Look up your original loyalty reference (e.g., receipt/order ID).
- Create a debit transaction for the same amount (or the differential if partially refunded). Use a new
Idempotency-Keybut keep a clear reference that ties it to the refund event. - Confirm the updated
balance_after.
See the admin create transaction for details
Tip: If your business rules prefer reversal semantics, you can store the original transaction ID in the debit’s metadata/reference for reconciliation.
👤 Showing Balance & History to the User (Public Bearer)
Your front end (app or webframe) uses the public bearer to show the authenticated user’s wallet:
- Get my balance: /reference/wallet_me_balance
See the wallet balance for details
- Get my transactions: /reference/wallet_me_transactions Optional filters: type=[credit|debit], date ranges, pagination via next_token.
See the list transactions for details
These endpoints are read-only and scoped to the bearer’s subject; no user ID is needed in the path.
🧾 Admin Reads & Reconciliation
Your ops or reporting jobs can read wallets and transaction history on the backend:
See the get wallet balance (admin) for details
See the list transactions (admin) for details
See the list users (admin) for details
⚙️ Concurrency & Race Conditions
If POS and your e-commerce flow can post transactions simultaneously for the same user:
- Always send one transaction per atomic event (not batched math).
- Rely on the API’s atomic update to maintain a correct
balance_after. - If you receive a 409 due to ordering constraints, retry with backoff; keep the same
Idempotency-Keyif the payload is identical.
🚨 Error Handling & Retries
- Idempotent retries: For network timeouts/5xx, retry with the same
Idempotency-Key. - Validation (400/422): Fix the payload (amount format, currency, user ID) and either:
- Retry with the same
Idempotency-Keyif it’s truly the same intended transaction, or - Use a new key only if you’re intentionally creating a different transaction.
- Retry with the same
- Rate limits (429): Respect Retry-After and back off.
🔒 Security Notes
- Never put tokens in URLs; use the Authorization: Bearer … header over HTTPS.
- Backend bearer must live only on your server.
- Public bearer is short-lived; store in memory/session where possible and rotate.
- Use
Idempotency-Keyfor every credit/debit. - Log transaction reference and transaction.id for later audits.
✅ Testing & Go-Live Checklist
- Create user implicitly via first credit in a sandbox.
- Verify debit works and properly reduces balance.
- Confirm /wallet/me endpoints render correct balance & history with public bearer.
- Exercise idempotent retry (same key, same payload) to prove no double posting.
- Verify pagination with limit + next_token on transaction lists.
- Document your mapping: POS receipt/order → reference.
Updated about 2 months ago
