# Integrar boletos com a Calebe Pay Esta é a API implementada pela Calebe Pay, com exemplos prontos para backend e agentes de programação. Valores são **inteiros em centavos BRL**. `1250` significa R$ 12,50. Não envie o JSON da Safe2Pay diretamente: a Calebe Pay adapta seu contrato ao provider. ## Escolha o ambiente | Provider / ambiente | `simulated` | Resultado | | --- | --- | --- | | `simulator` / `sandbox` | `true` | Registro local; `boleto_url`, `boleto_digitable_line` e `boleto_barcode` são nulos; nenhum título pagável | | `safe2pay` / `sandbox` | `false` | Chamada ao sandbox da Safe2Pay com credencial sandbox; não representa dinheiro real | | `safe2pay` / `production` | `false` | Emissão financeira real, protegida pelas configurações de autorização do servidor | **Boleto Safe2Pay suporta sandbox; Pix Safe2Pay não.** O adapter envia `PaymentMethod: "1"` e `IsSandbox: true` para boleto sandbox, conforme a [referência oficial](https://developers.safe2pay.com.br/reference/cobranca-criar). O suporte do código não comprova que uma conta ou credencial foi homologada. Testes locais e de contrato não substituem uma chamada remota autorizada. Produção exige `allow_live_payments` efetivo habilitado pelo super admin e uma autorização restrita `LIVE_PAYMENT_SCOPE_JSON` com `method: "boleto"`, ou o opt-in explícito de produção geral `ALLOW_UNSCOPED_LIVE_PAYMENTS=true`. O controle é salvo em `/admin/settings/payments` sem reinício; `ALLOW_LIVE_PAYMENTS` é apenas padrão inicial/fallback sem registro. As autorizações de escopo continuam no servidor. Escopos antigos sem `method` autorizam apenas Pix. Nunca amplie esses controles automaticamente ao receber um erro. ## Preparação no painel 1. Super admin configura um provider habilitado. `enabled_methods` precisa conter `boleto`; o padrão conservador é `["pix"]`. Alterar para `["pix", "boleto"]` mantém os dois métodos. `[]` desabilita os métodos. Não basta cadastrar a credencial. 2. Super admin cria uma regra `method: "boleto"`, com o provider, tenant e `max_amount` em centavos. Regras são escolhidas por proximidade na árvore e prioridade, como no Pix. 3. Um administrador emite uma chave do tenant alvo com `scopes: ["boleto:write", "transactions:read"]`. `boleto:write` não é adicionado a chaves antigas nem ao padrão de novas chaves. O segredo só aparece na criação. 4. Configure `CALEBE_API_URL` e `CALEBE_API_KEY` no backend integrador. Não exponha a chave em React, Expo, Electron renderer, prompts ou logs. As operações administrativas documentadas no [OpenAPI](./openapi.json) são `PATCH /admin/providers/{id}` com `enabled_methods`, `POST /admin/rules` com `method: "boleto"` e `POST /admin/api-keys` com scopes explícitos. Elas usam sessão Bearer. O cliente integra por `X-API-Key`. ## Primeiro boleto no simulador Configure o simulador com capacidade e regra de boleto como acima. Na raiz do projeto, prepare **uma vez** o payload fictício com vencimento em sete dias, considerando a data de São Paulo: ```sh node docs/examples/prepare-boleto.mjs .local/boleto-request.json export CALEBE_API_URL=http://localhost:18080 export CALEBE_REQUEST_FILE=.local/boleto-request.json # CALEBE_API_KEY vem do ambiente seguro do seu servidor. # Grave esta chave no pedido antes da primeira chamada e não a troque em retries. export CALEBE_IDEMPOTENCY_KEY=pedido-boleto-exemplo-42-tentativa-1 curl --fail-with-body "$CALEBE_API_URL/v1/boleto" \ -H "X-API-Key: $CALEBE_API_KEY" \ -H "Idempotency-Key: $CALEBE_IDEMPOTENCY_KEY" \ -H 'Content-Type: application/json' \ --data-binary "@$CALEBE_REQUEST_FILE" ``` O preparador não substitui um arquivo existente. Preserve esse arquivo nos retries, inclusive depois do vencimento; não recalcule a data a cada tentativa. Para um pedido novo, grave outro payload e outra chave comercial deliberadamente. Dados fictícios servem ao simulador local; para integração remota use dados e configuração aceitos pelo provider. Alternativas à chamada cURL, com **o mesmo arquivo e chave**: ```sh node docs/examples/create-boleto.mjs python3 docs/examples/create_boleto.py go run docs/examples/create_boleto.go ``` Os exemplos não repetem chamadas automaticamente e não seguem redirects com credenciais. Uma resposta incerta exige consultar a tentativa existente e preservar a chave. ## Contrato de criação `POST /v1/boleto` usa o tenant da API key: **não envie `tenant_id` ou `provider_id`**. O endpoint do painel `POST /admin/boleto` aceita `tenant_id` autorizado na árvore. Ambos exigem `Idempotency-Key` com 1–128 caracteres de `[A-Za-z0-9._:-]`. ```json { "amount": 1250, "reference": "pedido-boleto-42", "description": "Pedido por boleto", "customer": { "name": "Cliente de demonstração", "document": "00000000000", "email": "cliente@example.com", "phone": "11999999999", "address": { "zip_code": "01001000", "street": "Rua Exemplo", "number": "100", "complement": "Sala 1", "district": "Centro", "city": "São Paulo", "state": "SP" } }, "boleto": { "due_date": "2026-10-01", "instruction": "Não receber após a baixa", "penalty_rate": 2, "interest_rate": 1, "cancel_after_due": true, "days_before_cancel": 5 } } ``` A data acima é ilustrativa: para uma nova emissão, escolha hoje ou até 365 dias à frente em `America/Sao_Paulo`. O preparador evita exemplos com data vencida. A Calebe Pay converte a data ISO para o formato da Safe2Pay internamente. Não envie `expires_in` no boleto. | Campo | Regra | | --- | --- | | `amount` | Inteiro de 1 a 100000000 centavos; também sujeito ao limite da regra | | `reference` / `description` | Obrigatórios; 1–100 / 1–200 bytes UTF-8 | | `customer.name` | 2–120 bytes | | `customer.document` | String alfanumérica de 11 ou 14 caracteres, sem pontuação; a API local não comprova identidade | | `customer.email` / `phone` | Ambos obrigatórios; e-mail até 254 bytes, telefone não vazio após trim e até 20 bytes | | `address.zip_code` | 8 dígitos como string | | `address.street` / `number` | 2–120 / 1–20 bytes; número é string, por exemplo `"S/N"` | | `address.complement` | Opcional, até 120 bytes | | `address.district` / `city` / `state` | Bairro/cidade 2–80 bytes; UF brasileira de duas letras | | `boleto.due_date` | Obrigatório, `YYYY-MM-DD`; hoje até +365 dias em São Paulo na primeira emissão | | `instruction` | Opcional, até 200 bytes | | `penalty_rate` / `interest_rate` | Percentuais de 0 a 100, no máximo duas casas; padrão 0 | | `cancel_after_due` / `days_before_cancel` | Booleano padrão false / inteiro de 0 a 120, padrão 0 | Multa, juros e prazo de baixa enviados ao provider dependem de sua configuração contratada. Alterar, cancelar ou estornar boleto depois da emissão não possui endpoint nesta versão. ## Resposta e idempotência Nova emissão conhecida retorna `201 {"data": ...}`; replay retorna `200` e `Idempotency-Replayed: true`, ou `202` quando o registro ainda está `processing`. `202` indica processamento/resultado incerto, não pagamento. Guarde pelo menos `id`, `method`, `status`, `environment`, `simulated` e a referência comercial no pedido. O objeto `Transaction` contém `method: "boleto"`, `boleto_due_date`, `boleto_url`, `boleto_digitable_line` e `boleto_barcode`. Os últimos três são strings ou nulos, sem garantia de disponibilidade numa resposta parcial. Não transforme linha/código de barras em número; zeros fazem parte do dado. No simulador esses três campos permanecem nulos. Um `boleto_url` real deve ser apresentado como o documento do provider; o app não precisa reconstruir o boleto. **O namespace idempotente é compartilhado por Pix e boleto.** `(tenant, Idempotency-Key)` identifica uma tentativa. Usar uma chave previamente empregada em Pix no endpoint de boleto retorna `409 idempotency_conflict`. O corpo normalizado inclui o método: mantenha corpo, método e chave idênticos no retry. O vencimento original continua válido para recuperar uma tentativa idempotente antiga, sem nova emissão. `reference` não é única. A referência enviada à Safe2Pay é o ID local `txn_...`, usado na reconciliação, enquanto a referência comercial permanece na Calebe Pay. ## Consultar e confirmar ```http GET /v1/transactions?method=boleto&reference=pedido-boleto-42&limit=50&offset=0 GET /v1/transactions/{id} POST /v1/transactions/{id}/refresh ``` Os dois GET leem o estado local e exigem `transactions:read`. O refresh de boleto exige **`transactions:read` e `boleto:write`**, consulta o provider e nunca cria outra cobrança. Um pai pode ler sua subárvore com `include_descendants=true`; criar no filho exige a chave daquele filho. Na Safe2Pay, intenção de pagamento do boleto pode produzir `processing` antes da confirmação bancária. Somente `paid` em produção confirma pagamento real. `pending` e `processing` não liberam o produto; `unknown` exige reconciliação. Não deduza baixa/expiração apenas da data local: banco e regras do boleto determinam o estado. Veja o [ciclo de vida oficial](https://developers.safe2pay.com.br/docs/ciclo-de-vida-de-um-boleto). Ainda não há webhook de saída: faça polling limitado no seu backend, retome pendências após reinícios e respeite o rate limit. ID do provider, valor e referência retornados no refresh devem coincidir com a transação local. ## Diagnóstico sem nova emissão Na primeira recusa confirmada, `502 provider_rejected` inclui `error.transaction_id` e os campos diretos nullable `provider_error_code`, `provider_error_message` e `provider_http_status`. Consulte o ID por GET local para ler o diagnóstico persistido. O HTTP remoto pode ser `200` em uma rejeição de negócio; ele não substitui o `status` da transação. Uma resposta incerta continua sendo `202` com `data.status: "unknown"` e os diagnósticos disponíveis. Repetir a mesma chave e o mesmo corpo de uma tentativa `failed` retorna `200` com o registro anterior, sem novo boleto no provider. Preserve inclusive o vencimento original. Se o histórico não capturou detalhes, eles permanecem nulos: não emita outra cobrança para tentar reconstruir o erro antigo. Veja o [contrato completo de diagnóstico](integration.md#diagnóstico-persistido-de-pix-e-boleto). Uma falha ao consultar o provider em `/refresh` pode trazer diagnóstico atual em `error`, mas não altera o estado nem o diagnóstico da criação persistidos. Use o `request_id` do erro para rastrear essa consulta separadamente. | Código | Ação | | --- | --- | | `403 insufficient_scope` | Emitir chave autorizada com `boleto:write`; a chave atual não ganha permissão automaticamente | | `422 provider_not_configured` | Conferir regra `method=boleto`, provider habilitado e `enabled_methods` contendo boleto | | `422 provider_method_disabled` | Capacidade desabilitada no provider; administrador revisa a configuração | | `422 validation_error` | Corrigir endereço, vencimento, valor ou opções antes de nova solicitação válida | | `409 idempotency_conflict` | Recuperar payload/método/chave originais; não trocar chave para contornar | | `403 live_payments_disabled` / `live_payment_out_of_scope` | Produção bloqueada ou fora da autorização; não ampliar automaticamente | | `502 provider_rejected` | Recusa confirmada; GET pelo `error.transaction_id` recupera diagnóstico local sem reenvio | | `202`, timeout, `503 reconciliation_required` | Pode haver cobrança; preservar os dados e reconciliar antes de qualquer nova tentativa | | `502 provider_refresh_failed` | Consulta falhou/divergiu; estado local preservado; investigar sem criar outro boleto | Para agentes: implemente esse fluxo no backend, persista payload/chave **antes** da rede, reconheça todos os estados e ambientes, e use o [OpenAPI](./openapi.json) como contrato. Não invente `/cancel`, `/refund`, webhook ou geração de PDF local.