docs

PIX Automático

Débito recorrente direto da conta do cliente (BACEN PIX Auto) — autoriza UMA vez via QR Code, depois cobranças subsequentes acontecem automaticamente com o mesmo contract_id.

PIX Automático é débito recorrente direto da conta bancária do cliente — taxa ~10× menor que cartão recorrente, sem expiração, sem CVV errado, sem chargeback de cartão.

A Zhex implementa via Asaas como adquirente. O fluxo é simples:

  1. Você define um contract_id (qualquer string que identifique a recorrência no seu lado — ex: o ID da assinatura do cliente no seu DB).
  2. Cria o primeiro PaymentIntent com payment_method_types: ['pix_automatic'] e confirma passando o contract_id.
  3. A Zhex retorna um QR Code — o cliente escaneia UMA vez no app do banco dele e autoriza débito recorrente.
  4. A partir desse momento, cobranças subsequentes são automáticas — você cria novos PaymentIntents com o mesmo contract_id e a Zhex debita sem interação do cliente.

Open Finance? Não.

A Zhex gera o contract_id internamente como referência da autorização. Você não precisa integrar com Open Finance externo nem com Pluggy — o contract_id é uma string sua, qualquer valor que identifique a recorrência no seu lado (ex: sub_user_42_2026 ou assinatura-pro-mensal-cus_xxx).

Quando faz sentido

  • SaaS BR com mensalidade fixa — taxa ~10× menor que cartão, churn involuntário próximo de zero.
  • Academia, escola, clube de assinatura — ticket previsível.
  • B2B com NF-e mensal — sem fricção de cartão corporativo.

Quando NÃO usar: ticket muito variável, público que prioriza programa de pontos do cartão.

1. Primeira cobrança (gera autorização + QR Code)

import { Zhex } from '@zhexio/node';
const zhex = new Zhex(process.env.ZHEX_SECRET_KEY!);

// 1. Cria o intent
const intent = await zhex.paymentIntents.create({
  amount: 9990,                      // R$ 99,90
  currency: 'brl',
  customer: 'cus_…',
  payment_method_types: ['pix_automatic'],
  products: [{ product: 'prod_…', price: 'price_…', quantity: 1 }],
  description: 'Assinatura Pro — mensal',
});

// 2. Confirma com o contract_id
const confirmed = await zhex.paymentIntents.confirm(intent.id, {
  pix_automatic: {
    contract_id: `sub_${customerId}_pro_mensal`,   // referência interna sua
    frequency: 'MONTHLY',
    start_date: '2026-05-01',                       // opcional
    finish_date: '2027-05-01',                      // opcional
  },
});

// 3. Apresenta o QR Code pro cliente escanear
const qr = confirmed.next_action!.pix_display_qr_code!;
console.log(qr.qr_code_data);     // BR Code (string copy-paste)
console.log(qr.qr_code_url);      // PNG base64 ou URL hosted
console.log(qr.expires_at);       // Unix timestamp

A resposta do confirm vem em requires_action com o QR Code:

{
  "id": "pi_…",
  "status": "requires_action",
  "next_action": {
    "type": "pix_display_qr_code",
    "pix_display_qr_code": {
      "qr_code_data": "00020126360014BR.GOV.BCB.PIX0114+5511...",
      "qr_code_url": "data:image/png;base64,iVBORw0KGgo...",
      "expires_at": 1717420800
    }
  }
}

Renderize o QR no front. O cliente escaneia, autoriza no app do banco, e o BACEN confirma. A Zhex envia o webhook payment_intent.succeeded quando rola.

Frequências aceitas

ValorSignificado
MONTHLYMensal
QUARTERLYTrimestral
SEMIANNUALSemestral
YEARLYAnual

Datas opcionais

  • start_date (YYYY-MM-DD) — primeiro débito. Default: hoje.
  • finish_date (YYYY-MM-DD) — fim do contrato. Default: aberto (até cliente cancelar no app do banco).

O que é o contract_id?

É sua referência interna pra essa recorrência. Pode ser:

  • O ID da assinatura no seu DB: sub_42 ou sub_user_uuid
  • Slug semântico: pro-mensal-cus_8a7d2f5e
  • Qualquer string única — a Zhex usa pra correlacionar a autorização com a "linha" de cobrança no seu lado.

Importante: salve esse contract_id no seu DB junto com o customer.id, porque você vai precisar dele pra disparar as cobranças subsequentes.

2. Cobranças seguintes (sem QR Code)

Pra cobrar de novo no próximo ciclo, crie um novo PaymentIntent com o mesmo contract_id. A autorização Open Finance já existe — não tem QR, não tem app do banco, é débito direto:

const next = await zhex.paymentIntents.create({
  amount: 9990,
  currency: 'brl',
  customer: 'cus_…',
  payment_method_types: ['pix_automatic'],
  products: [{ product: 'prod_…', price: 'price_…', quantity: 1 }],
});

await zhex.paymentIntents.confirm(next.id, {
  pix_automatic: {
    contract_id: 'sub_user_42_pro_mensal',  // mesmo da primeira vez
    frequency: 'MONTHLY',
  },
});
// next.status: 'processing' — nada de QR Code, vai direto pra succeeded via webhook

Se você usa CustomerSubscription da Zhex, o cron interno faz isso automaticamente — você só configura o Subscription template no dashboard com método PIX_AUTOMATIC e a Zhex chama confirm no contract_id salvo a cada ciclo.

3. Webhook de confirmação

Configure um webhook Custom Webhook em Dashboard → Integrações → Webhooks. Quando o cliente autorizar (primeira vez) ou o BACEN debitar (cobranças seguintes), chega:

{
  "id": "evt_…",
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_…",
      "amount": 9990,
      "currency": "BRL",
      "status": "approved",
      "paymentMethod": { "type": "PIX_AUTOMATIC" },
      "approvedAt": "2026-05-01T03:00:15Z"
    }
  }
}

Veja Webhooks pra detalhes de assinatura HMAC e idempotência.

Falha de débito

Se o cliente estiver sem saldo, o BACEN retenta automaticamente em até 3 dias úteis. Durante esse período o PI fica processing. Após esgotada a janela, chega payment_intent.payment_failed:

last_payment_error.codeCausaAção típica
insufficient_fundsSem saldo após retry BACENNotificar cliente, oferecer outro método
authorization_revokedCliente revogou no app do bancoPedir nova autorização (novo intent + QR)
authorization_expiredfinish_date foi atingidoRenovar — novo contract_id

Cancelar a recorrência

Tem dois caminhos:

  1. Cliente revoga no app do banco dele (regra BACEN — ele tem esse poder sempre). A próxima cobrança volta com authorization_revoked.
  2. Você cancela do seu lado sem que ele revogue — basta parar de criar novos PaymentIntents. Se está usando CustomerSubscription, chame:
await zhex.customerSubscriptions.cancel('cs_…', {
  cancellation_reason: 'requested_by_customer',
});

Test mode

PIX Automático em test mode usa o MockPaymentAdapter — qualquer contract_id aprova, retorna QR Code fake, e webhook payment_intent.succeeded chega via /v1/test_helpers/payment_intents/:id/succeed.

Próximos passos

Esta página foi útil?

Atualizado em

Nesta página