docs

One-click PIX

Cliente já comprou uma vez — próxima cobrança rápida com PaymentMethod salvo (cartão). PIX Automático em roadmap.

Cenário: cliente já comprou uma vez. Próxima compra você quer cobrar com fricção mínima.

A versão definitiva desse fluxo (débito direto via PIX Automático sem QR) está em desenvolvimento — veja PIX Automático. Hoje, o caminho mais próximo de "one-click" é cartão salvo (PaymentMethod reutilizável).

PIX Automático — em desenvolvimento

Débito recorrente direto via PIX Auto (BACEN) ainda não está exposto na API V1. Este recipe cobre o que funciona hoje: cartão salvo via tokenização. Quando PIX Automático entrar, expandimos esta página com o fluxo equivalente sem QR.

Fluxo: cartão salvo (1-clique)

1. Primeira compra — tokenizar e salvar

Cliente preenche o cartão via @zhexio/zhex-js. Você cria o PaymentMethod (pm_*) atrelado ao customer:

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

app.post('/api/checkout/first', async (req, res) => {
  const { email, name, token, amount } = req.body;

  // 1) Customer (idempotente por email)
  const customer = await zhex.customers.create(
    { email, name },
    { idempotencyKey: `customer:${email}` },
  );

  // 2) Salvar cartão como PaymentMethod (consome o token)
  const pm = await zhex.paymentMethods.create({
    type: 'card',
    token,
    customer: customer.id,
  });
  await db.users.update({
    where: { email },
    data: {
      zhexCustomerId: customer.id,
      defaultZhexPaymentMethod: pm.id,
    },
  });

  // 3) Cobrar a primeira compra com tokenização nova
  // (token foi consumido pelo paymentMethods.create acima — para cobrar nesta
  // mesma sessão, gere novo token via zhex.js antes deste passo, ou faça a
  // cobrança direta SEM salvar o cartão e salve numa próxima compra)
  res.json({ ok: true, customerId: customer.id, paymentMethodId: pm.id });
});

2. Próxima compra — recobrar via cartão salvo

Cobrança contra um pm_* salvo passa direto, sem nova tokenização — basta enviar o pm_* no confirm com off_session: true:

app.post('/api/checkout/repeat', async (req, res) => {
  const { userId, amount, orderId } = req.body;

  const user = await db.users.findUnique({ where: { id: userId } });
  if (!user?.zhexCustomerId || !user?.defaultZhexPaymentMethod) {
    return res.status(400).json({ error: 'no_saved_card' });
  }

  const intent = await zhex.paymentIntents.create(
    {
      amount,
      currency: 'brl',
      customer: user.zhexCustomerId,
      payment_method_types: ['card'],
      description: `Pedido ${orderId}`,
    },
    { idempotencyKey: `order:${orderId}` },
  );

  const confirmed = await zhex.paymentIntents.confirm(intent.id, {
    payment_method: user.defaultZhexPaymentMethod,  // pm_*
    off_session: true,                               // cliente não está presente
  });

  // confirmed.status:
  //   'succeeded'         — cobrança ok, libera produto
  //   'requires_action'   — issuer pediu 3DS; envie email de autorização
  //   'canceled'          — cartão recusado, peça outro
  res.json({ status: confirmed.status, paymentIntentId: confirmed.id });
});

A Zhex valida que o pm_* pertence ao mesmo customer do intent e à mesma partição (livemode); cross-customer ou cross-mode retornam payment_method_not_found (mesma resposta que id inexistente — sem leak de quais cartões existem).

3. Listar cartões salvos do customer

Para a tela "Seus cartões salvos":

const cards = await zhex.paymentMethods.list({
  customer: user.zhexCustomerId,
  limit: 10,
});

// cards.data: [{ id, card: { brand, last4, exp_month, exp_year } }, ...]

4. Detach (remover cartão)

await zhex.paymentMethods.detach('pm_…');

Marca o pm_* como inativo. Histórico de transações que o usaram é preservado.

Quando o cartão expira

Quando uma cobrança recorrente falha porque o cartão expirou, você recebe payment_intent.payment_failed. Trate com email automático "atualize seu cartão" → linka para uma página com @zhexio/zhex-js mountando novo Card Element → tokeniza → cria novo pm_* → marca como default no seu BD.

if (event.type === 'payment_intent.payment_failed') {
  const intent = event.data.object;
  if (intent.customer) {
    await sendUpdateCardEmail(intent.customer, {
      reason: intent.last_payment_error?.code ?? 'unknown',
      retryUrl: `https://meusite.com/billing/update?intent=${intent.id}`,
    });
  }
}

Quando NÃO usar cartão salvo

  • Cliente nunca comprou. Não há pm_* para reutilizar — use o fluxo padrão de tokenização.
  • Cliente pediu "comprar como convidado". Não persista o cartão; cobre via tok_* direto e descarte.
Esta página foi útil?

Atualizado em

Nesta página