openapi: 3.0.3
info:
  title: PixUSDT API
  version: 1.0.0
  description: >-
    API REST para gerar cobranças PIX e receber USDT na sua carteira automaticamente.
    Auth via Bearer token, webhooks assinados via HMAC-SHA256.
  contact:
    name: PixUSDT Suporte
    url: https://pixusdt.org/support
    email: suporte@pixusdt.org

servers:
  - url: https://pixusdt.org/v1
    description: Produção

security:
  - BearerAuth: []

tags:
  - name: Conta
    description: Informações da conta autenticada
  - name: Cobranças
    description: Criar e consultar cobranças PIX
  - name: Conversões USDT
    description: Histórico de conversões BRL→USDT enviadas on-chain

paths:
  /account:
    get:
      summary: Info da conta
      description: Retorna informações do dono da API key. Útil pra validar a chave durante setup.
      tags: [Conta]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Account'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /balance:
    get:
      summary: Saldo bruto e líquido
      tags: [Conta]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /charges:
    post:
      summary: Cria cobrança PIX
      description: Gera um QR code PIX. Aceita webhook_url inline pra receber callback assinado quando for pago/expirar.
      tags: [Cobranças]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChargeRequest'
      responses:
        '201':
          description: Cobrança criada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Charge'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimit'
        '502':
          $ref: '#/components/responses/PspError'
    get:
      summary: Lista cobranças
      tags: [Cobranças]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, paid, expired, failed, refunded]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ChargeListItem'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'

  /transactions:
    get:
      summary: Lista conversões USDT
      description: Histórico de envios USDT confirmados, com TX hash on-chain.
      tags: [Conversões USDT]
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [sent, confirmed, pending] }
        - name: charge_id
          in: query
          schema: { type: string }
        - name: external_id
          in: query
          description: Filtra pelo external_id da charge
          schema: { type: string }
        - name: network
          in: query
          schema: { type: string, enum: [TRON, POLYGON, BSC] }
        - name: limit
          in: query
          schema: { type: integer, default: 50, maximum: 200 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Transaction'
                  total:  { type: integer }
                  limit:  { type: integer }
                  offset: { type: integer }
        '401':
          $ref: '#/components/responses/Unauthorized'

  /transactions/{id}:
    get:
      summary: Consulta uma conversão USDT
      tags: [Conversões USDT]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transaction'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /charges/{id}:
    get:
      summary: Consulta cobrança por id ou external_id
      tags: [Cobranças]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID retornado pelo PixUSDT OU external_id que você passou na criação.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Charge'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

  schemas:
    Account:
      type: object
      properties:
        id:    { type: integer, example: 1 }
        name:  { type: string,  example: Loja Exemplo }
        email: { type: string,  format: email }
        role:  { type: string,  example: admin }
        usdt_wallet:
          type: object
          description: Carteira USDT cadastrada (não-custodial — operador envia USDT direto pra cá)
          properties:
            address:
              type: string
              nullable: true
              example: TXyz...AbCd
            network:
              type: string
              nullable: true
              enum: [TRON, POLYGON, BSC]
              example: TRON
            configured:
              type: boolean
              description: true quando address está preenchido

    Balance:
      type: object
      properties:
        gross_received_brl: { type: number, example: 12483.50 }
        pending_brl:        { type: number, example: 347.00 }
        total_fees_brl:     { type: number, example: 374.50 }
        net_brl:            { type: number, example: 12109.00 }

    CreateChargeRequest:
      type: object
      required: [amount]
      properties:
        amount:
          type: number
          minimum: 1
          maximum: 100000
          example: 49.90
          description: 'Valor em reais (BRL). Limite absoluto da API é R$ 100.000, mas existe limite per-conta (max_charge_brl, default R$ 200). Ultrapassar retorna 400 amount_above_limit.'
        expires_in_minutes:
          type: integer
          minimum: 5
          maximum: 1440
          default: 30
          description: 'Tempo até o QR expirar. Default 30. Valores abaixo de 30 são automaticamente elevados pra 30 (piso silencioso) pra evitar pix.expired prematuro antes do pix.received quando o PSP confirma o pagamento atrasado.'
        external_id:
          type: string
          maxLength: 120
          example: pedido-12345
          description: ID do seu sistema (idempotência)
        description:
          type: string
          maxLength: 255
          example: Plano Premium mensal
        webhook_url:
          type: string
          format: uri
          maxLength: 500
          example: https://seu-sistema.com/webhook/pixusdt
          description: URL de callback assinada com HMAC-SHA256. Recebe pix.received e pix.expired.
        customer:
          type: object
          properties:
            name:     { type: string, example: João Silva }
            email:    { type: string, format: email }
            document: { type: string, example: '12345678901' }

    Charge:
      type: object
      properties:
        id:           { type: string, example: '1842' }
        external_id:  { type: string, nullable: true, example: pedido-12345 }
        status:
          type: string
          enum: [pending, paid, expired, failed, refunded]
        amount:       { type: number, example: 49.90 }
        amount_cents: { type: integer, example: 4990 }
        fees:
          type: object
          properties:
            platform_fee_pct:   { type: number, example: 3.0 }
            percentage_fee_brl: { type: number, example: 1.50 }
            fixed_fee_brl:      { type: number, example: 0.55, description: 'Taxa fixa aplicada em todas as cobranças, independente do valor' }
            total_fee_brl:      { type: number, example: 2.05 }
            net_brl:            { type: number, example: 47.85 }
        pix:
          type: object
          properties:
            qr_code:        { type: string, description: Código PIX copia-e-cola }
            qr_code_base64:     { type: string, nullable: true, description: 'String PRONTA pra <img src=>. Pode ser data URI (data:image/png;base64,...) ou URL externa. NÃO concatene "data:image/png;base64," antes desse valor — vai duplicar o prefixo. Pra montar o data URI manualmente, use qr_code_base64_raw.' }
            qr_code_image_url:  { type: string, nullable: true, description: 'Alias de qr_code_base64 (mesmo valor, nome mais explícito). Use direto em <img src=>.' }
            qr_code_base64_raw: { type: string, nullable: true, description: 'Base64 PURO da imagem PNG, sem o prefixo data:image/png;base64,. Null quando o PSP devolve URL ao invés de base64. Use SÓ se for montar o data URI manualmente.' }
            copy_paste:     { type: string }
        payer:
          type: object
          nullable: true
          properties:
            name:     { type: string }
            document: { type: string }
        expires_at:  { type: string, format: date-time }
        paid_at:     { type: string, format: date-time, nullable: true }
        created_at:  { type: string, format: date-time }
        psp_id:      { type: string, nullable: true }
        webhook_url: { type: string, nullable: true }

    ChargeListItem:
      type: object
      properties:
        id:          { type: string }
        external_id: { type: string, nullable: true }
        status:      { type: string }
        amount:      { type: number }
        created_at:  { type: string, format: date-time }
        paid_at:     { type: string, format: date-time, nullable: true }
        expires_at:  { type: string, format: date-time }

    Transaction:
      type: object
      description: Conversão BRL→USDT realizada. Cada PIX paid gera uma transaction quando o operador envia o USDT on-chain.
      properties:
        id:              { type: string }
        charge_id:       { type: string, nullable: true }
        external_id:     { type: string, nullable: true, description: external_id da charge associada }
        psp_id:          { type: string, nullable: true }
        amount_brl:      { type: number, description: Valor PIX original em reais }
        usdt_amount:     { type: number, description: Quantidade de USDT enviada }
        rate_brl:        { type: number, description: Cotação BRL/USDT no momento }
        spread_pct:      { type: number }
        network:
          type: string
          enum: [TRON, POLYGON, BSC]
        to_address:      { type: string, description: Carteira de destino do lojista }
        tx_hash:         { type: string, nullable: true }
        explorer_url:    { type: string, nullable: true, description: Link direto pro block explorer da rede }
        network_fee_usd: { type: number }
        status:
          type: string
          enum: [pending, sent, confirmed, failed]
        sent_at:         { type: string, format: date-time, nullable: true }
        confirmed_at:    { type: string, format: date-time, nullable: true }
        created_at:      { type: string, format: date-time }

    WebhookEvent:
      type: object
      description: >-
        Payload enviado para o webhook_url. Validação anti-replay:
        cliente DEVE verificar (1) X-Webhook-Timestamp dentro de janela ±5min e
        (2) X-Webhook-Signature == HMAC-SHA256 de "{timestamp}.{body_bruto}" usando webhook_secret.
        Comparação deve ser em tempo constante.
      properties:
        event:
          type: string
          enum: [pix.received, pix.expired, usdt.sent, usdt.confirmed]
        timestamp: { type: string, format: date-time }
        data:
          type: object
          properties:
            charge:
              type: object
              properties:
                id:             { type: integer }
                external_id:    { type: string, nullable: true }
                amount_brl:     { type: number }
                status:         { type: string }
                paid_at:        { type: string, nullable: true }
                created_at:     { type: string }
                psp_id:         { type: string, nullable: true }
                payer_name:     { type: string, nullable: true }
                payer_document: { type: string, nullable: true }
            transaction:
              type: object
              nullable: true
              description: Presente em eventos usdt.sent e usdt.confirmed
              properties:
                id:           { type: integer }
                usdt_amount:  { type: number }
                rate_brl:     { type: number }
                network:
                  type: string
                  enum: [TRON, POLYGON, BSC]
                to_address:   { type: string }
                tx_hash:      { type: string }
                network_fee:  { type: number }
                explorer_url: { type: string }
                batch:        { type: boolean, description: 'true se foi parte de envio em lote' }
                batch_size:   { type: integer, nullable: true }
                status:       { type: string }

    Error:
      type: object
      properties:
        error:   { type: string, example: validation_error }
        message: { type: string, example: Dados inválidos }
        details: { type: object }

  responses:
    Unauthorized:
      description: API key ausente, inválida ou revogada
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    ValidationError:
      description: Body inválido ou campo faltando
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Recurso não encontrado
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimit:
      description: Limite de requisições excedido
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    PspError:
      description: Erro no PSP que processa o PIX
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
