{
  "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": {}
}
