openapi: 3.1.0

info:
  title: Arbor Gateway API
  version: 1.0.0
  description: |
    # Bem-vindo à API Arbor Gateway

    O **Arbor Gateway** é uma plataforma de pagamentos focada no mercado brasileiro.
    Com nossa API você pode receber pagamentos via **PIX**, cobrar **cartão internacional**
    (USD/EUR), realizar **transferências PIX**, consultar seu **saldo**, configurar
    **split de pagamentos** e **webhooks** para notificações em tempo real.

    ## API e Checkout Hospedado

    A API pública cria **Pix** e **cartão internacional** (`currency` `"USD"` ou `"EUR"`).
    Cartão nacional (BRL) não existe nesta API — `POST /api/v1/card` sem moeda ou com `"BRL"`
    responde 400. Use o Checkout Hospedado (pay.sejaarbor.com → Checkout).

    Só no Checkout Hospedado, além de Pix e cartão nacional: cartão internacional, Apple Pay
    e Google Pay (dentro do cartão internacional), SEPA, PayPal, Klarna, iDEAL, Bancontact,
    MB WAY, Amazon Pay, Revolut Pay e Satispay. Não há rota de API para esses meios.
    Boleto ainda não está disponível, nem na API nem no checkout.

    Pix e cartão em real exigem CPF ou CNPJ do comprador. Pagamento internacional não.
    Cartão aprovado não vira saldo sacável na hora.

    ## Como começar

    1. Acesse o [Dashboard](https://pay.sejaarbor.com) e gere suas credenciais em **Configurações → API Keys**
    2. Autentique-se enviando `client_id:client_secret` para `POST /api/auth`
    3. Use o **Bearer token** retornado em todas as requisições

    ## Base URL

    ```
    https://api.pay.sejaarbor.com
    ```

    ## Valores monetários

    Todos os valores são em **centavos (inteiros)**.

    | Valor real | Em centavos |
    |------------|-------------|
    | R$ 1,00    | `100`       |
    | R$ 10,50   | `1050`      |
    | R$ 150,00  | `15000`     |

    ## Idempotência

    Para transferências PIX, envie o header `x-idempotency-key` com um valor único por operação.
    Isso garante que em caso de retry a operação não seja duplicada.

  contact:
    name: Suporte Arbor
    email: suporte@sejaarbor.com
    url: https://sejaarbor.com

servers:
  - url: https://api.pay.sejaarbor.com
    description: API Arbor Gateway

tags:
  - name: Autenticação
    description: |
      Geração de tokens de acesso via API Key.

      Use suas credenciais (`client_id` e `client_secret`) com **Basic Auth** para obter um JWT Bearer token.
      O token é necessário em todas as demais rotas.

  - name: PIX IN
    description: |
      Receba pagamentos instantâneos via QR Code PIX.

      Crie um QR Code e exiba para o seu cliente pagar. Ao receber o pagamento, você será
      notificado via webhook (se configurado).

  - name: PIX OUT
    description: |
      Envie transferências PIX para qualquer chave PIX.

      Suporta chaves do tipo CPF, CNPJ, e-mail, telefone e chave aleatória (EVP).
      Requer o header `x-idempotency-key` para evitar duplicações.

  - name: Cartão
    description: |
      Receba pagamentos com cartão de crédito **internacional** (USD/EUR), à vista.

      > Cartão **nacional** (BRL) não está disponível por esta API — só pelo Checkout
      > Hospedado do Arbor.

      ### Tokenização — o PAN nunca deve chegar ao seu servidor

      O número do cartão deve ser capturado direto no navegador do seu cliente com um
      Payment Element, antes de qualquer dado tocar o seu backend — isso mantém você
      fora do escopo pesado de PCI-DSS. Resumo do fluxo (peça ao suporte Arbor o guia
      completo e sua chave publicável):

      ```
      1. No navegador, monte o Payment Element com sua chave publicável
         (peça ao suporte Arbor — ainda não é auto-serviço pelo Dashboard)
      2. elements.submit() para validar o preenchimento
      3. createConfirmationToken({ elements }) → devolve confirmationToken.id
         (formato ctoken_...)
      4. Sua aplicação chama POST /api/v1/card com card.token = esse id
      ```

      Esta API **não aceita PAN cru** — `card.token` (o ConfirmationToken) é sempre
      obrigatório, nunca opcional.

      ### Moeda e endereço de cobrança são obrigatórios

      `currency` deve ser `"USD"` ou `"EUR"` — sem isso (ou com `"BRL"`) a requisição é
      recusada com 400. `customer.billingAddress` completo também é **obrigatório**. Sem
      isso a cobrança é recusada antes mesmo de chegar no emissor.

      ### O dinheiro não cai no saldo na hora

      Cartão nunca credita saldo disponível imediatamente, mesmo já aprovado — existe uma
      esteira de liquidação (prazo configurável, redutível pagando uma taxa de
      antecipação) antes do valor virar saldo sacável. Acompanhe pelo Dashboard, seção
      **Carteira**, ou pelo campo `status` da transação.
#
#   - name: Assinaturas
#     description: |
#       Cobre seus clientes de forma recorrente (mensal, semanal, anual...) via cartão de
#       crédito — assine uma vez, e cada ciclo é cobrado automaticamente sem você precisar
#       chamar a API de novo.
#
#       ### Quem cobra cada ciclo é o processador, não você
#
#       Depois que a assinatura é criada, é o Pagar.me (nosso processador) quem debita o
#       cartão automaticamente em cada ciclo. Você não precisa rodar nenhum job — apenas
#       escute os webhooks (`subscription_charged`, `subscription_payment_failed`) e o evento
#       genérico `transaction_paid`, que já dispara pra cada cobrança de ciclo do mesmo jeito
#       que dispara pra uma venda avulsa de cartão.
#
#       ### Plano ou avulsa
#
#       Duas formas de criar: `planId` referenciando um plano criado antes (`POST
#       /api/v1/plans`, ou pelo Dashboard em **Planos**) — útil quando você tem um catálogo
#       fixo de preços — ou avulsa, passando `amountInCents` + `interval` direto na criação da
#       assinatura, sem catálogo nenhum.
#
#       ### Mesmas regras de cartão avulso
#
#       `card` e `customer.billingAddress` seguem exatamente as mesmas regras de
#       `POST /api/v1/card` — tokenize no navegador (ver a seção **Cartão**), endereço de
#       cobrança obrigatório. E o dinheiro de cada cobrança de ciclo passa pela mesma esteira
#       de liquidação D+X — uma assinatura não credita saldo na hora, igual venda de cartão
#       avulsa não credita.

  - name: Sandbox
    description: |
      Ambiente de testes isolado, com seu próprio par de credenciais — self-service no
      Dashboard em **Chaves de API → Sandbox → Ativar ambiente sandbox**. O saldo do
      sandbox começa zerado e é separado do saldo real; webhooks são cadastrados à parte,
      com o token sandbox.

      Cartão (`POST /api/v1/card`) no sandbox aceita qualquer `card.token` e responde
      `paid` na hora; um token que contenha `decline` (ex.: `ctoken_decline`) é recusado.

      Toda cobrança criada com uma chave sandbox transaciona contra um adquirente
      simulado e **nunca movimenta dinheiro real**. Pagamentos PIX não são confirmados
      automaticamente — use os endpoints de simulação abaixo para disparar manualmente
      o webhook `transaction_paid` / `transfer_completed`, exatamente como aconteceria
      em produção.

      Os demais endpoints da API (criar cobrança, consultar, criar transferência,
      configurar webhooks) funcionam de forma idêntica em ambos os ambientes — a única
      diferença é qual par de credenciais você usa em `POST /api/auth`.

  - name: Saldo
    description: |
      Consulte os saldos das carteiras da sua conta em tempo real.

      Os valores são retornados em **reais** (string com 2 casas decimais).

      | Campo | Descrição |
      |-------|-----------|
      | `pix.balance` | Saldo disponível para uso |
      | `pixBlocked.balance` | Saldo bloqueado (cautelar / MED) |
      | `reserve.balance` | Valor em reserva |

  - name: Webhooks
    description: |
      Receba notificações em tempo real sobre eventos da sua conta.

      Configure uma URL e escolha quais eventos deseja monitorar.
      O Arbor enviará um `POST` com o payload do evento para a URL configurada.

      Além dos webhooks cadastrados em `/api/v1/webhook`, quando uma cobrança ou
      transferência é criada com `postbackUrl`, o mesmo callback também é enviado para
      essa URL.

      ### Eventos disponíveis

      Use os eventos abaixo no campo `events` ao criar um webhook.

      | Fluxo | Evento | Quando é enviado |
      |-------|--------|------------------|
      | PIX IN | `transaction_created` | Cobrança PIX criada |
      | PIX IN | `transaction_paid` | Cobrança PIX paga |
      | PIX IN | `transaction_refunded` | Cobrança PIX estornada |
      | PIX IN | `transaction_updated` | Cobrança PIX atualizada com outro status |
      | PIX IN | `infraction` | Infração/MED criada ou atualizada para uma cobrança PIX |
      | PIX OUT | `transfer_created` | Transferência PIX criada |
      | PIX OUT | `transfer_updated` | Transferência PIX atualizada com outro status |
      | PIX OUT | `transfer_completed` | Transferência PIX concluída |
      | PIX OUT | `transfer_canceled` | Transferência PIX cancelada ou recusada |

      ### Headers enviados no callback

      | Header | Valor |
      |--------|-------|
      | `Content-Type` | `application/json` |
      | `User-Agent` | `Arbor Gateway` |
      | `ArborGateway-Signature` | Assinatura HMAC-SHA256 no formato `t={timestamp},v1={hash}` quando o webhook possui `signatureSecret` |

      ### Payload de PIX IN

      Enviado nos eventos `transaction_created`, `transaction_paid`, `transaction_refunded`
      e `transaction_updated`.

      ```json
      {
        "id": "8d0f3b8f-5c2f-4a10-8dd6-8bbf5e9b1d11",
        "type": "transaction",
        "event": "transaction_paid",
        "scope": "user",
        "transaction": {
          "id": "transaction_abc123",
          "amount": 5000,
          "status": "paid",
          "pix": {
            "endToEndId": "E1234567820241231000000000000001",
            "payerInfo": {
              "name": "Joao Silva",
              "document": "11122233344"
            }
          }
        }
      }
      ```

      ### Payload de PIX OUT

      Enviado nos eventos `transfer_created`, `transfer_updated`, `transfer_completed`
      e `transfer_canceled`.

      ```json
      {
        "id": "5f9e0c94-60a4-4df8-bb81-7f6efc6f61b0",
        "type": "transfer",
        "event": "transfer_completed",
        "scope": "user",
        "transfer": {
          "id": "transfer_abc123",
          "amount": 10000,
          "status": "paid",
          "pix": {
            "endToEndId": "E1234567820241231000000000000002",
            "creditorAccount": {
              "name": "Fornecedor Exemplo",
              "document": "12345678000199"
            }
          }
        }
      }
      ```

      ### Payload de infração PIX IN

      Enviado no evento `infraction`.

      ```json
      {
        "id": "3db3ac40-a069-45e6-8cb8-6d55d09f516d",
        "type": "infraction",
        "event": "infraction",
        "scope": "user",
        "infraction": {
          "id": "infraction_abc123",
          "amount": 5000,
          "status": "OPEN",
          "pix": {
            "endToEndId": "E1234567820241231000000000000001"
          }
        },
        "transaction": {
          "id": "transaction_abc123",
          "amount": 5000,
          "status": "infraction",
          "pix": {
            "endToEndId": "E1234567820241231000000000000001",
            "payerInfo": {
              "name": "Joao Silva",
              "document": "11122233344"
            }
          }
        }
      }
      ```

  - name: Formato dos Webhooks
    description: |
      Exemplos dos payloads enviados pelo Arbor nos callbacks.

      Esta seção é apenas referência de formato. Os callbacks são enviados via `POST`
      para a URL configurada no webhook ou no `postbackUrl`.

paths:

  # ─────────────────────────────────────────────
  # AUTENTICAÇÃO
  # ─────────────────────────────────────────────
  /api/auth:
    post:
      tags:
        - Autenticação
      summary: Gerar token de acesso
      operationId: auth-generate-token
      description: |
        Gera um **token JWT** para autenticar as demais requisições da API.

        ### Como montar o header Basic Auth

        Concatene `client_id:client_secret` e codifique em **Base64**:

        ```bash
        echo -n "SUA_API_KEY:SEU_API_SECRET" | base64
        # → U1VBX0FQSV9LRVk6U0VVX0FQSV9TRUNSRVQ=
        ```

        Então envie no header:
        ```
        Authorization: Basic U1VBX0FQSV9LRVk6U0VVX0FQSV9TRUNSRVQ=
        ```

        > ⚠️ Esta rota **não aceita body**. Apenas o header `Authorization`.

        ### Token retornado

        Use o `token` retornado como Bearer em todas as outras rotas:
        ```
        Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
        ```
      security:
        - basicAuth: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/auth' \
              -H 'Authorization: Basic SEU_BASIC_AUTH_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const credentials = Buffer.from('SUA_API_KEY:SEU_API_SECRET').toString('base64')

            const response = await fetch('https://api.pay.sejaarbor.com/api/auth', {
              method: 'POST',
              headers: {
                'Authorization': `Basic ${credentials}`
              }
            })

            const { token } = await response.json()
        - lang: Python
          label: Python
          source: |
            import requests
            import base64

            credentials = base64.b64encode(b'SUA_API_KEY:SEU_API_SECRET').decode()

            response = requests.post(
                'https://api.pay.sejaarbor.com/api/auth',
                headers={'Authorization': f'Basic {credentials}'}
            )

            token = response.json()['token']
        - lang: PHP
          label: PHP
          source: |
            $credentials = base64_encode('SUA_API_KEY:SEU_API_SECRET');

            $ch = curl_init('https://api.pay.sejaarbor.com/api/auth');
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Basic ' . $credentials
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
            $token = $response['token'];
      responses:
        '200':
          description: Token gerado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  token:
                    type: string
                    description: JWT Bearer token — use em todas as próximas requisições
                    example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiY29tcGFueSI6eyJpZCI6ImNseDFhYmMifX19..."
                  tokenType:
                    type: string
                    example: "Bearer"
                  expiresIn:
                    type: integer
                    description: Tempo de expiração em milissegundos
                    example: 60000
              example:
                success: true
                token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                tokenType: "Bearer"
                expiresIn: 60000
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  # ─────────────────────────────────────────────
  # PIX IN
  # ─────────────────────────────────────────────
  /api/v1/pix/in/qrcode:
    post:
      tags:
        - PIX IN
      summary: Criar QR Code PIX
      operationId: pix-in-create-qrcode
      description: |
        Cria um **QR Code PIX** para receber um pagamento imediato.

        Retorna o **código EMV** (copia e cola) e a **imagem Base64** do QR Code,
        prontos para exibir ao seu cliente.

        ### Fluxo completo

        ```
        1. Sua aplicação chama POST /api/v1/pix/in/qrcode
        2. O Arbor retorna emv + qrCode (Base64)
        3. Você exibe o QR Code ao cliente
        4. Cliente paga no app do banco
        5. O Arbor envia notificação no postbackUrl (se informado)
        ```

        ### Recebendo a confirmação

        Configure `postbackUrl` para receber uma notificação automática quando o pagamento
        for confirmado, sem precisar fazer polling.
      security:
        - bearerAuth: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
              -H 'Content-Type: application/json' \
              -d '{
                "amountInCents": 5000,
                "description": "Pagamento de serviços",
                "postbackUrl": "https://exemplo.com.br/webhook",
                "customer": {
                  "name": "João Silva",
                  "email": "joao@exemplo.com",
                  "documentType": "cpf",
                  "document": "11122233344",
                  "phone": "(11) 91234-5678"
                }
              }'
        - lang: JavaScript
          label: Node.js
          source: |
            const response = await fetch('https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode', {
              method: 'POST',
              headers: {
                'Authorization': 'Bearer SEU_TOKEN_AQUI',
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                amountInCents: 5000,
                description: 'Pagamento de serviços',
                postbackUrl: 'https://exemplo.com.br/webhook',
                customer: {
                  name: 'João Silva',
                  email: 'joao@exemplo.com',
                  documentType: 'cpf',
                  document: '11122233344',
                  phone: '(11) 91234-5678'
                }
              })
            })

            const { data } = await response.json()
            console.log(data.pix.emv)    // código copia e cola
            console.log(data.pix.qrCode) // imagem Base64
        - lang: Python
          label: Python
          source: |
            import requests

            response = requests.post(
                'https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode',
                headers={
                    'Authorization': 'Bearer SEU_TOKEN_AQUI',
                    'Content-Type': 'application/json'
                },
                json={
                    'amountInCents': 5000,
                    'description': 'Pagamento de servicos',
                    'postbackUrl': 'https://exemplo.com.br/webhook',
                    'customer': {
                        'name': 'Joao Silva',
                        'email': 'joao@exemplo.com',
                        'documentType': 'cpf',
                        'document': '11122233344',
                        'phone': '(11) 91234-5678'
                    }
                }
            )

            data = response.json()['data']
            print(data['pix']['emv'])
        - lang: PHP
          label: PHP
          source: |
            $payload = json_encode([
                'amountInCents' => 5000,
                'description' => 'Pagamento de servicos',
                'postbackUrl' => 'https://exemplo.com.br/webhook',
                'customer' => [
                    'name' => 'Joao Silva',
                    'email' => 'joao@exemplo.com',
                    'documentType' => 'cpf',
                    'document' => '11122233344',
                    'phone' => '(11) 91234-5678'
                ]
            ]);

            $ch = curl_init('https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode');
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI',
                'Content-Type: application/json'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amountInCents
                - customer
              properties:
                amountInCents:
                  type: integer
                  minimum: 100
                  description: Valor da cobrança em centavos (mínimo R$ 1,00)
                  example: 5000
                description:
                  type: string
                  maxLength: 140
                  description: Descrição que aparecerá no extrato do pagador
                  example: "Pagamento de serviços"
                postbackUrl:
                  type: string
                  format: uri
                  description: URL para receber notificação quando o pagamento for confirmado
                  example: "https://exemplo.com.br/webhook"
                customer:
                  $ref: '#/components/schemas/Customer'
                items:
                  type: array
                  description: Itens do pedido (opcional)
                  items:
                    type: object
                    required:
                      - title
                      - tangible
                      - quantity
                      - amountInCents
                    properties:
                      title:
                        type: string
                        example: "Produto A"
                      tangible:
                        type: boolean
                        example: true
                      quantity:
                        type: integer
                        example: 2
                      amountInCents:
                        type: integer
                        example: 2500
                      shippingAddress:
                        $ref: '#/components/schemas/Address'
                trackingParameters:
                  $ref: '#/components/schemas/TrackingParameters'
                splits:
                  type: array
                  description: |
                    Distribui automaticamente parte do valor líquido para outras empresas Arbor.
                    A soma dos percentuais não pode ultrapassar 100%.
                    O split é executado no momento em que o pagamento é confirmado.
                  items:
                    $ref: '#/components/schemas/SplitItem'
            examples:
              minimo:
                summary: Cobrança mínima
                value:
                  amountInCents: 5000
                  customer:
                    name: "João Silva"
                    email: "joao@exemplo.com"
                    documentType: "cpf"
                    document: "11122233344"
                    phone: "(11) 91234-5678"
              completo:
                summary: Cobrança com itens e webhook
                value:
                  amountInCents: 15000
                  description: "Pedido #1234"
                  postbackUrl: "https://exemplo.com.br/webhook"
                  trackingParameters:
                    src: "facebook"
                    sck: "campanha-setembro"
                    utm_source: "facebook"
                    utm_campaign: "lancamento"
                    utm_medium: "cpc"
                    utm_content: "criativo-a"
                    utm_term: "pix"
                  customer:
                    name: "Maria Souza"
                    email: "maria@exemplo.com"
                    documentType: "cpf"
                    document: "99988877766"
                    phone: "(21) 98765-4321"
                  items:
                    - title: "Produto A"
                      tangible: true
                      quantity: 2
                      amountInCents: 5000
                    - title: "Produto B"
                      tangible: true
                      quantity: 1
                      amountInCents: 5000
              comSplit:
                summary: Cobrança com split entre parceiros
                value:
                  amountInCents: 20000
                  description: "Venda marketplace #5678"
                  postbackUrl: "https://exemplo.com.br/webhook"
                  customer:
                    name: "Carlos Ferreira"
                    email: "carlos@exemplo.com"
                    documentType: "cpf"
                    document: "33344455566"
                    phone: "(31) 97777-8888"
                  splits:
                    - email: "parceiro1@empresa.com"
                      percentage: 30
                    - email: "parceiro2@empresa.com"
                      percentage: 15
      responses:
        '200':
          description: QR Code PIX criado com sucesso
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixQRCodeResponse'
              example:
                success: true
                message: "Transação criada com sucesso"
                data:
                  id: "trx_1a2b3c4d5e6f7g8h9i0j"
                  pix:
                    emv: "00020126580014br.gov.bcb.pix0136..."
                    qrCode: "iVBORw0KGgo..."
                  status: "pending"
                  fees: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/pix/in/qrcode/{transactionId}:
    get:
      tags:
        - PIX IN
      summary: Consultar QR Code PIX
      operationId: pix-in-get-qrcode
      description: |
        Consulta uma cobrança PIX criada anteriormente e retorna o **EMV** e a
        **imagem Base64** do QR Code.

        Use o `id` retornado na criação do QR Code.
      security:
        - bearerAuth: []
      parameters:
        - name: transactionId
          in: path
          required: true
          description: ID da transação retornado na criação do QR Code
          schema:
            type: string
            example: "trx_1a2b3c4d5e6f7g8h9i0j"
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X GET 'https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/trx_1a2b3c4d5e6f7g8h9i0j' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const transactionId = 'trx_1a2b3c4d5e6f7g8h9i0j'

            const response = await fetch(
              `https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/${transactionId}`,
              {
                headers: { 'Authorization': 'Bearer SEU_TOKEN_AQUI' }
              }
            )

            const { data } = await response.json()
            console.log(data.pix.emv)
            console.log(data.pix.qrCode)
        - lang: Python
          label: Python
          source: |
            import requests

            transaction_id = 'trx_1a2b3c4d5e6f7g8h9i0j'
            response = requests.get(
                f'https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/{transaction_id}',
                headers={'Authorization': 'Bearer SEU_TOKEN_AQUI'}
            )

            data = response.json()['data']
            print(data['pix']['emv'])
        - lang: PHP
          label: PHP
          source: |
            $transactionId = 'trx_1a2b3c4d5e6f7g8h9i0j';

            $ch = curl_init("https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/{$transactionId}");
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      responses:
        '200':
          description: QR Code PIX encontrado com sucesso
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixQRCodeResponse'
              example:
                success: true
                message: "QR Code PIX encontrado com sucesso"
                data:
                  id: "trx_1a2b3c4d5e6f7g8h9i0j"
                  pix:
                    emv: "00020126580014br.gov.bcb.pix0136..."
                    qrCode: "iVBORw0KGgo..."
                  status: "pending"
                  fees: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: QR Code PIX não encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "NOT_FOUND"
                message: "QR Code PIX não encontrado"
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/pix/in/qrcode/{transactionId}/pay:
    post:
      tags:
        - Sandbox
      summary: Simular pagamento PIX (sandbox)
      operationId: sandbox-pix-in-pay
      description: |
        Confirma manualmente o pagamento de uma cobrança PIX criada com uma chave
        **sandbox**. Dispara o mesmo webhook `transaction_paid` que produção enviaria.

        Retorna `403 Forbidden` se a transação não pertencer a uma chave sandbox, e
        `400 Bad Request` se ela já não estiver mais `pending`.
      security:
        - bearerAuth: []
      parameters:
        - name: transactionId
          in: path
          required: true
          description: ID da transação retornado na criação do QR Code
          schema:
            type: string
            example: "trx_1a2b3c4d5e6f7g8h9i0j"
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/trx_1a2b3c4d5e6f7g8h9i0j/pay' \
              -H 'Authorization: Bearer SEU_TOKEN_SANDBOX_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const transactionId = 'trx_1a2b3c4d5e6f7g8h9i0j'

            const response = await fetch(
              `https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/${transactionId}/pay`,
              {
                method: 'POST',
                headers: { 'Authorization': 'Bearer SEU_TOKEN_SANDBOX_AQUI' }
              }
            )

            const { data } = await response.json()
            console.log(data.status)
        - lang: Python
          label: Python
          source: |
            import requests

            transaction_id = 'trx_1a2b3c4d5e6f7g8h9i0j'
            response = requests.post(
                f'https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/{transaction_id}/pay',
                headers={'Authorization': 'Bearer SEU_TOKEN_SANDBOX_AQUI'}
            )

            data = response.json()['data']
            print(data['status'])
        - lang: PHP
          label: PHP
          source: |
            $transactionId = 'trx_1a2b3c4d5e6f7g8h9i0j';

            $ch = curl_init("https://api.pay.sejaarbor.com/api/v1/pix/in/qrcode/{$transactionId}/pay");
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_SANDBOX_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      responses:
        '200':
          description: Pagamento simulado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      status:
                        type: string
                      endToEndId:
                        type: string
              example:
                success: true
                message: "Pagamento simulado com sucesso"
                data:
                  id: "trx_1a2b3c4d5e6f7g8h9i0j"
                  status: "paid"
                  endToEndId: "E0000000020260810144500ABCDE123456"
        '400':
          description: Transação não está mais pendente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Transação não pertence a uma chave sandbox
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Transação não encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'

  # ─────────────────────────────────────────────
  # PIX OUT
  # ─────────────────────────────────────────────
  /api/v1/pix/out/pixkey:
    post:
      tags:
        - PIX OUT
      summary: Enviar transferência PIX
      operationId: pix-out-send-pixkey
      description: |
        Realiza uma **transferência PIX** para qualquer chave PIX.

        ### Tipos de chave PIX aceitos

        | Tipo | Formato | Exemplo |
        |------|---------|---------|
        | CPF | Apenas dígitos | `11122233344` |
        | CNPJ | Apenas dígitos | `11222333000181` |
        | E-mail | Email válido | `destino@email.com` |
        | Telefone | Com `+55` | `+5511999999999` |
        | Chave aleatória | UUID | `123e4567-e89b-12d3-a456-426614174000` |

        ### Idempotência obrigatória

        Envie um **UUID único** no header `x-idempotency-key` para cada nova transferência.
        Se a requisição falhar, reuse o mesmo UUID no retry — isso garante que a transferência
        não seja duplicada.

        ```
        x-idempotency-key: 550e8400-e29b-41d4-a716-446655440000
        ```

        > ⚠️ A transferência é irreversível após processamento. Verifique a chave PIX antes de enviar.
      security:
        - bearerAuth: []
      parameters:
        - name: x-idempotency-key
          in: header
          required: true
          description: UUID único para garantir que a transferência não seja duplicada em caso de retry
          schema:
            type: string
            example: "550e8400-e29b-41d4-a716-446655440000"
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
              -H 'Content-Type: application/json' \
              -H 'x-idempotency-key: 550e8400-e29b-41d4-a716-446655440000' \
              -d '{
                "pixKey": "fornecedor@exemplo.com",
                "amount": 10000,
                "currency": "BRL",
                "description": "Pagamento de fornecedor",
                "postbackUrl": "https://exemplo.com.br/webhook/pix-out"
              }'
        - lang: JavaScript
          label: Node.js
          source: |
            const { randomUUID } = require('crypto')

            const response = await fetch('https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey', {
              method: 'POST',
              headers: {
                'Authorization': 'Bearer SEU_TOKEN_AQUI',
                'Content-Type': 'application/json',
                'x-idempotency-key': randomUUID()
              },
              body: JSON.stringify({
                pixKey: 'fornecedor@exemplo.com',
                amount: 10000,
                currency: 'BRL',
                description: 'Pagamento de fornecedor',
                postbackUrl: 'https://exemplo.com.br/webhook/pix-out'
              })
            })

            const { data } = await response.json()
            console.log(data.status) // 'queued'
        - lang: Python
          label: Python
          source: |
            import requests
            import uuid

            response = requests.post(
                'https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey',
                headers={
                    'Authorization': 'Bearer SEU_TOKEN_AQUI',
                    'Content-Type': 'application/json',
                    'x-idempotency-key': str(uuid.uuid4())
                },
                json={
                    'pixKey': 'fornecedor@exemplo.com',
                    'amount': 10000,
                    'currency': 'BRL',
                    'description': 'Pagamento de fornecedor',
                    'postbackUrl': 'https://exemplo.com.br/webhook/pix-out'
                }
            )

            data = response.json()['data']
            print(data['status'])
        - lang: PHP
          label: PHP
          source: |
            $payload = json_encode([
                'pixKey' => 'fornecedor@exemplo.com',
                'amount' => 10000,
                'currency' => 'BRL',
                'description' => 'Pagamento de fornecedor',
                'postbackUrl' => 'https://exemplo.com.br/webhook/pix-out'
            ]);

            $ch = curl_init('https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey');
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI',
                'Content-Type: application/json',
                'x-idempotency-key: ' . sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
                    mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
                    mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
                    mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff))
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pixKey
                - amount
              properties:
                pixKey:
                  type: string
                  description: Chave PIX de destino (CPF, CNPJ, email, telefone ou chave aleatória)
                  example: "fornecedor@exemplo.com"
                amount:
                  type: integer
                  minimum: 1
                  description: Valor da transferência em centavos
                  example: 10000
                currency:
                  type: string
                  description: Moeda (apenas BRL aceito)
                  default: "BRL"
                  example: "BRL"
                description:
                  type: string
                  maxLength: 140
                  description: Descrição da transferência
                  example: "Pagamento de fornecedor"
                postbackUrl:
                  type: string
                  format: uri
                  description: URL para receber notificação de status da transferência
                  example: "https://exemplo.com.br/webhook/pix-out"
            examples:
              email:
                summary: Transferência para e-mail
                value:
                  pixKey: "fornecedor@exemplo.com"
                  amount: 10000
                  currency: "BRL"
                  description: "Pagamento de fornecedor"
                  postbackUrl: "https://exemplo.com.br/webhook/pix-out"
              cpf:
                summary: Transferência para CPF
                value:
                  pixKey: "11122233344"
                  amount: 5000
                  currency: "BRL"
                  description: "Reembolso"
              chaveAleatoria:
                summary: Chave aleatória
                value:
                  pixKey: "123e4567-e89b-12d3-a456-426614174000"
                  amount: 25000
                  currency: "BRL"
      responses:
        '200':
          description: Transferência iniciada com sucesso
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixTransferResponse'
              example:
                success: true
                message: "Transferência criada com sucesso"
                data:
                  id: "transfer_abc123def456"
                  status: "pending"
                  amount: 10000
                  netAmount: 10000
                  fees: 0
                  pixKey: "fornecedor@exemplo.com"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Chave de idempotência já utilizada — operação não duplicada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "IDEMPOTENCY_CONFLICT"
                message: "Esta chave de idempotência já foi utilizada"
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/pix/out/pixkey/{transferId}:
    get:
      tags:
        - PIX OUT
      summary: Consultar transferência PIX
      operationId: pix-out-get-pixkey
      description: |
        Consulta uma transferência PIX criada anteriormente e retorna o status atual.

        Use o `id` retornado na criação da transferência PIX OUT.
      security:
        - bearerAuth: []
      parameters:
        - name: transferId
          in: path
          required: true
          description: ID da transferência retornado na criação do PIX OUT
          schema:
            type: string
            example: "transfer_abc123def456"
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X GET 'https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/transfer_abc123def456' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const transferId = 'transfer_abc123def456'

            const response = await fetch(
              `https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/${transferId}`,
              {
                headers: { 'Authorization': 'Bearer SEU_TOKEN_AQUI' }
              }
            )

            const { data } = await response.json()
            console.log(data.status)
        - lang: Python
          label: Python
          source: |
            import requests

            transfer_id = 'transfer_abc123def456'
            response = requests.get(
                f'https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/{transfer_id}',
                headers={'Authorization': 'Bearer SEU_TOKEN_AQUI'}
            )

            data = response.json()['data']
            print(data['status'])
        - lang: PHP
          label: PHP
          source: |
            $transferId = 'transfer_abc123def456';

            $ch = curl_init("https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/{$transferId}");
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      responses:
        '200':
          description: Transferência PIX encontrada com sucesso
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixTransferGetResponse'
              example:
                success: true
                data:
                  id: "transfer_abc123def456"
                  status: "completed"
                  amount: 10000
                  netAmount: 10000
                  fees: 0
                  pixKey: "fornecedor@exemplo.com"
                  endToEndId: "E1234567820241231000000000000002"
                  createdAt: "2026-01-15T13:44:25.838Z"
                  updatedAt: "2026-01-15T13:45:10.120Z"
                  processedAt: "2026-01-15T13:45:10.120Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Transferência PIX não encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "NOT_FOUND"
                message: "Transferência não encontrada"
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/pix/out/pixkey/{transferId}/complete:
    post:
      tags:
        - Sandbox
      summary: Simular conclusão de transferência PIX (sandbox)
      operationId: sandbox-pix-out-complete
      description: |
        Confirma manualmente a conclusão de uma transferência PIX criada com uma chave
        **sandbox**. Dispara o mesmo webhook `transfer_completed` que produção enviaria.

        No sandbox a transferência não passa por análise manual: vai direto para
        `banking_processing` e fica lá até este endpoint ser chamado. O saldo do sandbox
        precisa cobrir o valor — confirme antes uma cobrança com `/pay`.

        Retorna `403 Forbidden` se a transferência não pertencer a uma chave sandbox.
      security:
        - bearerAuth: []
      parameters:
        - name: transferId
          in: path
          required: true
          description: ID da transferência retornado na criação do PIX OUT
          schema:
            type: string
            example: "transfer_abc123def456"
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/transfer_abc123def456/complete' \
              -H 'Authorization: Bearer SEU_TOKEN_SANDBOX_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const transferId = 'transfer_abc123def456'

            const response = await fetch(
              `https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/${transferId}/complete`,
              {
                method: 'POST',
                headers: { 'Authorization': 'Bearer SEU_TOKEN_SANDBOX_AQUI' }
              }
            )

            const { data } = await response.json()
            console.log(data.status)
        - lang: Python
          label: Python
          source: |
            import requests

            transfer_id = 'transfer_abc123def456'
            response = requests.post(
                f'https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/{transfer_id}/complete',
                headers={'Authorization': 'Bearer SEU_TOKEN_SANDBOX_AQUI'}
            )

            data = response.json()['data']
            print(data['status'])
        - lang: PHP
          label: PHP
          source: |
            $transferId = 'transfer_abc123def456';

            $ch = curl_init("https://api.pay.sejaarbor.com/api/v1/pix/out/pixkey/{$transferId}/complete");
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_SANDBOX_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      responses:
        '200':
          description: Transferência concluída com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      status:
                        type: string
                      endToEndId:
                        type: string
              example:
                success: true
                message: "Transferência concluída com sucesso"
                data:
                  id: "transfer_abc123def456"
                  status: "completed"
                  endToEndId: "E0000000020260810144500ABCDE654321"
        '400':
          description: Transferência já concluída/cancelada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Transferência não pertence a uma chave sandbox
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Transferência não encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'

  # ─────────────────────────────────────────────
  # CARTÃO
  # ─────────────────────────────────────────────
  /api/v1/card:
    post:
      tags:
        - Cartão
      summary: Cobrar cartão de crédito internacional
      operationId: card-create
      description: |
        Cria uma cobrança de **cartão de crédito internacional** (USD/EUR), à vista.
        Cartão **nacional** (BRL) não está disponível por esta API — só pelo Checkout
        Hospedado.

        ### Fluxo completo

        ```
        1. No navegador do comprador, capture o cartão com o Payment Element
           (ver aba "Cartão" acima) usando sua chave publicável
        2. elements.submit() e depois createConfirmationToken({ elements })
        3. Você recebe confirmationToken.id (formato ctoken_...)
        4. Sua aplicação chama POST /api/v1/card com card.token = esse id e
           currency "USD" ou "EUR"
        5. O Arbor retorna o resultado — normalmente já síncrono (aprovado ou
           recusado). Em desafio 3DS/SCA, vem requiresAction + clientSecret pra
           completar o desafio no navegador
        ```

        `card.token` (o ConfirmationToken) é **sempre obrigatório** — esta API não
        aceita PAN cru. `card.last4`/`card.brand` são apenas ecoados na resposta como
        você enviou (não são extraídos por esta API) — envie um placeholder se não
        tiver esses dados, ex. `"card"` / `"0000"`.

        ### Sem parcelamento (por enquanto)

        `card.installments` existe no schema mas ainda **não tem efeito** em cartão
        internacional — toda cobrança sai à vista, independente do valor enviado.

        ### Endereço de cobrança obrigatório

        `customer.billingAddress` completo é **obrigatório**. Sem isso a cobrança é
        recusada antes mesmo de chegar no emissor.
      security:
        - bearerAuth: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/v1/card' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
              -H 'Content-Type: application/json' \
              -d '{
                "amountInCents": 1999,
                "currency": "USD",
                "description": "Pedido #1234",
                "card": {
                  "token": "ctoken_1AbCdEfGhIjKlMnOp",
                  "last4": "0000",
                  "brand": "card",
                  "holderName": "John Doe"
                },
                "customer": {
                  "name": "John Doe",
                  "email": "john@example.com",
                  "documentType": "cpf",
                  "document": "11122233344",
                  "phone": "+1 555 123 4567",
                  "billingAddress": {
                    "street": "5th Avenue",
                    "number": "100",
                    "neighborhood": "Manhattan",
                    "city": "New York",
                    "state": "NY",
                    "zipCode": "10001"
                  }
                }
              }'
        - lang: JavaScript
          label: Node.js
          source: |
            // 1) Capturar o cartão no NAVEGADOR do comprador com o Payment Element
            //    (nunca no seu servidor) — peça ao suporte Arbor o guia de integração
            //    const result = await createConfirmationToken({ elements })
            //    const confirmationTokenId = result.confirmationToken.id

            // 2) Cobrar — isso sim roda no seu backend
            const response = await fetch('https://api.pay.sejaarbor.com/api/v1/card', {
              method: 'POST',
              headers: {
                'Authorization': 'Bearer SEU_TOKEN_AQUI',
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                amountInCents: 1999,
                currency: 'USD',
                description: 'Pedido #1234',
                card: {
                  token: confirmationTokenId,
                  last4: '0000',
                  brand: 'card',
                  holderName: 'John Doe'
                },
                customer: {
                  name: 'John Doe',
                  email: 'john@example.com',
                  documentType: 'cpf',
                  document: '11122233344',
                  phone: '+1 555 123 4567',
                  billingAddress: {
                    street: '5th Avenue',
                    number: '100',
                    neighborhood: 'Manhattan',
                    city: 'New York',
                    state: 'NY',
                    zipCode: '10001'
                  }
                }
              })
            })

            const { data } = await response.json()
            console.log(data.status) // "paid" | "pending" | "refused"
        - lang: Python
          label: Python
          source: |
            import requests

            response = requests.post(
                'https://api.pay.sejaarbor.com/api/v1/card',
                headers={
                    'Authorization': 'Bearer SEU_TOKEN_AQUI',
                    'Content-Type': 'application/json'
                },
                json={
                    'amountInCents': 1999,
                    'currency': 'USD',
                    'description': 'Pedido #1234',
                    'card': {
                        'token': confirmation_token_id,
                        'last4': '0000',
                        'brand': 'card',
                        'holderName': 'John Doe'
                    },
                    'customer': {
                        'name': 'John Doe',
                        'email': 'john@example.com',
                        'documentType': 'cpf',
                        'document': '11122233344',
                        'phone': '+1 555 123 4567',
                        'billingAddress': {
                            'street': '5th Avenue',
                            'number': '100',
                            'neighborhood': 'Manhattan',
                            'city': 'New York',
                            'state': 'NY',
                            'zipCode': '10001'
                        }
                    }
                }
            )

            data = response.json()['data']
            print(data['status'])
        - lang: PHP
          label: PHP
          source: |
            $payload = json_encode([
                'amountInCents' => 1999,
                'currency' => 'USD',
                'description' => 'Pedido #1234',
                'card' => [
                    'token' => $confirmationTokenId,
                    'last4' => '0000',
                    'brand' => 'card',
                    'holderName' => 'John Doe'
                ],
                'customer' => [
                    'name' => 'John Doe',
                    'email' => 'john@example.com',
                    'documentType' => 'cpf',
                    'document' => '11122233344',
                    'phone' => '+1 555 123 4567',
                    'billingAddress' => [
                        'street' => '5th Avenue',
                        'number' => '100',
                        'neighborhood' => 'Manhattan',
                        'city' => 'New York',
                        'state' => 'NY',
                        'zipCode' => '10001'
                    ]
                ]
            ]);

            $ch = curl_init('https://api.pay.sejaarbor.com/api/v1/card');
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI',
                'Content-Type: application/json'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amountInCents
                - currency
                - card
                - customer
              properties:
                amountInCents:
                  type: integer
                  minimum: 100
                  description: Valor total da cobrança em centavos, na moeda de `currency`
                  example: 1999
                currency:
                  type: string
                  enum: [USD, EUR]
                  description: Cartão nacional (BRL) não está disponível por esta API — só USD/EUR.
                  example: "USD"
                description:
                  type: string
                  description: Descrição interna do pedido
                  example: "Pedido #1234"
                postbackUrl:
                  type: string
                  format: uri
                  description: URL para receber notificação quando o status mudar
                  example: "https://exemplo.com/webhook"
                card:
                  type: object
                  required:
                    - token
                    - last4
                    - brand
                    - holderName
                  description: |
                    Esta API só aceita cartão **tokenizado** (`token`) — PAN cru
                    (`number`/`cvv`/`expirationDate`) não é suportado pra cartão
                    internacional.
                  properties:
                    token:
                      type: string
                      description: ConfirmationToken (ver aba Cartão) — formato ctoken_...
                      example: "ctoken_1AbCdEfGhIjKlMnOp"
                    last4:
                      type: string
                      description: |
                        Ecoado na resposta como enviado — não é validado nem extraído
                        por esta API. Envie um placeholder se não tiver o dado.
                      example: "0000"
                    brand:
                      type: string
                      description: Mesmo espírito de `last4` — ecoado, não validado.
                      example: "card"
                    holderName:
                      type: string
                      description: Nome impresso no cartão
                      example: "John Doe"
                    installments:
                      type: integer
                      minimum: 1
                      maximum: 12
                      default: 1
                      description: Aceito pelo schema, mas ainda sem efeito em cartão internacional — sempre cobrado à vista.
                      example: 1
                trackingParameters:
                  $ref: '#/components/schemas/TrackingParameters'
                customer:
                  allOf:
                    - $ref: '#/components/schemas/Customer'
                  required:
                    - billingAddress
                  description: |
                    Igual ao `customer` do PIX, mas com `billingAddress` **obrigatório**
                    pra cartão.
            examples:
              internacional:
                summary: Cartão internacional (USD)
                value:
                  amountInCents: 1999
                  currency: "USD"
                  description: "Pedido #1234"
                  card:
                    token: "ctoken_1AbCdEfGhIjKlMnOp"
                    last4: "0000"
                    brand: "card"
                    holderName: "John Doe"
                  trackingParameters:
                    src: "google"
                    sck: "ads-us"
                    utm_source: "google"
                    utm_campaign: "launch"
                    utm_medium: "cpc"
                    utm_content: "creative-a"
                    utm_term: "card"
                  customer:
                    name: "John Doe"
                    email: "john@example.com"
                    documentType: "cpf"
                    document: "11122233344"
                    phone: "+1 555 123 4567"
                    billingAddress:
                      street: "5th Avenue"
                      number: "100"
                      neighborhood: "Manhattan"
                      city: "New York"
                      state: "NY"
                      zipCode: "10001"
      responses:
        '201':
          description: Cobrança processada — confira `data.status` para o resultado
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      card:
                        type: object
                        properties:
                          token:
                            type: string
                          brand:
                            type: string
                          last4:
                            type: string
                      status:
                        type: string
                        enum: [paid, pending, refused, canceled]
                        description: |
                          `paid`/`refused` normalmente já vêm síncronos nesta resposta.
                          `pending` com `requiresAction: true` significa desafio 3DS/SCA —
                          complete com `handleNextAction({ clientSecret })` no
                          navegador antes de considerar recusada.
                      requiresAction:
                        type: boolean
                      clientSecret:
                        type: string
                      fees:
                        type: number
              example:
                success: true
                message: "Transação criada com sucesso"
                data:
                  id: "trx_9f8e7d6c5b4a3f2e1d0c"
                  card:
                    brand: "card"
                    last4: "0000"
                  status: "paid"
                  fees: 87
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/transactions/{transactionId}:
    get:
      tags:
        - PIX IN
        - Cartão
      summary: Consultar transação
      operationId: transactions-get
      description: |
        Consulta uma transação pelo `id` — funciona para cobranças PIX e cartão.
        Use pra confirmar o resultado de cobranças criadas como `pending` (revisão
        antifraude) sem precisar depender só do webhook.
      security:
        - bearerAuth: []
      parameters:
        - name: transactionId
          in: path
          required: true
          schema:
            type: string
            example: "trx_9f8e7d6c5b4a3f2e1d0c"
      responses:
        '200':
          description: Transação encontrada
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    description: Registro completo da transação
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Transação não encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

#   # ─────────────────────────────────────────────
#   # ASSINATURAS
#   # ─────────────────────────────────────────────
#   /api/v1/plans:
#     post:
#       tags:
#         - Assinaturas
#       summary: Criar plano
#       operationId: plans-create
#       description: |
#         Cria um plano — o "molde" reutilizável de uma assinatura (nome, valor, intervalo).
#         Opcional: só use isso se quiser um catálogo fixo de preços; assinaturas avulsas
#         (sem plano) também são aceitas em `POST /api/v1/subscriptions`.
#       security:
#         - bearerAuth: []
#       x-codeSamples:
#         - lang: Shell
#           label: cURL
#           source: |
#             curl -X POST 'https://api.pay.sejaarbor.com/api/v1/plans' \
#               -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
#               -H 'Content-Type: application/json' \
#               -d '{
#                 "name": "Plano mensal",
#                 "amountInCents": 4990,
#                 "interval": "month",
#                 "intervalCount": 1
#               }'
#       requestBody:
#         required: true
#         content:
#           application/json:
#             schema:
#               type: object
#               required: [name, amountInCents, interval]
#               properties:
#                 name:
#                   type: string
#                 description:
#                   type: string
#                 amountInCents:
#                   type: integer
#                   description: Valor cobrado por ciclo, em centavos.
#                 interval:
#                   type: string
#                   enum: [day, week, month, year]
#                 intervalCount:
#                   type: integer
#                   default: 1
#                   description: "Ex.: interval=month + intervalCount=3 = a cada 3 meses."
#                 billingType:
#                   type: string
#                   enum: [prepaid, postpaid, exact_day]
#                   default: prepaid
#       responses:
#         '201':
#           description: Plano criado
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: object
#                     description: Plano criado, incluindo `id`.
#         '400':
#           $ref: '#/components/responses/BadRequest'
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#     get:
#       tags:
#         - Assinaturas
#       summary: Listar planos
#       operationId: plans-list
#       description: Lista os planos da sua empresa.
#       security:
#         - bearerAuth: []
#       parameters:
#         - name: active
#           in: query
#           required: false
#           schema:
#             type: string
#             enum: ["true", "false"]
#       responses:
#         '200':
#           description: Lista de planos
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: array
#                     items:
#                       type: object
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#
#   /api/v1/subscriptions:
#     post:
#       tags:
#         - Assinaturas
#       summary: Criar assinatura
#       operationId: subscriptions-create
#       description: |
#         Assina um cliente — cobrança recorrente automática via cartão. Informe **ou**
#         `planId` (assinatura de plano) **ou** `amountInCents` + `interval` (avulsa), nunca
#         os dois. `card` e `customer` seguem exatamente as mesmas regras de
#         `POST /api/v1/card` — tokenize no navegador, `billingAddress` é obrigatório.
#
#         A partir daqui, cada ciclo é cobrado automaticamente pelo processador — acompanhe
#         pelos webhooks `subscription_charged` / `subscription_payment_failed`, ou pelo
#         evento genérico `transaction_paid` (dispara igual pra cada cobrança de ciclo).
#       security:
#         - bearerAuth: []
#       x-codeSamples:
#         - lang: Shell
#           label: cURL
#           source: |
#             curl -X POST 'https://api.pay.sejaarbor.com/api/v1/subscriptions' \
#               -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
#               -H 'Content-Type: application/json' \
#               -d '{
#                 "planId": "plan_abc123",
#                 "card": {
#                   "token": "token_AljWYxUQNIOV1yKR",
#                   "last4": "0010",
#                   "brand": "Visa",
#                   "holderName": "João Silva"
#                 },
#                 "customer": {
#                   "name": "João Silva",
#                   "email": "joao@exemplo.com",
#                   "documentType": "cpf",
#                   "document": "11122233344",
#                   "billingAddress": {
#                     "street": "Rua Exemplo",
#                     "number": "100",
#                     "neighborhood": "Centro",
#                     "city": "São Paulo",
#                     "state": "SP",
#                     "zipCode": "01310-100"
#                   }
#                 }
#               }'
#       requestBody:
#         required: true
#         content:
#           application/json:
#             schema:
#               type: object
#               required: [customer, card]
#               properties:
#                 planId:
#                   type: string
#                   description: Referencia um plano existente. Omitido = assinatura avulsa.
#                 amountInCents:
#                   type: integer
#                   description: Obrigatório se `planId` não vier.
#                 interval:
#                   type: string
#                   enum: [day, week, month, year]
#                 intervalCount:
#                   type: integer
#                   default: 1
#                 billingType:
#                   type: string
#                   enum: [prepaid, postpaid, exact_day]
#                 description:
#                   type: string
#                 customer:
#                   allOf:
#                     - $ref: '#/components/schemas/Customer'
#                   required:
#                     - billingAddress
#                   description: Igual ao `customer` de `POST /api/v1/card` — `billingAddress` obrigatório.
#                 card:
#                   type: object
#                   description: Mesmo formato de `POST /api/v1/card` — token (recomendado) ou PAN cru.
#       responses:
#         '201':
#           description: Assinatura criada
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: object
#                     properties:
#                       id:
#                         type: string
#                       status:
#                         type: string
#                         enum: [pending, active, past_due, canceled, expired]
#         '400':
#           $ref: '#/components/responses/BadRequest'
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#     get:
#       tags:
#         - Assinaturas
#       summary: Listar assinaturas
#       operationId: subscriptions-list
#       description: Lista as assinaturas da sua empresa, com paginação e filtro por status.
#       security:
#         - bearerAuth: []
#       parameters:
#         - name: status
#           in: query
#           required: false
#           schema:
#             type: string
#             enum: [pending, active, past_due, canceled, expired]
#         - name: offset
#           in: query
#           required: false
#           schema:
#             type: string
#             default: "0"
#       responses:
#         '200':
#           description: Lista de assinaturas
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: array
#                     items:
#                       type: object
#                   totalRows:
#                     type: integer
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#
#   /api/v1/subscriptions/{subscriptionId}:
#     get:
#       tags:
#         - Assinaturas
#       summary: Consultar assinatura
#       operationId: subscriptions-get
#       security:
#         - bearerAuth: []
#       parameters:
#         - name: subscriptionId
#           in: path
#           required: true
#           schema:
#             type: string
#       responses:
#         '200':
#           description: Assinatura encontrada
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: object
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#
#   /api/v1/subscriptions/{subscriptionId}/cancel:
#     post:
#       tags:
#         - Assinaturas
#       summary: Cancelar assinatura
#       operationId: subscriptions-cancel
#       description: |
#         Cancela a assinatura no processador e localmente. O cliente para de ser cobrado a
#         partir do próximo ciclo — ciclos já cobrados não são estornados automaticamente.
#       security:
#         - bearerAuth: []
#       parameters:
#         - name: subscriptionId
#           in: path
#           required: true
#           schema:
#             type: string
#       responses:
#         '200':
#           description: Assinatura cancelada
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: object
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#         '404':
#           description: Assinatura não encontrada
#           content:
#             application/json:
#               schema:
#                 $ref: '#/components/schemas/Error'
#
#   /api/v1/subscriptions/{subscriptionId}/card:
#     patch:
#       tags:
#         - Assinaturas
#       summary: Trocar cartão da assinatura
#       operationId: subscriptions-update-card
#       description: Atualiza o cartão que será cobrado nos próximos ciclos, sem recriar a assinatura.
#       security:
#         - bearerAuth: []
#       parameters:
#         - name: subscriptionId
#           in: path
#           required: true
#           schema:
#             type: string
#       requestBody:
#         required: true
#         content:
#           application/json:
#             schema:
#               type: object
#               required: [card]
#               properties:
#                 card:
#                   type: object
#                   description: Mesmo formato de `POST /api/v1/card` — token (recomendado) ou PAN cru.
#       responses:
#         '200':
#           description: Cartão atualizado
#           content:
#             application/json:
#               schema:
#                 type: object
#                 properties:
#                   success:
#                     type: boolean
#                   data:
#                     type: object
#         '400':
#           $ref: '#/components/responses/BadRequest'
#         '401':
#           $ref: '#/components/responses/Unauthorized'
#
  # ─────────────────────────────────────────────
  # SALDO
  # ─────────────────────────────────────────────
  /api/v1/balance:
    get:
      tags:
        - Saldo
      summary: Consultar saldo
      operationId: balance-get
      description: |
        Retorna os **saldos atuais** das carteiras da empresa autenticada.

        ### Carteiras retornadas

        | Campo | Descrição |
        |-------|-----------|
        | `pix.balance` | Saldo livre disponível para uso e transferências |
        | `pixBlocked.balance` | Saldo bloqueado por cautelar ou MED (indisponível) |
        | `reserve.balance` | Valor em reserva (aguardando liquidação) |

        > Os valores são retornados em **reais** como string com 2 casas decimais (ex: `"150.00"`).
      security:
        - bearerAuth: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X GET 'https://api.pay.sejaarbor.com/api/v1/balance' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const response = await fetch('https://api.pay.sejaarbor.com/api/v1/balance', {
              headers: { 'Authorization': 'Bearer SEU_TOKEN_AQUI' }
            })

            const { data } = await response.json()
            console.log('Saldo disponível: R$', data.pix.balance)
            console.log('Bloqueado: R$',        data.pixBlocked.balance)
            console.log('Reserva: R$',          data.reserve.balance)
        - lang: Python
          label: Python
          source: |
            import requests

            response = requests.get(
                'https://api.pay.sejaarbor.com/api/v1/balance',
                headers={'Authorization': 'Bearer SEU_TOKEN_AQUI'}
            )

            data = response.json()['data']
            print(f"Saldo disponivel: R$ {data['pix']['balance']}")
            print(f"Bloqueado:        R$ {data['pixBlocked']['balance']}")
            print(f"Reserva:          R$ {data['reserve']['balance']}")
        - lang: PHP
          label: PHP
          source: |
            $ch = curl_init('https://api.pay.sejaarbor.com/api/v1/balance');
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            $response = json_decode(curl_exec($ch), true);
            $saldo = $response['data']['pix']['balance'];
            echo "Saldo disponivel: R$ {$saldo}";
      responses:
        '200':
          description: Saldos retornados com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      pix:
                        type: object
                        properties:
                          balance:
                            type: string
                            description: Saldo disponivel em reais (2 casas decimais)
                            example: "1250.00"
                      pixBlocked:
                        type: object
                        properties:
                          balance:
                            type: string
                            description: Saldo bloqueado em reais (cautelar ou MED)
                            example: "0.00"
                      reserve:
                        type: object
                        properties:
                          balance:
                            type: string
                            description: Saldo em reserva em reais
                            example: "300.00"
              example:
                success: true
                data:
                  pix:
                    balance: "1250.00"
                  pixBlocked:
                    balance: "0.00"
                  reserve:
                    balance: "300.00"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  # ─────────────────────────────────────────────
  # FORMATO DOS WEBHOOKS
  # ─────────────────────────────────────────────
  /docs/webhooks/pagamento:
    get:
      tags:
        - Formato dos Webhooks
      summary: Pagamento
      operationId: webhook-format-payment
      description: |
        Exemplo do payload enviado nos callbacks de **PIX IN**.

        Este item é apenas referência de formato. Não é uma rota da API.

        Eventos relacionados:

        | Evento | Quando é enviado |
        |--------|------------------|
        | `transaction_created` | Cobrança PIX criada |
        | `transaction_paid` | Cobrança PIX paga |
        | `transaction_refunded` | Cobrança PIX estornada |
        | `transaction_updated` | Cobrança PIX atualizada com outro status |
        | `infraction` | Infração/MED criada ou atualizada para uma cobrança PIX |
      responses:
        '200':
          description: Exemplo de payload de callback de pagamento
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookPaymentPayload'
              example:
                id: "8d0f3b8f-5c2f-4a10-8dd6-8bbf5e9b1d11"
                type: "transaction"
                event: "transaction_paid"
                scope: "user"
                transaction:
                  id: "transaction_abc123"
                  amount: 5000
                  status: "paid"
                  pix:
                    endToEndId: "E1234567820241231000000000000001"
                    payerInfo:
                      name: "Joao Silva"
                      document: "11122233344"

  /docs/webhooks/transferencia:
    get:
      tags:
        - Formato dos Webhooks
      summary: Transferência
      operationId: webhook-format-transfer
      description: |
        Exemplo do payload enviado nos callbacks de **PIX OUT**.

        Este item é apenas referência de formato. Não é uma rota da API.

        Eventos relacionados:

        | Evento | Quando é enviado |
        |--------|------------------|
        | `transfer_created` | Transferência PIX criada |
        | `transfer_updated` | Transferência PIX atualizada com outro status |
        | `transfer_completed` | Transferência PIX concluída |
        | `transfer_canceled` | Transferência PIX cancelada ou recusada |
      responses:
        '200':
          description: Exemplo de payload de callback de transferência
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookTransferPayload'
              example:
                id: "5f9e0c94-60a4-4df8-bb81-7f6efc6f61b0"
                type: "transfer"
                event: "transfer_completed"
                scope: "user"
                transfer:
                  id: "transfer_abc123"
                  amount: 10000
                  status: "paid"
                  pix:
                    endToEndId: "E1234567820241231000000000000002"
                    creditorAccount:
                      name: "Fornecedor Exemplo"
                      document: "12345678000199"

  # ─────────────────────────────────────────────
  # WEBHOOKS
  # ─────────────────────────────────────────────
  /api/v1/webhook:
    get:
      tags:
        - Webhooks
      summary: Listar webhooks
      operationId: webhooks-list
      description: |
        Retorna todos os **webhooks configurados** na sua conta.
      security:
        - bearerAuth: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X GET 'https://api.pay.sejaarbor.com/api/v1/webhook' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const response = await fetch('https://api.pay.sejaarbor.com/api/v1/webhook', {
              headers: { 'Authorization': 'Bearer SEU_TOKEN_AQUI' }
            })
            const { data } = await response.json()
            const { webhooks } = data
        - lang: Python
          label: Python
          source: |
            import requests

            response = requests.get(
                'https://api.pay.sejaarbor.com/api/v1/webhook',
                headers={'Authorization': 'Bearer SEU_TOKEN_AQUI'}
            )
            webhooks = response.json()['data']['webhooks']
        - lang: PHP
          label: PHP
          source: |
            $ch = curl_init('https://api.pay.sejaarbor.com/api/v1/webhook');
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = json_decode(curl_exec($ch), true);
            $webhooks = $response['data']['webhooks'];
      responses:
        '200':
          description: Lista de webhooks
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      webhooks:
                        type: array
                        items:
                          $ref: '#/components/schemas/Webhook'
                      pagination:
                        type: object
                        additionalProperties: true
              example:
                success: true
                data:
                  webhooks:
                    - id: 123
                      name: "Meu webhook"
                      url: "https://exemplo.com.br/webhook"
                      events:
                        - "transaction_paid"
                        - "transfer_completed"
                      isActive: true
                      signatureSecret: "secret_abc123"
                      mtlsCapable: false
                      createdAt: "2024-12-31T12:00:00Z"
                  pagination:
                    offset: 0
                    pageSize: 10
                    totalCount: 1
                    currentPage: 1
                    totalPages: 1
                    hasNext: false
                    hasPrevious: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

    post:
      tags:
        - Webhooks
      summary: Criar webhook
      operationId: webhooks-create
      description: |
        Configura um novo **endpoint de webhook** para receber notificações de eventos.

        ### Como validar a autenticidade

        O Arbor envia um `signatureSecret` na criação. Use-o para validar
        que as notificações são legítimas via HMAC-SHA256:

        ```javascript
        const crypto = require('crypto')

        function isValidArborWebhook(payload, signature, secret) {
          const [timestampPart, signaturePart] = signature.split(',')
          const timestamp = timestampPart.replace('t=', '')
          const receivedSignature = signaturePart.replace('v1=', '')

          const expected = crypto
            .createHmac('sha256', secret)
            .update(`${timestamp}.${JSON.stringify(payload)}`)
            .digest('hex')

          return expected === receivedSignature
        }
        ```
      security:
        - bearerAuth: []
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X POST 'https://api.pay.sejaarbor.com/api/v1/webhook' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
              -H 'Content-Type: application/json' \
              -d '{
                "url": "https://exemplo.com.br/webhook",
                "events": ["transaction_paid", "transfer_completed"]
              }'
        - lang: JavaScript
          label: Node.js
          source: |
            const response = await fetch('https://api.pay.sejaarbor.com/api/v1/webhook', {
              method: 'POST',
              headers: {
                'Authorization': 'Bearer SEU_TOKEN_AQUI',
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                url: 'https://exemplo.com.br/webhook',
                events: ['transaction_paid', 'transfer_completed']
              })
            })

            const webhook = await response.json()
        - lang: Python
          label: Python
          source: |
            import requests

            response = requests.post(
                'https://api.pay.sejaarbor.com/api/v1/webhook',
                headers={
                    'Authorization': 'Bearer SEU_TOKEN_AQUI',
                    'Content-Type': 'application/json'
                },
                json={
                    'url': 'https://exemplo.com.br/webhook',
                    'events': ['transaction_paid', 'transfer_completed']
                }
            )
        - lang: PHP
          label: PHP
          source: |
            $payload = json_encode([
                'url' => 'https://exemplo.com.br/webhook',
                'events' => ['transaction_paid', 'transfer_completed']
            ]);

            $ch = curl_init('https://api.pay.sejaarbor.com/api/v1/webhook');
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI',
                'Content-Type: application/json'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = json_decode(curl_exec($ch), true);
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - events
              properties:
                url:
                  type: string
                  format: uri
                  description: URL HTTPS que receberá as notificações via POST
                  example: "https://exemplo.com.br/webhook"
                events:
                  type: array
                  minItems: 1
                  description: Eventos que devem disparar a notificação
                  items:
                    type: string
                    enum:
                      - transaction_created
                      - transaction_paid
                      - transaction_refunded
                      - transaction_updated
                      - transfer_created
                      - transfer_updated
                      - transfer_completed
                      - transfer_canceled
                      - infraction
                  example:
                    - "transaction_paid"
                    - "transfer_completed"
            examples:
              basico:
                summary: Apenas pagamentos confirmados
                value:
                  url: "https://exemplo.com.br/webhook"
                  events:
                    - "transaction_paid"
              completo:
                summary: Todos os eventos de transação e transferência
                value:
                  url: "https://exemplo.com.br/webhook"
                  events:
                    - "transaction_created"
                    - "transaction_paid"
                    - "transaction_refunded"
                    - "transaction_updated"
                    - "transfer_created"
                    - "transfer_updated"
                    - "transfer_completed"
                    - "transfer_canceled"
                    - "infraction"
      responses:
        '201':
          description: Webhook criado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/Webhook'
              example:
                success: true
                data:
                  id: 123
                  name: "Meu webhook"
                  url: "https://exemplo.com.br/webhook"
                  events:
                    - "transaction_paid"
                    - "transfer_completed"
                  isActive: true
                  signatureSecret: "secret_abc123"
                  mtlsCapable: false
                  createdAt: "2024-12-31T12:00:00Z"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/webhook/{id}:
    delete:
      tags:
        - Webhooks
      summary: Deletar webhook
      operationId: webhooks-delete
      description: |
        Remove um webhook permanentemente pelo seu ID.

        > ⚠️ Esta ação é **irreversível**. O webhook deixará de receber notificações imediatamente.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID do webhook a ser removido
          schema:
            type: string
            example: "webhook_abc123xyz"
      x-codeSamples:
        - lang: Shell
          label: cURL
          source: |
            curl -X DELETE 'https://api.pay.sejaarbor.com/api/v1/webhook/webhook_abc123xyz' \
              -H 'Authorization: Bearer SEU_TOKEN_AQUI'
        - lang: JavaScript
          label: Node.js
          source: |
            const webhookId = 'webhook_abc123xyz'

            const response = await fetch(
              `https://api.pay.sejaarbor.com/api/v1/webhook/${webhookId}`,
              {
                method: 'DELETE',
                headers: { 'Authorization': 'Bearer SEU_TOKEN_AQUI' }
              }
            )

            if (response.status === 200) {
              console.log('Webhook removido com sucesso')
            }
        - lang: Python
          label: Python
          source: |
            import requests

            webhook_id = 'webhook_abc123xyz'

            response = requests.delete(
                f'https://api.pay.sejaarbor.com/api/v1/webhook/{webhook_id}',
                headers={'Authorization': 'Bearer SEU_TOKEN_AQUI'}
            )

            if response.status_code == 200:
                print('Webhook removido com sucesso')
        - lang: PHP
          label: PHP
          source: |
            $webhookId = 'webhook_abc123xyz';

            $ch = curl_init("https://api.pay.sejaarbor.com/api/v1/webhook/{$webhookId}");
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer SEU_TOKEN_AQUI'
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_exec($ch);

            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if ($httpCode === 200) {
                echo 'Webhook removido com sucesso';
            }
      responses:
        '200':
          description: Webhook removido com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
              example:
                success: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Webhook não encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "NOT_FOUND"
                message: "Webhook não encontrado"
        '429':
          $ref: '#/components/responses/RateLimited'

# ─────────────────────────────────────────────
# COMPONENTES
# ─────────────────────────────────────────────
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        Autenticação Basic para obter o token JWT.
        Use `client_id` como usuário e `client_secret` como senha, codificados em Base64.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Token JWT obtido via `POST /api/auth`.
        Envie no header: `Authorization: Bearer <token>`

  responses:
    Unauthorized:
      description: Token ausente, inválido ou expirado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "UNAUTHORIZED"
            message: "Token de autenticação inválido ou expirado"

    BadRequest:
      description: Dados inválidos na requisição
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "INVALID_REQUEST"
            message: "O campo 'amountInCents' é obrigatório"

    RateLimited:
      description: Limite de requisições excedido
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "RATE_LIMITED"
            message: "Muitas requisições. Tente novamente em alguns minutos."

  schemas:
    TrackingParameters:
      type: object
      description: |
        Rastreamento da campanha, opcional em Pix e cartão. Todos os campos são opcionais.
        O valor enviado aqui prevalece. Se um campo não vier neste objeto, a Arbor tenta
        o mesmo nome na query da URL de origem (header Origin ou Referer) e do `postbackUrl`.
      properties:
        src:
          type: string
          nullable: true
          example: "facebook"
        sck:
          type: string
          nullable: true
          example: "campanha-setembro"
        utm_source:
          type: string
          nullable: true
          example: "facebook"
        utm_campaign:
          type: string
          nullable: true
          example: "lancamento"
        utm_medium:
          type: string
          nullable: true
          example: "cpc"
        utm_content:
          type: string
          nullable: true
          example: "criativo-a"
        utm_term:
          type: string
          nullable: true
          example: "pix"
    Customer:
      type: object
      required:
        - documentType
        - document
      properties:
        name:
          type: string
          description: Nome completo do cliente
          example: "João Silva"
        email:
          type: string
          format: email
          description: E-mail do cliente
          example: "joao@exemplo.com"
        documentType:
          type: string
          enum:
            - cpf
            - cnpj
          description: Tipo do documento
          example: "cpf"
        document:
          type: string
          description: CPF (apenas dígitos) ou CNPJ (apenas dígitos)
          example: "11122233344"
        phone:
          type: string
          description: Telefone do cliente com DDD
          example: "(11) 91234-5678"
        billingAddress:
          $ref: '#/components/schemas/Address'

    Item:
      type: object
      required:
        - title
        - tangible
        - quantity
        - amountInCents
      properties:
        title:
          type: string
          description: Nome do produto ou serviço
          example: "Produto A"
        tangible:
          type: boolean
          description: Indica se o item é físico
          example: true
        quantity:
          type: integer
          minimum: 1
          description: Quantidade do item
          example: 2
        amountInCents:
          type: integer
          description: Valor do item em centavos
          example: 2500
        shippingAddress:
          $ref: '#/components/schemas/Address'

    Address:
      type: object
      properties:
        street:
          type: string
          description: Logradouro
          example: "Rua Exemplo"
        number:
          type: string
          description: Número
          example: "100"
        neighborhood:
          type: string
          description: Bairro
          example: "Centro"
        city:
          type: string
          description: Cidade
          example: "São Paulo"
        state:
          type: string
          description: UF do estado
          example: "SP"
        zipCode:
          type: string
          description: CEP
          example: "00000-000"

    PixQRCodeResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: "Transação criada com sucesso"
        data:
          type: object
          properties:
            id:
              type: string
              description: ID único da transação
              example: "trx_1a2b3c4d5e6f7g8h9i0j"
            pix:
              type: object
              properties:
                emv:
                  type: string
                  description: Código EMV (copia e cola) para pagamento PIX
                  example: "00020126580014br.gov.bcb.pix..."
                qrCode:
                  type: string
                  description: Imagem do QR Code em Base64
                  example: "iVBORw0KGgo..."
            status:
              type: string
              enum:
                - pending
                - paid
                - canceled
                - refunded
              description: Status da transação
              example: "pending"
            fees:
              type: integer
              description: Taxa cobrada em centavos
              example: 0

    PixTransferResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: "Transferência criada com sucesso"
        data:
          type: object
          properties:
            id:
              type: string
              description: ID único da transferência
              example: "transfer_abc123def456"
            status:
              type: string
              enum:
                - queued
                - processing
                - pending
                - pending_analysis
                - banking_processing
                - banking_refused
                - banking_error
                - manual_analysis
                - completed
                - canceled
                - refused
                - error
              description: Status da transferência
              example: "pending"
            amount:
              type: integer
              description: Valor total em centavos
              example: 10000
            netAmount:
              type: integer
              description: Valor líquido após taxas em centavos
              example: 10000
            fees:
              type: integer
              description: Taxas cobradas em centavos
              example: 0
            pixKey:
              type: string
              description: Chave PIX de destino
              example: "fornecedor@exemplo.com"

    PixTransferGetResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            id:
              type: string
              description: ID único da transferência
              example: "transfer_abc123def456"
            status:
              type: string
              enum:
                - pending
                - queued
                - processing
                - pending_analysis
                - banking_processing
                - banking_refused
                - banking_error
                - manual_analysis
                - canceled
                - refused
                - completed
                - error
              description: Status da transferência
              example: "completed"
            amount:
              type: integer
              description: Valor total em centavos
              example: 10000
            netAmount:
              type: integer
              description: Valor líquido após taxas em centavos
              example: 10000
            fees:
              type: integer
              description: Taxas cobradas em centavos
              example: 0
            pixKey:
              type: string
              description: Chave PIX de destino
              example: "fornecedor@exemplo.com"
            endToEndId:
              type: string
              nullable: true
              description: Identificador fim a fim do PIX quando disponível
              example: "E1234567820241231000000000000002"
            createdAt:
              type: string
              format: date-time
              example: "2026-01-15T13:44:25.838Z"
            updatedAt:
              type: string
              format: date-time
              example: "2026-01-15T13:45:10.120Z"
            processedAt:
              type: string
              format: date-time
              nullable: true
              example: "2026-01-15T13:45:10.120Z"

    Webhook:
      type: object
      properties:
        id:
          type: integer
          description: ID único do webhook
          example: 123
        name:
          type: string
          nullable: true
          description: Nome do webhook
          example: "Meu webhook"
        url:
          type: string
          format: uri
          description: URL que recebe as notificações
          example: "https://exemplo.com.br/webhook"
        events:
          type: array
          description: Lista de eventos configurados
          items:
            type: string
          example:
            - "transaction_paid"
            - "transfer_completed"
        isActive:
          type: boolean
          description: Se o webhook está ativo
          example: true
        signatureSecret:
          type: string
          nullable: true
          description: Secret usado para validar a assinatura dos callbacks
          example: "secret_abc123"
        mtlsCapable:
          type: boolean
          nullable: true
          example: false
        createdAt:
          type: string
          format: date-time
          description: Data e hora de criação
          example: "2024-12-31T12:00:00Z"

    WebhookPaymentPayload:
      type: object
      description: Payload enviado nos callbacks de PIX IN.
      properties:
        id:
          type: string
          description: ID único do disparo do webhook
          example: "8d0f3b8f-5c2f-4a10-8dd6-8bbf5e9b1d11"
        type:
          type: string
          enum:
            - transaction
          example: "transaction"
        event:
          type: string
          enum:
            - transaction_created
            - transaction_paid
            - transaction_refunded
            - transaction_updated
          example: "transaction_paid"
        scope:
          type: string
          enum:
            - user
            - postback
            - system
          example: "user"
        transaction:
          type: object
          properties:
            id:
              type: string
              example: "transaction_abc123"
            amount:
              type: integer
              description: Valor em centavos
              example: 5000
            status:
              type: string
              example: "paid"
            pix:
              type: object
              nullable: true
              properties:
                endToEndId:
                  type: string
                  nullable: true
                  example: "E1234567820241231000000000000001"
                payerInfo:
                  type: object
                  nullable: true
                  additionalProperties: true

    WebhookTransferPayload:
      type: object
      description: Payload enviado nos callbacks de PIX OUT.
      properties:
        id:
          type: string
          description: ID único do disparo do webhook
          example: "5f9e0c94-60a4-4df8-bb81-7f6efc6f61b0"
        type:
          type: string
          enum:
            - transfer
          example: "transfer"
        event:
          type: string
          enum:
            - transfer_created
            - transfer_updated
            - transfer_completed
            - transfer_canceled
          example: "transfer_completed"
        scope:
          type: string
          enum:
            - user
            - postback
            - system
          example: "user"
        transfer:
          type: object
          properties:
            id:
              type: string
              example: "transfer_abc123"
            amount:
              type: integer
              description: Valor em centavos
              example: 10000
            status:
              type: string
              example: "paid"
            pix:
              type: object
              properties:
                endToEndId:
                  type: string
                  nullable: true
                  example: "E1234567820241231000000000000002"
                creditorAccount:
                  type: object
                  nullable: true
                  additionalProperties: true

    SplitItem:
      type: object
      required:
        - email
        - percentage
      properties:
        email:
          type: string
          format: email
          description: E-mail da empresa Arbor que receberá o split (deve ter conta ativa)
          example: "parceiro@empresa.com"
        percentage:
          type: number
          minimum: 0.01
          maximum: 100
          description: |
            Percentual do valor liquido a distribuir (0.01 a 100).
            A soma de todos os splits nao pode ultrapassar 100%.
          example: 30

    Error:
      type: object
      properties:
        error:
          type: string
          description: Código do erro
          example: "INVALID_REQUEST"
        message:
          type: string
          description: Descrição do erro
          example: "O campo 'amount' é obrigatório"
        details:
          type: object
          description: Detalhes adicionais do erro
          additionalProperties: true
