Wallet Payments
- Base path
/api/v1/wallet- Identity
- Ed25519 request signature —
X-Key-Id,X-Date,X-Nonce,Digest(when there is a body),X-Signature. ±5 min clock window, single-use nonce, optional per-key IP allow-list. - Authorization
- The key must carry the wallet product and the wallet permission the call requires; a call without it gets
403 WALLET_INSUFFICIENT_SCOPE. Permissions per endpoint: Key permissions. - Cross-surface
- These endpoints are on the s2s surface (
payments/buy,collect,payments/:id/approve,payments/:id). End-user approval lives on the portal side (me/payments/:requestId/approve,me/authorizations/*) and requires step-up.
A wallet payment is an individual user paying a business account inside the program from their balance. No card network is involved; money moves inside the tenant, which makes it instant and low-cost.
There are two collection models:
| Model | Who starts it | When to use |
|---|---|---|
Payment request (payments/buy) | The business, targeting the buyer by phone or wallet number | E-commerce, remote sales, invoice collection |
Code collection (CPM) (collect) | The business, using a single-use code the customer shows | Physical checkout, POS, in-store |
Payment request
Create the request
POST /api/v1/wallet/payments/buy| Field | Type | Required | Description |
|---|---|---|---|
buyerWalletNo | string | ⚠️ | Buyer's wallet number |
buyerPhone | string | ⚠️ | Buyer's phone |
amount | string | ✅ | Minor-unit |
currency | string | ✅ | |
paymentPurpose | string | ✅ | Purpose code |
orderRef | string | — | Your order number |
cashierRef | string | — | Cashier/branch reference |
otpDestination, otpChannel, locale | string | — | For buyer confirmation |
⚠️ One of buyerWalletNo or buyerPhone is required.
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/payments/buy" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-ORD-4471" \
-H "X-Key-Id: …" -H "X-Signature: …" \
-d '{
"buyerPhone": "+905321234567",
"amount": "18900",
"currency": "TRY",
"paymentPurpose": "PURCHASE",
"orderRef": "ORD-4471"
}'The customer approves
The payment does not complete until the customer approves. Approval arrives two ways:
- From the portal/app — the customer calls
POST /api/portal/v1/wallet/me/payments/{requestId}/approvein your app. - From your server —
POST /api/v1/wallet/payments/{id}/approvecarryingbuyerEndUserId,integrityHash,nonceandotpCode.buyerEndUserIdis the paying end user and is required; if it does not match the payer of the payment request, the request is rejected with404.
{
"status": "success",
"code": "SUCCESS",
"data": {
"operationId": "e9c4a7b2-1f3d-4a6e-b8c5-7d2f0e9a3b16",
"status": "COMPLETED",
"ledgerTxnRef": "ltx_01J8ZW…"
}
}Confirm the result
GET /api/v1/wallet/payments/{id}Gate "payment succeeded, release the goods" on COMPLETED only. INITIALIZED and CHALLENGED merely mean a request was opened; the customer may never approve it.
When the outcome is not final yet: INDETERMINATE
The approval response may carry status: "INDETERMINATE". This is not a rejection: the amount may already have been debited from the customer's wallet, but the final state of the payment is not settled yet. The wallet settles it on its own shortly, as COMPLETED or FAILED/REVERSED.
When you see INDETERMINATE, do not treat the payment as failed, do not send the customer to another payment method and do not open a new payment request — if the first payment completes, the customer is charged twice. Query the result with GET /api/v1/wallet/payments/{id} or resend the approval with the same Idempotency-Key; once the payment settles, the final state is returned.
Code collection (CPM)
At a physical checkout the customer generates a single-use code in the app; the cashier scans it and collects.
The customer generates a code
POST /api/portal/v1/wallet/me/payment-code # generate
DELETE /api/portal/v1/wallet/me/payment-code # revokeThe code is short-lived and single-use — it is consumed the moment it is scanned.
The business collects
POST /api/v1/wallet/collect| Field | Type | Required | Description |
|---|---|---|---|
code | string | ✅ | The code the customer showed |
amount | string | ✅ | Minor-unit |
currency | string | ✅ | |
orderRef | string | — | Receipt/order reference |
paymentPurpose | string | — | |
locale | string | — |
The code is single-use: on a network error at the till, do not rescan the code — repeat the same request with the same Idempotency-Key. If the code is already consumed, ask the customer for a new one.
When you display the code, show the remaining validity too. An expired code fails at the till as "invalid", which cashiers usually misread as a balance problem.
Commission
Payments into a business account are subject to the program's commission schedule. Commission is computed at payment time and deducted from the business's receivable.
GET /api/v1/wallet/business-accounts/{id}/commissionCommission schedules are versioned and approved from the merchant panel: Panel — Limit & Fee Schemes.
Fees and commission are different: a fee is charged to whoever performs the operation, commission is charged to the collecting business. They appear in separate fields on the operation (feeTotal, commissionTotal).
Refunds
Refunding a wallet payment credits the wallet. Refunding a card payment into a wallet is a separate product: Refund to Wallet.
Webhooks
| Event | When |
|---|---|
wallet.payment.completed | Payment completed and credited to the business |
wallet.payment.failed | Payment failed |
wallet.payment.reversed | A completed payment was reversed |
wallet.refund.credited | A refund was credited to the wallet |
wallet.refund.reversed | A refund was reversed |
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
Payment stuck at CHALLENGED | The customer never approved | It becomes EXPIRED; open a new request |
Approval returned INDETERMINATE | The outcome is not final yet; the amount may have been debited | Do not treat it as failed; query it or re-approve with the same key (details) |
| Code reported "invalid" | Expired or already consumed | Ask the customer for a new code |
| Commission differs from expectation | The active commission schedule version changed | Check commissionScheduleVersionId on the operation |
| Buyer not found | The phone is not registered in the program | Check first with POST /wallet/wallets/lookup |