Skip to main content

PDV — Payment via printed QR + PIN

Regional route (wallet-api, /v1/events/{eventId}/...), for a participant without the app: the cashier already issued the credential (see Cashier → printed QR credential with a PIN); the PDV scans the QR, enters the amount, and asks the participant to type their PIN.

EndpointWhat it does
POST /v1/events/{eventId}/pdv/paymentsDebits the participant's wallet by the given amount and credits the event's EVENT_REVENUE account, all in the same transaction.
curl -X POST http://127.0.0.1:8787/v1/events/{eventId}/pdv/payments \
-H "Authorization: Bearer <PDV accessToken>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <client-generated uuid>" \
-d '{
"qrPayload": "wallet://credential?v=1&event=<eventId>&credential=<QR token>",
"pin": "482913",
"amountMinor": 2500
}'
Response
{
"walletId": "018f26d7-...",
"eventId": "018f26d7-...",
"availableAmountMinor": 16750,
"replayed": false
}

What the endpoint checks, in this order:

  1. Is the QR from this event? qrPayload carries its own eventId inside it — if it doesn't match the URL's event, the response is 404 CREDENTIAL_NOT_FOUND, same as an unknown QR (it never reveals that the token exists, only that it isn't valid here).
  2. Is the credential active, unexpired, and unlocked? If not, 409 CREDENTIAL_NOT_ACTIVE / 409 CREDENTIAL_EXPIRED / 403 CREDENTIAL_LOCKED.
  3. Does the PIN match? If not, 403 INVALID_PIN — and the attempt is recorded against the credential. 5 wrong attempts in a row lock the credential for 15 minutes (403 CREDENTIAL_LOCKED); the participant has to wait, or the cashier has to reissue a new credential.
  4. Is there enough balance? If not, 409 INSUFFICIENT_BALANCE.

The wallet stays locked for the entire check-and-debit — if two PDVs try to charge the same wallet at the same time (or a recharge and a charge happen to overlap), one waits for the other to finish; they never run in parallel.

Requires the Idempotency-Key header, with the same behavior as the cashier's top-up: repeating the same key returns the same result without charging twice (replayed: true in the response, to make it clear nothing was charged again), and a second call with the same key while the first is still in flight gets 409 IDEMPOTENCY_KEY_IN_PROGRESS. Reusing the same key with a different amount or QR gets 409 IDEMPOTENCY_KEY_BODY_MISMATCH instead of charging (or returning) the wrong value — generate a new key for each charge.