Gateways de Pagamento
Visão Geral
O sistema suporta gateways de maquininha e de pagamento online, além do fluxo manual (sem integração).
| Gateway | Configuração | Fluxo | Status |
|---|---|---|---|
| Stone | Env vars no backend + serial por PDV | Automático — cria intent, aguarda webhook | Completo |
| Mercado Pago Point | Access Token + Device ID por tenant | PIX QR dinâmico + validação de credenciais da maquininha | Completo |
| Checkout Bricks (wallet online) | mpPublicKey no tenant | Frontend coleta token via SDK → backend cobra via /v1/payments | Completo |
| Manual | Nenhuma | Atendente confirma pagamento no sistema após usar a maquininha | Sempre disponível |
Stone
Variáveis de Ambiente (backend)
Configure em backend/.env:
| Variável | Obrigatório | Descrição |
|---|---|---|
STONE_CLIENT_ID | Sim | Client ID do app Stone (OAuth2 client_credentials) |
STONE_CLIENT_SECRET | Sim | Client Secret do app Stone |
STONE_WEBHOOK_SECRET | Recomendado | Chave para verificar assinatura HMAC-SHA256 dos webhooks. Se ausente, verificação é ignorada (aviso em log) |
STONE_SANDBOX | Não | true (padrão) = ambiente sandbox. Defina false para produção |
Configuração por PDV
No painel Admin → Filiais, preencha o campo Stone Terminal Serial com o serial da maquininha (impresso na etiqueta traseira, formato T-XXXXXXX). O serial identifica qual maquininha física receberá a cobrança.
Fluxo Completo de Pagamento
Atendente confirma pagamento com cartão no PDV
│
▼
Frontend: POST /api/t/:slug/orders → cria pedido
│
▼
Frontend: POST /api/t/:slug/orders/:id/process-payment
{ method, gateway: "stone", stoneTerminalSerial }
│
▼
Backend: StoneService.createPaymentIntent()
• Obtém OAuth2 token (cache em memória, renovado 60s antes do vencimento)
• POST https://[sandbox-]api.openbank.stone.com.br/api/v1/transactions
{ amount (centavos), payment_type, terminal_id, external_id }
• Retorna { status: "processing" }
│
▼
Maquininha Stone: exibe solicitação de pagamento ao cliente
│
▼
Stone → POST /api/stone/webhook (endpoint público, sem /t/:slug)
Header: X-Stone-Signature: hmac-sha256(rawBody, STONE_WEBHOOK_SECRET)
Body: { external_id, status: "authorized"|"declined", id: transactionId }
│
▼
Backend: StoneController.handleWebhook()
• Verifica assinatura HMAC (constante-time)
• Chama OrdersService.updatePaymentFromWebhook(externalId, { paymentStatus, transactionId })
• Emite WebSocket event "paymentUpdate" no room kitchen-{tenantId}
│
▼
Frontend: ouve "paymentUpdate" via socket kitchen
• status "approved" → toast de sucesso, limpa carrinho
• status "declined" → exibe motivo, oferece retry
• timeout 60s → resolve como "declined"Endpoint Webhook
POST /api/stone/webhook- Rota global (sem prefixo
/t/:slug) - Deve ser registrada no painel da Stone como URL de callback
- Responde
200 { received: true }em todos os casos (inclusive erros de validação de assinatura retornam 401)
Cancelamento
StoneService.cancelIntent(transactionId) cancela uma transação pendente no terminal via POST /api/v1/transactions/:id/cancel. Chamado automaticamente em caso de timeout no frontend.
Mercado Pago Point
Configuração
Em Admin → Configurações → Mercado Pago Point:
| Campo | Descrição |
|---|---|
mpAccessToken | Access Token da conta MP (formato APP_USR-...). Obtenha em mercadopago.com.br/developers |
mpDeviceId | Device ID da maquininha Point registrada na conta. Visível no painel MP ou via API GET /point/integration-api/devices |
As credenciais são armazenadas no nível do tenant (Tenant.mpAccessToken, Tenant.mpDeviceId, Tenant.mpPublicKey).
Validação de Credenciais
Endpoint: POST /api/t/:slug/mercadopago/test-credentialsPermissão: ManageSettings
{
"accessToken": "APP_USR-...",
"deviceId": "PAX_A910__SMARTPOS123" // opcional
}Passos internos:
GET https://api.mercadopago.com/point/integration-api/devicescom o token — valida autenticidade- Se
deviceIdfornecido:GET /point/integration-api/devices/:deviceId— confirma que pertence à conta
Resposta em caso de sucesso:
{ "success": true, "deviceCount": 2, "device": { ... } }Checkout Bricks (Apple Pay / Google Pay)
Pré-requisitos
mpAccessTokenconfigurado no tenant (usado pelo backend para cobrar)mpPublicKeyconfigurado no tenant (exposta ao frontend para inicializar o SDK)
Ambas as credenciais estão em Admin → Configurações → Mercado Pago Point.
Fluxo Completo
CheckoutView → cliente seleciona "📱 Carteira" → navigate /wallet-pay
│
▼
WalletPaymentView: GET /t/:slug/checkout/config → { mpPublicKey }
initMercadoPago(mpPublicKey, { locale: 'pt-BR' })
<Payment> brick renderiza (Apple Pay / Google Pay / cartão online)
│
▼
Cliente autentica biometria no dispositivo (Touch ID / Face ID / impressão digital)
│
▼
onSubmit(formData) → POST /t/:slug/checkout/wallet-payment
│
▼
Backend: calcula amount, MercadoPagoService.processWalletPayment()
POST https://api.mercadopago.com/v1/payments
{ token, payment_method_id, issuer_id, payer, ... }
│
▼
status === 'approved' → ordersService.create() → pedido criado
│
▼
WalletPaymentView: exibe OrderConfirmation com número do pedidoEndpoint: Configuração Pública
GET /api/t/:slug/checkout/config- Rota pública (sem autenticação)
- Resposta:
{ mpPublicKey: string | null } - Retorna
nullse o tenant não configuroumpPublicKey
Endpoint: Pagamento via Wallet
POST /api/t/:slug/checkout/wallet-payment- Rota pública (sem autenticação)
- Aceita
WalletPaymentDto: dados do cliente + carrinho +token(gerado pelo brick) - Cria o pedido no sistema após aprovação do pagamento
Detecção Automática de Wallet
O Checkout Bricks chama canMakePayment() internamente e exibe as opções disponíveis no dispositivo:
| Dispositivo | Wallet disponível |
|---|---|
| iPhone com Safari + Apple Pay configurado | Apple Pay |
| Android com Chrome + Google Pay configurado | Google Pay |
| Desktop ou kiosk compartilhado | Formulário de cartão online (sem biometria) |
Kiosks: Em terminais compartilhados, os botões Apple Pay / Google Pay não aparecem porque o dispositivo não tem wallet configurada. O brick exibe formulário de cartão online. Para desabilitar cartão online em kiosks, use a propriedade
customizationdo brick.
Roteamento Automático
PDV (maquininha física)
A lógica de seleção de gateway para pagamentos no balcão ocorre no hook usePdvActions.handleProcessCard():
activePdv.stoneTerminalSerial preenchido?
├── Sim → gateway = "stone"
└── Não → gateway = "mock" (manual — sem processamento real)Checkout Online (cliente)
A lógica de exibição de opções de pagamento ocorre no CheckoutView com base nas configurações do tenant:
mpAccessToken configurado?
├── Não → banner "Pagamento no estabelecimento"
└── Sim → picker de método de pagamento:
• PIX ⚡ (sempre)
• Cartão 💳 (sempre, via maquininha ao lado)
• 📱 Carteira (somente se mpPublicKey também configurado)Sem Gateway Configurado
Quando nenhum gateway está ativo (sem serial Stone, sem MP habilitado), o PaymentModal exibe:
"Aproxime ou insira o cartão na maquininha e confirme o pagamento"
O atendente processa manualmente na maquininha e confirma no sistema. Nenhuma integração ocorre — o pedido é registrado com paymentMethod mas sem transactionId.