Skip to main content
Este guia mostra como integrar a Chargefy em uma aplicação Next.js completa, do setup inicial até o processamento de pagamentos com PIX, Cartão de Crédito e Boleto.

Pré-requisitos

Setup do projeto

1. Instalar dependências

npm install @chargefy/sdk

2. Configurar variáveis de ambiente

Crie ou atualize o arquivo .env.local:
.env.local
CHARGEFY_ACCESS_TOKEN=<supabase_jwt>
CHARGEFY_WEBHOOK_SECRET=whsec_seu_secret_aqui
NEXT_PUBLIC_CHARGEFY_BASE_URL=https://api.chargefy.io
Nunca exponha o CHARGEFY_ACCESS_TOKEN no frontend. Use-o apenas em Server Components, API Routes ou Server Actions.

3. Criar cliente Chargefy

Crie um arquivo utilitário para inicializar o SDK:
lib/chargefy.ts
import { Chargefy } from '@chargefy/sdk';

export const chargefy = new Chargefy({
  accessToken: process.env.CHARGEFY_ACCESS_TOKEN!,
  server: 'production', // ou 'sandbox' para testes
});

Listar produtos

Use um Server Component para buscar e exibir seus produtos:
app/products/page.tsx
import { chargefy } from '@/lib/chargefy';
import Link from 'next/link';

export default async function ProductsPage() {
  const { items: products } = await chargefy.products.list({
    is_archived: false,
  });

  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-8">
      {products.map((product) => (
        <div key={product.id} className="border rounded-lg p-6">
          <h2 className="text-xl font-bold">{product.name}</h2>
          <p className="text-gray-600 mt-2">{product.description}</p>

          {product.prices.map((price) => (
            <div key={price.id} className="mt-4">
              <span className="text-2xl font-bold">
                {new Intl.NumberFormat('pt-BR', {
                  style: 'currency',
                  currency: 'BRL',
                }).format(price.price_amount / 100)}
              </span>
              {price.type === 'recurring' && (
                <span className="text-gray-500">
                  /{price.recurring_interval === 'month' ? 'mês' : 'ano'}
                </span>
              )}
            </div>
          ))}

          <Link
            href={`/checkout?product=${product.id}`}
            className="mt-4 block text-center bg-black text-white py-2 rounded-lg"
          >
            Comprar
          </Link>
        </div>
      ))}
    </div>
  );
}

Criar sessão de checkout

Server Action

Crie uma Server Action para iniciar o checkout:
app/actions/checkout.ts
'use server';

import { chargefy } from '@/lib/chargefy';
import { redirect } from 'next/navigation';

export async function createCheckout(formData: FormData) {
  const productId = formData.get('productId') as string;

  const checkout = await chargefy.checkouts.create({
    product_price_id: productId,
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?checkout_id={CHECKOUT_ID}`,
    customer_email: formData.get('email') as string,
  });

  redirect(checkout.url);
}

Página de checkout

app/checkout/page.tsx
import { createCheckout } from '@/app/actions/checkout';

export default function CheckoutPage({
  searchParams,
}: {
  searchParams: { product: string };
}) {
  return (
    <div className="max-w-md mx-auto p-8">
      <h1 className="text-2xl font-bold mb-6">Finalizar Compra</h1>

      <form action={createCheckout}>
        <input type="hidden" name="productId" value={searchParams.product} />

        <div className="mb-4">
          <label className="block text-sm font-medium mb-1">Email</label>
          <input
            type="email"
            name="email"
            required
            className="w-full border rounded-lg px-3 py-2"
            placeholder="[email protected]"
          />
        </div>

        <button
          type="submit"
          className="w-full bg-black text-white py-3 rounded-lg font-medium"
        >
          Ir para pagamento
        </button>
      </form>
    </div>
  );
}

Checkout via API (Server-Side)

Para controle total, crie o checkout e confirme o pagamento diretamente via API:

Checkout com PIX

app/api/checkout/pix/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { chargefy } from '@/lib/chargefy';

export async function POST(req: NextRequest) {
  const { productPriceId, email, name, taxId } = await req.json();

  // 1. Criar sessão de checkout
  const checkout = await chargefy.checkouts.create({
    product_price_id: productPriceId,
    customer_email: email,
  });

  // 2. Confirmar com PIX
  const confirmed = await chargefy.checkouts.confirm(checkout.id, {
    customer_name: name,
    customer_email: email,
    customer_tax_id: taxId, // CPF
    payment_method: 'pix',
  });

  return NextResponse.json({
    checkoutId: confirmed.id,
    status: confirmed.status,
    pixQrCode: confirmed.payment_data.pix_qr_code,
    pixQrCodeBase64: confirmed.payment_data.pix_qr_code_base64,
    pixCopyPaste: confirmed.payment_data.pix_copy_paste,
  });
}

Checkout com Cartão de Crédito

app/api/checkout/card/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { chargefy } from '@/lib/chargefy';

export async function POST(req: NextRequest) {
  const {
    productPriceId, email, name, taxId,
    cardNumber, holderName, expiryMonth, expiryYear, cvv,
    installments,
  } = await req.json();

  // 1. Criar sessão de checkout
  const checkout = await chargefy.checkouts.create({
    product_price_id: productPriceId,
    customer_email: email,
  });

  // 2. Confirmar com cartão
  const confirmed = await chargefy.checkouts.confirm(checkout.id, {
    customer_name: name,
    customer_email: email,
    customer_tax_id: taxId,
    payment_method: 'credit_card',
    card_number: cardNumber,
    holder_name: holderName,
    expiry_month: expiryMonth,
    expiry_year: expiryYear,
    cvv: cvv,
    installments: installments || 1, // 1-12x
  });

  return NextResponse.json({
    checkoutId: confirmed.id,
    status: confirmed.status, // 'succeeded' para cartão
  });
}

Checkout com Boleto

app/api/checkout/boleto/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { chargefy } from '@/lib/chargefy';

export async function POST(req: NextRequest) {
  const { productPriceId, email, name, taxId, dueDate } = await req.json();

  // 1. Criar sessão de checkout
  const checkout = await chargefy.checkouts.create({
    product_price_id: productPriceId,
    customer_email: email,
  });

  // 2. Confirmar com boleto
  const confirmed = await chargefy.checkouts.confirm(checkout.id, {
    customer_name: name,
    customer_email: email,
    customer_tax_id: taxId,
    payment_method: 'boleto',
    due_date: dueDate, // 'YYYY-MM-DD'
  });

  return NextResponse.json({
    checkoutId: confirmed.id,
    status: confirmed.status, // 'confirmed' (aguardando compensação)
    boletoBarcode: confirmed.payment_data.boleto_barcode,
    boletoUrl: confirmed.payment_data.boleto_url,
    boletoDueDate: confirmed.payment_data.boleto_due_date,
  });
}

Página de sucesso

app/checkout/success/page.tsx
import { chargefy } from '@/lib/chargefy';

export default async function CheckoutSuccessPage({
  searchParams,
}: {
  searchParams: { checkout_id: string };
}) {
  const checkout = await chargefy.checkouts.get(searchParams.checkout_id);

  return (
    <div className="max-w-md mx-auto p-8 text-center">
      {checkout.status === 'succeeded' ? (
        <>
          <div className="text-green-500 text-5xl mb-4"></div>
          <h1 className="text-2xl font-bold">Pagamento confirmado!</h1>
          <p className="text-gray-600 mt-2">
            Sua cobranca #{checkout.id} foi processada com sucesso.
          </p>
        </>
      ) : checkout.status === 'confirmed' ? (
        <>
          <div className="text-yellow-500 text-5xl mb-4"></div>
          <h1 className="text-2xl font-bold">Pagamento em processamento</h1>
          <p className="text-gray-600 mt-2">
            Aguardando confirmação do pagamento via{' '}
            {checkout.payment_method === 'pix' ? 'PIX' : 'Boleto'}.
          </p>
        </>
      ) : (
        <>
          <div className="text-red-500 text-5xl mb-4"></div>
          <h1 className="text-2xl font-bold">Erro no pagamento</h1>
          <p className="text-gray-600 mt-2">
            Houve um problema ao processar seu pagamento. Tente novamente.
          </p>
        </>
      )}
    </div>
  );
}

Receber webhooks

Webhooks são essenciais para receber notificações de pagamentos assíncronos (PIX e Boleto) e atualizações de assinaturas.

1. Criar endpoint de webhook

app/api/webhooks/chargefy/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.CHARGEFY_WEBHOOK_SECRET!;

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string,
): boolean {
  const [timestamp, hash] = signature.split(',');
  const ts = timestamp.replace('t=', '');
  const sig = hash.replace('v1=', '');

  const signedContent = `${ts}.${payload}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedContent)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(sig, 'hex'),
    Buffer.from(expectedSig, 'hex'),
  );
}

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get('webhook-signature') || '';

  // Verificar assinatura HMAC
  if (!verifyWebhookSignature(body, signature, WEBHOOK_SECRET)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case 'checkout.updated':
      await handleCheckoutUpdated(event.data);
      break;

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

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

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

  return NextResponse.json({ received: true });
}

async function handleCheckoutUpdated(data: any) {
  if (data.status === 'succeeded') {
    // Pagamento confirmado (PIX pago, boleto compensado)
    console.log(`Checkout ${data.id} concluído com sucesso`);
    // Atualizar banco de dados, enviar email, liberar acesso, etc.
  }
}

async function handleSubscriptionActive(data: any) {
  console.log(`Assinatura ${data.id} ativa para cliente ${data.customer_id}`);
  // Liberar acesso ao produto recorrente
}

async function handleSubscriptionCanceled(data: any) {
  console.log(`Assinatura ${data.id} cancelada`);
  // Revogar acesso ao produto
}

2. Configurar webhook no dashboard

1

Acessar configurações

Vá até Dashboard → Configurações → Webhooks e clique em “Novo Webhook”.
2

Configurar URL

Insira a URL do endpoint: https://seusite.com/api/webhooks/chargefy
3

Selecionar eventos

Selecione os eventos que deseja receber: checkout.updated, subscription.active, subscription.canceled.
4

Copiar secret

Copie o Webhook Secret gerado e adicione ao seu .env.local como CHARGEFY_WEBHOOK_SECRET.

3. Testar localmente com ngrok

Para receber webhooks em desenvolvimento local:
# Instalar ngrok
npm install -g ngrok

# Criar tunnel para sua aplicação local
ngrok http 3000
Use a URL HTTPS gerada pelo ngrok (ex: https://abc123.ngrok.io/api/webhooks/chargefy) como URL do webhook no dashboard.
No ambiente sandbox, webhooks são enviados imediatamente, sem delay de processamento.

Gerenciar assinaturas

Listar assinaturas do cliente

app/dashboard/subscriptions/page.tsx
import { chargefy } from '@/lib/chargefy';

export default async function SubscriptionsPage() {
  const { items: subscriptions } = await chargefy.subscriptions.list({
    active: true,
  });

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-6">Minhas Assinaturas</h1>

      {subscriptions.map((sub) => (
        <div key={sub.id} className="border rounded-lg p-6 mb-4">
          <div className="flex justify-between items-center">
            <div>
              <h3 className="font-bold">{sub.product.name}</h3>
              <p className="text-sm text-gray-500">
                {new Intl.NumberFormat('pt-BR', {
                  style: 'currency',
                  currency: 'BRL',
                }).format(sub.amount / 100)}
                /{sub.recurring_interval === 'month' ? 'mês' : 'ano'}
              </p>
              <p className="text-sm text-gray-400 mt-1">
                Próxima cobrança:{' '}
                {new Date(sub.current_period_end).toLocaleDateString('pt-BR')}
              </p>
            </div>
            <div>
              <span
                className={`px-3 py-1 rounded-full text-sm ${
                  sub.status === 'active'
                    ? 'bg-green-100 text-green-800'
                    : 'bg-gray-100 text-gray-800'
                }`}
              >
                {sub.status === 'active' ? 'Ativa' : sub.status}
              </span>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

Cancelar assinatura (Server Action)

app/actions/subscription.ts
'use server';

import { chargefy } from '@/lib/chargefy';
import { revalidatePath } from 'next/cache';

export async function cancelSubscription(subscriptionId: string) {
  await chargefy.subscriptions.cancel(subscriptionId);
  revalidatePath('/dashboard/subscriptions');
}

export async function reactivateSubscription(subscriptionId: string) {
  await chargefy.subscriptions.update(subscriptionId, {
    status: 'active',
  });
  revalidatePath('/dashboard/subscriptions');
}

Checkout embarcado

Para uma experiência de checkout sem redirecionamento, use o checkout embarcado:
app/components/EmbeddedCheckout.tsx
'use client';

import { useEffect, useRef } from 'react';

interface EmbeddedCheckoutProps {
  checkoutId: string;
  clientSecret: string;
}

export function EmbeddedCheckout({
  checkoutId,
  clientSecret,
}: EmbeddedCheckoutProps) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // Carregar script do checkout embarcado
    const script = document.createElement('script');
    script.src = 'https://cdn.chargefy.io/checkout/embed.global.js';
    script.async = true;

    script.onload = () => {
      // @ts-ignore
      window.ChargelyCheckout.mount({
        container: containerRef.current,
        clientSecret,
        theme: 'auto', // 'light' | 'dark' | 'auto'
        onSuccess: (data: any) => {
          console.log('Pagamento confirmado!', data);
          window.location.href = `/checkout/success?checkout_id=${checkoutId}`;
        },
        onError: (error: any) => {
          console.error('Erro no pagamento:', error);
        },
      });
    };

    document.body.appendChild(script);

    return () => {
      document.body.removeChild(script);
    };
  }, [checkoutId, clientSecret]);

  return <div ref={containerRef} className="min-h-[400px]" />;
}

Usar o componente

app/checkout/embedded/page.tsx
import { chargefy } from '@/lib/chargefy';
import { EmbeddedCheckout } from '@/app/components/EmbeddedCheckout';

export default async function EmbeddedCheckoutPage({
  searchParams,
}: {
  searchParams: { product: string };
}) {
  const checkout = await chargefy.checkouts.create({
    product_price_id: searchParams.product,
  });

  return (
    <div className="max-w-lg mx-auto p-8">
      <h1 className="text-2xl font-bold mb-6">Finalizar Compra</h1>
      <EmbeddedCheckout
        checkoutId={checkout.id}
        clientSecret={checkout.client_secret}
      />
    </div>
  );
}

Estrutura final do projeto

app/
├── actions/
│   ├── checkout.ts          # Server Actions de checkout
│   └── subscription.ts      # Server Actions de assinatura
├── api/
│   ├── checkout/
│   │   ├── pix/route.ts     # API Route: checkout PIX
│   │   ├── card/route.ts    # API Route: checkout cartão
│   │   └── boleto/route.ts  # API Route: checkout boleto
│   └── webhooks/
│       └── chargefy/route.ts # Webhook handler
├── checkout/
│   ├── page.tsx             # Página de checkout
│   ├── embedded/page.tsx    # Checkout embarcado
│   └── success/page.tsx     # Página de sucesso
├── components/
│   └── EmbeddedCheckout.tsx # Componente de checkout embarcado
├── dashboard/
│   └── subscriptions/
│       └── page.tsx         # Gerenciamento de assinaturas
├── products/
│   └── page.tsx             # Listagem de produtos
└── layout.tsx
lib/
└── chargefy.ts              # Cliente Chargefy SDK
.env.local                   # Variáveis de ambiente

Checklist de produção

Antes de ir para produção, verifique:
  • CHARGEFY_ACCESS_TOKEN definido apenas no servidor (não exposto no frontend)
  • Webhook signature verification está habilitada
  • HTTPS habilitado em todos os endpoints
  • Validação de input em todos os formulários
  • Testou checkout com PIX no sandbox
  • Testou checkout com Cartão de Crédito (aprovação e recusa)
  • Testou checkout com Boleto (geração e compensação simulada)
  • Testou parcelamento (1x, 6x, 12x)
  • Página de sucesso trata todos os status (succeeded, confirmed, failed)
  • Endpoint de webhook responde com 200 em menos de 30 segundos
  • Handlers são idempotentes (processar o mesmo evento 2x não causa duplicação)
  • Logs de webhook configurados para debugging
  • Tratamento de erros em todos os handlers
  • Fluxo de cancelamento funciona corretamente
  • Webhook subscription.canceled revoga acesso
  • Webhook subscription.active libera acesso
  • Página mostra status correto da assinatura

Próximos passos

Checkout Embarcado

Customize a experiência de checkout diretamente no seu site.

Webhooks

Configure webhooks para receber notificações em tempo real.

SDK TypeScript

Referência completa do SDK com todos os métodos disponíveis.

Ambiente Sandbox

Teste sua integração com dados simulados antes de ir para produção.