Pular para o conteúdo

Começar

Sua primeira venda

Do zero à fatura paga: qual caminho escolher, o que guardar, e o que fazer no clique duplo.

Existem três formas de cobrar por um produto, e elas resolvem o mesmo problema com trabalhos muito diferentes do seu lado. Escolha primeiro, implemente depois.

Qual caminho é o seu

O que você constróiQuando usar
Link de pagamentonadaVenda pontual, cobrança por WhatsApp, primeira venda antes de existir app
checkout()sua página de "obrigado"Você tem app e quer o comprador dentro dele, mas não quer montar tela de pagamento
invoices.createForProduct()a tela de pagamento inteiraVocê quer controle total do visual e do fluxo

Os três terminam na mesma fatura e no mesmo webhook. A diferença é quanto da experiência é sua.

Na dúvida, comece pelo link

É o único que não exige nada do seu app. Você troca depois — o produto e o catálogo são os mesmos.

O caminho do meio, ponta a ponta

Assumindo que você já tem um produto publicado (veja catálogo) e uma chave:

// 1. cria cliente + fatura, e devolve a URL hospedada
const { invoice, url } = await infi.checkout({
  slug: "seu-tenant",
  productId,
  customer: {
    externalId: seuUserId,          // o id do SEU usuário
    email: "[email protected]",
    taxId: "52998224725",           // Pix e boleto exigem CPF/CNPJ
  },
  successUrl: "https://seu-app.com/obrigado",
});
 
// 2. GUARDE ISSO. É o passo que ninguém documenta e todo mundo esquece.
await db.pedidos.insert({ userId: seuUserId, invoiceId: invoice.id, status: "aberta" });

Você precisa mapear `invoiceId` → seu usuário

Nós não sabemos quem é o seu usuário — você passa um externalId e nós devolvemos um invoiceId. Quando o pagamento confirmar, o webhook traz o invoiceId, não o seu usuário. Se você não guardou o par, recebeu dinheiro e não sabe de quem.

É a decisão de arquitetura mais importante desta página e ela cabe em uma linha de tabela no seu banco.

Cobrar, e mostrar o Pix na sua tela

const pay = await infi.pay.charge({ slug, invoiceId: invoice.id, method: "pix" });
 
pay.pixPayload;   // copia-e-cola EMV — renderize como QR
pay.pixQrImage;   // PNG em base64, já pronto: use se vier
pay.pixExpiresAt; // quando o QR morre

Hoje só Pix tem artefato pra sua tela

boleto e card retornam apenas invoiceUrl — a página hospedada do provedor. Não existe campo de linha digitável nem de código de barras na resposta, e clientSecret/publishableKey (cartão confirmado no navegador) só aparecem onde o cartão está habilitado no tenant.

Como o pagador não deve ir pro site do provedor, o caminho hoje para boleto e cartão é o link de pagamento ou o url que o checkout() devolve — os dois são checkout nosso, com a sua marca de merchant. Verifique cardEnabled na leitura pública do link antes de oferecer cartão.

Saber que pagou

Não confie no retorno do charge: ele volta pending. Pagamento é assíncrono.

// produção: webhook assinado
// sandbox: polling, porque registrar webhook responde 503
const pago = await infi.pay.waitForPaid({ slug, invoiceId: invoice.id, timeoutMs: 15000 });

Quando confirmar, use o invoiceId do evento pra achar o pedido que você guardou no passo 2. Detalhes em webhooks.

O comprador clicou duas vezes em "Comprar"

Todo método que não é GET na API autenticada exige Idempotency-Key (as rotas públicas de /pay/*, que o navegador do comprador chama, não exigem). Isso não é burocracia: é o que impede que dois cliques virem duas faturas.

// a MESMA chave para a MESMA intenção de compra
const chave = `pedido-${seuUserId}-${productId}-${new Date().toISOString().slice(0,10)}`;
 
const { invoiceId } = await infi.checkout({ slug, productId, customer, idempotencyKey: chave });
await infi.pay.charge({ slug, invoiceId, method: "pix", idempotencyKey: `${chave}-pix` });

A partir de @beinfi/[email protected] os dois aceitam idempotencyKey (e desde a 0.10.4 o checkout() devolve invoiceId já tipado como string, sem precisar de !). Os métodos de recurso (products.create, invoices.create, coupons.create, …) já recebiam a chave como último argumento.

  • Mesma chave, mesmo corpo → você recebe a resposta original de volta. Uma fatura só.
  • Mesma chave, corpo diferente → 409 idempotency_key_reused. É proteção: quer dizer que você reusou a chave pra outra coisa.
  • Sem chave → 400 idempotency_key_required.

O SDK gera uma automaticamente quando você não passa — o que protege contra retry de rede, não contra clique duplo, porque cada chamada nova ganha chave nova. Para o clique duplo, a chave tem que vir de algo estável na sua intenção, como no exemplo acima.

O mais simples é não deixar clicar duas vezes

Desabilite o botão no primeiro clique e trate a Idempotency-Key como a rede de segurança, não como a primeira linha de defesa.

Antes de vender: o nome que o comprador vê

Seu tenant nasce com um nome de placeholder. Se você não trocar, o checkout e o link de pagamento dizem literalmente "New app" — e ninguém compra de uma loja chamada New app.

await infi.account.update({ name: "Cafeteria Orvalho" });
// opcional, citado no mandato de pagamento:
await infi.account.update({ termsUrl: "https://cafeteriaorvalho.com/termos" });

Vale na hora, sem republicar produto e sem gerar link novo — o mesmo link passa a mostrar o nome novo. infi.account.get() lê de volta. (A partir do @beinfi/[email protected]; antes disso é PATCH /account/tenant.)

Vendeu. Agora entrega

Se o que você vende é um arquivo ou um acesso, não monte isso à mão: anexe o entregável ao produto e a Infi manda o link pessoal pro comprador quando o pagamento confirma — e te devolve o mesmo link pra você mostrar na sua página de obrigado. Está em entregar o produto.

O lado do comprador — descobrir que pagou, entregar, e os dois polling que ninguém adivinha — está em a página de obrigado.