Pagar via QR ou NFC
O PDV declara um valor e gera um payload — QR na tela ou transmitido por NFC, tanto faz, o app trata os dois do mesmo jeito. O app do participante lê esse payload, confirma o pagamento no próprio app (login já é o fator de autenticação — sem PIN, diferente do fluxo do QR impresso do caixa, que não tem login nenhum por trás).
PDV declara o valor
→ PDV gera o payload (QR/NFC) com um token opaco
→ app do participante lê o payload
→ app resolve o token no servidor (mostra valor + PDV antes de qualquer coisa)
→ participante confirma
→ app aprova o pagamento (debita a wallet)
O payload não contém o valor — só event e um token opaco de uso
único:
wallet://payment?v=1&event=<eventId>&intent=<token>
O app extrai eventId e token desse payload; o valor de verdade só vem
do passo de resolver, abaixo — nunca confie em nada que não seja a resposta
do servidor pra decidir quanto vai ser cobrado.
1. Resolver (antes de confirmar)
| Endpoint | O que faz |
|---|---|
GET /v1/events/{eventId}/wallet/payment-intents/{token} | Consulta pura — nunca cobra nada. Devolve o valor e qual PDV declarou, pra montar a tela de confirmação. |
- curl
- JavaScript
- Python
curl "http://127.0.0.1:8787/v1/events/{eventId}/wallet/payment-intents/{token}" \
-H "Authorization: Bearer <accessToken do participante>"
const response = await fetch(
`http://127.0.0.1:8787/v1/events/${eventId}/wallet/payment-intents/${token}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const { amountMinor, status, operatorUsername, operatorDescription } = await response.json();
import requests
response = requests.get(
f"http://127.0.0.1:8787/v1/events/{event_id}/wallet/payment-intents/{token}",
headers={"Authorization": f"Bearer {access_token}"},
)
intent = response.json()
{
"intentId": "018f26d7-8a7b-7e7d-8c96-4f0b8d0f2b13",
"amountMinor": 2500,
"status": "pending",
"operatorUsername": "PDV-00184723",
"operatorDescription": "Entrada Principal",
"expiresAt": "2026-09-09T12:05:00.000Z"
}
404 PAYMENT_INTENT_NOT_FOUND se o token não existe (ou já expirou faz
tempo e foi limpo) nesse evento.
2. Aprovar (o toque de "Confirmar" do participante)
| Endpoint | O que faz |
|---|---|
POST /v1/events/{eventId}/wallet/payment-intents/{token}/approve | Debita a wallet do participante autenticado pelo valor do intent, credita EVENT_REVENUE do evento. |
Exige o header Idempotency-Key — mesmo mecanismo já documentado na
recarga do caixa: repetir a mesma chave
devolve o mesmo resultado sem cobrar de novo (replayed: true na
resposta, indicando que nada foi debitado agora).
- curl
- JavaScript
- Python
curl -X POST "http://127.0.0.1:8787/v1/events/{eventId}/wallet/payment-intents/{token}/approve" \
-H "Authorization: Bearer <accessToken do participante>" \
-H "Idempotency-Key: <uuid gerado pelo cliente>"
const response = await fetch(
`http://127.0.0.1:8787/v1/events/${eventId}/wallet/payment-intents/${token}/approve`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Idempotency-Key": crypto.randomUUID(),
},
},
);
const { availableAmountMinor, replayed } = await response.json();
import requests
import uuid
response = requests.post(
f"http://127.0.0.1:8787/v1/events/{event_id}/wallet/payment-intents/{token}/approve",
headers={
"Authorization": f"Bearer {access_token}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
result = response.json()
{
"walletId": "018f26d7-...",
"eventId": "018f26d7-...",
"availableAmountMinor": 16750,
"replayed": false
}
O que pode dar errado, e por quê:
| Código | Situação |
|---|---|
404 PAYMENT_INTENT_NOT_FOUND | Token inválido ou de outro evento. |
404 WALLET_NOT_FOUND | Você ainda não ativou a wallet nesse evento. |
409 PAYMENT_INTENT_ALREADY_RESOLVED | Alguém já aprovou, recusou, ou o intent já expirou. Cada intent só pode ser resolvido uma vez. |
409 PAYMENT_INTENT_EXPIRED | Passou do tempo (curto, poucos minutos) sem ninguém aprovar. Peça um novo QR ao PDV. |
409 INSUFFICIENT_BALANCE | Sua wallet não tem saldo suficiente. |
409 IDEMPOTENCY_KEY_IN_PROGRESS | Já existe uma aprovação em andamento com essa mesma chave; tente de novo em seguida. |
Diferente do QR impresso do caixa, aqui não existe PIN — o próprio login do app já é o fator que prova quem está pagando. Por isso a confirmação explícita no passo 2 é obrigatória: o app nunca aprova sozinho, sem o participante ver o valor e tocar em confirmar.