Skip to content

Gateways de Pagamento

Visão Geral

O sistema suporta gateways de maquininha e de pagamento online, além do fluxo manual (sem integração).

GatewayConfiguraçãoFluxoStatus
StoneEnv vars no backend + serial por PDVAutomático — cria intent, aguarda webhookCompleto
Mercado Pago PointAccess Token + Device ID por tenantPIX QR dinâmico + validação de credenciais da maquininhaCompleto
Checkout Bricks (wallet online)mpPublicKey no tenantFrontend coleta token via SDK → backend cobra via /v1/paymentsCompleto
ManualNenhumaAtendente confirma pagamento no sistema após usar a maquininhaSempre disponível

Stone

Variáveis de Ambiente (backend)

Configure em backend/.env:

VariávelObrigatórioDescrição
STONE_CLIENT_IDSimClient ID do app Stone (OAuth2 client_credentials)
STONE_CLIENT_SECRETSimClient Secret do app Stone
STONE_WEBHOOK_SECRETRecomendadoChave para verificar assinatura HMAC-SHA256 dos webhooks. Se ausente, verificação é ignorada (aviso em log)
STONE_SANDBOXNãotrue (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:

CampoDescrição
mpAccessTokenAccess Token da conta MP (formato APP_USR-...). Obtenha em mercadopago.com.br/developers
mpDeviceIdDevice 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

json
{
  "accessToken": "APP_USR-...",
  "deviceId": "PAX_A910__SMARTPOS123"   // opcional
}

Passos internos:

  1. GET https://api.mercadopago.com/point/integration-api/devices com o token — valida autenticidade
  2. Se deviceId fornecido: GET /point/integration-api/devices/:deviceId — confirma que pertence à conta

Resposta em caso de sucesso:

json
{ "success": true, "deviceCount": 2, "device": { ... } }

Checkout Bricks (Apple Pay / Google Pay)

Pré-requisitos

  • mpAccessToken configurado no tenant (usado pelo backend para cobrar)
  • mpPublicKey configurado 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 pedido

Endpoint: Configuração Pública

GET /api/t/:slug/checkout/config
  • Rota pública (sem autenticação)
  • Resposta: { mpPublicKey: string | null }
  • Retorna null se o tenant não configurou mpPublicKey

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:

DispositivoWallet disponível
iPhone com Safari + Apple Pay configuradoApple Pay
Android com Chrome + Google Pay configuradoGoogle Pay
Desktop ou kiosk compartilhadoFormulá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 customization do 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.


Relacionados

Lançado sob a licença MIT.