O Ticketz PRO inclui um driver de gateway de pagamento Genérico Externo que permite integrar qualquer provedor de cobrança sem modificar o código-fonte do backend. Em vez de escrever um novo driver em TypeScript, você implementa um único endpoint HTTP que recebe chamadas RPC do Ticketz e retorna os dados necessários.
Referência canônica: um contrato formal OpenAPI 3.0 está disponível em External-Payment-Driver-OpenAPI.yaml. Use-o para validar sua implementação com ferramentas OpenAPI ou para gerar stubs de servidor.
1. Como funciona
O ExternalPaymentDriver armazena/lê os dados das faturas no banco de dados do Ticketz e encaminha toda operação de negócio para o seu endpoint:
- Criar pagamento — o Ticketz pede ao seu endpoint que gere um código PIX, boleto, cobrança de cartão de crédito ou link de checkout externo.
- Criar assinatura — o Ticketz pede ao seu endpoint que crie uma assinatura recorrente e retorne um
subscriptionId. - Sincronizar pagamento de assinatura — o Ticketz pede ao seu endpoint o próximo pagamento pendente da assinatura (ex.: próximo QR Code PIX, código de barras do boleto ou URL da fatura hospedada) para que a fatura seja atualizada antes do vencimento.
- Obter status da assinatura — o Ticketz pede ao seu endpoint o status atual da assinatura durante o job diário de reconciliação.
- Webhook — o Ticketz encaminha o payload do webhook do provedor para o seu endpoint para que você o valide e informe ao Ticketz se a fatura foi paga ou expirada.
- Verificar status — o Ticketz pede ao seu endpoint o status atual de uma fatura em aberto.
- Reportar detalhes do driver — o Ticketz pede ao seu endpoint os campos dinâmicos, modos suportados e operações suportadas em uma única chamada
getDriverDetails. O resultado é cacheado e reutilizado até um refresh forçado.
Seu endpoint é responsável por toda a lógica específica do provedor: autenticação, chamadas de API, validação de assinaturas, idempotência, etc.
O driver externo também suporta os seguintes recursos avançados:
- Campos de configuração dinâmicos — seu endpoint pode declarar campos adicionais de configuração que aparecem na UI do Ticketz. Eles são retornados dentro de
getDriverDetails. - Arquivos de certificado privados — certificados enviados são armazenados de forma segura em
backend/privatee enviados ao seu endpoint codificados em base64 a cada chamada RPC. - Desabilitar modos — modos individuais podem ser desabilitados via parâmetros do driver (
_externalDisablePix,_externalDisableBoleto,_externalDisableCc,_externalDisableLink). QuandogetDriverDetailsreporta os modos suportados, a lista final é a interseção dos modos do endpoint e dos modos habilitados localmente. - Detalhes do driver em cache — a resposta de
getDriverDetailsé cacheada no primeiro uso e persistida até um super usuário chamarPOST /payment-gateways/external/refresh-cache.
2. Habilitando o driver
- Vá em Configurações → Gateway de Pagamento.
- Selecione Genérico Externo (
external). - Salve as configurações obrigatórias:
| Configuração | Valor recomendado |
|---|---|
| URL do Endpoint | https://your-service.example.com/ticketz-payment-gateway (obrigatório) |
| Token de Autenticação | Um segredo forte compartilhado apenas com seu serviço (enviado como Authorization: Bearer <token>) |
| Habilitar assinaturas | "true" para criar assinaturas recorrentes via createSubscription. |
Quando as assinaturas estiverem habilitadas, inclua também getSubscriptionStatus no array operations retornado por getDriverDetails para que o Ticketz possa reconciliar o status da assinatura todos os dias.
Após salvar a URL do Endpoint, o driver carrega todos os metadados do seu endpoint com uma única chamada RPC getDriverDetails na primeira vez que for necessário. Os botões Carregar campos e Carregar modos agora leem desse cache. Para forçar o driver a consultar o endpoint novamente (por exemplo, após alterar os campos ou modos suportados do seu lado), um super usuário pode chamar:
POST /payment-gateways/external/refresh-cache
A URL do webhook que seu provedor deve chamar é:
https://<seu-backend-ticketz>/subscription/webhook
3. Protocolo RPC
Todas as chamadas são requisições POST ao seu endpoint com Content-Type: application/json.
Envelope comum de requisição
{
"operation": "create | createSubscription | webhook | checkStatus | expire | getDriverDetails | getSubscriptionStatus",
"driver": "external",
"callbackUrl": "https://<seu-backend-ticketz>/subscription/webhook",
"invoiceId": 123,
"txId": "TX-ABC-123",
"paymentMethod": "pix",
"mode": "pix",
"price": 99.90,
"cardData": { ... },
"company": { ... },
"invoice": { ... },
"webhookBody": { ... },
"webhookHeaders": { ... },
"webhookQuery": { ... },
"currentSettings": { ... },
"fileData": { ... },
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}
Os campos são preenchidos conforme a operação:
| Operação | Campos preenchidos |
|---|---|
create |
invoiceId, price, paymentMethod, mode, cardData, company, invoice, currentSettings, fileData |
createSubscription |
Mesmo que create. |
syncSubscriptionPayment |
invoiceId, txId, subscriptionId, paymentMethod, company, invoice, currentSettings, fileData |
webhook |
webhookBody, webhookHeaders, webhookQuery, currentSettings, fileData |
checkStatus |
invoiceId, txId, paymentMethod, invoice, currentSettings, fileData |
expire |
invoiceId, txId, currentSettings, fileData |
getSubscriptionStatus |
subscriptionId, currentSettings, fileData |
getDriverDetails |
currentSettings, fileData |
Envelope comum de resposta
{
"success": true,
"errorCode": "ERR_MY_CUSTOM",
"errorMessage": "Erro legível",
"status": "paid | expired | ignored",
"paymentMethod": "pix | boleto | credit_card",
"subscriptionId": "sub-ABC-123",
"txId": "TX-ABC-123",
"value": 99.90,
"qrcode": { "qrcode": "0002012658..." },
"boletoUrl": "https://...",
"boletoTypeable": "00190.00009 ...",
"boletoBarcode": "0019000009...",
"checkoutUrl": "https://checkout.example.com/...",
"expireAt": "2026-06-20T23:59:59Z",
"dueDate": "2026-06-20",
"payGwData": { ... },
"paymentData": { ... },
"fields": [ ... ],
"webhookResponse": { ... }
}
Quando success é false em uma operação create, o Ticketz retorna uma resposta 400 ao frontend com:
{
"error": "ERR_MY_CUSTOM",
"message": "Erro legível"
}
errorCode e errorMessage são ambos opcionais. Se errorMessage for fornecido, ele é exibido diretamente no toast do frontend. Se apenas errorCode for fornecido, o frontend tenta traduzi-lo; caso contrário, exibe o próprio código.
fields é retornado dentro de getDriverDetails; os nomes dos campos retornados não devem incluir o prefixo _external — o Ticketz o adiciona automaticamente.
4. Operações em detalhe
4.1 create
O Ticketz envia esta operação quando o usuário solicita uma nova cobrança.
Exemplo de requisição:
{
"operation": "create",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"invoiceId": 123,
"price": 99.9,
"paymentMethod": "pix",
"company": {
"id": 5,
"name": "Acme SA",
"email": "financeiro@acme.test",
"dueDate": "2026-06-17",
"recurrence": "MENSAL",
"fiscalData": {
"document": "12.345.678/0001-90",
"name": "Acme SA",
"fiscalEmail": "financeiro@acme.test",
"postalCode": "01001000",
"address": "Rua Exemplo",
"addressNumber": "100",
"province": "Centro",
"city": "São Paulo",
"state": "SP"
},
"externalIds": {}
},
"invoice": {
"id": 123,
"detail": "Assinatura Mensal",
"status": "open",
"value": 99.9,
"currency": "BRL",
"dueDate": "2026-06-17",
"companyId": 5
}
}
Resposta esperada para PIX:
{
"success": true,
"paymentMethod": "pix",
"txId": "external-tx-123",
"value": 99.9,
"qrcode": { "qrcode": "00020126580014br.gov.bcb.pix..." },
"expireAt": "2026-06-17T23:59:59.000Z",
"payGwData": { "externalId": "external-tx-123", "raw": "..." }
}
Resposta esperada para boleto:
{
"success": true,
"paymentMethod": "boleto",
"txId": "external-tx-456",
"value": 99.9,
"boletoUrl": "https://banco.example.com/boleto/456",
"boletoTypeable": "00190.00009 01234.567890 12345.678901 2 12345678901234",
"boletoBarcode": "00191234567890123456789012345678901234567890123",
"dueDate": "2026-06-20",
"payGwData": { "externalId": "external-tx-456" }
}
O Ticketz armazena txId, paymentMethod, payGwData (como string JSON) e paymentData na fatura. payGwData e paymentData são opcionais; se omitidos, o Ticketz constrói paymentData a partir dos campos de boleto/PIX. Quando uma cobrança expira ou é invalidada, o Ticketz marca a fatura como expired e registra expiredAt em paymentData para fins de auditoria.
Semântica dos campos de data:
- Use
expireAtpara cobranças de curta duração, como Pix. O frontend renderiza uma contagem regressiva ao vivo e pode expirar a cobrança automaticamente. - Use
dueDatepara cobranças de longo prazo, como boleto. O frontend a exibe como uma data de calendário em vez de contagem regressiva. - Quando ambos os campos são retornados, o Ticketz mantém apenas o mais relevante:
- cobranças
pixmantêmexpireAt. - boletos e demais cobranças mantêm
dueDate. - A resposta da API e o
paymentDataarmazenado conterão apenas o campo escolhido.
- cobranças
4.2 webhook
Seu provedor envia um webhook ao Ticketz, e o Ticketz o encaminha para o seu endpoint.
Exemplo de requisição:
{
"operation": "webhook",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"webhookBody": {
"event": "payment.received",
"data": {
"txId": "external-tx-123",
"amount": 99.9,
"status": "paid"
}
},
"webhookHeaders": {
"content-type": "application/json",
"x-signature": "sha256=..."
},
"webhookQuery": {}
}
Resposta esperada:
{
"success": true,
"status": "paid",
"txId": "external-tx-123",
"value": 99.9
}
Valores permitidos para status:
paid—PaymentGatewayServiceslocaliza a fatura em aberto pelotxId, verifica se ovaluepago é ao menos o valor da fatura, marca a fatura como paga e estende a data de vencimento da empresa.expired—PaymentGatewayServiceslocaliza a fatura em aberto pelotxId, marca a fatura comoexpirede registraexpiredAtempaymentDatapara fins de auditoria.ignored— o Ticketz não faz nada (use para eventos de ping, duplicados, etc.).
Para paid e expired, txId é obrigatório para que o Ticketz encontre a fatura internamente, a menos que um subscriptionId seja fornecido. Quando subscriptionId está presente e txId é desconhecido, o Ticketz resolve a fatura em aberto mais antiga com aquele id de assinatura, atualiza seu txId e processa o pagamento. O driver em si não acessa a tabela Invoices; ele apenas reporta txId, subscriptionId e value. O value pago é opcional; quando omitido, o valor da fatura é assumido. Se o valor pago for menor que o valor da fatura, o webhook é tratado como ignored. Se o valor pago for maior que o valor da fatura, a diferença é armazenada na fatura como creditBalance e aplicada como desconto na próxima fatura de assinatura.
Resposta de webhook personalizada
Alguns provedores exigem uma resposta HTTP específica quando entregam um webhook. Por padrão, o Ticketz responde ao provedor com { "ok": true }. Você pode sobrescrever isso incluindo um objeto webhookResponse na sua resposta de webhook:
{
"success": true,
"status": "paid",
"txId": "external-tx-123",
"value": 99.9,
"webhookResponse": {
"statusCode": 200,
"body": { "received": true },
"headers": {
"content-type": "application/json"
}
}
}
statusCode— código HTTP que o Ticketz envia de volta. Padrão200quando omitido.body— corpo JSON retornado ao provedor.headers— cabeçalhos de resposta opcionais (objeto com valores string).
Se webhookResponse for omitido, o Ticketz mantém a resposta padrão { "ok": true }.
4.3 checkStatus
O Ticketz chama esta operação para faturas em aberto durante a verificação periódica, quando um admin solicita manualmente um refresh de status, e quando o usuário clica em Pagar em uma fatura que já possui uma cobrança em aberto.
Exemplo de requisição:
{
"operation": "checkStatus",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"txId": "external-tx-123",
"paymentMethod": "pix"
}
Resposta esperada:
{
"success": true,
"status": "paid"
}
Valores permitidos para status são paid, expired e ignored. Se o status da resposta for paid, o Ticketz executa o mesmo fluxo de pagamento usado pelos drivers nativos. Se o status da resposta for expired, o Ticketz marca a fatura como expired e registra expiredAt em paymentData.
4.4 expire
Reservado para uso futuro. O Ticketz pode chamar esta operação quando uma fatura é explicitamente cancelada. Seu endpoint não deve depender disso hoje.
4.5 getDriverDetails
Operação única usada para popular o cache do driver. O Ticketz a chama no primeiro uso e cacheia a resposta até um super usuário forçar um refresh. A resposta deve incluir fields, modes e operations.
O array
fieldssegue o mesmo schema usado pelo componenteDynamicFormdo frontend. Veja Campos de formulário dinâmico para a referência completa das propriedades e exemplos prontos para copiar.
Exemplo de requisição:
{
"operation": "getDriverDetails",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"currentSettings": {
"_externalEndpointUrl": "https://your-service.example.com/ticketz-payment-gateway",
"_externalAuthToken": "...",
"_externalCertFile": "1759325637143.pem"
},
"fileData": {
"_externalCertFile": "LS0tLS1CRUdJTi..."
}
}
Resposta esperada:
{
"success": true,
"fields": [
{
"name": "tenantId",
"title": "Tenant ID",
"description": "Tenant retornado pelo fluxo OAuth do provedor",
"type": "text",
"lgWidth": 12,
"required": true
}
],
"modes": ["pix", "boleto", "cc", "link"],
"operations": [
"create",
"createSubscription",
"syncSubscriptionPayment",
"webhook",
"checkStatus",
"getSubscriptionStatus",
"getDriverDetails"
]
}
4.6 createSubscription
Chamada quando _externalEnableSubscriptions é "true" e o usuário solicita uma nova cobrança. O payload da requisição é idêntico ao de create, mas o Ticketz espera um subscriptionId na resposta para conseguir casar futuras renovações de webhook com a empresa.
Resposta esperada:
{
"success": true,
"paymentMethod": "credit_card",
"subscriptionId": "sub-external-123",
"txId": "external-tx-789",
"value": 99.9,
"checkoutUrl": "https://checkout.example.com/sub-external-123",
"payGwData": { "subscriptionId": "sub-external-123" }
}
A validação é similar à de create, mas subscriptionId é obrigatório. Para assinaturas via PIX e boleto, os dados do primeiro pagamento (qrcode ou boletoUrl) também podem ser retornados para que o usuário pague imediatamente.
4.7 syncSubscriptionPayment
Chamada diariamente para toda fatura em aberto que possui um subscriptionId, e também quando o admin clica em Sincronizar pagamento na lista de faturas. Seu endpoint deve consultar o provedor sobre o próximo pagamento pendente/futuro da assinatura e retornar os dados pagáveis.
Resposta esperada quando existe um pagamento pendente:
{
"success": true,
"paymentMethod": "pix",
"txId": "external-tx-renewal-456",
"value": 99.9,
"qrcode": { "qrcode": "00020126580014br.gov.bcb.pix..." },
"expireAt": "2026-07-17T23:59:59Z",
"payGwData": { "subscriptionId": "sub-external-123", "paymentId": "external-tx-renewal-456" }
}
Resposta esperada quando não há pagamento pendente disponível:
{
"success": true
}
Quando txId é omitido, o Ticketz trata a resposta como “nenhum dado disponível” e deixa a fatura inalterada. Quando txId está presente, a resposta é validada como uma resposta de create e a fatura é atualizada com o novo txId, paymentMethod, payGwData e paymentData. Isso permite que o frontend exiba a cobrança em aberto (QR Code PIX, código de barras do boleto ou link de checkout) em vez de gerar uma nova.
4.8 getSubscriptionStatus
Chamada diariamente para toda empresa que possui uma assinatura externa ativa. Seu endpoint deve consultar o provedor sobre o status atual da assinatura e retornar um dos seguintes valores no campo status:
active— a assinatura está ativa; o Ticketz mantém a empresa marcada como assinante.inactive(oucanceled) — a assinatura não está mais ativa; o Ticketz atualiza a empresa localmente para que a geração de faturas volte ao ciclo de cobrança regular.
Resposta esperada:
{
"success": true,
"status": "active"
}
Retornar success: true sem o campo status, ou retornar success: false, é tratado como “status desconhecido” e a empresa é deixada inalterada.
5. Cabeçalhos de autenticação
O Ticketz envia o token configurado no cabeçalho Authorization:
Authorization: Bearer <token>
Seu endpoint deve validar esse token e rejeitar requisições desconhecidas.
6. Comportamento de retry e timeout
- O driver usa um timeout fixo de 30 segundos.
- O driver faz até 3 tentativas com atrasos de 1s, 3s e 5s.
- Retries só ocorrem em falhas transitórias: erros de rede, respostas HTTP
5xxou HTTP429 Too Many Requests. - Respostas HTTP
4xxe respostas de sucesso comsuccess: falsenão são retentadas. - Cada tentativa envia um novo
idempotencyKey(UUID v4); seu endpoint pode usá-lo para evitar cobranças duplicadas. - Webhooks são respondidos com
{ ok: true }ao provedor por padrão. Se sua resposta dewebhookincluirwebhookResponse, o Ticketz encaminha esse código de status, corpo e cabeçalhos ao provedor.
7. Validação de resposta
O Ticketz valida toda resposta de sucesso antes de aplicá-la. Se seu endpoint retornar um payload malformado, a operação é abortada e o usuário vê um erro ERR_EXTERNAL_*.
| Operação | Campos obrigatórios em success: true |
|---|---|
create |
paymentMethod (pix, boleto ou credit_card), txId não vazio, e os dados de pagamento exigidos pelo modo (qrcode.qrcode para PIX, boletoUrl para boleto, ou checkoutUrl para link). paymentData pode ser usado no lugar dos campos no topo. |
createSubscription |
paymentMethod, subscriptionId não vazio, e as mesmas regras de dados de pagamento de create. |
syncSubscriptionPayment |
Quando txId está presente, mesma validação de create. Quando txId é omitido, a resposta significa “nenhum pagamento pendente disponível” e é aceita sem validação adicional. |
webhook |
status (paid, expired ou ignored). paid/expired exigem txId e/ou subscriptionId. |
checkStatus |
status (paid, expired ou ignored). Uma resposta expired marca a fatura como expired e registra expiredAt em paymentData. |
getSubscriptionStatus |
status (active, inactive ou canceled). Outros valores são tratados como desconhecidos. |
getDriverDetails |
fields, modes e operations devem ser arrays válidos. modes só pode conter pix, boleto, cc ou link. |
Quando success: false, inclua errorCode e opcionalmente errorMessage. errorCode é encaminhado ao frontend e traduzido a partir de backendErrors.
8. Arquivos de certificado privados
Se o driver declarar um campo de arquivo com extra.file.private: true, o usuário envia o arquivo via /settings/privateFile. O arquivo é salvo em backend/private. A cada chamada RPC o driver lê o arquivo e o envia em fileData como string base64:
{
"fileData": {
"_externalCertFile": "LS0tLS1CRUdJTi..."
}
}
Seu endpoint pode usar esse certificado para autenticação mTLS ou para assinar requisições ao provedor. Não armazene o arquivo bruto em payGwData.
9. Idempotência
Seu endpoint deve ser idempotente para create, especialmente porque o Ticketz faz retry em falha. Um padrão comum é:
- Verificar se
txIdouinvoiceIdjá possui uma cobrança ativa no seu provedor. - Se sim, retornar os dados existentes (você pode setar
_reused: truena resposta). - Se não, criar uma nova cobrança.
10. Exemplo mínimo (Node.js / Express)
const express = require("express");
const app = express();
app.use(express.json());
const TOKEN = process.env.TICKETZ_GATEWAY_TOKEN;
function authorize(req) {
const header = req.headers.authorization || "";
return header === `Bearer ${TOKEN}`;
}
app.post("/ticketz-payment-gateway", async (req, res) => {
if (!authorize(req)) return res.status(401).json({ success: false });
const {
operation,
invoiceId,
txId,
price,
paymentMethod,
mode,
cardData,
webhookBody,
} = req.body;
if (operation === "create") {
const charge = await myProvider.createCharge({
amount: price,
method: paymentMethod,
mode,
cardData,
externalReference: String(invoiceId),
});
return res.json({
success: true,
paymentMethod,
txId: charge.id,
value: price,
qrcode: mode === "pix" ? { qrcode: charge.pixCopiaECola } : undefined,
boletoUrl: mode === "boleto" ? charge.pdfUrl : undefined,
boletoBarcode: mode === "boleto" ? charge.barcode : undefined,
checkoutUrl: mode === "link" ? charge.checkoutUrl : undefined,
expireAt: mode === "pix" ? charge.expiresAt : undefined,
dueDate: mode === "boleto" ? charge.dueDate : undefined,
});
}
if (operation === "webhook") {
const event = webhookBody?.event;
const data = webhookBody?.data || {};
if (event === "payment.received" && data.status === "paid") {
return res.json({
success: true,
status: "paid",
txId: data.txId,
value: data.amount,
});
}
return res.json({ success: true, status: "ignored" });
}
if (operation === "checkStatus") {
const status = await myProvider.checkCharge(txId);
return res.json({ success: true, status });
}
if (operation === "createSubscription") {
const subscription = await myProvider.createSubscription({
amount: price,
method: paymentMethod,
mode,
cardData,
externalReference: String(invoiceId),
});
return res.json({
success: true,
paymentMethod,
subscriptionId: subscription.id,
txId: subscription.firstPaymentId,
value: price,
checkoutUrl: subscription.checkoutUrl,
});
}
if (operation === "getDriverDetails") {
return res.json({
success: true,
fields: [
{
name: "providerAccountId",
title: "Provider Account ID",
type: "text",
lgWidth: 12,
required: true,
},
],
modes: ["pix", "boleto", "cc", "link"],
operations: [
"create",
"createSubscription",
"webhook",
"checkStatus",
"getDriverDetails",
],
});
}
return res
.status(400)
.json({ success: false, errorMessage: "Unknown operation" });
});
app.listen(3000);
11. Integração com o frontend
A tela de configurações do Gateway de Pagamento carrega a lista de drivers em GET /payment-gateways/drivers. Para o driver external ela renderiza os campos base mais quaisquer campos dinâmicos retornados por getDriverDetails. Campos de arquivo usam o upload existente em /settings/privateFile, e campos de ação disparam POST /payment-gateways/external/fields, que retorna os campos em cache. Para forçar um refresh dos metadados em cache, um super usuário pode chamar POST /payment-gateways/external/refresh-cache.
O checkout carrega o driver ativo em GET /payment-gateways/active e renderiza um seletor de rádio para cada modo suportado retornado em supportedModes, exceto link. O modo link nunca é exibido como opção selecionável; se for o único modo suportado pelo driver ativo, o checkout cria a cobrança e redireciona o usuário para o checkoutUrl retornado pelo driver.
Quando apenas um modo que não seja link está disponível, o seletor de rádio é ocultado e o checkout avança automaticamente para aquele método:
pix/boleto— a cobrança é criada imediatamente e o usuário é levado à tela de sucesso.cc— oCreditCardFormé exibido e o usuário preenche os dados do cartão antes de clicar em Pagar.
Para o modo cc o CreditCardForm nativo captura os dados do cartão e os envia em cardData. Para o modo link o frontend redireciona para o checkoutUrl retornado pelo driver.
Reutilizando cobranças em aberto existentes
Quando uma fatura em aberto já possui paymentData com QR Code, URL de boleto ou link de checkout, a lista de faturas exibe os botões Ver Pix, Ver Boleto ou Pagar Externo em vez de gerar uma cobrança duplicada. Clicar em Pagar em tal fatura primeiro chama POST /invoices/:id/check-payment para confirmar que a cobrança ainda é válida:
- Se a cobrança está
paid→ a fatura é marcada como paga e o usuário vê um toast de confirmação. - Se a cobrança está
expired→ os dados da cobrança são limpos e o checkout abre para o usuário gerar uma nova cobrança. - Se a cobrança ainda está em aberto → um modal mostra o QR Code PIX, código de barras do boleto ou link de checkout existente.
12. Troubleshooting
| Sintoma | Causa provável |
|---|---|
ERR_EXTERNAL_ENDPOINT_NOT_CONFIGURED |
_externalEndpointUrl está vazio. |
ERR_EXTERNAL_RPC_CALL_FAILED |
Erro de rede ou seu endpoint retornou 5xx após os retries. |
| Webhook não dispara | O provedor está chamando a URL errada; verifique callbackUrl e regras de firewall. |
| Fatura não marcada como paga | Sua resposta de webhook não incluiu status: "paid" mais um txId válido, ou o value pago é menor que o valor da fatura. |
ERR_EXTERNAL_MISSING_PAYMENT_DATA |
Resposta de create sem qrcode para PIX, boletoUrl para boleto, ou checkoutUrl para link (ou o equivalente dentro de paymentData). |
ERR_EXTERNAL_INVALID_FIELDS |
Array fields de getDriverDetails inválido ou um campo sem name, title, type ou required. |
ERR_EXTERNAL_INVALID_STATUS |
Resposta de webhook ou checkStatus com status diferente de paid, expired ou ignored. |
ERR_EXTERNAL_INVALID_MODES |
Array modes de getDriverDetails não é um array ou contém valores diferentes de pix, boleto, cc ou link. |
ERR_EXTERNAL_INVALID_OPERATIONS |
Array operations de getDriverDetails não é um array. |
ERR_EXTERNAL_MISSING_SUBSCRIPTION_ID |
Resposta de createSubscription não incluiu um subscriptionId não vazio. |
| Create sempre cria cobranças duplicadas | Seu endpoint não é idempotente; verifique invoiceId/txId antes de criar. |
| Modo não aparece no checkout | O modo está desabilitado por _externalDisable* ou não foi retornado por getDriverDetails.modes. |
| Assinatura não criada | _externalEnableSubscriptions não é "true" ou getDriverDetails.operations não inclui createSubscription. |
13. Checklist de segurança
- Use HTTPS para a URL do endpoint e do webhook.
- Valide o token de autorização em toda requisição.
- Valide assinaturas de webhook do provedor dentro do seu endpoint.
- Trate
invoiceIdetxIdcomo strings opacas. - Não exponha credenciais internas do provedor em
payGwData.