Wallet
QR

QR

Who calls these endpoints?External API (s2s)
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
QR issue/read is SHARED between s2s and panel (POST qr, GET qr, GET qr/:id, POST qr/:id/revoke); GET qr/:id/image and POST qr/parse are s2s-only. QR producer-code management and terminal provisioning are panel-only.

QR works in both directions: a user generates a code to be paid, or a business shows a code to be paid to. Every code is signed, single-use and time-limited.

QR runs closed loop today: the code you generate is read inside your own ecosystem — your app, your businesses, your terminals. For a common-standard format readable by banks and other payment apps, see Interoperability below.

Generating a code

POST /api/v1/wallet/qr
FieldTypeRequiredDescription
qrTypestring✅P2P_STATIC, P2P_DYNAMIC, MERCHANT_STATIC or MERCHANT_DYNAMIC — person-to-person or business code; a static code carries no amount or expiry, a dynamic code carries both
currencystring✅
amountstring—Fixed-amount code. Omit and the payer enters the amount
targetEndUserIdstring—The user the code belongs to
targetExternalCustomerIdstring—Target by your own customer id
programIdstring—Required when you run more than one program
expiresInSecondsnumber—Validity window
metadataobject—Your own references (order no, till no, …)
curl -X POST "https://api-gateway.lunixi.com/api/v1/wallet/qr" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: qr-ORD-4471" \
  -H "X-Key-Id: …" -H "X-Signature: …" \
  -d '{
    "qrType": "MERCHANT_DYNAMIC",
    "targetEndUserId": "8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34",
    "amount": "18900",
    "currency": "TRY",
    "expiresInSeconds": 300,
    "metadata": { "orderRef": "ORD-4471" }
  }'
⚠️

Generating an open-amount code (omitting amount) means anyone who sees it can enter any amount. For till use, put the amount inside the code — otherwise what the cashier scanned and what the customer paid can diverge.

Fetching the image

GET /api/v1/wallet/qr/{id}/image?format=svg&scale=6
ParameterValueDefault
formatsvg | pngsvg
scalePixels per module—

Always fetch the rendered code from this endpoint rather than drawing the payload yourself — if the signature or format version changes, your own rendering silently becomes invalid.

Parsing a code

POST /api/v1/wallet/qr/parse
{ "payload": "ver:TRQR1|typ:MERCHANT_DYNAMIC|…" }

Parsing tells you whether the code is valid, which program it belongs to, its target and its amount if fixed. Do not write your own parser — the format is versioned and validation depends on the signature.

Paying by QR

In the end-user app the flow is three steps:

Scan and parse

POST /api/portal/v1/wallet/me/qr/parse

Show the user who is collecting and how much.

Initialize the payment

POST /api/portal/v1/wallet/me/qr/pay
FieldRequiredDescription
payload✅The scanned code
sourceWalletAccountId✅Account to pay from
amount—Required when the code is open-amount
otpDestination, otpChannel, locale—For confirmation

Complete

POST /api/portal/v1/wallet/me/qr/pay/{qrId}/complete

With integrityHash, nonce and otpCode when required.

Revocation and expiry

POST /api/v1/wallet/qr/{id}/revoke
GET  /api/v1/wallet/qr/{id}
GET  /api/v1/wallet/qr

Expired codes become invalid automatically. A revoked code cannot be reused — even if it is still on the user's screen, scanning it is rejected.

The list endpoint (GET /wallet/qr) filters by programId, qrType, status, targetEndUserId and is cursor-paginated. Use it to track active codes instead of keeping your own code ledger.

Interoperability

Coming soon — common-standard QR format

Today's QR format is closed loop and claims no compliance with an external standard: the code is read only inside your ecosystem, and bank or third-party payment apps cannot decode it.

The reason a common-standard format is not emitted is regulatory rather than technical: publishing a format before the applicable technical guideline and field structure are settled produces a code believed to be compliant that no reader understands. Once that is settled the format will be supported and this box removed.

Today: no standard compliance is claimed. Status: Roadmap.

If an unknown encoding scheme is selected, the platform refuses to generate the code rather than silently falling back to the closed-loop format. That makes "I thought I emitted a standard code" impossible.

Common mistakes

SymptomCauseFix
"Invalid" when scannedExpired or revokedGenerate a new code; show remaining validity
Paid amount differs from expectedOpen-amount codeUse fixed-amount codes at the till
Code not readable in another programCodes are program-scopedGenerate with the correct programId
Self-rendered code not readablePayload was hand-builtUse GET /wallet/qr/{id}/image