Cartão
Cartão com Zhex Elements (hosted fields), 3DS automático, parcelamento BR 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-jsimport { 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'],
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);
}Parcelamento (installments)
Hoje o parcelamento é configurado a nível de produto no dashboard (Produtos → Pagamento → Parcelas e juros). A API V1 não aceita ainda payment_method_options.card.installments no body do PaymentIntent — o intent herda a configuração do produto associado.
Modelos disponíveis no dashboard:
- Sem juros (merchant) — você assume o juros. A taxa de antecipação é descontada no payout.
- Com juros (consumer) — emissor cobra juros do cliente. Você recebe à vista.
Em parcelamento ≥ 4x, a maioria dos emissores força challenge 3DS independente da configuração — regra anti-fraude da bandeira.
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_*):
| PAN | Comportamento |
|---|---|
4242 4242 4242 4242 | Sucesso |
4000 0000 0000 0002 | card_declined |
4000 0000 0000 9995 | insufficient_funds |
4000 0000 0000 0069 | expired_card |
4000 0000 0000 0127 | incorrect_cvc |
4000 0000 0000 0119 | processing_error (retryable) |
4000 0000 0000 3220 | Força requires_action (3DS) |
4000 0000 0000 9987 | lost_card decline |
4000 0000 0000 9979 | stolen_card decline |
4000 0000 0000 0259 | Sucesso + simula charge.dispute.created |
4000 0000 0000 1976 | Sucesso + 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 paraPaymentMethod(pm_*) via tokenização. statement_descriptoralinhado com a descrição do produto — cliente reconhece a cobrança 30 dias depois e não abre chargeback por desconhecimento.
Atualizado em