openapi: 3.1.0
info:
  title: Allya Payments API
  version: v1
  description: >-
    API pública da Allya Payments. Use `Authorization: Bearer sk_test_...` em sandbox ou `sk_live_...` em produção.
    Schemas e rotas são gerados a partir dos schemas Zod do código — qualquer divergência quebra o typecheck.
  contact:
    name: Allya Payments
    url: https://payments.allyasolutions.com
servers:
  - url: https://payments-api.allyasolutions.com
    description: Produção e sandbox (ambiente é resolvido pelo prefixo da API key).
security:
  - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Allya API Key
      description: 'Use a API key do projeto/ambiente como bearer token: `Authorization: Bearer sk_test_...` ou `sk_live_...`.'
  schemas:
    ValidationIssue:
      type: object
      properties:
        path:
          type: array
          items:
            anyOf:
              - type: string
              - type: number
          description: Caminho do campo inválido no payload (formato Zod).
        message:
          type: string
          description: Descrição do problema encontrado.
      required:
        - path
        - message
      description: Detalhe individual de uma validação de payload.
    ErrorInvalidInput:
      type: object
      properties:
        error:
          type: string
          enum:
            - invalid_input
        issues:
          type: array
          items:
            $ref: '#/components/schemas/ValidationIssue'
      required:
        - error
        - issues
      description: Payload rejeitado pelo Zod ou regra de domínio. `issues[]` lista cada campo problemático.
    ErrorInvalidJson:
      type: object
      properties:
        error:
          type: string
          enum:
            - invalid_json
      required:
        - error
      description: Body recebido não é JSON válido.
    ErrorUnauthorized:
      type: object
      properties:
        error:
          type: string
          enum:
            - unauthorized
        message:
          type: string
      required:
        - error
      description: Header `Authorization` ausente, malformado ou contém API key revogada.
    ErrorNotFound:
      type: object
      properties:
        error:
          type: string
          enum:
            - not_found
      required:
        - error
      description: Recurso não existe no ambiente da API key.
    ErrorRateLimit:
      type: object
      properties:
        error:
          type: string
          enum:
            - rate_limit_exceeded
      required:
        - error
      description: Limite por API key excedido. Consulte o header `Retry-After` na resposta.
    ErrorGateway:
      type: object
      properties:
        error:
          type: string
          enum:
            - gateway_error
        provider:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway envolvido no erro.
      required:
        - error
        - provider
      description: O gateway retornou erro 4xx/5xx ou timeout. Retentar com backoff é seguro (idempotência por externalId).
    ErrorNoRoutingRule:
      type: object
      properties:
        error:
          type: string
          enum:
            - no_routing_rule
        message:
          type: string
      required:
        - error
        - message
      description: Nenhum gateway configurado em **Roteamento** para esse método no ambiente da API key.
    ErrorProviderNotConfigured:
      type: object
      properties:
        error:
          type: string
          enum:
            - provider_not_configured
        provider:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway envolvido no erro.
      required:
        - error
        - provider
      description: A regra de roteamento aponta para um gateway sem credenciais cadastradas neste ambiente.
    ErrorProviderDisabled:
      type: object
      properties:
        error:
          type: string
          enum:
            - provider_disabled
        provider:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway envolvido no erro.
      required:
        - error
        - provider
      description: O gateway alvo está desativado no painel (**Gateways → editar**).
    ErrorMethodNotSupported:
      type: object
      properties:
        error:
          type: string
          enum:
            - method_not_supported
        provider:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway envolvido no erro.
        method:
          type: string
          enum:
            - pix
            - card
      required:
        - error
        - provider
        - method
      description: O gateway resolvido pelo roteamento não suporta esse método de pagamento.
    ErrorBillingInactive:
      type: object
      properties:
        error:
          type: string
          description: Motivo do bloqueio retornado pelo módulo de billing.
        message:
          type: string
      required:
        - error
        - message
      description: Assinatura da plataforma inativa ou em past_due. Regularize o billing da organização.
    ErrorBillingMonthlyTxLimit:
      type: object
      properties:
        error:
          type: string
          enum:
            - billing_monthly_tx_limit_reached
        message:
          type: string
        limit:
          type: integer
        used:
          type: integer
      required:
        - error
        - message
        - limit
        - used
      description: Cota mensal de transações do plano atingida.
    ErrorMissingGatewayRef:
      type: object
      properties:
        error:
          type: string
          enum:
            - missing_gateway_ref
      required:
        - error
      description: Recurso não tem referência ao gateway, então não há o que consultar/cancelar/sincronizar.
    ErrorNotImplemented:
      type: object
      properties:
        error:
          type: string
          enum:
            - not_implemented
        action:
          type: string
        provider:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway envolvido no erro.
      required:
        - error
        - action
        - provider
      description: O adapter do gateway ainda não implementa essa ação (cancel/sync/etc.).
    ErrorActionNotImplemented:
      type: object
      properties:
        error:
          type: string
          enum:
            - action_not_implemented
        action:
          type: string
        provider:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway envolvido no erro.
      required:
        - error
        - action
        - provider
      description: Variante de `not_implemented` em assinaturas — ação opcional não suportada pelo gateway.
    ErrorAlreadyCanceled:
      type: object
      properties:
        error:
          type: string
          enum:
            - already_canceled
        status:
          type: string
      required:
        - error
        - status
      description: O recurso já estava cancelado quando o cancel foi chamado.
    ErrorCannotCancelPaid:
      type: object
      properties:
        error:
          type: string
          enum:
            - cannot_cancel_paid
      required:
        - error
      description: Pagamento já está pago. Cancel não é refund — use o painel do gateway para estornar.
    ErrorCannotCancelPix:
      type: object
      properties:
        error:
          type: string
          enum:
            - cannot_cancel_pix
      required:
        - error
      description: Pix expira automaticamente; não há cancel manual.
    ErrorCannotCancelStatus:
      type: object
      properties:
        error:
          type: string
          enum:
            - cannot_cancel_status
        status:
          type: string
      required:
        - error
        - status
      description: 'O status atual do pagamento não permite cancel (ex.: `expired`, `failed`).'
    ErrorMissingQuery:
      type: object
      properties:
        error:
          type: string
          enum:
            - missing_query
        message:
          type: string
      required:
        - error
        - message
      description: Query string obrigatória ausente.
    ErrorInvalidId:
      type: object
      properties:
        error:
          type: string
          enum:
            - invalid_id
      required:
        - error
      description: Parâmetro `:id` da URL está vazio ou malformado.
    ErrorInternal:
      type: object
      properties:
        error:
          type: string
          enum:
            - internal_error
      required:
        - error
      description: Falha inesperada no servidor. Logs detalhados ficam internos; entre em contato se persistir.
    Payment:
      type: object
      properties:
        id:
          type: string
          description: ID público com prefixo `pay_`.
        status:
          type: string
          enum:
            - created
            - pending
            - processing
            - paid
            - failed
            - canceled
            - expired
          description: Status normalizado. Veja a referência de status para o ciclo completo.
        method:
          type: string
          enum:
            - pix
            - card
        gateway:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
          description: Gateway que atendeu a chamada, definido pelo roteamento do ambiente.
        amount:
          type: integer
          description: Valor em centavos.
        currency:
          type: string
          description: Moeda. Hoje só `BRL`.
        externalId:
          type:
            - string
            - 'null'
          description: Identificador idempotente do cliente; obrigatório no momento da criação.
        gatewayRef:
          type:
            - string
            - 'null'
          description: ID do recurso no gateway (`pay_*` na Allya, `cha_*` no Abacate, etc.).
        customer:
          type:
            - object
            - 'null'
          properties:
            name:
              type:
                - string
                - 'null'
              description: Nome do cliente enviado na criação.
            email:
              type:
                - string
                - 'null'
              description: E-mail do cliente enviado na criação.
            documentLast4:
              type:
                - string
                - 'null'
              description: Últimos 4 dígitos do CPF/CNPJ. O documento completo nunca é retornado nem persistido em plaintext.
          required:
            - name
            - email
            - documentLast4
        checkoutUrl:
          type:
            - string
            - 'null'
          description: URL hospedada pelo gateway quando `method=card`; `null` para Pix.
        pix:
          type:
            - object
            - 'null'
          properties:
            qrCode:
              type: string
              description: Imagem (data URL/base64) ou URL do QR Code, quando o gateway retorna.
            qrCodeText:
              type: string
              description: Código Pix copia e cola (começa com `00020101...`), quando o gateway retorna.
        card:
          type:
            - object
            - 'null'
          properties:
            brand:
              type: string
              description: Bandeira do cartão (`visa`, `mastercard`, etc.).
            last4:
              type: string
              description: Últimos 4 dígitos do cartão.
      required:
        - id
        - status
        - method
        - gateway
        - amount
        - currency
        - externalId
        - gatewayRef
        - customer
        - checkoutUrl
        - pix
        - card
      description: Representação normalizada de um pagamento avulso. Mesma estrutura para POST, GET, sync e cancel.
    Subscription:
      type: object
      properties:
        id:
          type: string
          description: ID público com prefixo `sub_`.
        status:
          type: string
          enum:
            - trialing
            - active
            - past_due
            - canceled
          description: Estado normalizado. Veja referência de status de assinatura para o ciclo completo.
        method:
          type: string
          enum:
            - pix
            - card
        gateway:
          type: string
          enum:
            - abacate_pay
            - asaas
            - pagarme
        amount:
          type: integer
          description: Valor recorrente em centavos.
        currency:
          type: string
        interval:
          type: string
          enum:
            - monthly
            - yearly
        trialDays:
          type: integer
          description: Dias de trial configurados na criação.
        trialEndsAt:
          type:
            - string
            - 'null'
          description: ISO timestamp do fim do trial; `null` quando não há trial.
        nextBillingAt:
          type:
            - string
            - 'null'
          description: ISO timestamp da próxima cobrança recorrente prevista.
        cancelAtPeriodEnd:
          type: boolean
          description: Quando `true`, cancel já foi solicitado e tem efeito ao fim do ciclo corrente.
        canceledAt:
          type:
            - string
            - 'null'
          description: ISO timestamp do momento em que o cancel foi efetivado.
        externalId:
          type:
            - string
            - 'null'
        gatewayRef:
          type:
            - string
            - 'null'
          description: ID do recurso no gateway. No Pagar.me, vira `sub_*` após o cliente concluir o checkout (antes é `pl_*`).
        checkoutUrl:
          type:
            - string
            - 'null'
          description: URL hospedada para o cliente cadastrar o cartão.
        customer:
          type:
            - object
            - 'null'
          properties:
            name:
              type:
                - string
                - 'null'
            email:
              type:
                - string
                - 'null'
            documentLast4:
              type:
                - string
                - 'null'
          required:
            - name
            - email
            - documentLast4
        createdAt:
          type: string
          description: ISO timestamp de criação na Allya.
      required:
        - id
        - status
        - method
        - gateway
        - amount
        - currency
        - interval
        - trialDays
        - trialEndsAt
        - nextBillingAt
        - cancelAtPeriodEnd
        - canceledAt
        - externalId
        - gatewayRef
        - checkoutUrl
        - customer
        - createdAt
      description: Representação normalizada de uma assinatura recorrente. Mesma estrutura para POST, GET e cancel.
    AuthTest:
      type: object
      properties:
        ok:
          type: boolean
          enum:
            - true
        organization:
          type: object
          properties:
            id:
              type: string
              description: ID com prefixo `org_`.
          required:
            - id
        project:
          type: object
          properties:
            id:
              type: string
              description: ID com prefixo `prj_`.
          required:
            - id
        environment:
          type: object
          properties:
            id:
              type: string
              description: ID com prefixo `env_`.
            kind:
              type: string
              enum:
                - sandbox
                - production
              description: Ambiente resolvido a partir da API key (`sk_test_` ou `sk_live_`).
          required:
            - id
            - kind
        apiKey:
          type: object
          properties:
            id:
              type: string
              description: ID com prefixo `key_`.
          required:
            - id
      required:
        - ok
        - organization
        - project
        - environment
        - apiKey
      description: Confirma que a API key é válida e expõe os IDs da hierarquia organização → projeto → ambiente.
    Health:
      type: object
      properties:
        ok:
          type: boolean
          enum:
            - true
        service:
          type: string
          enum:
            - allya-payments
      required:
        - ok
        - service
      description: Liveness check público (sem autenticação).
  parameters: {}
paths:
  /v1/health:
    get:
      summary: Liveness check
      description: Endpoint público sem autenticação. Útil para uptime monitors. Não consulta banco nem gateway.
      tags:
        - Health
      security: []
      responses:
        '200':
          description: Serviço respondendo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
  /v1/auth/test:
    get:
      summary: Validar API key
      description: >-
        Devolve a hierarquia organização → projeto → ambiente atrelada à API key recebida. Bom para confirmar `sk_live_`
        antes de liberar tráfego real.
      tags:
        - Auth
      responses:
        '200':
          description: API key válida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthTest'
        '401':
          description: API key ausente, malformada ou revogada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
  /v1/payments:
    post:
      summary: Criar pagamento
      description: >-
        Cria uma cobrança Pix ou cartão. **Idempotente por `externalId`** dentro do ambiente: retentar com o mesmo valor
        devolve a cobrança já existente sem criar nova no gateway. Cartão usa checkout hospedado — a Allya nunca recebe
        número/CVV.
      tags:
        - Payments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: integer
                  exclusiveMinimum: 0
                  description: Valor em centavos. `1990` representa R$ 19,90.
                  example: 4990
                currency:
                  type: string
                  enum:
                    - BRL
                  default: BRL
                method:
                  type: string
                  description: Método de pagamento. Aceita também `PIX`/`CARD` em uppercase.
                  enum:
                    - pix
                    - card
                  example: pix
                customer:
                  type: object
                  properties:
                    name:
                      type: string
                      minLength: 1
                      maxLength: 255
                      description: Nome completo do cliente.
                      example: Cliente Teste
                    email:
                      type: string
                      format: email
                      description: E-mail do cliente.
                      example: cliente@example.com
                    document:
                      type: string
                      minLength: 8
                      maxLength: 32
                      description: >-
                        CPF (11 dígitos) ou CNPJ (14 dígitos). Só dígitos ou formato livre — a Allya remove pontuação
                        antes de enviar ao gateway.
                      example: '00000000000'
                    phone:
                      type: string
                      maxLength: 32
                      description: >-
                        Telefone com DDD + número (mín. 10 dígitos). **Obrigatório quando method=card e o gateway
                        resolvido é Pagar.me** — sem isso, o Pagar.me rejeita o paymentlink com 422.
                      example: '11999999999'
                  required:
                    - name
                    - email
                    - document
                externalId:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: >-
                    Identificador único do pedido no seu sistema. Funciona como chave de idempotência: reenviar com o
                    mesmo valor retorna o pagamento já criado.
                  example: pedido_42
                metadata:
                  type: object
                  additionalProperties: {}
                  description: >-
                    Dados livres para correlação interna (até 4 KiB serializados, profundidade máx. 4). Aceita apenas
                    JSON. Evite enviar PII ou segredos.
                  example:
                    orderId: pedido_42
                    source: checkout
                card:
                  type: object
                  properties:
                    installments:
                      type: integer
                      minimum: 1
                      maximum: 24
                      default: 1
                      description: >-
                        Número de parcelas para checkout hospedado de cartão. Limite real depende do gateway resolvido
                        (Abacate Pay: 1–12; Asaas/Pagar.me: 1–24). Ignorado em assinatura.
                      example: 1
              required:
                - amount
                - method
                - customer
                - externalId
            example:
              amount: 4990
              currency: BRL
              method: pix
              externalId: pedido_42
              customer:
                name: Cliente Teste
                email: cliente@example.com
                document: '00000000000'
                phone: '11999999999'
              metadata:
                orderId: pedido_42
                source: checkout
      responses:
        '201':
          description: Pagamento criado (ou já existente quando `externalId` é repetido).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          description: Body inválido — `invalid_json` ou `invalid_input`.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorInvalidJson'
                  - $ref: '#/components/schemas/ErrorInvalidInput'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '402':
          description: Billing da plataforma bloqueia a chamada.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorBillingInactive'
                  - $ref: '#/components/schemas/ErrorBillingMonthlyTxLimit'
        '409':
          description: >-
            Sem regra de roteamento, gateway desativado, sem credenciais, ou método não suportado pelo gateway
            resolvido.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorNoRoutingRule'
                  - $ref: '#/components/schemas/ErrorProviderNotConfigured'
                  - $ref: '#/components/schemas/ErrorProviderDisabled'
                  - $ref: '#/components/schemas/ErrorMethodNotSupported'
        '429':
          description: Limite por API key excedido — consulte `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorRateLimit'
        '500':
          description: Erro inesperado da Allya.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInternal'
        '502':
          description: Gateway retornou erro ou timeout. Seguro retentar (idempotência por `externalId`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorGateway'
    get:
      summary: Buscar pagamento por externalId
      description: >-
        Consulta idempotente: retorna o pagamento cadastrado com o `externalId` informado, sem chamar o gateway. Use
        para conferir antes de fazer um `POST` retry.
      tags:
        - Payments
      parameters:
        - schema:
            type: string
            minLength: 1
            description: Identificador idempotente enviado na criação.
          required: true
          description: Identificador idempotente enviado na criação.
          name: externalId
          in: query
      responses:
        '200':
          description: Pagamento encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          description: Query `externalId` ausente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMissingQuery'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: Nenhum pagamento com esse `externalId` no ambiente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
  /v1/payments/{id}:
    get:
      summary: Consultar pagamento
      description: >-
        Lê o pagamento direto do banco da Allya — **não consulta o gateway**. Para forçar reconciliação com o gateway,
        use `POST /v1/payments/{id}/sync`.
      tags:
        - Payments
      parameters:
        - schema:
            type: string
            description: ID público com prefixo `pay_`.
          required: true
          description: ID público com prefixo `pay_`.
          name: id
          in: path
      responses:
        '200':
          description: Pagamento encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          description: Parâmetro `id` vazio.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInvalidId'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: ID não existe no ambiente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
  /v1/payments/{id}/cancel:
    post:
      summary: Cancelar pagamento
      description: >-
        Cancela uma cobrança de cartão ainda não paga. **Não é refund**: pagamentos pagos retornam `409
        cannot_cancel_paid`. Pix expira sozinho — `cannot_cancel_pix` é retornado para tentativas em Pix.
      tags:
        - Payments
      parameters:
        - schema:
            type: string
            description: ID público com prefixo `pay_`.
          required: true
          description: ID público com prefixo `pay_`.
          name: id
          in: path
      responses:
        '200':
          description: 'Cancelamento confirmado. Resposta é o pagamento com `status: canceled`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          description: '`id` vazio ou método `cannot_cancel_pix`.'
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorInvalidId'
                  - $ref: '#/components/schemas/ErrorCannotCancelPix'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: Pagamento não existe.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
        '409':
          description: Conflito de estado — já pago, já cancelado, status incompatível, ou faltam dados para cancelar.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorCannotCancelPaid'
                  - $ref: '#/components/schemas/ErrorAlreadyCanceled'
                  - $ref: '#/components/schemas/ErrorCannotCancelStatus'
                  - $ref: '#/components/schemas/ErrorMissingGatewayRef'
                  - $ref: '#/components/schemas/ErrorProviderNotConfigured'
        '429':
          description: Limite por API key excedido — consulte `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorRateLimit'
        '500':
          description: Erro inesperado da Allya.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInternal'
        '501':
          description: Adapter do gateway não implementa cancel.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotImplemented'
        '502':
          description: Gateway retornou erro ou timeout.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorGateway'
  /v1/payments/{id}/sync:
    post:
      summary: Reconciliar pagamento com o gateway
      description: >-
        Consulta o gateway na hora, atualiza o status local e dispara webhook outbound se houve mudança. Use quando
        suspeitar que um webhook foi perdido ou para recuperar o QR Pix do Asaas após a 2ª chamada falhar.
      tags:
        - Payments
      parameters:
        - schema:
            type: string
            description: ID público com prefixo `pay_`.
          required: true
          description: ID público com prefixo `pay_`.
          name: id
          in: path
      responses:
        '200':
          description: Pagamento sincronizado — resposta no mesmo formato de `GET /v1/payments/{id}`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          description: '`id` vazio.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInvalidId'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: Pagamento não existe no ambiente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
        '409':
          description: Pagamento sem `gatewayRef` ou gateway sem credenciais.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorMissingGatewayRef'
                  - $ref: '#/components/schemas/ErrorProviderNotConfigured'
        '429':
          description: Limite por API key excedido — consulte `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorRateLimit'
        '500':
          description: Erro inesperado da Allya.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInternal'
        '501':
          description: Adapter do gateway ainda não implementa sync.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotImplemented'
        '502':
          description: Gateway retornou erro ou timeout.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorGateway'
  /v1/subscriptions:
    post:
      summary: Criar assinatura recorrente
      description: >-
        Cria uma assinatura cobrada via cartão. **Idempotente por `externalId`** dentro do ambiente. No Pagar.me, o
        campo `gateway.pagarme.planId` é **obrigatório** (Plan deve existir antes na API do Pagar.me).
      tags:
        - Subscriptions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: integer
                  exclusiveMinimum: 0
                  description: Valor recorrente em centavos.
                  example: 4990
                currency:
                  type: string
                  enum:
                    - BRL
                  default: BRL
                interval:
                  type: string
                  description: Periodicidade da cobrança. Aceita também `MONTHLY`/`YEARLY`.
                  enum:
                    - monthly
                    - yearly
                  example: monthly
                trialDays:
                  type:
                    - integer
                    - 'null'
                  minimum: 0
                  maximum: 365
                  default: 0
                  description: >-
                    Dias de trial antes da primeira cobrança real (0–365). No Pagar.me, o trial real é definido no Plan;
                    este campo só mantém o status `trialing` local consistente.
                  example: 7
                customer:
                  type: object
                  properties:
                    name:
                      type: string
                      minLength: 1
                      maxLength: 255
                      description: Nome completo do cliente.
                      example: Cliente Teste
                    email:
                      type: string
                      format: email
                      description: E-mail do cliente.
                      example: cliente@example.com
                    document:
                      type: string
                      minLength: 8
                      maxLength: 32
                      description: >-
                        CPF (11 dígitos) ou CNPJ (14 dígitos). Só dígitos ou formato livre — a Allya remove pontuação
                        antes de enviar ao gateway.
                      example: '00000000000'
                    phone:
                      type: string
                      maxLength: 32
                      description: >-
                        Telefone com DDD + número (mín. 10 dígitos). **Obrigatório quando o roteamento de SUBSCRIPTION
                        resolver para Pagar.me** — sem isso, o Pagar.me rejeita o paymentlink com 422.
                      example: '11999999999'
                  required:
                    - name
                    - email
                    - document
                externalId:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: Identificador único da assinatura no seu sistema. Idempotente por ambiente.
                  example: assinatura_cliente_42_plano_pro_mensal
                metadata:
                  type: object
                  additionalProperties: {}
                  description: Dados livres para correlação interna. Evite enviar PII ou segredos.
                  example:
                    orderId: assinatura_cliente_42
                    plan: pro_mensal
                card:
                  type: object
                  properties:
                    installments:
                      type: integer
                      minimum: 1
                      maximum: 24
                      description: Ignorado em assinatura — recorrência não parcela.
                      example: 1
                gateway:
                  type: object
                  properties:
                    pagarme:
                      type: object
                      properties:
                        planId:
                          type: string
                          minLength: 1
                          description: >-
                            ID do Plan pré-criado na API do Pagar.me (`POST /core/v5/plans`). Obrigatório quando o
                            roteamento resolve para Pagar.me; ignorado nos demais gateways.
                          example: plan_XnB8eYvR7K1aZ4Qm
                      required:
                        - planId
              required:
                - amount
                - interval
                - customer
                - externalId
            example:
              amount: 4990
              currency: BRL
              interval: monthly
              trialDays: 7
              externalId: assinatura_cliente_42_plano_pro_mensal
              customer:
                name: Cliente Teste
                email: cliente@example.com
                document: '00000000000'
                phone: '11999999999'
              metadata:
                orderId: assinatura_cliente_42
                plan: pro_mensal
              gateway:
                pagarme:
                  planId: plan_XnB8eYvR7K1aZ4Qm
      responses:
        '201':
          description: Assinatura criada (ou já existente quando `externalId` é repetido).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '400':
          description: Body inválido.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorInvalidJson'
                  - $ref: '#/components/schemas/ErrorInvalidInput'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '402':
          description: Billing da plataforma bloqueia a chamada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBillingInactive'
        '409':
          description: Sem roteamento, gateway sem credenciais, método não suportado ou ação ausente no adapter.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorNoRoutingRule'
                  - $ref: '#/components/schemas/ErrorProviderNotConfigured'
                  - $ref: '#/components/schemas/ErrorProviderDisabled'
                  - $ref: '#/components/schemas/ErrorMethodNotSupported'
                  - $ref: '#/components/schemas/ErrorActionNotImplemented'
        '429':
          description: Limite por API key excedido — consulte `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorRateLimit'
        '500':
          description: Erro inesperado da Allya.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInternal'
        '502':
          description: Gateway retornou erro ou timeout.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorGateway'
    get:
      summary: Buscar assinatura por externalId
      description: Consulta idempotente por `externalId` — não chama o gateway.
      tags:
        - Subscriptions
      parameters:
        - schema:
            type: string
            minLength: 1
          required: true
          name: externalId
          in: query
      responses:
        '200':
          description: Assinatura encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '400':
          description: Query `externalId` ausente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMissingQuery'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: Nenhuma assinatura com esse `externalId` no ambiente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
  /v1/subscriptions/{id}:
    get:
      summary: Consultar assinatura
      description: Lê a assinatura direto do banco da Allya. Não há `sync` para assinaturas hoje.
      tags:
        - Subscriptions
      parameters:
        - schema:
            type: string
            description: ID público com prefixo `sub_`.
          required: true
          description: ID público com prefixo `sub_`.
          name: id
          in: path
      responses:
        '200':
          description: Assinatura encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: Assinatura não existe.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
        '500':
          description: Erro inesperado da Allya.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInternal'
  /v1/subscriptions/{id}/cancel:
    post:
      summary: Cancelar assinatura
      description: >-
        Cancela imediatamente (`atPeriodEnd=false`, default) ou ao fim do ciclo corrente (`atPeriodEnd=true`).
        Restrições por gateway: Abacate Pay e Pagar.me não suportam `atPeriodEnd=true`.
      tags:
        - Subscriptions
      parameters:
        - schema:
            type: string
            description: ID público com prefixo `sub_`.
          required: true
          description: ID público com prefixo `sub_`.
          name: id
          in: path
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                atPeriodEnd:
                  type:
                    - boolean
                    - 'null'
                  default: false
                  description: >-
                    Quando `true`, cancela ao fim do ciclo corrente (não suportado em Abacate Pay e Pagar.me). Default
                    `false` = cancela imediato.
                  example: false
      responses:
        '200':
          description: Cancelamento confirmado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '400':
          description: Body inválido.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorInvalidJson'
                  - $ref: '#/components/schemas/ErrorInvalidInput'
        '401':
          description: API key inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorUnauthorized'
        '404':
          description: Assinatura não existe.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorNotFound'
        '409':
          description: Já cancelada, sem `gatewayRef`, gateway sem credenciais, ou ação não suportada pelo gateway.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ErrorAlreadyCanceled'
                  - $ref: '#/components/schemas/ErrorMissingGatewayRef'
                  - $ref: '#/components/schemas/ErrorProviderNotConfigured'
                  - $ref: '#/components/schemas/ErrorActionNotImplemented'
        '500':
          description: Erro inesperado da Allya.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInternal'
        '502':
          description: Gateway retornou erro ou timeout.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorGateway'
webhooks: {}
