Skip to main content
Este guia cobre o fluxo completo de pagamento com cartão de crédito na Chargefy: desde a criação da sessão de checkout até o tratamento de respostas, incluindo parcelamento, pré-autorização e boas práticas de segurança.

Visão geral do fluxo

O pagamento com cartão de crédito na Chargefy segue este fluxo:
Cliente preenche dados → Tokenização segura → Confirmação → Aprovado/Recusado
1

Criar sessão de checkout

Seu backend cria uma sessão via API com o produto e valor.
2

Coletar dados do cartão

O frontend coleta os dados do cartão de forma segura (tokenização client-side).
3

Confirmar pagamento

Envie os dados tokenizados junto com o número de parcelas para confirmar o pagamento.
4

Tratar resposta

Verifique se o pagamento foi aprovado, recusado ou se houve erro, e aja de acordo.

Pré-requisitos

npm install @chargefy/sdk
Configure a variável de ambiente:
.env
CHARGEFY_ACCESS_TOKEN=<supabase_jwt>

Passo 1: Criar sessão de checkout

Crie uma sessão de checkout no seu backend informando o produto e opções de pagamento.
import { Chargefy } from '@chargefy/sdk'

const chargefy = new Chargefy({
  accessToken: process.env.CHARGEFY_ACCESS_TOKEN!,
  // server: 'sandbox' // Para ambiente de testes
})

async function criarCheckoutCartao() {
  const checkout = await chargefy.checkouts.create({
    product_price_id: 'price_xxx',
    payment_method: 'credit_card',
    success_url: 'https://meusite.com.br/pagamento/sucesso',
    customer_email: '[email protected]',
  })

  return {
    id: checkout.id,
    client_secret: checkout.client_secret,
    url: checkout.url,
    amount: checkout.amount, // Valor em centavos (ex: 9990 = R$ 99,90)
  }
}
A resposta inclui o client_secret, que sera usado para confirmar o pagamento no frontend.

Passo 2: Coletar dados do cartão com segurança

Os dados do cartão devem ser coletados diretamente no frontend e enviados para confirmacao. A Chargefy realiza a tokenizacao dos dados sensiveis de forma segura.
Conformidade PCI-DSS: Nunca armazene, registre em logs ou trafegue dados completos de cartao pelo seu servidor em texto plano. A tokenizacao da Chargefy garante que os dados sensiveis sao processados em ambiente seguro e certificado PCI-DSS Level 1.
Os campos necessarios para o cartao sao:
CampoTipoDescricaoExemplo
card_numberstringNumero do cartao (sem espacos)"4111111111111111"
holder_namestringNome impresso no cartao"JOAO SILVA"
expiry_monthstringMes de validade (2 digitos)"12"
expiry_yearstringAno de validade (4 digitos)"2028"
cvvstringCodigo de seguranca (3-4 digitos)"123"

Passo 3: Confirmar pagamento com dados do cartao

Com a sessao criada e os dados do cartao coletados, confirme o pagamento:
async function confirmarPagamentoCartao(
  clientSecret: string,
  dadosCartao: {
    card_number: string
    holder_name: string
    expiry_month: string
    expiry_year: string
    cvv: string
  },
  parcelas: number = 1
) {
  const resultado = await chargefy.checkouts.confirm(clientSecret, {
    customer_name: 'Joao Silva',
    customer_email: '[email protected]',
    customer_tax_id: '123.456.789-00',
    payment_method: 'credit_card',
    card: {
      card_number: dadosCartao.card_number,
      holder_name: dadosCartao.holder_name,
      expiry_month: dadosCartao.expiry_month,
      expiry_year: dadosCartao.expiry_year,
      cvv: dadosCartao.cvv,
    },
    installments: parcelas, // 1 a 12
  })

  return resultado
}
Os dados do cartao sao tokenizados automaticamente durante a confirmacao. A Chargefy nunca armazena os dados sensiveis do cartao — apenas o token gerado e utilizado para processar a transacao.

Passo 4: Parcelamento (1-12x)

A Chargefy suporta parcelamento de 1 a 12 vezes no cartao de credito. O comportamento varia conforme a configuracao do produto:
ParcelasTipoDescricao
1A vistaValor integral sem acrescimo
2-6Sem juros (configuravel)Valor dividido igualmente
7-12Com juros (configuravel)Juros repassados ao cliente

Exemplo: Calcular parcelas para exibicao

async function calcularParcelas(checkoutId: string) {
  const checkout = await chargefy.checkouts.get(checkoutId)
  const valorTotal = checkout.amount // em centavos

  const opcoesParcelas = []

  for (let i = 1; i <= 12; i++) {
    const temJuros = i > 6 // Exemplo: juros a partir de 7x
    const taxaJuros = temJuros ? 0.0199 : 0 // 1.99% ao mes
    const valorComJuros = temJuros
      ? valorTotal * Math.pow(1 + taxaJuros, i)
      : valorTotal
    const valorParcela = Math.ceil(valorComJuros / i)

    opcoesParcelas.push({
      parcelas: i,
      valor_parcela: valorParcela, // centavos
      valor_total: temJuros ? Math.ceil(valorComJuros) : valorTotal,
      tem_juros: temJuros,
      label: temJuros
        ? `${i}x de R$ ${(valorParcela / 100).toFixed(2)} (com juros)`
        : `${i}x de R$ ${(valorParcela / 100).toFixed(2)} sem juros`,
    })
  }

  return opcoesParcelas
}

// Exemplo de saida para produto de R$ 199,90:
// [
//   { parcelas: 1, label: "1x de R$ 199,90 sem juros" },
//   { parcelas: 2, label: "2x de R$ 99,95 sem juros" },
//   { parcelas: 3, label: "3x de R$ 66,64 sem juros" },
//   ...
//   { parcelas: 12, label: "12x de R$ 20,23 (com juros)" },
// ]
As regras de parcelamento (quantidade maxima de parcelas, juros por faixa) sao configuradas por produto no dashboard da Chargefy. A API retorna as opcoes disponiveis automaticamente.

Passo 5: Tratar a resposta

Apos confirmar o pagamento, verifique o status da resposta para tomar a acao adequada:
async function processarPagamento(clientSecret: string, dadosCartao: any) {
  try {
    const resultado = await chargefy.checkouts.confirm(clientSecret, {
      customer_name: 'Joao Silva',
      customer_email: '[email protected]',
      customer_tax_id: '123.456.789-00',
      payment_method: 'credit_card',
      card: dadosCartao,
      installments: 1,
    })

    switch (resultado.status) {
      case 'succeeded':
        // Pagamento aprovado imediatamente
        console.log('Pagamento aprovado! Transacao:', resultado.transaction_id)
        // Liberar acesso, enviar email de confirmacao, etc.
        return { sucesso: true, transacao: resultado.transaction_id }

      case 'confirmed':
        // Pagamento em processamento (pre-autorizacao)
        console.log('Pagamento em analise:', resultado.transaction_id)
        return { sucesso: true, pendente: true, transacao: resultado.transaction_id }

      case 'failed':
        // Pagamento recusado pela operadora
        console.log('Pagamento recusado:', resultado.error?.message)
        return {
          sucesso: false,
          erro: resultado.error?.message || 'Pagamento recusado pela operadora',
          codigo: resultado.error?.code,
        }

      default:
        return { sucesso: false, erro: 'Status desconhecido' }
    }
  } catch (error: any) {
    // Erro de comunicacao ou validacao
    console.error('Erro ao processar pagamento:', error.message)
    return { sucesso: false, erro: 'Erro interno ao processar pagamento' }
  }
}

Codigos de recusa comuns

CodigoDescricaoAcao recomendada
insufficient_fundsSaldo/limite insuficienteSolicitar outro cartao ou valor menor
card_declinedCartao recusado pela operadoraVerificar dados ou usar outro cartao
expired_cardCartao vencidoSolicitar cartao valido
invalid_cvvCVV incorretoSolicitar novo CVV
processing_errorErro no processamentoTentar novamente em alguns segundos
fraud_suspectedSuspeita de fraudeContatar operadora do cartao

Pre-autorizacao e captura

Para cenarios onde voce precisa reservar o valor no cartao e capturar depois (ex: marketplaces, entregas, reservas), use o fluxo de pre-autorizacao:

1. Criar checkout com pre-autorizacao

const checkout = await chargefy.checkouts.create({
  product_price_id: 'price_xxx',
  payment_method: 'credit_card',
  capture: false, // Pre-autorizacao: reserva sem capturar
  success_url: 'https://meusite.com.br/reserva/confirmada',
})

2. Confirmar pagamento (reserva o valor)

const resultado = await chargefy.checkouts.confirm(clientSecret, {
  customer_name: 'Joao Silva',
  customer_email: '[email protected]',
  customer_tax_id: '123.456.789-00',
  payment_method: 'credit_card',
  card: {
    card_number: '4111111111111111',
    holder_name: 'JOAO SILVA',
    expiry_month: '12',
    expiry_year: '2028',
    cvv: '123',
  },
  installments: 1,
})

// Status sera 'confirmed' (valor reservado, aguardando captura)
console.log('Valor reservado:', resultado.status)
console.log('Transaction ID:', resultado.transaction_id)

3. Capturar o valor (quando pronto para cobrar)

// Captura total
const captura = await chargefy.transactions.capture(resultado.transaction_id)
console.log('Valor capturado:', captura.status) // 'succeeded'

// Captura parcial (ex: valor menor que o reservado)
const capturaParcial = await chargefy.transactions.capture(
  resultado.transaction_id,
  {
    amount: 5000, // Capturar apenas R$ 50,00 de R$ 99,90 reservados
  }
)

4. Cancelar pre-autorizacao (liberar o valor)

// Se nao quiser mais cobrar, libere o valor reservado
const cancelamento = await chargefy.transactions.void(resultado.transaction_id)
console.log('Pre-autorizacao cancelada:', cancelamento.status)
A pre-autorizacao expira em 7 dias. Se nao for capturada ou cancelada nesse prazo, o valor e liberado automaticamente para o titular do cartao.

Componente React para formulario de cartao

Exemplo de componente React para coletar dados do cartao com validacao:
components/CardForm.tsx
import { useState, FormEvent } from 'react'

interface CardFormData {
  card_number: string
  holder_name: string
  expiry_month: string
  expiry_year: string
  cvv: string
  installments: number
}

interface CardFormProps {
  amount: number // valor em centavos
  maxInstallments?: number
  onSubmit: (data: CardFormData) => Promise<void>
}

export function CardForm({ amount, maxInstallments = 12, onSubmit }: CardFormProps) {
  const [loading, setLoading] = useState(false)
  const [erro, setErro] = useState<string | null>(null)
  const [form, setForm] = useState<CardFormData>({
    card_number: '',
    holder_name: '',
    expiry_month: '',
    expiry_year: '',
    cvv: '',
    installments: 1,
  })

  function formatarNumeroCartao(valor: string): string {
    const numeros = valor.replace(/\D/g, '').slice(0, 16)
    return numeros.replace(/(\d{4})(?=\d)/g, '$1 ')
  }

  function detectarBandeira(numero: string): string {
    const num = numero.replace(/\D/g, '')
    if (/^4/.test(num)) return 'Visa'
    if (/^5[1-5]/.test(num)) return 'Mastercard'
    if (/^3[47]/.test(num)) return 'Amex'
    if (/^(636368|438935|504175|451416|509|606282|301)/.test(num)) return 'Elo'
    return ''
  }

  function gerarOpcoesParcelas() {
    const opcoes = []
    for (let i = 1; i <= maxInstallments; i++) {
      const temJuros = i > 6
      const taxa = temJuros ? 0.0199 : 0
      const total = temJuros ? amount * Math.pow(1 + taxa, i) : amount
      const parcela = Math.ceil(total / i)

      opcoes.push({
        valor: i,
        label: temJuros
          ? `${i}x de R$ ${(parcela / 100).toFixed(2)} (com juros)`
          : `${i}x de R$ ${(parcela / 100).toFixed(2)} sem juros`,
      })
    }
    return opcoes
  }

  async function handleSubmit(e: FormEvent) {
    e.preventDefault()
    setErro(null)

    // Validacoes basicas
    const numero = form.card_number.replace(/\D/g, '')
    if (numero.length < 13 || numero.length > 19) {
      setErro('Numero do cartao invalido')
      return
    }
    if (!form.holder_name.trim()) {
      setErro('Nome do titular obrigatorio')
      return
    }
    if (!form.expiry_month || !form.expiry_year) {
      setErro('Data de validade obrigatoria')
      return
    }
    if (form.cvv.length < 3) {
      setErro('CVV invalido')
      return
    }

    setLoading(true)
    try {
      await onSubmit({
        ...form,
        card_number: numero, // Enviar sem espacos
      })
    } catch (err: any) {
      setErro(err.message || 'Erro ao processar pagamento')
    } finally {
      setLoading(false)
    }
  }

  const bandeira = detectarBandeira(form.card_number)

  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-md">
      {/* Numero do cartao */}
      <div>
        <label className="block text-sm font-medium mb-1">
          Numero do cartao {bandeira && <span className="text-gray-500">({bandeira})</span>}
        </label>
        <input
          type="text"
          inputMode="numeric"
          placeholder="0000 0000 0000 0000"
          value={formatarNumeroCartao(form.card_number)}
          onChange={(e) =>
            setForm({ ...form, card_number: e.target.value })
          }
          className="w-full border rounded-lg px-3 py-2"
          autoComplete="cc-number"
          required
        />
      </div>

      {/* Nome do titular */}
      <div>
        <label className="block text-sm font-medium mb-1">
          Nome impresso no cartao
        </label>
        <input
          type="text"
          placeholder="JOAO SILVA"
          value={form.holder_name}
          onChange={(e) =>
            setForm({ ...form, holder_name: e.target.value.toUpperCase() })
          }
          className="w-full border rounded-lg px-3 py-2"
          autoComplete="cc-name"
          required
        />
      </div>

      {/* Validade e CVV */}
      <div className="flex gap-4">
        <div className="flex-1">
          <label className="block text-sm font-medium mb-1">Validade</label>
          <div className="flex gap-2">
            <select
              value={form.expiry_month}
              onChange={(e) =>
                setForm({ ...form, expiry_month: e.target.value })
              }
              className="flex-1 border rounded-lg px-3 py-2"
              autoComplete="cc-exp-month"
              required
            >
              <option value="">Mes</option>
              {Array.from({ length: 12 }, (_, i) => (
                <option key={i + 1} value={String(i + 1).padStart(2, '0')}>
                  {String(i + 1).padStart(2, '0')}
                </option>
              ))}
            </select>
            <select
              value={form.expiry_year}
              onChange={(e) =>
                setForm({ ...form, expiry_year: e.target.value })
              }
              className="flex-1 border rounded-lg px-3 py-2"
              autoComplete="cc-exp-year"
              required
            >
              <option value="">Ano</option>
              {Array.from({ length: 10 }, (_, i) => {
                const ano = new Date().getFullYear() + i
                return (
                  <option key={ano} value={String(ano)}>
                    {ano}
                  </option>
                )
              })}
            </select>
          </div>
        </div>

        <div className="w-24">
          <label className="block text-sm font-medium mb-1">CVV</label>
          <input
            type="text"
            inputMode="numeric"
            placeholder="123"
            maxLength={4}
            value={form.cvv}
            onChange={(e) =>
              setForm({ ...form, cvv: e.target.value.replace(/\D/g, '') })
            }
            className="w-full border rounded-lg px-3 py-2"
            autoComplete="cc-csc"
            required
          />
        </div>
      </div>

      {/* Parcelas */}
      <div>
        <label className="block text-sm font-medium mb-1">Parcelas</label>
        <select
          value={form.installments}
          onChange={(e) =>
            setForm({ ...form, installments: Number(e.target.value) })
          }
          className="w-full border rounded-lg px-3 py-2"
        >
          {gerarOpcoesParcelas().map((opcao) => (
            <option key={opcao.valor} value={opcao.valor}>
              {opcao.label}
            </option>
          ))}
        </select>
      </div>

      {/* Erro */}
      {erro && (
        <div className="bg-red-50 text-red-700 px-4 py-2 rounded-lg text-sm">
          {erro}
        </div>
      )}

      {/* Botao */}
      <button
        type="submit"
        disabled={loading}
        className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium
                   hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
      >
        {loading
          ? 'Processando...'
          : `Pagar R$ ${(amount / 100).toFixed(2)}`}
      </button>

      <p className="text-xs text-gray-500 text-center">
        Pagamento processado com seguranca pela Chargefy. Seus dados estao protegidos.
      </p>
    </form>
  )
}

Exemplo completo: Express + React

Backend (Express)

src/routes/card-checkout.ts
import { Router } from 'express'
import { Chargefy } from '@chargefy/sdk'

const chargefy = new Chargefy({
  accessToken: process.env.CHARGEFY_ACCESS_TOKEN!,
})

export const cardCheckoutRouter = Router()

// 1. Criar sessao de checkout
cardCheckoutRouter.post('/create', async (req, res) => {
  try {
    const { productPriceId, customerEmail } = req.body

    const checkout = await chargefy.checkouts.create({
      product_price_id: productPriceId,
      payment_method: 'credit_card',
      success_url: `${process.env.FRONTEND_URL}/pagamento/sucesso`,
    })

    res.json({
      checkoutId: checkout.id,
      clientSecret: checkout.client_secret,
      amount: checkout.amount,
      currency: checkout.currency,
    })
  } catch (error: any) {
    console.error('Erro ao criar checkout:', error.message)
    res.status(400).json({ error: 'Falha ao criar sessao de checkout' })
  }
})

// 2. Confirmar pagamento com cartao
cardCheckoutRouter.post('/confirm', async (req, res) => {
  try {
    const {
      clientSecret,
      customerName,
      customerEmail,
      customerTaxId,
      card,
      installments,
    } = req.body

    const resultado = await chargefy.checkouts.confirm(clientSecret, {
      customer_name: customerName,
      customer_email: customerEmail,
      customer_tax_id: customerTaxId,
      payment_method: 'credit_card',
      card: {
        card_number: card.card_number,
        holder_name: card.holder_name,
        expiry_month: card.expiry_month,
        expiry_year: card.expiry_year,
        cvv: card.cvv,
      },
      installments: installments || 1,
    })

    if (resultado.status === 'succeeded') {
      res.json({
        success: true,
        transactionId: resultado.transaction_id,
        message: 'Pagamento aprovado com sucesso!',
      })
    } else if (resultado.status === 'failed') {
      res.status(402).json({
        success: false,
        error: resultado.error?.message || 'Pagamento recusado',
        code: resultado.error?.code,
      })
    } else {
      res.json({
        success: true,
        pending: true,
        transactionId: resultado.transaction_id,
        message: 'Pagamento em processamento',
      })
    }
  } catch (error: any) {
    console.error('Erro ao confirmar pagamento:', error.message)
    res.status(500).json({
      success: false,
      error: 'Erro interno ao processar pagamento',
    })
  }
})

Frontend (React)

pages/Checkout.tsx
import { useState, useEffect } from 'react'
import { CardForm } from '../components/CardForm'

export function CheckoutPage() {
  const [checkout, setCheckout] = useState<{
    clientSecret: string
    amount: number
  } | null>(null)
  const [resultado, setResultado] = useState<{
    success: boolean
    message: string
  } | null>(null)

  useEffect(() => {
    // Criar sessao de checkout ao montar a pagina
    fetch('/api/card-checkout/create', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        productPriceId: 'price_xxx',
        customerEmail: '[email protected]',
      }),
    })
      .then((res) => res.json())
      .then(setCheckout)
  }, [])

  async function handlePayment(cardData: any) {
    const response = await fetch('/api/card-checkout/confirm', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        clientSecret: checkout!.clientSecret,
        customerName: cardData.holder_name,
        customerEmail: '[email protected]',
        customerTaxId: '123.456.789-00',
        card: cardData,
        installments: cardData.installments,
      }),
    })

    const data = await response.json()

    if (data.success) {
      setResultado({
        success: true,
        message: data.message,
      })
    } else {
      throw new Error(data.error || 'Pagamento recusado')
    }
  }

  if (resultado?.success) {
    return (
      <div className="text-center py-12">
        <h2 className="text-2xl font-bold text-green-600">
          Pagamento aprovado!
        </h2>
        <p className="mt-2 text-gray-600">{resultado.message}</p>
      </div>
    )
  }

  if (!checkout) {
    return <div className="text-center py-12">Carregando checkout...</div>
  }

  return (
    <div className="max-w-lg mx-auto py-12 px-4">
      <h1 className="text-2xl font-bold mb-6">Finalizar pagamento</h1>
      <CardForm
        amount={checkout.amount}
        maxInstallments={12}
        onSubmit={handlePayment}
      />
    </div>
  )
}

Testes no sandbox

Use o ambiente sandbox para testar pagamentos sem cobrar de verdade. Utilize os cartoes de teste abaixo:

Cartoes de teste

NumeroResultadoUso
4111 1111 1111 1111AprovadoSimular pagamento bem-sucedido
4000 0000 0000 0002RecusadoSimular cartao recusado
Para cartoes de teste, use qualquer data de validade futura (ex: 12/2028) e qualquer CVV de 3 digitos (ex: 123). O nome do titular pode ser qualquer valor.

Testar via cURL

# Teste com cartao aprovado
curl -X POST https://api.chargefy.io/api/v1/checkouts/cs_sandbox_xxx/confirm \
  -H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Teste Aprovado",
    "customer_email": "[email protected]",
    "customer_tax_id": "000.000.000-00",
    "payment_method": "credit_card",
    "card": {
      "card_number": "4111111111111111",
      "holder_name": "TESTE APROVADO",
      "expiry_month": "12",
      "expiry_year": "2028",
      "cvv": "123"
    },
    "installments": 1
  }'

# Teste com cartao recusado
curl -X POST https://api.chargefy.io/api/v1/checkouts/cs_sandbox_xxx/confirm \
  -H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Teste Recusado",
    "customer_email": "[email protected]",
    "customer_tax_id": "000.000.000-00",
    "payment_method": "credit_card",
    "card": {
      "card_number": "4000000000000002",
      "holder_name": "TESTE RECUSADO",
      "expiry_month": "12",
      "expiry_year": "2028",
      "cvv": "123"
    },
    "installments": 1
  }'

Boas praticas de seguranca

1

Nunca registre dados de cartao em logs

Nunca use console.log(), logger.info() ou qualquer mecanismo de log para registrar numero do cartao, CVV ou data de validade. Registre apenas os 4 ultimos digitos para referencia.
2

Use HTTPS em producao

Todo trafego contendo dados de pagamento deve ocorrer exclusivamente sobre HTTPS/TLS. Nunca transmita dados de cartao em conexoes nao criptografadas.
3

Prefira tokenizacao client-side

Sempre que possivel, use o Checkout Embed ou a tokenizacao Chargefy.js no frontend para que os dados do cartao nunca passem pelo seu servidor.
4

Valide no servidor

Mesmo com validacoes no frontend, sempre valide os dados novamente no backend antes de enviar para a API da Chargefy.
5

Implemente idempotencia

Use chaves de idempotencia para evitar cobranças duplicadas em caso de retry ou timeout de rede.
6

Monitore transacoes

Configure webhooks para receber notificacoes em tempo real sobre aprovacoes, recusas e estornos.
PCI-DSS: Se voce processa, armazena ou transmite dados de cartao pelo seu servidor, sua aplicacao precisa estar em conformidade com o padrao PCI-DSS. A maneira mais simples de evitar esse requisito e usar o Checkout Embed da Chargefy ou a tokenizacao client-side via Chargefy.js, onde os dados sensiveis nunca passam pela sua infraestrutura.

Proximos passos

Checkout PIX

Integre pagamentos via PIX com QR Code.

Webhooks

Receba notificacoes de pagamento em tempo real.

Checkout Embed

Incorpore o checkout diretamente no seu site.

Reembolsos

Processe estornos totais e parciais.