Skip to main content
Este guia mostra como integrar a Chargefy em um backend Express/Node.js, incluindo criação de checkouts, processamento de pagamentos e recebimento de webhooks.

Pré-requisitos

Setup do projeto

1. Criar projeto e instalar dependências

mkdir meu-backend && cd meu-backend
npm init -y
npm install express @chargefy/sdk dotenv
npm install -D typescript @types/express @types/node tsx

2. Configurar TypeScript

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

3. Configurar variáveis de ambiente

.env
CHARGEFY_ACCESS_TOKEN=<supabase_jwt>
CHARGEFY_WEBHOOK_SECRET=whsec_seu_secret_aqui
PORT=3001
Nunca commite o arquivo .env no repositório. Adicione-o ao .gitignore.

4. Criar cliente Chargefy

src/lib/chargefy.ts
import { Chargefy } from '@chargefy/sdk'

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

Estrutura do projeto

src/
├── lib/
│   └── chargefy.ts       # Cliente SDK
├── routes/
│   ├── checkout.ts        # Rotas de checkout
│   ├── products.ts        # Rotas de produtos
│   ├── subscriptions.ts   # Rotas de assinaturas
│   └── webhooks.ts        # Receptor de webhooks
└── index.ts               # Entry point

Criar o servidor Express

src/index.ts
import 'dotenv/config'
import express from 'express'
import { checkoutRouter } from './routes/checkout.js'
import { productsRouter } from './routes/products.js'
import { subscriptionsRouter } from './routes/subscriptions.js'
import { webhooksRouter } from './routes/webhooks.js'

const app = express()
const PORT = process.env.PORT || 3001

// Webhooks precisam do body raw para verificação HMAC
// Registrar ANTES do express.json()
app.use('/webhooks', webhooksRouter)

// JSON parser para demais rotas
app.use(express.json())

app.use('/api/products', productsRouter)
app.use('/api/checkout', checkoutRouter)
app.use('/api/subscriptions', subscriptionsRouter)

app.listen(PORT, () => {
  console.log(`Servidor rodando em http://localhost:${PORT}`)
})
A rota de webhooks é registrada antes do express.json() porque a verificação HMAC requer o body raw (não parseado).

Listar produtos

src/routes/products.ts
import { Router } from 'express'
import { chargefy } from '../lib/chargefy.js'

export const productsRouter = Router()

// Listar todos os produtos
productsRouter.get('/', async (req, res) => {
  try {
    const products = await chargefy.products.list({
      isArchived: false,
      limit: 20,
      page: 1,
    })

    res.json(products)
  } catch (error) {
    console.error('Erro ao listar produtos:', error)
    res.status(500).json({ error: 'Falha ao buscar produtos' })
  }
})

// Obter produto por ID
productsRouter.get('/:id', async (req, res) => {
  try {
    const product = await chargefy.products.get(req.params.id)
    res.json(product)
  } catch (error) {
    res.status(404).json({ error: 'Produto não encontrado' })
  }
})

Criar checkout e processar pagamentos

src/routes/checkout.ts
import { Router } from 'express'
import { chargefy } from '../lib/chargefy.js'

export const checkoutRouter = Router()

// Criar sessão de checkout
checkoutRouter.post('/sessions', async (req, res) => {
  try {
    const { productPriceId, customerEmail, successUrl } = req.body

    const checkout = await chargefy.checkouts.create({
      productPriceId,
      customerEmail,
      successUrl: successUrl || 'https://meusite.com.br/sucesso',
    })

    res.json({
      id: checkout.id,
      clientSecret: checkout.clientSecret,
      url: checkout.url,
      expiresAt: checkout.expiresAt,
    })
  } catch (error) {
    console.error('Erro ao criar checkout:', error)
    res.status(400).json({ error: 'Falha ao criar sessão de checkout' })
  }
})

Confirmar pagamento com PIX

// Confirmar checkout com PIX
checkoutRouter.post('/sessions/:clientSecret/confirm/pix', async (req, res) => {
  try {
    const { clientSecret } = req.params
    const { customerEmail, customerName } = req.body

    const result = await chargefy.checkouts.confirm(clientSecret, {
      customerEmail,
      customerName,
      paymentMethod: 'pix',
    })

    // Retorna QR Code para exibir ao cliente
    res.json({
      status: result.status,
      pixQrCode: result.paymentDetails?.pixQrCode,
      pixQrCodeBase64: result.paymentDetails?.pixQrCodeBase64,
      pixCopyPaste: result.paymentDetails?.pixCopyPaste,
      expiresAt: result.paymentDetails?.expiresAt,
    })
  } catch (error) {
    console.error('Erro no pagamento PIX:', error)
    res.status(400).json({ error: 'Falha ao processar pagamento PIX' })
  }
})

Confirmar pagamento com Cartão de Crédito

// Confirmar checkout com Cartão de Crédito
checkoutRouter.post('/sessions/:clientSecret/confirm/card', async (req, res) => {
  try {
    const { clientSecret } = req.params
    const {
      customerEmail,
      customerName,
      cardNumber,
      holderName,
      expirationMonth,
      expirationYear,
      securityCode,
      installments,
    } = req.body

    const result = await chargefy.checkouts.confirm(clientSecret, {
      customerEmail,
      customerName,
      paymentMethod: 'credit_card',
      card: {
        cardNumber,
        holderName,
        expirationMonth,
        expirationYear,
        securityCode,
      },
      installments: installments || 1, // 1-12x
    })

    res.json({
      status: result.status,
      checkoutId: result.id,
    })
  } catch (error) {
    console.error('Erro no pagamento com cartão:', error)
    res.status(400).json({ error: 'Falha ao processar pagamento com cartão' })
  }
})
Em produção, nunca trafegue dados de cartão pelo seu servidor sem certificação PCI-DSS. Use o Checkout Embed ou a tokenização client-side via Chargefy.js para enviar apenas o card_id tokenizado.

Confirmar pagamento com Boleto

// Confirmar checkout com Boleto
checkoutRouter.post('/sessions/:clientSecret/confirm/boleto', async (req, res) => {
  try {
    const { clientSecret } = req.params
    const { customerEmail, customerName } = req.body

    const result = await chargefy.checkouts.confirm(clientSecret, {
      customerEmail,
      customerName,
      paymentMethod: 'boleto',
    })

    res.json({
      status: result.status,
      boletoBarcode: result.paymentDetails?.boletoBarcode,
      boletoUrl: result.paymentDetails?.boletoUrl,
      boletoDueDate: result.paymentDetails?.boletoDueDate,
    })
  } catch (error) {
    console.error('Erro no pagamento com boleto:', error)
    res.status(400).json({ error: 'Falha ao processar pagamento com boleto' })
  }
})

Consultar status do checkout

// Consultar status de um checkout
checkoutRouter.get('/sessions/:clientSecret/status', async (req, res) => {
  try {
    const checkout = await chargefy.checkouts.getByClientSecret(
      req.params.clientSecret
    )

    res.json({
      id: checkout.id,
      status: checkout.status,
      paymentMethod: checkout.paymentMethod,
      amount: checkout.amount,
      currency: checkout.currency,
    })
  } catch (error) {
    res.status(404).json({ error: 'Checkout não encontrado' })
  }
})

Receber Webhooks

A verificação HMAC garante que o webhook veio da Chargefy e não foi alterado em trânsito.
src/routes/webhooks.ts
import { Router, raw } from 'express'
import crypto from 'crypto'

export const webhooksRouter = Router()

// Body raw necessário para verificação HMAC
webhooksRouter.use(raw({ type: 'application/json' }))

function verifyWebhookSignature(
  payload: Buffer,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )
}

webhooksRouter.post('/', (req, res) => {
  const signature = req.headers['webhook-signature'] as string
  const webhookSecret = process.env.CHARGEFY_WEBHOOK_SECRET!

  if (!signature) {
    return res.status(401).json({ error: 'Assinatura ausente' })
  }

  // Verificar assinatura HMAC
  const isValid = verifyWebhookSignature(req.body, signature, webhookSecret)

  if (!isValid) {
    console.error('Assinatura de webhook inválida')
    return res.status(401).json({ error: 'Assinatura inválida' })
  }

  const event = JSON.parse(req.body.toString())

  // Processar evento
  switch (event.type) {
    case 'checkout.updated':
      handleCheckoutUpdated(event.data)
      break

    case 'subscription.created':
      handleSubscriptionCreated(event.data)
      break

    case 'subscription.active':
      handleSubscriptionActive(event.data)
      break

    case 'subscription.canceled':
      handleSubscriptionCanceled(event.data)
      break

    case 'refund.created':
      handleRefundCreated(event.data)
      break

    default:
      console.log(`Evento não tratado: ${event.type}`)
  }

  // Sempre responder 200 rapidamente para evitar retries
  res.status(200).json({ received: true })
})

// Handlers de eventos
function handleCheckoutUpdated(data: any) {
  console.log('Checkout atualizado:', data.id, '→', data.status)

  if (data.status === 'succeeded') {
    // Pagamento confirmado — liberar acesso, enviar email, etc.
  }
}

function handleSubscriptionCreated(data: any) {
  console.log('Nova assinatura:', data.id)
  // Registrar assinatura no seu banco
}

function handleSubscriptionActive(data: any) {
  console.log('Assinatura ativa:', data.id)
  // Liberar acesso ao serviço
}

function handleSubscriptionCanceled(data: any) {
  console.log('Assinatura cancelada:', data.id)
  // Revogar acesso ao final do período
}

function handleRefundCreated(data: any) {
  console.log('Reembolso criado:', data.id)
  // Processar reembolso no seu sistema
}
Responda ao webhook com status 200 o mais rápido possível. Processe lógica pesada (emails, integrações) de forma assíncrona usando filas como BullMQ.

Gerenciar assinaturas

src/routes/subscriptions.ts
import { Router } from 'express'
import { chargefy } from '../lib/chargefy.js'

export const subscriptionsRouter = Router()

// Listar assinaturas de um cliente
subscriptionsRouter.get('/customer/:customerId', async (req, res) => {
  try {
    const subscriptions = await chargefy.subscriptions.list({
      customerId: req.params.customerId,
    })

    res.json(subscriptions)
  } catch (error) {
    res.status(500).json({ error: 'Falha ao listar assinaturas' })
  }
})

// Obter detalhes de uma assinatura
subscriptionsRouter.get('/:id', async (req, res) => {
  try {
    const subscription = await chargefy.subscriptions.get(req.params.id)
    res.json(subscription)
  } catch (error) {
    res.status(404).json({ error: 'Assinatura não encontrada' })
  }
})

// Cancelar assinatura
subscriptionsRouter.post('/:id/cancel', async (req, res) => {
  try {
    const subscription = await chargefy.subscriptions.cancel(req.params.id)

    res.json({
      id: subscription.id,
      status: subscription.status,
      cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
      currentPeriodEnd: subscription.currentPeriodEnd,
    })
  } catch (error) {
    res.status(400).json({ error: 'Falha ao cancelar assinatura' })
  }
})

// Reativar assinatura cancelada (antes do fim do período)
subscriptionsRouter.post('/:id/reactivate', async (req, res) => {
  try {
    const subscription = await chargefy.subscriptions.update(req.params.id, {
      cancelAtPeriodEnd: false,
    })

    res.json({
      id: subscription.id,
      status: subscription.status,
    })
  } catch (error) {
    res.status(400).json({ error: 'Falha ao reativar assinatura' })
  }
})

Testar localmente com webhooks

Use ngrok para expor seu servidor local e receber webhooks:
# Terminal 1 — Iniciar servidor
npx tsx src/index.ts

# Terminal 2 — Expor com ngrok
ngrok http 3001
Copie a URL gerada pelo ngrok (ex: https://abc123.ngrok.app) e configure no dashboard da Chargefy como endpoint de webhook:
https://abc123.ngrok.app/webhooks

Testar com curl

# Criar sessão de checkout
curl -X POST http://localhost:3001/api/checkout/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "productPriceId": "price_xxxx",
    "customerEmail": "[email protected]"
  }'

# Confirmar pagamento com PIX
curl -X POST http://localhost:3001/api/checkout/sessions/cs_xxx/confirm/pix \
  -H "Content-Type: application/json" \
  -d '{
    "customerEmail": "[email protected]",
    "customerName": "João Silva"
  }'

Scripts do package.json

package.json
{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Checklist de produção

1

Trocar para token de produção

Substitua o token de sandbox pelo token de produção no .env.
2

Configurar webhook endpoint

Registre a URL de produção no dashboard da Chargefy.
3

Habilitar HTTPS

Use um proxy reverso (nginx, Caddy) ou plataforma com TLS (Railway, Render, Fly.io).
4

Adicionar rate limiting

Use express-rate-limit para proteger seus endpoints públicos.
5

Processar webhooks com filas

Use BullMQ + Redis para processar eventos de forma assíncrona e resiliente.
6

Monitorar erros

Configure Sentry ou similar para capturar erros em produção.

Próximos passos

Checkout PIX

Guia detalhado do fluxo de pagamento PIX.

Webhooks

Documentação completa de webhooks e eventos.

SDK TypeScript

Referência completa do SDK.

Sandbox

Como testar pagamentos sem cobrar de verdade.