Pharos Docs
Cobranças

Cart Checkout

Crie um carrinho controlado pelo backend com Checkout Sessions e checkout hospedado.

Use Checkout Sessions quando seu backend controla um carrinho ou tentativa de compra específica. A sessão pode referenciar preços do catálogo com priceId ou salvar preços inline com priceData sem criar produtos, planos ou preços.

Mantenha x-api-key no backend. Não chame /api/v1 pelo navegador nem aceite preços, recorrência ou conta como dados autorizados enviados pelo cliente.

Convenções da API

Crie chaves de API separadas com as permissões mínimas necessárias. Consultas exigem READ; criar, atualizar, excluir, expirar, cancelar e reembolsar exigem WRITE. Uma chave válida sem a permissão da operação retorna 403; uma chave ausente, inválida ou expirada retorna 401.

Liste Produtos, Links de pagamento e Checkout Sessions com limit (20 por padrão, 100 no máximo), startingAfter ou endingBefore. Use somente um cursor e obtenha o próximo em meta.lastId ou o anterior em meta.firstId. Todas as listas retornam { data, meta }.

Todos os erros retornam { error: { code, message, details? }, correlationId }. Registre o identificador de correlação com o identificador da sua solicitação. Tente novamente somente falhas seguras para a operação e reutilize o mesmo Idempotency-Key; consulte o recurso atualizado antes de repetir um conflito de versão. Consulte a Referência da API para ver status e esquemas de cada operação.

IDs de recursos

Os IDs do Pharos são strings opacas com prefixo de recurso, como cs_…, price_…, cus_… ou pi_…. Armazene e envie-os sem alterações; não interprete o sufixo. Durante a janela de compatibilidade, a API também aceita um CUID histórico retornado antes deste formato.

Arquitetura

Navegador do comprador
    → seu backend
        → Pharos /api/v1/checkout-sessions
            → checkout hospedado /checkout/session/:id
                → cálculo, fatura, assinatura e pagamento
                    → webhook assinado ao backend

Seu backend monta o carrinho e modifica a sessão. O Pharos gerencia o checkout e os registros comerciais. O navegador precisa apenas da url retornada.

Fluxo de compra

  1. O navegador solicita checkout ao backend usando a identidade do carrinho, não preços autorizados pelo navegador.
  2. Seu backend cria uma sessão com POST /api/v1/checkout-sessions e um Idempotency-Key estável para a operação.
  3. Salve id, version, ETag e url associados ao carrinho.
  4. Se precisar modificar, faça enquanto a sessão estiver open e não preparada. Cada operação usa sua própria chave e o último ETag em If-Match.
  5. Retorne a URL ao navegador para abrir o checkout hospedado.
  6. O comprador preenche dados e forma de pagamento. Alterações de quantidade respeitam os limites salvos pelo servidor.
  7. O Pharos calcula o resultado e cria cliente, fatura, assinatura e pagamento conforme necessário.
  8. O pagamento pode ser imediato, exigir ação ou continuar pendente.
  9. URLs de sucesso e cancelamento controlam navegação; não comprovam pagamento nem expiram a sessão.
  10. Reconcilie por checkoutSession.id e clientReferenceId e confirme a compra somente com invoice.paid verificado e deduplicado.

Criar uma sessão no backend

const pharosApi = 'https://api.example.com/api/v1'

type Cart = {
  id: string
  items: Array<{ priceId: string; quantity: number }>
}

export async function createCheckout(cart: Cart) {
  const response = await fetch(`${pharosApi}/checkout-sessions`, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-api-key': process.env.PHAROS_API_KEY!,
      'idempotency-key': `cart:${cart.id}:checkout-session`,
    },
    body: JSON.stringify({
      mode: 'PAYMENT',
      theme: 'light',
      clientReferenceId: cart.id,
      lineItems: cart.items.map((item) => ({
        ...item,
        adjustableQuantity: {
          enabled: true,
          minimum: 1,
          maximum: 10,
        },
      })),
      successUrl: 'https://shop.example/success?session={CHECKOUT_SESSION_ID}',
      cancelUrl: 'https://shop.example/cart',
    }),
  })

  if (!response.ok) {
    throw new Error(await response.text())
  }

  return {
    session: await response.json(),
    etag: response.headers.get('etag'),
  }
}

Derive a chave de criação do carrinho ou tentativa. Repetir a mesma operação com a mesma chave retorna a mesma sessão lógica. Reutilizar com corpo diferente causa conflito de idempotência.

Associar um cliente conhecido

Envie customer com o ID de um Customer existente quando seu aplicativo já conhece o comprador. O Pharos valida que ele pertença à mesma conta e ambiente e preenche o checkout hospedado com o perfil salvo. Quando enviar customer, omita customerEmail; use customerEmail para um convidado cujo perfil deva ser resolvido ou criado durante o checkout.

customerUpdate define quais campos salvos o comprador pode substituir durante o checkout. Os campos configurados como auto são salvos automaticamente no Customer quando o checkout é concluído com sucesso. Cartões elegíveis são salvos automaticamente depois que o comprador aceita os termos obrigatórios do checkout. savedPaymentMethodOptions.redisplay: "enabled" controla se as formas salvas aparecem em sessões posteriores; a opção legada save é aceita por compatibilidade, mas não altera mais o comportamento do checkout.

Escolher catálogo ou preços inline

Use priceId quando a oferta já existe no catálogo. Use priceData para salvar um preço contextual definido pelo backend:

const customLineItem = {
  priceData: {
    currency: 'USD',
    unitAmount: '25.00',
    productData: {
      name: 'Custom support package',
      description: 'Configured for this order',
    },
    recurring: {
      interval: 'MONTH',
      intervalCount: 1,
      trialDays: 7,
    },
  },
  quantity: 1,
}

Os dados inline ficam salvos na sessão. O navegador do checkout não pode substituir valores unitários ou recorrência.

Modificar um carrinho aberto

export async function addItem(sessionId: string, etag: string, priceId: string) {
  const response = await fetch(`${pharosApi}/checkout-sessions/${sessionId}/line-items`, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-api-key': process.env.PHAROS_API_KEY!,
      'idempotency-key': crypto.randomUUID(),
      'if-match': etag,
    },
    body: JSON.stringify({
      lineItems: [{ priceId, quantity: 1 }],
    }),
  })

  if (response.status === 409) {
    throw new Error('Reload and reconcile the session before retrying')
  }
  if (!response.ok) {
    throw new Error(await response.text())
  }

  return {
    session: await response.json(),
    etag: response.headers.get('etag'),
  }
}

Após uma alteração bem-sucedida, atualize sessão, version e ETag salvos. Em 409 SESSION_VERSION_CONFLICT, consulte a sessão, reconcilie o carrinho e crie uma nova alteração. Não repita cegamente dados de uma versão antiga.

Dentro de /api/v1, também pode usar:

  • PATCH /checkout-sessions/{id} para configurações não monetárias admitidas, como tema, redirecionamentos, email, referência ou metadata.
  • PATCH /checkout-sessions/{id}/line-items/{itemId} para quantidade ou limites ajustáveis.
  • DELETE /checkout-sessions/{id}/line-items/{itemId} para remover um item permitido.
  • POST /checkout-sessions/{id}/expire para impedir outro uso do carrinho.

Alterações por API exigem sessão aberta cujo snapshot não esteja bloqueado pela preparação do pagamento.

Redirecionar pelo navegador

const response = await fetch('/api/cart/checkout', { method: 'POST' })
if (!response.ok) throw new Error('Checkout could not be created')

const { url } = await response.json()
window.location.assign(url)

Seu handler /api/cart/checkout realiza a chamada autenticada ao Pharos. Não exponha a chave de API na resposta ou código do cliente.

Estados e confirmação

Para uma cobrança direta pelo backend, crie um PaymentIntent com POST /api/v1/payment_intents. Ele não cobra por padrão: envie confirm: true ou chame /api/v1/payment_intents/{id}/confirm para criar um Charge. Consulte um Charge pelo ID e crie um Refund separado com POST /api/v1/refunds.

O status pode ser open, complete ou expired; descreve o fluxo do comprador. paymentStatus pode ser unpaid, processing, paid, failed ou no_payment_required; descreve a cobrança.

O redirecionamento de sucesso e checkout_session.completed não são sinais para entrega da compra. Consulte a sessão para reconciliar os registros e confirme somente com invoice.paid assinado. Um fluxo gratuito ou de teste exige sua política de acesso e contrato de eventos correspondente.

Resolver falhas

  • Em falhas de rede, repita com a mesma chave apenas a operação exata.
  • Em 400, corrija a requisição ou composição do carrinho.
  • Em 401 ou 403, confira chave, ambiente e permissões.
  • Em 404, confirme que chave e sessão correspondem à mesma conta.
  • Em 409, diferencie conflitos de idempotência e versão e reconcilie antes de repetir.
  • Se o snapshot estiver bloqueado, crie outra tentativa em vez de modificar.
  • Se o pagamento estiver em processamento, aguarde o webhook assinado.

Configuração comercial

Consulte Produtos e preços, Cupons, Promoções e Descontos. Condições por produto exigem identidades correspondentes do catálogo; um nome inline igual não garante elegibilidade. Use apenas opções admitidas pela API, sem presumir que cada ajuste do painel tem equivalente público.

Consulte Assinaturas e Faturas para interpretar os registros criados.

Continuar