docs

Cartão

Cartão com Zhex Elements (hosted fields), 3DS automático e BIN routing inteligente.

A integração de cartão sempre passa por zhex.js (hosted fields). Seu servidor recebe tok_*, nunca PAN/CVV — escopo PCI-DSS SAQ-A.

1. Renderizar Elements

Instale o pacote no front e monte o iframe — não há bundle global via <script> hoje (a versão CDN entra numa minor próxima):

npm install @zhexio/zhex-js
import { Zhex } from '@zhexio/zhex-js';

const zhex = Zhex('zk_live_...');
const elements = zhex.elements();
const card = elements.create('card');
card.mount('#card-element');

document
  .getElementById('payment-form')!
  .addEventListener('submit', async (e) => {
    e.preventDefault();

    const result = await zhex.createToken(card);
    if ('error' in result) {
      alert(result.error.message);
      return;
    }

    // tok_* vai pro seu servidor por fetch normal
    const res = await fetch('/api/charge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token: result.token.id }),
    });
    const intent = await res.json();
    // intent.status: 'succeeded' | 'requires_action' | 'processing' | 'canceled'
  });

A zk_live_* (publishable) pode aparecer no front — só consegue criar Token. Não roda charge, não vê dados sensíveis, não lista clientes.

2. Confirmar no servidor

A confirmação na API V1 é dois passos: cria o intent, depois confirma com o token.

import { zhex } from '@/lib/zhex';

const intent = await zhex.paymentIntents.create({
  amount: 4990,
  currency: 'brl',
  customer: 'cus_…',                    // crie um Customer antes
  payment_method_types: ['card'],
  products: [{ product: 'prod_…', price: 'price_…', quantity: 1 }],
  description: 'Curso Node Avançado',
});

const confirmed = await zhex.paymentIntents.confirm(intent.id, {
  payment_method: tokenId,              // tok_* recebido do front
});
// confirmed.status: 'succeeded' | 'requires_action' | 'processing' | 'canceled'

3DS (3D Secure)

3DS reduz chargeback transferindo a responsabilidade para o emissor (liability shift). A Zhex aplica automaticamente quando o adquirente sinaliza risco ou o emissor exige.

Quando o cliente precisa autenticar, o PaymentIntent volta em requires_action com next_action:

{
  "id": "pi_…",
  "status": "requires_action",
  "next_action": {
    "type": "redirect_to_url",
    "redirect_to_url": {
      "url": "https://3ds.acquirer.com/challenge/...",
      "return_url": "https://meusite.com/checkout/success"
    }
  },
  "last_payment_error": null
}

No front, redirecione o cliente para next_action.redirect_to_url.url. Após o challenge, o cliente volta ao seu return_url (configurado pelo adquirente) — escute o webhook payment_intent.succeeded ou payment_intent.payment_failed como fonte da verdade do desfecho.

const confirmed = await zhex.paymentIntents.confirm(intent.id, {
  payment_method: tokenId,
});

if (confirmed.status === 'requires_action' && confirmed.next_action?.type === 'redirect_to_url') {
  // Cliente vai pro app do banco / página 3DS
  return res.redirect(confirmed.next_action.redirect_to_url.url);
}

BIN routing

A Zhex usa smart routing baseado no BIN do cartão (primeiros 6 dígitos):

  • Cartão internacional → adquirente cross-border (cost-optimized)
  • Cartão BR premium → adquirente com maior approval para esse BIN
  • Cartão BR pré-pago → roteamento que aceita pré-pago (alguns adquirentes recusam)

Você não configura nada — é automático. O detalhe de qual adquirente atendeu cada transação fica visível no dashboard (Transações → detalhe → Roteamento).

Cartões de teste

Em test mode (zsk_test_*):

PANComportamento
4929 9999 9999 9991Sucesso
4929 0000 0000 0000card_declined
4929 0000 0000 0091insufficient_funds
4929 0000 0000 0067expired_card
4929 0000 0000 0059incorrect_cvc
4929 0000 0000 0042processing_error (retryable)
4929 0000 0000 3004Força requires_action (3DS)
4929 0000 0000 0083lost_card decline
4929 0000 0000 0075stolen_card decline
4929 0000 0000 2501Sucesso + simula charge.dispute.created
4929 0000 0000 1909Sucesso + simula chargeback product_not_received

CVV qualquer (3 ou 4 dígitos), validade futura. Mais cenários em Test mode.

Boas práticas

  • Sempre 3DS em ticket alto (> R$ 200 BR ou > $ 50 internacional) — chargeback ratio cai de 0,3% para < 0,05%.
  • Não armazene tok_* — single-use, 15min TTL. Para reutilizar, suba para PaymentMethod (pm_*) via tokenização.
  • statement_descriptor alinhado com a descrição do produto — cliente reconhece a cobrança 30 dias depois e não abre chargeback por desconhecimento.
Esta página foi útil?

Atualizado em

Nesta página