openapi: 3.1.0
info:
  title: API de Prestando
  version: 2.0.0
  description: |
    Bienvenido a la documentación oficial de la API de **Prestando** — la plataforma de financiamiento BNPL (Compra Ahora, Paga Después) para América Latina.

    Con esta API puedes:
    - Crear y gestionar **sesiones de pago** BNPL para tu checkout
    - Consultar el **estado de órdenes** y cuotas en tiempo real
    - Configurar y administrar **webhooks** para recibir eventos
    - Consultar **opciones de cuotas** para mostrárselas a tus clientes antes del checkout
    - Acceder a la **bitácora de comunicaciones** para depurar integraciones

    ## Autenticación

    Todas las peticiones al API de comercios requieren **API Key + firma HMAC**:

    ```
    Authorization: Bearer apk_live_<tu_secreto>
    X-Prestando-Timestamp: 1714000000
    X-Prestando-Signature: sha256=<base64(HMAC-SHA256(secreto, timestamp + "." + cuerpo_raw))>
    ```

    - El timestamp debe estar en segundos (Unix). El servidor rechaza peticiones con desfase mayor a ±300 s.
    - El cuerpo de la firma es el JSON crudo de la petición. Para GET sin cuerpo, usa una cadena vacía.
    - Usa el prefijo `apk_live_` para producción y `apk_test_` para sandbox.

    ## Entornos

    | Entorno | URL base |
    |---------|----------|
    | Producción | `https://api.prestando.com` |
    | Sandbox | `https://sandbox.api.prestando.com` |

  contact:
    name: Soporte para Desarrolladores
    email: dev@prestando.com
    url: https://prestando.com/developers
  license:
    name: Propietaria
    url: https://prestando.com/terminos

servers:
  - url: https://api.prestando.com
    description: Producción
  - url: https://sandbox.api.prestando.com
    description: Sandbox (pruebas)

security:
  - MerchantApiKey: []
    MerchantSignature: []
    MerchantTimestamp: []

tags:
  - name: Preview
    description: Consulta de opciones de cuotas sin autenticación. Úsalo en tu página de producto.
  - name: Checkout Sessions
    description: Creación y confirmación de sesiones de pago BNPL.
  - name: Órdenes
    description: Consulta de órdenes y su detalle (cuotas, pagos, webhooks).
  - name: Webhooks
    description: Gestión de endpoints de webhooks y consulta de entregas.
  - name: API Keys
    description: Creación y revocación de claves API.
  - name: Bitácora
    description: Bitácora de comunicaciones para depuración.
  - name: Comercio público
    description: Datos públicos de un comercio (sin autenticación).

paths:

  /api/public/bnpl/installment-preview:
    post:
      operationId: previewInstallments
      tags:
        - Preview
      summary: Vista previa de opciones de cuotas
      description: |
        Devuelve las opciones de financiamiento disponibles para un comercio y monto dado.
        Este endpoint es público — no requiere API key ni firma.

        Úsalo para mostrar "Paga en 4 cuotas de RD$ 625" en tu página de producto antes de que el cliente llegue al checkout.

        Si `businessRelationshipId` se omite, se devuelven opciones para todas las relaciones activas del comercio.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstallmentPreviewRequest'
            example:
              merchantId: "merch_festiva_prod"
              amountMinor: 250000
              currency: "DOP"
      responses:
        '200':
          description: Opciones de cuotas disponibles.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InstallmentPreviewResponse'
              example:
                relationships:
                  - relationshipId: "rel_abc123"
                    lenderDisplayName: "FinanciadoraDR"
                    financingModel: "ZERO_RISK_PREPAYMENT"
                    currency: "DOP"
                    frequency: "WEEKLY"
                    maxAmountMinor: 6000000
                    options:
                      - installments: 4
                        installmentAmountMinor: 62500
                        totalAmountMinor: 250000
                        feeAmountMinor: 0
                        firstDueDate: "2026-05-02"
                        dueDates: ["2026-05-02", "2026-05-09", "2026-05-16", "2026-05-23"]
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          description: Demasiadas peticiones. Límite por IP — 20 req/min.

  /api/public/bnpl/checkout-sessions:
    post:
      operationId: createCheckoutSession
      tags:
        - Checkout Sessions
      summary: Crear sesión de pago
      description: |
        Crea una sesión de pago BNPL para un cliente. Devuelve un `sessionId` que se pasa
        al SDK para abrir el popup de checkout.

        El backend calcula las opciones de cuotas según la relación comercial activa.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCheckoutSessionRequest'
            example:
              merchantId: "merch_festiva_prod"
              businessRelationshipId: "rel_abc123"
              externalOrderId: "FESTIVA-20260425-9912"
              description: "Entrada concierto Sech — VIP x2"
              amountMinor: 450000
              currency: "DOP"
              serviceDate: "2026-06-15"
              maxInstallments: 4
              frequency: "WEEKLY"
      responses:
        '201':
          description: Sesión creada correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'

  /api/public/bnpl/checkout-sessions/{sessionId}:
    get:
      operationId: getCheckoutSession
      tags:
        - Checkout Sessions
      summary: Consultar sesión de pago
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: Sesión encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutSessionResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/public/bnpl/checkout-sessions/{sessionId}/confirm:
    post:
      operationId: confirmCheckoutSession
      tags:
        - Checkout Sessions
      summary: Confirmar sesión de pago
      description: |
        Confirma la sesión de pago con los datos del cliente y el plan elegido.
        Crea el financiamiento y devuelve su detalle.

        Este endpoint es llamado por el popup de Prestando — no necesitas invocarlo directamente.
      parameters:
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmCheckoutSessionRequest'
      responses:
        '200':
          description: Financiamiento creado correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinancingCreatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          description: La sesión ya fue confirmada o expiró.

  /api/merchant/orders:
    get:
      operationId: listMerchantOrders
      tags:
        - Órdenes
      summary: Listar órdenes del comercio
      parameters:
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
            enum: [PENDING, ACTIVE, COMPLETED, CANCELLED, REJECTED, LATE, DEFAULTED, IN_COLLECTIONS]
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
        - name: cursor
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      responses:
        '200':
          description: Lista de órdenes.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MerchantOrderSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/merchant/orders/{purchaseIntentId}:
    get:
      operationId: getMerchantOrder
      tags:
        - Órdenes
      summary: Detalle de una orden
      parameters:
        - name: purchaseIntentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Detalle de la orden.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantOrderDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/merchant/webhooks/endpoints:
    get:
      operationId: listWebhookEndpoints
      tags:
        - Webhooks
      summary: Listar endpoints de webhooks
      parameters:
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Lista de endpoints configurados.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebhookEndpoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createWebhookEndpoint
      tags:
        - Webhooks
      summary: Crear endpoint de webhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookEndpointRequest'
      responses:
        '201':
          description: Endpoint creado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/merchant/webhooks/deliveries:
    get:
      operationId: listWebhookDeliveries
      tags:
        - Webhooks
      summary: Listar entregas de webhooks
      parameters:
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Lista de entregas.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebhookDelivery'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/merchant/webhooks/deliveries/{deliveryId}/retry:
    post:
      operationId: retryWebhookDelivery
      tags:
        - Webhooks
      summary: Reenviar entrega de webhook
      description: |
        Crea una nueva entrega (clon) del webhook. La entrega original permanece en su estado terminal.
        El nuevo registro se despacha en el siguiente ciclo del scheduler (segundos).
      parameters:
        - name: deliveryId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Nueva entrega (clon) creada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/merchant/api-keys:
    get:
      operationId: listMerchantApiKeys
      tags:
        - API Keys
      summary: Listar claves API
      parameters:
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Lista de claves API.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MerchantApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createMerchantApiKey
      tags:
        - API Keys
      summary: Crear clave API
      description: |
        El campo `secret` solo se incluye en la respuesta de creación —
        guárdalo de forma segura, no podrás consultarlo de nuevo.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMerchantApiKeyRequest'
      responses:
        '201':
          description: Clave creada. El `secret` solo aparece en esta respuesta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuedMerchantApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/merchant/api-keys/{keyId}:
    delete:
      operationId: revokeMerchantApiKey
      tags:
        - API Keys
      summary: Revocar clave API
      parameters:
        - name: keyId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Clave revocada.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/merchant/communication-logs:
    get:
      operationId: listCommunicationLogs
      tags:
        - Bitácora
      summary: Consultar bitácora de comunicaciones
      description: |
        Devuelve entradas de la bitácora de comunicaciones del comercio. Incluye tanto peticiones
        entrantes (INBOUND) al API de Prestando como salientes (OUTBOUND) a webhooks del comercio.
        Retención: 90 días.
      parameters:
        - name: merchantId
          in: query
          required: true
          schema:
            type: string
        - name: direction
          in: query
          schema:
            type: string
            enum: [INBOUND, OUTBOUND]
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      responses:
        '200':
          description: Lista de entradas de la bitácora.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CommunicationLogEntry'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/public/merchants/{merchantId}:
    get:
      operationId: getPublicMerchant
      tags:
        - Comercio público
      summary: Datos públicos del comercio
      description: |
        Devuelve información pública del comercio para el popup de checkout.
        No requiere autenticación. Respuesta cacheada 5 minutos.
      security: []
      parameters:
        - name: merchantId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Datos del comercio.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicMerchantResponse'
        '404':
          $ref: '#/components/responses/NotFound'

webhooks:
  financing.created:
    post:
      summary: Financiamiento creado
      description: Se dispara cuando un cliente confirma una sesión de pago y se crea el financiamiento.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'
            example:
              eventType: financing.created
              occurredAt: "2026-04-25T14:30:00Z"
              merchantId: "merch_festiva_prod"
              data:
                financingId: "fin_abc"
                purchaseIntentId: "pi_xyz"
                externalOrderId: "FESTIVA-20260425-9912"
                amountMinor: 450000
                currency: "DOP"
                installmentCount: 4
                status: ACTIVE

  payment.succeeded:
    post:
      summary: Pago exitoso
      description: Se dispara cuando se cobra una cuota exitosamente.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'

  payment.failed:
    post:
      summary: Pago fallido
      description: Se dispara cuando falla el intento de cobro de una cuota.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'

  financing.completed:
    post:
      summary: Financiamiento completado
      description: Se dispara cuando se cobran todas las cuotas del financiamiento.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'

  fulfillment.allowed:
    post:
      summary: Entrega habilitada
      description: |
        Se dispara junto con `financing.completed` cuando el modelo de financiamiento
        permite la entrega del bien o servicio al cliente. Úsalo para disparar la
        emisión de las entradas / confirmación del pedido.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'

  financing.cancelled:
    post:
      summary: Financiamiento cancelado
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'

  loan.late:
    post:
      summary: Financiamiento en mora
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'

components:
  securitySchemes:
    MerchantApiKey:
      type: http
      scheme: bearer
      description: |
        Tu clave API con prefijo `apk_live_` (producción) o `apk_test_` (sandbox).
        Ejemplo: `Authorization: Bearer apk_live_abc123...`
    MerchantSignature:
      type: apiKey
      in: header
      name: X-Prestando-Signature
      description: |
        Firma HMAC-SHA256 del cuerpo crudo de la petición.
        Formato: `sha256=<base64(HMAC-SHA256(secreto, timestamp + "." + cuerpo_raw))>`
    MerchantTimestamp:
      type: apiKey
      in: header
      name: X-Prestando-Timestamp
      description: Timestamp Unix en segundos. Desfase máximo ±300 s.

  parameters:
    SessionId:
      name: sessionId
      in: path
      required: true
      schema:
        type: string
        format: uuid

  responses:
    BadRequest:
      description: Datos de entrada inválidos.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Falta la API key, la firma es inválida, o el timestamp expiró.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Recurso no encontrado.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UnprocessableEntity:
      description: La petición está bien formada pero no puede procesarse.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  schemas:

    ErrorResponse:
      type: object
      properties:
        message:
          type: string
          example: El monto supera el límite permitido para esta relación comercial.

    InstallmentPreviewRequest:
      type: object
      required: [merchantId, amountMinor, currency]
      properties:
        merchantId:
          type: string
        businessRelationshipId:
          type: string
          description: Omite para obtener opciones de todas las relaciones activas.
        amountMinor:
          type: integer
          description: Monto en centavos (ej. 250000 = RD$ 2,500.00).
        currency:
          type: string
          example: DOP

    InstallmentOption:
      type: object
      properties:
        installments:
          type: integer
        installmentAmountMinor:
          type: integer
        totalAmountMinor:
          type: integer
        feeAmountMinor:
          type: integer
        firstDueDate:
          type: string
          format: date
        dueDates:
          type: array
          items:
            type: string
            format: date

    RelationshipPreview:
      type: object
      properties:
        relationshipId:
          type: string
        lenderDisplayName:
          type: string
        financingModel:
          type: string
        currency:
          type: string
        frequency:
          type: string
          enum: [WEEKLY, BIWEEKLY, MONTHLY]
        maxAmountMinor:
          type: integer
        options:
          type: array
          items:
            $ref: '#/components/schemas/InstallmentOption'

    InstallmentPreviewResponse:
      type: object
      properties:
        relationships:
          type: array
          items:
            $ref: '#/components/schemas/RelationshipPreview'

    CreateCheckoutSessionRequest:
      type: object
      required: [merchantId, businessRelationshipId, externalOrderId, amountMinor, currency, serviceDate, maxInstallments]
      properties:
        merchantId:
          type: string
        businessRelationshipId:
          type: string
        externalOrderId:
          type: string
          maxLength: 120
        description:
          type: string
          maxLength: 255
        amountMinor:
          type: integer
        currency:
          type: string
          example: DOP
        serviceDate:
          type: string
          format: date
        maxInstallments:
          type: integer
          minimum: 1
          maximum: 12
        frequency:
          type: string
          enum: [WEEKLY, BIWEEKLY, MONTHLY]
          default: WEEKLY
        metadataJson:
          type: string

    CheckoutSessionResponse:
      type: object
      properties:
        sessionId:
          type: string
          format: uuid
        merchantId:
          type: string
        businessRelationshipId:
          type: string
        externalOrderId:
          type: string
        description:
          type: string
          nullable: true
        amountMinor:
          type: integer
        currency:
          type: string
        serviceDate:
          type: string
          format: date
        maxInstallments:
          type: integer
        frequency:
          type: string
        status:
          type: string
          enum: [OPEN, CONFIRMED, CANCELLED, EXPIRED]
        expiresAt:
          type: string
          format: date-time
        financingId:
          type: string
          nullable: true
        purchaseIntentId:
          type: string
          nullable: true
        plans:
          type: array
          items:
            $ref: '#/components/schemas/CheckoutPlan'

    CheckoutPlan:
      type: object
      properties:
        installments:
          type: integer
        installmentAmountMinor:
          type: integer
        frequency:
          type: string
        firstDueDate:
          type: string
          format: date
        dueDates:
          type: array
          items:
            type: string
            format: date

    ConfirmCheckoutSessionRequest:
      type: object
      required: [installmentCount, customerExternalAuthId, customerFullName, customerEmail]
      properties:
        installmentCount:
          type: integer
        customerExternalAuthId:
          type: string
        customerFullName:
          type: string
        customerEmail:
          type: string
          format: email
        customerPhone:
          type: string
        paymentMethodToken:
          type: string

    FinancingCreatedResponse:
      type: object
      properties:
        financingId:
          type: string
          format: uuid
        purchaseIntentId:
          type: string
          format: uuid
        status:
          type: string
          enum: [PENDING, ACTIVE, COMPLETED, CANCELLED, LATE, DELINQUENT, IN_COLLECTIONS, DEFAULTED, REJECTED, EXPIRED]
        totalAmountMinor:
          type: integer
        currency:
          type: string
        installmentCount:
          type: integer
        frequency:
          type: string
        serviceDate:
          type: string
          format: date
        fulfillmentAllowed:
          type: boolean
        installments:
          type: array
          items:
            $ref: '#/components/schemas/InstallmentRow'

    InstallmentRow:
      type: object
      properties:
        installmentId:
          type: string
          format: uuid
        installmentNumber:
          type: integer
        dueDate:
          type: string
          format: date
        amountMinor:
          type: integer
        status:
          type: string
          enum: [PENDING, PAID, LATE, FAILED, CANCELLED]
        paidAt:
          type: string
          format: date-time
          nullable: true
        retryCount:
          type: integer

    MerchantOrderSummary:
      type: object
      properties:
        purchaseIntentId:
          type: string
          format: uuid
        externalOrderId:
          type: string
        description:
          type: string
          nullable: true
        currency:
          type: string
        amountMinor:
          type: integer
        serviceDate:
          type: string
          format: date
        status:
          type: string
        financingAgreementId:
          type: string
          nullable: true
        totalInstallments:
          type: integer
          nullable: true
        paidInstallments:
          type: integer
          nullable: true
        customerFullName:
          type: string
          nullable: true
        customerEmail:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time

    MerchantOrderDetail:
      allOf:
        - $ref: '#/components/schemas/MerchantOrderSummary'
        - type: object
          properties:
            customerId:
              type: string
            financingModel:
              type: string
              nullable: true
            financingStatus:
              type: string
              nullable: true
            principalAmountMinor:
              type: integer
            feeAmountMinor:
              type: integer
            totalAmountMinor:
              type: integer
            installmentCount:
              type: integer
            frequency:
              type: string
              nullable: true
            startDate:
              type: string
              format: date
              nullable: true
            deadlineDate:
              type: string
              format: date
              nullable: true
            fulfillmentAllowedAt:
              type: string
              format: date-time
              nullable: true
            installments:
              type: array
              items:
                $ref: '#/components/schemas/InstallmentRow'
            payments:
              type: array
              items:
                type: object
                properties:
                  paymentId:
                    type: string
                  installmentId:
                    type: string
                  provider:
                    type: string
                  amountMinor:
                    type: integer
                  status:
                    type: string
                  gatewayReference:
                    type: string
                    nullable: true
                  failureCode:
                    type: string
                    nullable: true
                  processedAt:
                    type: string
                    format: date-time
                    nullable: true
                  createdAt:
                    type: string
                    format: date-time
            webhookDeliveries:
              type: array
              items:
                $ref: '#/components/schemas/WebhookDelivery'
            recentCommunicationLogs:
              type: array
              items:
                $ref: '#/components/schemas/CommunicationLogEntry'

    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        merchantId:
          type: string
        name:
          type: string
        targetUrl:
          type: string
          format: uri
        eventFilters:
          type: array
          items:
            type: string
        active:
          type: boolean
        maxRetries:
          type: integer
        createdAt:
          type: string
          format: date-time

    CreateWebhookEndpointRequest:
      type: object
      required: [merchantId, name, targetUrl, eventFilters]
      properties:
        merchantId:
          type: string
        name:
          type: string
          maxLength: 120
        targetUrl:
          type: string
          format: uri
        eventFilters:
          type: array
          items:
            type: string
            enum:
              - financing.created
              - payment.succeeded
              - payment.failed
              - financing.completed
              - financing.cancelled
              - fulfillment.allowed
              - loan.late
              - loan.defaulted
        secret:
          type: string
          description: Secreto HMAC para verificar la firma de los eventos. Opcional.
        maxRetries:
          type: integer
          default: 3

    WebhookDelivery:
      type: object
      properties:
        id:
          type: string
          format: uuid
        endpointId:
          type: string
          format: uuid
        eventType:
          type: string
        status:
          type: string
          enum: [PENDING, DELIVERED, FAILED]
        attemptCount:
          type: integer
        responseCode:
          type: integer
          nullable: true
        lastAttemptAt:
          type: string
          format: date-time
          nullable: true
        nextAttemptAt:
          type: string
          format: date-time
          nullable: true
        parentDeliveryId:
          type: string
          format: uuid
          nullable: true
          description: Presente si esta entrega es un reenvío de otra.
        createdAt:
          type: string
          format: date-time

    MerchantApiKey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        merchantId:
          type: string
        keyPrefix:
          type: string
          example: apk_live_ab12cd
        environment:
          type: string
          enum: [LIVE, SANDBOX]
        status:
          type: string
          enum: [ACTIVE, REVOKED]
        name:
          type: string
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time

    IssuedMerchantApiKey:
      allOf:
        - $ref: '#/components/schemas/MerchantApiKey'
        - type: object
          properties:
            secret:
              type: string
              description: El secreto completo. Solo se devuelve en el momento de creación.
              example: apk_live_ab12cd34ef56...

    CreateMerchantApiKeyRequest:
      type: object
      required: [merchantId, environment, name]
      properties:
        merchantId:
          type: string
        environment:
          type: string
          enum: [LIVE, SANDBOX]
        name:
          type: string
          maxLength: 120
          example: Backend producción

    CommunicationLogEntry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        merchantId:
          type: string
        direction:
          type: string
          enum: [INBOUND, OUTBOUND]
        protocol:
          type: string
        method:
          type: string
          nullable: true
        path:
          type: string
          nullable: true
        requestHeadersJson:
          type: string
          nullable: true
        requestBodyExcerpt:
          type: string
          nullable: true
        responseStatus:
          type: integer
          nullable: true
        responseBodyExcerpt:
          type: string
          nullable: true
        correlationId:
          type: string
          nullable: true
        occurredAt:
          type: string
          format: date-time
        latencyMs:
          type: integer
          nullable: true
        errorMessage:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time

    PublicMerchantResponse:
      type: object
      properties:
        merchantId:
          type: string
        displayName:
          type: string
        logoUrl:
          type: string
          format: uri
          nullable: true
        countryCode:
          type: string
          example: DO

    WebhookEvent:
      type: object
      description: |
        Payload enviado a tu endpoint de webhook.
        Firmado con `X-Prestando-Signature: sha256=<base64(HMAC-SHA256(secret, payload))>`.
      properties:
        eventType:
          type: string
          example: financing.created
        occurredAt:
          type: string
          format: date-time
        merchantId:
          type: string
        data:
          type: object
          description: Datos específicos del evento. Varía según `eventType`.
