openapi: 3.1.0
info:
  title: Kaia-Pay REST API v1 (Fase A)
  version: 1.0.0
  description: |
    API REST para comercios de Kaia-Pay. Superficie paralela al MCP merchant —mismos casos de uso,
    mismas reglas de autenticación e idempotencia— orientada a integraciones backend (web/app).

    **Autenticación:** `Authorization: Bearer pbk_live_…` (API key de comercio).

    **Idempotencia:** las mutaciones (POST) requieren la cabecera `Idempotency-Key` (8-64 chars).
    Repetir la misma clave dentro de 24 h devuelve la respuesta cacheada.

    **Errores:** todas las respuestas siguen `{ok: boolean, data?: …, error?: {code, message, details?}}`.

servers:
  - url: https://gw.kaia-pay.pay-bo.com/v1
    description: Producción

security:
  - merchantApiKey: []

components:
  securitySchemes:
    merchantApiKey:
      type: http
      scheme: bearer
      bearerFormat: pbk_live_…
      description: API key del comercio

  parameters:
    chargeId:
      name: chargeId
      in: path
      required: true
      schema:
        type: string
        minLength: 3
        maxLength: 64
      description: ID del cobro (ch_…)
    idempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 8
        maxLength: 64
      description: Clave de idempotencia para mutaciones

  schemas:
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: object

    OkResponse:
      type: object
      required: [ok]
      properties:
        ok:
          type: boolean
        data:
          type: object
        error:
          $ref: '#/components/schemas/Error'

    CreateChargeInput:
      type: object
      required: [amount]
      properties:
        amount:
          type: number
          minimum: 0.01
          description: Monto en Bs (BOB), hasta 2 decimales
        description:
          type: string
          maxLength: 120
          description: Glosa del cobro
        fechaVencimiento:
          type: string
          pattern: '^\d{4}-\d{2}-\d{2}$'
          description: YYYY-MM-DD en America/La_Paz
        tiempoDuracion:
          type: integer
          minimum: 1
          maximum: 999
          description: Minutos de vigencia
        pos:
          type: string
          maxLength: 50
        cajero:
          type: string
          maxLength: 50
        canal:
          type: string
          enum: [WEB, MOVIL]
        nombreComercio:
          type: string
          maxLength: 20
        externalReference:
          type: string
          maxLength: 100
        metadata:
          type: object
          additionalProperties:
            type: string
            maxLength: 500
        idempotencyKey:
          type: string
          minLength: 8
          maxLength: 64
          description: Opcional en el cuerpo; si falta se usa la cabecera

    ChargeView:
      type: object
      properties:
        chargeId:
          type: string
        reference:
          type: string
          description: idTrans KPY-…
        status:
          type: string
          enum: [created, qr_pending, qr_issued, paid, paid_late, cancel_requested, cancelled, expired, failed]
        amount:
          type: number
        amountCents:
          type: integer
        currency:
          type: string
        description:
          type: string
        externalReference:
          type: string
          nullable: true
        expiresAt:
          type: string
          format: date-time
          nullable: true
        paidAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        qr:
          type: object
          nullable: true
          properties:
            expiresAt:
              type: string
              format: date-time
        qr_image_url:
          type: string
          nullable: true
        qr_page_url:
          type: string
          nullable: true

    PaymentStatus:
      type: object
      properties:
        chargeId:
          type: string
        reference:
          type: string
        status:
          type: string
        paid:
          type: boolean
        paidAt:
          type: string
          format: date-time
          nullable: true
        amount:
          type: number
        amountCents:
          type: integer
        currency:
          type: string
        detectedBy:
          type: string
          enum: [polling, webhook]
          nullable: true
        webhookReceived:
          type: boolean

    LimitsUsage:
      type: object
      properties:
        currency:
          type: string
        perTx:
          type: number
        weeklyLimit:
          type: number
        weeklyUsed:
          type: number
        weeklyRemaining:
          type: number
        status:
          type: string

paths:
  /charges:
    post:
      operationId: createCharge
      summary: Crear cobro QR
      description: |
        Crea un cobro QR. Equivale a la tool MCP `create_qr_charge`.
        Límites: 100 Bs por cobro, 500 Bs en 7 días móviles (o los del comercio).
      parameters:
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChargeInput'
      responses:
        '201':
          description: Cobro creado
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ChargeView'
        '400':
          description: Parámetros inválidos
        '401':
          description: API key inválida
        '409':
          description: Límite excedido o conflicto de idempotencia
        '429':
          description: Rate limited

    get:
      operationId: listCharges
      summary: Listar cobros
      description: Lista cobros con filtros y paginación por cursor.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [created, qr_pending, qr_issued, paid, paid_late, cancel_requested, cancelled, expired, failed]
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: externalReference
          in: query
          schema:
            type: string
            maxLength: 100
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          schema:
            type: string
            maxLength: 200
      responses:
        '200':
          description: Lista de cobros
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              $ref: '#/components/schemas/ChargeView'
                          nextCursor:
                            type: string
                            nullable: true

  /charges/{chargeId}:
    get:
      operationId: getCharge
      summary: Obtener cobro
      description: Devuelve el cobro con su historial de estados.
      parameters:
        - $ref: '#/components/parameters/chargeId'
      responses:
        '200':
          description: Cobro encontrado
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ChargeView'
        '404':
          description: Cobro no encontrado

  /charges/{chargeId}/qr:
    get:
      operationId: getQrImage
      summary: QR del cobro
      description: |
        Imagen del QR y enlaces para compartir. Cada llamada genera enlaces nuevos.
        Con `Accept: image/png` devuelve la imagen directamente.
      parameters:
        - $ref: '#/components/parameters/chargeId'
      responses:
        '200':
          description: QR disponible
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          chargeId:
                            type: string
                          reference:
                            type: string
                          qrState:
                            type: string
                            enum: [payable, paid, expired, cancelled, unavailable]
                          qr_image_url:
                            type: string
                            nullable: true
                          qr_page_url:
                            type: string
                            nullable: true
            image/png:
              schema:
                type: string
                format: binary
        '410':
          description: QR no pagable (pagado, vencido o anulado)

  /charges/{chargeId}/cancel:
    post:
      operationId: cancelCharge
      summary: Anular cobro
      description: Anula (bloquea) el QR para que no se pueda pagar. Irreversible.
      parameters:
        - $ref: '#/components/parameters/chargeId'
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  maxLength: 200
      responses:
        '200':
          description: Cobro anulado o en proceso de anulación
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          chargeId:
                            type: string
                          status:
                            type: string
        '400':
          description: Transición no válida
        '404':
          description: Cobro no encontrado

  /charges/{chargeId}/refresh:
    post:
      operationId: refreshChargeStatus
      summary: Reconsultar estado
      description: Fuerza la consulta del estado del cobro al proveedor.
      parameters:
        - $ref: '#/components/parameters/chargeId'
        - $ref: '#/components/parameters/idempotencyKey'
      responses:
        '200':
          description: Estado actualizado
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ChargeView'

  /payments/status:
    get:
      operationId: getPaymentStatus
      summary: Estado de pago
      description: Estado de pago de un cobro (solo lectura, no consulta al proveedor).
      parameters:
        - name: reference
          in: query
          schema:
            type: string
          description: Referencia KPY-… (exactamente uno de reference o chargeId)
        - name: chargeId
          in: query
          schema:
            type: string
          description: ID del cobro (exactamente uno de reference o chargeId)
      responses:
        '200':
          description: Estado de pago
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/PaymentStatus'
        '400':
          description: Debe indicar reference o chargeId (exactamente uno)
        '404':
          description: Cobro no encontrado

  /limits:
    get:
      operationId: getLimitsAndUsage
      summary: Límites y consumo
      description: Límite por cobro, límite y consumo semanal, estado de la demo.
      responses:
        '200':
          description: Límites del comercio
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkResponse'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/LimitsUsage'
