Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.chargefy.io/llms.txt

Use this file to discover all available pages before exploring further.

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.unit_amount / 100)}
              </span>
              {price.type === 'recurring' && (
                <span className="text-gray-500">
                  /{price.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_session_id={CHECKOUT_SESSION_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="seu@email.com"
          />
        </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.session.async_payment_succeeded':
      await handleAsyncPaymentSucceeded(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 handleAsyncPaymentSucceeded(data: any) {
  // Pagamento assíncrono 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.session.async_payment_succeeded, 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.

Ambiente Sandbox

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