> ## Documentation Index
> Fetch the complete documentation index at: https://scaleup-c34c4386.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Guia Express / Node.js

> Integre a Chargefy com Express — API de checkout, processamento de pagamentos PIX/Cartão/Boleto e webhooks

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

* Node.js 18+
* Express 4.x ou 5.x
* Uma conta Chargefy com [Supabase JWT](https://scaleup-c34c4386.mintlify.site/integrate/authentication.md)
* Pelo menos um [produto criado](https://scaleup-c34c4386.mintlify.site/features/products.md) no dashboard

## Setup do projeto

### 1. Criar projeto e instalar dependências

```bash theme={null}
mkdir meu-backend && cd meu-backend
npm init -y
```

```bash npm theme={null}
npm install express @chargefy/sdk dotenv
npm install -D typescript @types/express @types/node tsx
```

```bash pnpm theme={null}
pnpm add express @chargefy/sdk dotenv
pnpm add -D typescript @types/express @types/node tsx
```

### 2. Configurar TypeScript

```json tsconfig.json theme={null}
{
  "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

```bash .env theme={null}
CHARGEFY_ACCESS_TOKEN=<supabase_jwt>
CHARGEFY_WEBHOOK_SECRET=whsec_seu_secret_aqui
PORT=3001
```

> [!WARNING]
>
> Nunca commite o arquivo `.env` no repositório. Adicione-o ao `.gitignore`.

### 4. Criar cliente Chargefy

```typescript src/lib/chargefy.ts theme={null}
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

```typescript src/index.ts theme={null}
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}`)
})
```

> [!NOTE]
>
> A rota de webhooks é registrada **antes** do `express.json()` porque a verificação HMAC requer o body raw (não parseado).

## Listar produtos

```typescript src/routes/products.ts theme={null}
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

```typescript src/routes/checkout.ts theme={null}
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

```typescript theme={null}
// 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

```typescript theme={null}
// 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' })
  }
})
```

> [!WARNING]
>
> Em produção, **nunca trafegue dados de cartão pelo seu servidor** sem certificação PCI-DSS. Use o [Checkout Embed](https://scaleup-c34c4386.mintlify.site/features/checkout/embed.md) ou a tokenização client-side via Chargefy.js para enviar apenas o `card_id` tokenizado.

### Confirmar pagamento com Boleto

```typescript theme={null}
// 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

```typescript theme={null}
// 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.

```typescript src/routes/webhooks.ts theme={null}
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
}
```

> [!TIP]
>
> 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](https://docs.bullmq.io/).

## Gerenciar assinaturas

```typescript src/routes/subscriptions.ts theme={null}
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](https://ngrok.com/) para expor seu servidor local e receber webhooks:

```bash theme={null}
# 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](https://chargefy.io) como endpoint de webhook:

```
https://abc123.ngrok.app/webhooks
```

### Testar com curl

```bash theme={null}
# Criar sessão de checkout
curl -X POST http://localhost:3001/api/checkout/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "productPriceId": "price_xxxx",
    "customerEmail": "cliente@email.com"
  }'

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

## Scripts do package.json

```json package.json theme={null}
{
  "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

> [card] [Checkout PIX](https://scaleup-c34c4386.mintlify.site/guides/pix-checkout.md)
>
> Guia detalhado do fluxo de pagamento PIX.

> [card] [Webhooks](https://scaleup-c34c4386.mintlify.site/integrate/webhooks/endpoints.md)
>
> Documentação completa de webhooks e eventos.

> [card] [SDK TypeScript](https://scaleup-c34c4386.mintlify.site/integrate/sdk/typescript.md)
>
> Referência completa do SDK.

> [card] [Sandbox](https://scaleup-c34c4386.mintlify.site/integrate/sandbox.md)
>
> Como testar pagamentos sem cobrar de verdade.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.