{
  "openapi": "3.0.3",
  "info": {
    "title": "PagoFino Developers API",
    "version": "2.0.0",
    "description": "API REST de PagoFino Developers. Verifica pagos de Pago Móvil capturados por la app oficial de PagoFino (Google Play) y recibe webhooks firmados en tu servidor.\n\n**Autenticación:** crea tu API Key en el panel (`/panel`) y envíala en el header `X-API-Key`. Cada llave solo da acceso a los pagos de su propia cuenta.\n\n**Evita referencias reutilizadas:** usa `POST /v1/pagos/verificar` con `consumir: true` para verificar y conciliar el pago en una sola llamada. Un pago conciliado ya no vuelve a dar `verificado: true`.\n\n**Créditos:** 1 crédito = 1 pago registrado por la app; verificar y consultar es ilimitado. Sin saldo, los endpoints de `/v1/pagos` responden `402 SALDO_AGOTADO` (los pagos se siguen registrando). Cada respuesta incluye el header `X-PagoFino-Creditos` con tu saldo.",
    "contact": {
      "name": "Soporte para Desarrolladores PagoFino",
      "url": "https://pagofino.com"
    }
  },
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Servidor local de desarrollo"
    },
    {
      "url": "https://api.pagofino.com",
      "description": "Servidor de producción"
    }
  ],
  "tags": [
    {
      "name": "Pagos"
    },
    {
      "name": "Webhooks"
    },
    {
      "name": "Cuenta"
    },
    {
      "name": "Estado"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API Key creada en el panel de PagoFino Developers (pfd_live_…)"
      }
    },
    "schemas": {
      "Pago": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1842
          },
          "referencia": {
            "type": "string",
            "example": "004512"
          },
          "monto": {
            "type": "number",
            "example": 1250.5
          },
          "moneda": {
            "type": "string",
            "example": "Bs"
          },
          "estado": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "failed"
            ],
            "description": "pending = recibido y sin usar; confirmed = ya conciliado; failed = rechazado"
          },
          "origen": {
            "type": "string",
            "nullable": true,
            "example": "APP PAGOFINO"
          },
          "banco": {
            "type": "string",
            "nullable": true
          },
          "telefono": {
            "type": "string",
            "nullable": true
          },
          "cedula": {
            "type": "string",
            "nullable": true
          },
          "verificado_por": {
            "type": "string",
            "nullable": true,
            "example": "API · Mi Tienda"
          },
          "verificado_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "fecha": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Verificacion": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "verificado": {
            "type": "boolean",
            "description": "true solo si el pago existe y no había sido usado antes. Es el único campo que debes revisar para aprobar una venta."
          },
          "resultado": {
            "type": "string",
            "enum": [
              "disponible",
              "conciliado",
              "ya_conciliado",
              "rechazado",
              "no_encontrado"
            ]
          },
          "mensaje": {
            "type": "string"
          },
          "criterios": {
            "type": "object"
          },
          "pago": {
            "$ref": "#/components/schemas/Pago"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string",
            "example": "INVALID_API_KEY"
          },
          "mensaje": {
            "type": "string"
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "example": "https://mi-erp.com/webhooks/pagofino"
          },
          "descripcion": {
            "type": "string",
            "nullable": true
          },
          "eventos": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "pago.recibido",
                "pago.confirmado"
              ]
            }
          },
          "activo": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "responses": {
      "NoAutorizado": {
        "description": "API Key ausente, inválida o revocada",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Prohibido": {
        "description": "Cuenta suspendida o licencia vencida",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "SinSaldo": {
        "description": "Saldo de créditos agotado: recarga en el panel",
        "content": {
          "application/json": {
            "example": {
              "ok": false,
              "error": "SALDO_AGOTADO",
              "creditos": 0,
              "mensaje": "Tu saldo de créditos está agotado…"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Estado"
        ],
        "summary": "Estado de la API",
        "security": [],
        "responses": {
          "200": {
            "description": "API operativa"
          }
        }
      }
    },
    "/v1/cuenta": {
      "get": {
        "tags": [
          "Cuenta"
        ],
        "summary": "Cuenta dueña de la API Key",
        "description": "Útil para comprobar que tu API Key funciona.",
        "responses": {
          "200": {
            "description": "Datos de la cuenta",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "cuenta": {
                    "username": "mitienda",
                    "nombre_comercio": "Mi Tienda C.A.",
                    "plan": "developer",
                    "estado": "activo",
                    "licencia_expira": null,
                    "creditos": 2996
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/Prohibido"
          }
        }
      }
    },
    "/v1/pagos/verificar": {
      "get": {
        "tags": [
          "Pagos"
        ],
        "summary": "Consultar si un pago llegó (sin conciliarlo)",
        "parameters": [
          {
            "name": "referencia",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Referencia completa o sus últimos 4 a 6 dígitos"
          },
          {
            "name": "monto",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Monto esperado (tolerancia ±0,05). Acepta 1250.50 o 1.250,50"
          },
          {
            "name": "moneda",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Bs, USD, USDT…"
          },
          {
            "name": "max_horas",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 72,
              "maximum": 720
            },
            "description": "Antigüedad máxima del pago"
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado de la verificación",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verificacion"
                },
                "example": {
                  "ok": true,
                  "verificado": true,
                  "resultado": "disponible",
                  "mensaje": "Pago recibido y disponible. Concílialo para que no pueda reutilizarse.",
                  "pago": {
                    "id": 1842,
                    "referencia": "004512",
                    "monto": 1250.5,
                    "moneda": "Bs",
                    "estado": "pending",
                    "fecha": "2026-10-27T15:04:00Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Parámetros inválidos"
          },
          "401": {
            "$ref": "#/components/responses/NoAutorizado"
          },
          "402": {
            "$ref": "#/components/responses/SinSaldo"
          }
        }
      },
      "post": {
        "tags": [
          "Pagos"
        ],
        "summary": "Verificar y conciliar un pago en una sola llamada (recomendado)",
        "description": "Con `consumir: true` el pago se marca como conciliado de forma atómica: si dos cajas consultan la misma referencia a la vez, solo una recibe `verificado: true`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "referencia": "004512",
                "monto": 1250.5,
                "moneda": "Bs",
                "consumir": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado de la verificación",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verificacion"
                },
                "example": {
                  "ok": true,
                  "verificado": true,
                  "resultado": "conciliado",
                  "mensaje": "Pago verificado y conciliado. Ya no podrá usarse de nuevo.",
                  "pago": {
                    "id": 1842,
                    "referencia": "004512",
                    "monto": 1250.5,
                    "moneda": "Bs",
                    "estado": "confirmed",
                    "verificado_por": "API · Mi Tienda"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Parámetros inválidos"
          },
          "401": {
            "$ref": "#/components/responses/NoAutorizado"
          },
          "402": {
            "$ref": "#/components/responses/SinSaldo"
          }
        }
      }
    },
    "/v1/pagos": {
      "get": {
        "tags": [
          "Pagos"
        ],
        "summary": "Listar pagos",
        "parameters": [
          {
            "name": "estado",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "confirmed",
                "failed",
                "all"
              ]
            }
          },
          {
            "name": "moneda",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "referencia",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Contiene el texto"
          },
          {
            "name": "desde",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "hasta",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 250
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de pagos",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "total": 1,
                  "limit": 50,
                  "offset": 0,
                  "pagos": [
                    {
                      "id": 1842,
                      "referencia": "004512",
                      "monto": 1250.5,
                      "moneda": "Bs",
                      "estado": "pending"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NoAutorizado"
          },
          "402": {
            "$ref": "#/components/responses/SinSaldo"
          }
        }
      }
    },
    "/v1/pagos/{id}": {
      "get": {
        "tags": [
          "Pagos"
        ],
        "summary": "Detalle de un pago",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pago",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "pago": {
                      "$ref": "#/components/schemas/Pago"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No existe en tu cuenta"
          },
          "402": {
            "$ref": "#/components/responses/SinSaldo"
          }
        }
      }
    },
    "/v1/pagos/conciliar": {
      "post": {
        "tags": [
          "Pagos"
        ],
        "summary": "Conciliar un pago",
        "description": "Marca un pago pendiente como conciliado (confirmed). Usa el `id` o la referencia completa.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "id": 1842
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pago conciliado"
          },
          "404": {
            "description": "No existe en tu cuenta"
          },
          "409": {
            "description": "Ya estaba conciliado (YA_CONCILIADO) o rechazado (PAGO_RECHAZADO)"
          },
          "402": {
            "$ref": "#/components/responses/SinSaldo"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar tus webhooks",
        "responses": {
          "200": {
            "description": "Lista de webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Registrar un webhook",
        "description": "La URL debe ser https:// y pública. La respuesta incluye el `secreto` para validar la firma HMAC-SHA256 del header `X-PagoFino-Signature` (formato `sha256=<hex>` calculado sobre el cuerpo crudo). Cada envío incluye X-PagoFino-Delivery (igual entre reintentos) para que ignores duplicados.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "url": "https://mi-erp.com/webhooks/pagofino",
                "descripcion": "ERP",
                "eventos": [
                  "pago.recibido",
                  "pago.confirmado"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook creado (incluye el secreto)"
          },
          "400": {
            "description": "URL o eventos inválidos"
          },
          "409": {
            "description": "Límite de 5 webhooks"
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Eliminar un webhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eliminado"
          },
          "404": {
            "description": "No existe en tu cuenta"
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Enviar un pago simulado a tu webhook (pasa por la cola y se envía al instante)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "evento": "pago.recibido"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado de la entrega"
          },
          "404": {
            "description": "No existe en tu cuenta"
          }
        }
      }
    },
    "/v1/webhooks/entregas": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Historial y cola de avisos",
        "description": "Cada aviso queda en una cola y se reintenta durante ~24 h (1 min, 5 min, 15 min, 30 min, 1 h, 3 h, 6 h, 12 h). Estados: pendiente, entregado, fallido.",
        "parameters": [
          {
            "name": "estado",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pendiente",
                "entregado",
                "fallido"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Avisos de tu cuenta",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "total": 1,
                  "entregas": [
                    {
                      "id": "7c1e…",
                      "evento": "pago.recibido",
                      "prueba": false,
                      "referencia": "004512",
                      "estado": "entregado",
                      "intentos": 1,
                      "http_status": 200,
                      "error": null
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/entregas/{id}/reenviar": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Reenviar un aviso",
        "description": "Vuelve a poner en la cola un aviso entregado o fallido.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Aviso en cola"
          },
          "404": {
            "description": "No existe en tu cuenta o ya está en cola"
          }
        }
      }
    },
    "/precios": {
      "get": {
        "tags": [
          "Estado"
        ],
        "summary": "Paquetes de créditos vigentes",
        "security": [],
        "responses": {
          "200": {
            "description": "Paquetes y reglas de créditos"
          }
        }
      }
    },
    "/tasas": {
      "get": {
        "tags": [
          "Estado"
        ],
        "summary": "Tasa BCV del día (para recargas en bolívares)",
        "security": [],
        "responses": {
          "200": {
            "description": "Tasas BCV y paralelo"
          }
        }
      }
    }
  }
}