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
Criar sessão de checkout
Seu backend cria uma sessão via API com o produto e valor.
Coletar dados do cartão
O frontend coleta os dados do cartão de forma segura (tokenização client-side).
Confirmar pagamento
Envie os dados tokenizados junto com o número de parcelas para confirmar o pagamento.
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:
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.
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:
Campo Tipo Descricao Exemplo 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"
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:
Parcelas Tipo Descricao 1A vista Valor 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
Codigo Descricao Acao recomendada insufficient_fundsSaldo/limite insuficiente Solicitar outro cartao ou valor menor card_declinedCartao recusado pela operadora Verificar dados ou usar outro cartao expired_cardCartao vencido Solicitar cartao valido invalid_cvvCVV incorreto Solicitar novo CVV processing_errorErro no processamento Tentar novamente em alguns segundos fraud_suspectedSuspeita de fraude Contatar 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:
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.
Exemplo de componente React para coletar dados do cartao com validacao:
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)
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
Numero Resultado Uso 4111 1111 1111 1111Aprovado Simular pagamento bem-sucedido 4000 0000 0000 0002Recusado Simular 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
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.
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.
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.
Valide no servidor
Mesmo com validacoes no frontend, sempre valide os dados novamente no backend antes de enviar para a API da Chargefy.
Implemente idempotencia
Use chaves de idempotencia para evitar cobranças duplicadas em caso de retry ou timeout de rede.
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.