openapi: 3.1.0
info:
  title: Trust7 API
  version: 1.3.0
  description: |
    Gateway de pagamentos. Todos os valores monetários são inteiros na unidade
    mínima da moeda, acompanhados sempre do código ISO-4217.
    Endpoints que movem dinheiro exigem `Idempotency-Key`. Chaves secretas são enviadas como Bearer e devem permanecer apenas no backend do seller.
  contact:
    name: Trust7 Engineering
    url: https://docs.trust7.dev
servers:
  - url: https://trust7.dev/v1
    description: producao
  - url: https://api.sandbox.trust7.dev/v1
    description: sandbox
security:
  - bearerAuth: []
tags:
  - name: accounts
  - name: admin
  - name: payments
  - name: refunds
  - name: disputes
  - name: sellers
  - name: payouts
  - name: events
  - name: payment_methods
paths:
  /auth/register:
    post:
      tags: [accounts]
      summary: Cria uma conta de lojista e submete o cadastro para analise
      operationId: registerAccount
      security: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { $ref: '#/components/schemas/RegisterAccountRequest' }
      responses:
        '202':
          description: Cadastro recebido e em analise; nenhuma sessao e emitida ate a aprovacao
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RegistrationReceipt' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '409':
          description: Ja existe uma conta com o email informado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429': { $ref: '#/components/responses/RateLimitError' }
  /auth/login:
    post:
      tags: [accounts]
      summary: Autentica uma conta por email e senha
      operationId: loginAccount
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LoginRequest' }
      responses:
        '200':
          description: Sessao iniciada
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AccountSessionResponse' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '403':
          description: Cadastro ainda em analise (account_pending) ou reprovado (account_suspended)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429': { $ref: '#/components/responses/RateLimitError' }
  /auth/session:
    get:
      tags: [accounts]
      summary: Devolve a conta e a sessao atuais
      operationId: getAccountSession
      responses:
        '200':
          description: Sessao atual
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AccountSessionContext' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '403':
          description: A credencial e valida, mas nao pertence a uma conta do portal
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /auth/logout:
    post:
      tags: [accounts]
      summary: Revoga a sessao atual
      operationId: logoutAccount
      responses:
        '204':
          description: Sessao revogada
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '403':
          description: A credencial e valida, mas nao pertence a uma conta do portal
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /admin/auth/login:
    post:
      tags: [admin]
      summary: Autentica o administrador por usuario e senha no subdominio administrativo
      operationId: loginAdmin
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminLoginRequest' }
      responses:
        '200':
          description: Sessao administrativa iniciada
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminSessionResponse' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '429': { $ref: '#/components/responses/RateLimitError' }
  /admin/auth/session:
    get:
      tags: [admin]
      summary: Devolve o administrador e a sessao atuais
      operationId: getAdminSession
      responses:
        '200':
          description: Sessao administrativa atual
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminSessionContext' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '403':
          description: A credencial e valida, mas nao pertence ao portal administrativo
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /admin/auth/logout:
    post:
      tags: [admin]
      summary: Revoga a sessao administrativa atual
      operationId: logoutAdmin
      responses:
        '204':
          description: Sessao administrativa revogada
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '403':
          description: A credencial e valida, mas nao pertence ao portal administrativo
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /admin/acquirers:
    get:
      tags: [admin]
      summary: Lista somente adquirentes com conector instalado
      operationId: listAdminAcquirers
      responses:
        '200':
          description: Inventario dos gateways implementados e homologados na Trust7
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AcquirerList' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
  /admin/acquirers/{id}:
    get:
      tags: [admin]
      summary: Consulta uma adquirente integrada
      operationId: getAdminAcquirer
      parameters:
        - $ref: '#/components/parameters/PathId'
      responses:
        '200':
          description: Configuracao e uso atual da adquirente
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AcquirerView' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [admin]
      summary: Atualiza a configuracao de uma adquirente instalada
      description: |
        O identificador pertence ao conector implementado no backend; esta operacao nao cria
        gateways arbitrarios. `expected_revision` protege atualizacoes concorrentes.
      operationId: upsertAdminAcquirer
      parameters:
        - $ref: '#/components/parameters/PathId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpsertAcquirerRequest' }
      responses:
        '200':
          description: Adquirente persistida com uma nova revisao
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Acquirer' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/StateConflict' }
  /admin/acquirers/{id}/health:
    patch:
      tags: [admin]
      summary: Atualiza a saude operacional de uma adquirente
      operationId: setAdminAcquirerHealth
      parameters:
        - $ref: '#/components/parameters/PathId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SetAcquirerHealthRequest' }
      responses:
        '200':
          description: Estado de saude persistido com uma nova revisao
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Acquirer' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/StateConflict' }
  /payment-methods:
    get:
      tags: [payment_methods]
      summary: Lista métodos e capacidades disponíveis
      operationId: listPaymentMethods
      responses:
        '200':
          description: Catálogo da v1 conforme contrato e rotas homologadas do lojista
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/PaymentMethodCapability' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
  /payments:
    post:
      tags: [payments]
      summary: Cria um pagamento
      operationId: createPayment
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentRequest'
      responses:
        '201':
          description: Pagamento processado de forma sincrona
          headers:
            Idempotency-Replayed:
              schema: { type: boolean }
              description: Presente quando a resposta veio do cache de idempotencia
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Payment' }
        '202':
          description: Pagamento aceite, aguarda acao do portador ou processamento
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Payment' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '402': { $ref: '#/components/responses/CardError' }
        '409': { $ref: '#/components/responses/IdempotencyError' }
        '429': { $ref: '#/components/responses/RateLimitError' }
        '424': { $ref: '#/components/responses/PaymentDependencyError' }
    get:
      tags: [payments]
      summary: Lista pagamentos
      operationId: listPayments
      parameters:
        - { name: starting_after, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/PaymentStatus' } }
        - { name: created_gte, in: query, schema: { type: string, format: date-time } }
        - { name: created_lte, in: query, schema: { type: string, format: date-time } }
      responses:
        '200':
          description: Pagina de pagamentos
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Page'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/Payment' }
  /payments/{id}:
    get:
      tags: [payments]
      summary: Consulta um pagamento
      operationId: getPayment
      parameters:
        - $ref: '#/components/parameters/PathId'
      responses:
        '200':
          description: Pagamento
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Payment' }
        '404': { $ref: '#/components/responses/NotFound' }
  /payments/{id}/capture:
    post:
      tags: [payments]
      summary: Captura uma autorizacao
      description: |
        Captura total ou parcial. A captura parcial e permitida uma unica vez; o
        valor remanescente e anulado automaticamente.
      operationId: capturePayment
      parameters:
        - $ref: '#/components/parameters/PathId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: integer
                  format: int64
                  minimum: 1
                  description: Omitido captura o valor total autorizado
      responses:
        '200':
          description: Capturado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Payment' }
        '409': { $ref: '#/components/responses/StateConflict' }
  /payments/{id}/void:
    post:
      tags: [payments]
      summary: Cancela uma autorizacao nao capturada
      operationId: voidPayment
      parameters:
        - $ref: '#/components/parameters/PathId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Cancelado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Payment' }
        '409': { $ref: '#/components/responses/StateConflict' }
  /payments/{id}/refunds:
    post:
      tags: [refunds]
      summary: Cria um reembolso
      operationId: createRefund
      parameters:
        - $ref: '#/components/parameters/PathId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: integer
                  format: int64
                  minimum: 1
                  description: Omitido reembolsa o saldo capturado remanescente
                reason:
                  type: string
                  enum: [requested_by_customer, duplicate, fraudulent, product_not_received, other]
      responses:
        '201':
          description: Reembolso criado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Refund' }
        '409': { $ref: '#/components/responses/StateConflict' }
  /webhook-endpoints:
    post:
      tags: [events]
      summary: Cadastra um endpoint de webhook
      operationId: createWebhookEndpoint
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateWebhookEndpointRequest' }
      responses:
        '201':
          description: Endpoint criado; o secret aparece somente nesta resposta
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CreatedWebhookEndpoint' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
    get:
      tags: [events]
      summary: Lista endpoints de webhook sem exibir secrets
      operationId: listWebhookEndpoints
      responses:
        '200':
          description: Endpoints da conta autenticada
          content:
            application/json:
              schema: { $ref: '#/components/schemas/WebhookEndpointList' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
  /webhook-endpoints/{id}:
    delete:
      tags: [events]
      summary: Remove um endpoint de webhook
      operationId: deleteWebhookEndpoint
      parameters:
        - $ref: '#/components/parameters/PathId'
      responses:
        '204': { description: Endpoint removido }
        '401': { $ref: '#/components/responses/AuthenticationError' }
        '404': { $ref: '#/components/responses/NotFound' }
  /webhook-deliveries:
    get:
      tags: [events]
      summary: Lista as entregas de webhook mais recentes
      operationId: listWebhookDeliveries
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
      responses:
        '200':
          description: Historico de entregas da conta autenticada
          content:
            application/json:
              schema: { $ref: '#/components/schemas/WebhookDeliveryList' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/AuthenticationError' }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Chave de API (`key_....sk_...`) ou sessao do portal (`ses_....ss_...`)
  parameters:
    PathId:
      name: id
      in: path
      required: true
      schema: { type: string }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: |
        Chave unica do cliente. Repeticao com o mesmo corpo devolve a resposta
        original; repeticao com corpo diferente devolve 409.
      schema: { type: string, minLength: 8, maxLength: 255 }
  schemas:
    RegisterAccountRequest:
      type: object
      additionalProperties: false
      required: [name, email, password, terms_accepted]
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 120
        email:
          type: string
          format: email
          maxLength: 254
        password:
          type: string
          format: password
          minLength: 8
          maxLength: 128
        terms_accepted:
          type: boolean
          const: true
    RegistrationReceipt:
      type: object
      required: [status, account_id, merchant_id, person_type, message]
      properties:
        status:
          type: string
          const: pending
        account_id:
          type: string
          pattern: '^usr_'
        merchant_id:
          type: string
          pattern: '^mer_'
        person_type:
          type: string
          enum: [individual, company]
        message:
          type: string
    LoginRequest:
      type: object
      additionalProperties: false
      required: [email, password]
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        password:
          type: string
          format: password
          maxLength: 128
    AdminLoginRequest:
      type: object
      additionalProperties: false
      required: [username, password]
      properties:
        username:
          type: string
          minLength: 3
          maxLength: 64
          pattern: '^[A-Za-z0-9._-]+$'
        password:
          type: string
          format: password
          maxLength: 128
    Admin:
      type: object
      required: [id, username, created_at]
      properties:
        id: { type: string, pattern: '^adm_' }
        username: { type: string }
        created_at: { type: string, format: date-time }
    AdminSessionResponse:
      type: object
      required: [token, token_type, expires_at, admin, session]
      properties:
        token:
          type: string
          pattern: '^ses_[^.]+\.ss_.+$'
          description: Token efemero mantido apenas na aba do console administrativo
        token_type: { type: string, const: Bearer }
        expires_at: { type: string, format: date-time }
        admin: { $ref: '#/components/schemas/Admin' }
        session: { $ref: '#/components/schemas/AccountSession' }
    AdminSessionContext:
      type: object
      required: [admin, session]
      properties:
        admin: { $ref: '#/components/schemas/Admin' }
        session: { $ref: '#/components/schemas/AccountSession' }
    Account:
      type: object
      required: [id, name, email, merchant_id, terms_accepted_at, created_at]
      properties:
        id: { type: string, pattern: '^usr_' }
        name: { type: string }
        email: { type: string, format: email }
        merchant_id: { type: string, pattern: '^mer_' }
        terms_accepted_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
    AccountSession:
      type: object
      required: [id, agent, ip, current, created_at, last_seen_at, expires_at]
      properties:
        id: { type: string, pattern: '^ses_' }
        agent: { type: string }
        ip: { type: string }
        current: { type: boolean }
        created_at: { type: string, format: date-time }
        last_seen_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
    AccountSessionResponse:
      type: object
      required: [token, token_type, expires_at, account, session]
      properties:
        token:
          type: string
          pattern: '^ses_[^.]+\.ss_.+$'
          description: Exibido apenas na criacao da sessao; deve ser mantido fora de armazenamento persistente no browser
        token_type: { type: string, const: Bearer }
        expires_at: { type: string, format: date-time }
        account: { $ref: '#/components/schemas/Account' }
        session: { $ref: '#/components/schemas/AccountSession' }
    AccountSessionContext:
      type: object
      required: [account, session]
      properties:
        account: { $ref: '#/components/schemas/Account' }
        session: { $ref: '#/components/schemas/AccountSession' }
    Currency:
      type: string
      pattern: '^[A-Z]{3}$'
      examples: [BRL]
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: integer
          format: int64
          description: Unidade minima da moeda
        currency: { $ref: '#/components/schemas/Currency' }
    PaymentStatus:
      type: string
      enum:
        - created
        - pending_action
        - authorizing
        - authorized
        - captured
        - partially_refunded
        - refunded
        - blocked_med
        - failed
        - canceled
        - expired
        - blocked
    CreateWebhookEndpointRequest:
      type: object
      additionalProperties: false
      required: [url]
      properties:
        url:
          type: string
          format: uri
          description: URL publica HTTP ou HTTPS; producao recusa destinos privados
        events:
          type: array
          uniqueItems: true
          description: Lista vazia ou `*` recebe todos os eventos
          items:
            oneOf:
              - $ref: '#/components/schemas/WebhookEventType'
              - type: string
                const: '*'
    WebhookEndpoint:
      type: object
      required: [id, merchant_id, url, events, disabled, created_at]
      properties:
        id: { type: string, pattern: '^whe_' }
        merchant_id: { type: string, pattern: '^mer_' }
        url: { type: string, format: uri }
        events:
          type: array
          uniqueItems: true
          items:
            oneOf:
              - $ref: '#/components/schemas/WebhookEventType'
              - type: string
                const: '*'
        disabled: { type: boolean }
        created_at: { type: string, format: date-time }
    CreatedWebhookEndpoint:
      allOf:
        - $ref: '#/components/schemas/WebhookEndpoint'
        - type: object
          required: [secret]
          properties:
            secret:
              type: string
              pattern: '^whsec_'
              writeOnly: true
              description: Exibido somente na criacao; guarde antes de fechar a resposta
    WebhookEndpointList:
      type: object
      required: [object, data]
      properties:
        object: { type: string, const: list }
        data:
          type: array
          items: { $ref: '#/components/schemas/WebhookEndpoint' }
    WebhookDelivery:
      type: object
      required: [id, event_id, event_type, endpoint_id, url, status, attempts, created_at]
      properties:
        id: { type: string, pattern: '^whd_' }
        event_id: { type: string, pattern: '^evt_' }
        event_type: { $ref: '#/components/schemas/WebhookEventType' }
        endpoint_id: { type: string, pattern: '^whe_' }
        url: { type: string, format: uri }
        status: { type: string, enum: [pending, delivered, failed] }
        attempts: { type: integer, minimum: 1, maximum: 10 }
        status_code: { type: integer, minimum: 100, maximum: 599 }
        error: { type: string }
        created_at: { type: string, format: date-time }
        completed_at: { type: string, format: date-time }
    WebhookDeliveryList:
      type: object
      required: [object, data]
      properties:
        object: { type: string, const: list }
        data:
          type: array
          items: { $ref: '#/components/schemas/WebhookDelivery' }
    WebhookEvent:
      type: object
      description: |
        Envelope de todo evento entregue por webhook. O consumidor deve deduplicar por `id`
        e descartar evento cujo `sequence` seja inferior ao ja aplicado para o mesmo `object_id`.
      required: [id, type, created_at, sequence, data]
      properties:
        id:
          type: string
          examples: [evt_01J8ZQ7]
        type:
          $ref: '#/components/schemas/WebhookEventType'
        created_at:
          type: string
          format: date-time
        sequence:
          type: integer
          format: int64
          description: Monotonico por objeto; fencing token contra reordenacao.
        object_id:
          type: string
          description: Id do agregado a que o `sequence` se refere.
        api_version:
          type: string
          description: Versao datada em que o payload foi serializado.
          examples: ['2026-01-15']
        data:
          type: object
          required: [object]
          properties:
            object:
              oneOf:
                - $ref: '#/components/schemas/Payment'
                - $ref: '#/components/schemas/Refund'
                - $ref: '#/components/schemas/Dispute'
                - $ref: '#/components/schemas/Payout'
                - $ref: '#/components/schemas/Seller'
                - $ref: '#/components/schemas/MedRetention'

    WebhookEventType:
      type: string
      enum:
        - payment.authorized
        - payment.captured
        - payment.canceled
        - payment.failed
        - payment.pending_action
        - payment.expired
        - payment.blocked_med
        - payment.med_released
        - payment.med_debited
        - refund.created
        - refund.failed
        - dispute.opened
        - dispute.won
        - dispute.lost
        - payout.paid
        - payout.failed
        - payout.returned
        - seller.status_changed

    MedRetention:
      type: object
      description: |
        Retencao cautelar do Mecanismo Especial de Devolucao do Pix (Res. BCB 493/2025).
        Incide sobre valor ja liquidado, e iniciada externamente, nunca por nos.
        Nao confundir com `Dispute`: cadeia regulatoria, prazos e provas sao distintos.
      required: [id, payment_id, amount, currency, status, deadline_at]
      properties:
        id:
          type: string
          examples: [med_01J8ZQA]
        payment_id:
          type: string
        amount:
          type: integer
          format: int64
        currency:
          $ref: '#/components/schemas/Currency'
        status:
          type: string
          enum: [blocked, released, debited]
        layer:
          type: integer
          minimum: 1
          maximum: 5
          description: Camada do rastreamento multicamadas em que o valor foi alcancado.
        deadline_at:
          type: string
          format: date-time
        evidence_required:
          type: array
          items:
            type: string
            enum: [transaction_data, customer_data, delivery_proof, contract, communication_log]
        created_at:
          type: string
          format: date-time

    AcquirerStatus:
      type: string
      enum: [active, inactive, maintenance]
    AcquirerHealth:
      type: string
      enum: [unknown, healthy, degraded, down]
    AcquirerCapability:
      type: string
      enum: [automatic_capture, manual_capture, partial_capture, installments, three_ds, recurring, refund]
    AcquirerFee:
      type: object
      additionalProperties: false
      required: [percent_bps, fixed_cents, settlement_days]
      properties:
        percent_bps:
          type: integer
          minimum: 0
          maximum: 10000
          description: Percentual em basis points; 250 equivale a 2,5%.
        fixed_cents:
          type: integer
          format: int64
          minimum: 0
          description: Parcela fixa na unidade minima da moeda.
        settlement_days:
          type: integer
          minimum: 0
          maximum: 365
          description: Prazo contratual de liquidacao em dias.
    AcquirerMethodConfig:
      type: object
      additionalProperties: false
      required: [enabled, capabilities, supported_currencies, fee]
      properties:
        enabled: { type: boolean }
        capabilities:
          type: array
          uniqueItems: true
          items: { $ref: '#/components/schemas/AcquirerCapability' }
        max_installments:
          type: integer
          minimum: 1
          maximum: 21
          description: Obrigatorio para cartoes habilitados; debito aceita somente 1.
        supported_brands:
          type: array
          uniqueItems: true
          items: { type: string }
        supported_currencies:
          type: array
          uniqueItems: true
          items:
            type: string
            pattern: '^[A-Z]{3}$'
        min_amount:
          type: integer
          format: int64
          minimum: 0
        max_amount:
          type: integer
          format: int64
          minimum: 0
          description: Zero significa que nao existe teto configurado.
        unavailable_reason:
          type: string
          description: Obrigatorio quando o metodo esta desabilitado.
        fee: { $ref: '#/components/schemas/AcquirerFee' }
    AcquirerMethods:
      type: object
      additionalProperties: false
      required: [pix, credit_card, debit_card, boleto]
      properties:
        pix: { $ref: '#/components/schemas/AcquirerMethodConfig' }
        credit_card: { $ref: '#/components/schemas/AcquirerMethodConfig' }
        debit_card: { $ref: '#/components/schemas/AcquirerMethodConfig' }
        boleto: { $ref: '#/components/schemas/AcquirerMethodConfig' }
    Acquirer:
      type: object
      required: [id, name, status, health, priority, methods, revision, created_at, updated_at]
      properties:
        id:
          type: string
          pattern: '^[a-z0-9][a-z0-9_-]+$'
        name:
          type: string
          minLength: 1
          maxLength: 120
        status: { $ref: '#/components/schemas/AcquirerStatus' }
        health: { $ref: '#/components/schemas/AcquirerHealth' }
        priority:
          type: integer
          minimum: 1
          maximum: 1000
          description: Menor numero recebe preferencia no roteamento.
        endpoint: { type: string, format: uri }
        credential_key_id:
          type: string
          description: Referencia da credencial; o segredo nunca integra este contrato.
        methods: { $ref: '#/components/schemas/AcquirerMethods' }
        revision:
          type: integer
          format: uint64
          minimum: 1
        last_checked_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    AcquirerView:
      allOf:
        - $ref: '#/components/schemas/Acquirer'
        - type: object
          required: [assigned_merchants]
          properties:
            assigned_merchants:
              type: integer
              minimum: 0
    AcquirerSummary:
      type: object
      required: [total, active, inactive, maintenance, healthy, degraded, down, routable, unavailable]
      properties:
        total: { type: integer, minimum: 0 }
        active: { type: integer, minimum: 0 }
        inactive: { type: integer, minimum: 0 }
        maintenance: { type: integer, minimum: 0 }
        healthy: { type: integer, minimum: 0 }
        degraded: { type: integer, minimum: 0 }
        down: { type: integer, minimum: 0 }
        routable: { type: integer, minimum: 0 }
        unavailable: { type: integer, minimum: 0 }
    AcquirerList:
      type: object
      required: [object, data, summary]
      properties:
        object: { type: string, const: list }
        data:
          type: array
          items: { $ref: '#/components/schemas/AcquirerView' }
        summary: { $ref: '#/components/schemas/AcquirerSummary' }
    UpsertAcquirerRequest:
      type: object
      additionalProperties: false
      required: [name, status, methods]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        status: { $ref: '#/components/schemas/AcquirerStatus' }
        endpoint: { type: string, format: uri }
        methods: { $ref: '#/components/schemas/AcquirerMethods' }
        expected_revision:
          type: integer
          format: uint64
          minimum: 1
    SetAcquirerHealthRequest:
      type: object
      additionalProperties: false
      required: [health]
      properties:
        health: { $ref: '#/components/schemas/AcquirerHealth' }
        expected_revision:
          type: integer
          format: uint64
          minimum: 1
    PaymentMethodCapability:
      type: object
      required: [method, enabled, capabilities]
      properties:
        method:
          type: string
          enum: [pix, credit_card, debit_card, boleto]
        enabled:
          type: boolean
          description: Verdadeiro somente quando existe rota homologada e habilitada para o lojista
        capabilities:
          type: array
          uniqueItems: true
          items:
            type: string
            enum: [automatic_capture, manual_capture, partial_capture, installments, three_ds, recurring, refund]
        max_installments:
          type: integer
          minimum: 1
          maximum: 21
        supported_brands:
          type: array
          uniqueItems: true
          items: { type: string }
        unavailable_reason:
          type: string
          description: Motivo estável para o método não estar ativo; ausente quando enabled=true

    CreatePaymentRequest:
      type: object
      required: [amount, currency, payment_method]
      properties:
        amount: { type: integer, format: int64, minimum: 1 }
        currency: { $ref: '#/components/schemas/Currency' }
        capture_method:
          type: string
          enum: [automatic, manual]
          default: automatic
        payment_method: { $ref: '#/components/schemas/PaymentMethodInput' }
        customer: { $ref: '#/components/schemas/Customer' }
        statement_descriptor:
          type: string
          maxLength: 22
          description: Aparece na fatura do portador; descritor obscuro gera disputa
        three_ds:
          type: object
          properties:
            preference:
              type: string
              enum: [auto, always, never]
              default: auto
        splits:
          type: array
          items: { $ref: '#/components/schemas/SplitInput' }
        metadata: { $ref: '#/components/schemas/Metadata' }
    PaymentMethodInput:
      oneOf:
        - $ref: '#/components/schemas/CardInput'
        - $ref: '#/components/schemas/PixInput'
        - $ref: '#/components/schemas/BoletoInput'
      discriminator:
        propertyName: type
        mapping:
          card: '#/components/schemas/CardInput'
          pix: '#/components/schemas/PixInput'
          boleto: '#/components/schemas/BoletoInput'
    CardInput:
      type: object
      required: [type, funding, token]
      properties:
        type: { type: string, const: card }
        funding:
          type: string
          enum: [credit, debit]
          description: Debito exige installments=1 e depende de rota explicitamente habilitada
        token:
          type: string
          description: Token do vault. PAN em claro nunca e aceite nesta API.
        installments: { type: integer, minimum: 1, maximum: 21, default: 1 }
        installment_type:
          type: string
          enum: [merchant, issuer]
          default: merchant
          description: merchant = sem juros para o portador; issuer = juros do emissor
      allOf:
        - if:
            properties:
              funding:
                const: debit
            required: [funding]
          then:
            properties:
              installments:
                const: 1
    PixInput:
      type: object
      required: [type]
      properties:
        type: { type: string, const: pix }
        expires_in_seconds: { type: integer, minimum: 60, maximum: 86400, default: 3600 }
    BoletoInput:
      type: object
      required: [type]
      properties:
        type: { type: string, const: boleto }
        due_date: { type: string, format: date }
    Customer:
      type: object
      properties:
        reference: { type: string, description: Identificador do lado do merchant }
        email: { type: string, format: email }
        document:
          type: object
          properties:
            type: { type: string, enum: [cpf, cnpj] }
            value: { type: string, description: Mascarado nas respostas }
    SplitInput:
      type: object
      required: [recipient_id, type]
      properties:
        recipient_id: { type: string }
        type: { type: string, enum: [percentage, flat, residual] }
        value:
          type: integer
          description: |
            Basis points quando `percentage` (7000 = 70%); unidade minima quando
            `flat`; ausente quando `residual`.
        liable_for_chargeback: { type: boolean, default: false }
    Payment:
      type: object
      required: [id, status, amount, currency, created_at]
      properties:
        id: { type: string, examples: [pay_01J8ZQ5MB2] }
        status: { $ref: '#/components/schemas/PaymentStatus' }
        amount: { type: integer, format: int64 }
        amount_captured: { type: integer, format: int64 }
        amount_refunded: { type: integer, format: int64 }
        currency: { $ref: '#/components/schemas/Currency' }
        capture_method: { type: string, enum: [automatic, manual] }
        payment_method: { $ref: '#/components/schemas/PaymentMethodOutput' }
        risk: { $ref: '#/components/schemas/RiskOutcome' }
        three_ds: { $ref: '#/components/schemas/ThreeDSOutcome' }
        acquirer: { $ref: '#/components/schemas/AcquirerOutcome' }
        next_action: { $ref: '#/components/schemas/NextAction' }
        failure: { $ref: '#/components/schemas/Error' }
        splits:
          type: array
          items: { $ref: '#/components/schemas/SplitInput' }
        metadata: { $ref: '#/components/schemas/Metadata' }
        created_at: { type: string, format: date-time }
    PaymentMethodOutput:
      type: object
      properties:
        type: { type: string, enum: [card, pix, boleto] }
        funding: { type: string, enum: [credit, debit] }
        brand: { type: string }
        last4: { type: string, pattern: '^[0-9]{4}$' }
        exp_month: { type: integer, minimum: 1, maximum: 12 }
        exp_year: { type: integer }
        fingerprint:
          type: string
          description: Estavel por cartao, sem permitir reconstruir o PAN
    RiskOutcome:
      type: object
      properties:
        decision: { type: string, enum: [approve, challenge, review, deny] }
        score: { type: integer, minimum: 0, maximum: 100 }
        reasons:
          type: array
          items: { type: string }
    ThreeDSOutcome:
      type: object
      description: |
        `liability_shift` e derivado do ECI devolvido na autorizacao, nunca do ECI
        enviado no pedido. Cartoes pre-pagos nao recebem deslocamento.
      properties:
        status:
          type: string
          enum: [not_attempted, authenticated, attempted, failed, unavailable, rejected]
        eci: { type: string }
        liability_shift: { type: boolean }
        version: { type: string, examples: ['2.2.0'] }
    AcquirerOutcome:
      type: object
      properties:
        name: { type: string }
        authorization_code: { type: string }
        nsu: { type: string }
        network_decline_code: { type: string }
        network_advice_code: { type: string }
    NextAction:
      type: object
      properties:
        type: { type: string, enum: [three_ds_redirect, pix_qr_code, boleto] }
        url: { type: string, format: uri }
        qr_code: { type: string }
        qr_code_base64: { type: string }
        barcode: { type: string }
        expires_at: { type: string, format: date-time }
    Refund:
      type: object
      properties:
        id: { type: string, examples: [ref_01J8ZQ8] }
        payment_id: { type: string }
        amount: { type: integer, format: int64 }
        currency: { $ref: '#/components/schemas/Currency' }
        status: { type: string, enum: [pending, succeeded, failed] }
        reason: { type: string }
        created_at: { type: string, format: date-time }
    Dispute:
      type: object
      properties:
        id: { type: string, examples: [dis_01J8ZQ9] }
        payment_id: { type: string }
        amount: { type: integer, format: int64 }
        currency: { $ref: '#/components/schemas/Currency' }
        status:
          type: string
          enum: [opened, under_review, evidence_submitted, won, lost, accepted, expired]
        reason_code: { type: string }
        evidence_due_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
    CreateSellerRequest:
      type: object
      required: [legal_name, document]
      properties:
        legal_name: { type: string }
        trade_name: { type: string }
        document:
          type: object
          required: [type, value]
          properties:
            type: { type: string, enum: [cpf, cnpj] }
            value: { type: string }
        bank_account:
          type: object
          properties:
            bank_code: { type: string }
            branch: { type: string }
            account: { type: string }
            account_type: { type: string, enum: [checking, savings, payment] }
            holder_document: { type: string }
        metadata: { $ref: '#/components/schemas/Metadata' }
    Seller:
      type: object
      properties:
        id: { type: string, examples: [sel_01J8ZQA] }
        legal_name: { type: string }
        status: { type: string, enum: [pending, active, suspended, rejected] }
        kyc_status: { type: string, enum: [pending, approved, rejected, review] }
        created_at: { type: string, format: date-time }
    Balance:
      type: object
      description: |
        Tres saldos distintos. `posted` e o usado na conciliacao contra o
        adquirente; `available` e o unico que pode ser repassado.
      properties:
        currency: { $ref: '#/components/schemas/Currency' }
        pending: { type: integer, format: int64 }
        posted: { type: integer, format: int64 }
        available: { type: integer, format: int64 }
        reserved: { type: integer, format: int64 }
    Payout:
      type: object
      properties:
        id: { type: string, examples: [pyt_01J8ZQB] }
        seller_id: { type: string }
        amount: { type: integer, format: int64 }
        currency: { $ref: '#/components/schemas/Currency' }
        status: { type: string, enum: [scheduled, processing, paid, failed, returned] }
        scheduled_for: { type: string, format: date }
        created_at: { type: string, format: date-time }
    Metadata:
      type: object
      maxProperties: 50
      additionalProperties: { type: string, maxLength: 500 }
    Page:
      type: object
      properties:
        object: { type: string, const: list }
        has_more: { type: boolean }
        next_cursor: { type: string, nullable: true }
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [type, code, message]
          properties:
            type:
              type: string
              enum:
                - validation_error
                - authentication_error
                - permission_error
                - not_found
                - state_error
                - idempotency_error
                - rate_limit_error
                - card_error
                - acquirer_error
                - payment_method_error
                - api_error
            code: { type: string }
            message: { type: string }
            partner:
              type: string
              description: Parceiro que falhou; ausente quando nao existe rota ativa
            advice:
              type: string
              enum:
                - do_not_try_again
                - try_again_later
                - update_payment_details
                - request_authentication
                - route_alternate
            param: { type: string, nullable: true }
            request_id: { type: string }
            doc_url: { type: string, format: uri }
  responses:
    ValidationError:
      description: Corpo invalido
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    AuthenticationError:
      description: Credencial ou assinatura invalida
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Recurso inexistente
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    CardError:
      description: Recusa do emissor
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    IdempotencyError:
      description: Chave de idempotencia reutilizada com corpo diferente, ou pedido em curso
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    StateConflict:
      description: Operacao invalida para o estado atual do recurso
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimitError:
      description: Quota excedida
      headers:
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    PaymentDependencyError:
      description: Falha de parceiro ou indisponibilidade do metodo solicitado
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
webhooks:
  payment.captured:
    post:
      summary: Captura confirmada
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookEvent' }
      responses:
        '200':
          description: Entrega aceite. Nao-2xx aciona retry com backoff.
  dispute.opened:
    post:
      summary: Disputa aberta pelo emissor
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookEvent' }
      responses:
        '200':
          description: Entrega aceite
