{"openapi":"3.0.0","paths":{"/health":{"get":{"operationId":"HealthController_check","parameters":[],"responses":{"200":{"description":"The Health Check is successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"info":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"error":{"type":"object","example":{},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"details":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true}}}}}}},"503":{"description":"The Health Check is not successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"info":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"error":{"type":"object","example":{"redis":{"status":"down","message":"Could not connect"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"details":{"type":"object","example":{"database":{"status":"up"},"redis":{"status":"down","message":"Could not connect"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true}}}}}}}},"tags":["Health"]}},"/v1/admin/merchants":{"post":{"description":"Crea un nuevo merchant en el gateway. Solo accesible con `ADMIN_API_TOKEN`.","operationId":"adminMerchantsCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMerchantDto"}}}},"responses":{"201":{"description":"Merchant creado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Email o slug en uso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Crear merchant","tags":["Admin · Merchants"]},"get":{"description":"Paginación por cursor. Devuelve `nextCursor` cuando hay más resultados.","operationId":"adminMerchantsList","parameters":[{"name":"limit","required":false,"in":"query","description":"1–100. Default 20.","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"`id` del último merchant de la página anterior.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantListDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar merchants","tags":["Admin · Merchants"]}},"/v1/admin/merchants/{id}":{"get":{"operationId":"adminMerchantsRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Obtener merchant por ID","tags":["Admin · Merchants"]},"patch":{"operationId":"adminMerchantsUpdate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMerchantDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Email en uso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Actualizar merchant","tags":["Admin · Merchants"]}},"/v1/merchants/me":{"get":{"description":"Devuelve el merchant dueño de la API key del header `Authorization`.","operationId":"merchantsRetrieveSelf","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener merchant autenticado","tags":["Merchants"]}},"/v1/admin/merchants/{merchantId}/api-keys":{"get":{"description":"Devuelve metadata de las API keys (prefix, tipo, ambiente, etiqueta, fechas). El valor completo (`secret`) **no** se devuelve nunca — solo en la creación.","operationId":"adminApiKeysList","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyDto"}}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar API keys de un merchant","tags":["Admin · API Keys"]},"post":{"description":"Emite una API key individual (privada o pública). Para alta de un comercio nuevo conviene usar `POST .../pair` que genera el par de una vez. El valor completo (`secret`) se devuelve **una sola vez** en esta respuesta.","operationId":"adminApiKeysCreate","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedApiKeyDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Emitir una API key suelta de un merchant","tags":["Admin · API Keys"]}},"/v1/admin/merchants/{merchantId}/api-keys/pair":{"post":{"description":"Genera de una sola vez la **clave privada** y la **clave pública** del mismo ambiente. Es el flujo recomendado para conectar un ERP, que necesita las dos. Los valores completos (`secret`) se devuelven **una sola vez** en esta respuesta.","operationId":"adminApiKeysCreatePair","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyPairDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedApiKeyPairDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Emitir par de claves (privada + pública) de un ambiente","tags":["Admin · API Keys"]}},"/v1/admin/merchants/{merchantId}/api-keys/{id}":{"delete":{"description":"Marca la key como revocada. Las nuevas requests con esa key serán rechazadas.","operationId":"adminApiKeysRevoke","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Revocada (o ya estaba revocada)."},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Key no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Revocar una API key","tags":["Admin · API Keys"]}},"/v1/api-keys":{"post":{"description":"Emite una nueva API key para el merchant autenticado. Requiere autenticarse con una key `secret` de scope `full`. El valor completo (`secret`) se devuelve **una sola vez** en esta respuesta.","operationId":"apiKeysCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedApiKeyDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key autenticada no tiene scope `full`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear API key","tags":["API Keys"]},"get":{"description":"Devuelve metadata. **Nunca** devuelve el valor completo, solo el `prefix`.","operationId":"apiKeysList","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyDto"}}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key autenticada no tiene scope `full`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Listar API keys del merchant","tags":["API Keys"]}},"/v1/api-keys/{id}":{"delete":{"description":"Marca la key como revocada (`revokedAt`). Idempotente: revocar una key ya revocada no falla.","operationId":"apiKeysRevoke","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key autenticada no tiene scope `full`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"API key no encontrada o no pertenece al merchant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Revocar API key","tags":["API Keys"]}},"/v1/customers":{"post":{"description":"Crea un customer asociado al merchant autenticado. Email y `(docType, docNumber)` son únicos por merchant.","operationId":"customersCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Email o documento ya existen para este merchant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear customer","tags":["Customers"]},"get":{"operationId":"customersList","parameters":[{"name":"limit","required":false,"in":"query","description":"1–100. Default 20.","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}},{"name":"email","required":false,"in":"query","description":"Filtra por email exacto (case-insensitive).","schema":{"type":"string"}},{"name":"docNumber","required":false,"in":"query","description":"Filtra por número de documento exacto.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerListDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Listar customers del merchant","tags":["Customers"]}},"/v1/customers/{id}":{"get":{"operationId":"customersRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Customer no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener customer por ID","tags":["Customers"]},"patch":{"operationId":"customersUpdate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Customer no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Email o documento en conflicto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Actualizar customer","tags":["Customers"]}},"/v1/provider-configs":{"post":{"description":"Registra credenciales de un provider (dLocal, Bancard, etc.) para una combinación `(provider, country, mode)`. Las credenciales se encriptan en reposo con AES-256-GCM y **nunca** se devuelven por API.","operationId":"providerConfigsCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProviderConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Ya existe una config para esa combinación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear configuración de provider","tags":["Provider Configs"]},"get":{"operationId":"providerConfigsList","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigListDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Listar configuraciones de provider","tags":["Provider Configs"]}},"/v1/provider-configs/{id}":{"get":{"operationId":"providerConfigsRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener provider config por ID","tags":["Provider Configs"]},"patch":{"description":"Si se envían `credentials`, **reemplazan** las anteriores (se re-encriptan).","operationId":"providerConfigsUpdate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProviderConfigDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Actualizar provider config","tags":["Provider Configs"]}},"/v1/admin/merchants/{merchantId}/provider-configs":{"post":{"description":"Asocia credenciales del comercio en un provider (dLocal, Bancard, etc.). Las credenciales se encriptan en reposo y nunca se devuelven por API.","operationId":"adminProviderConfigsCreate","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProviderConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant o config no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Ya existe config para esa combinación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Registrar pasarela del comercio","tags":["Admin · Provider Configs"]},"get":{"operationId":"adminProviderConfigsList","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigListDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant o config no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar pasarelas del comercio","tags":["Admin · Provider Configs"]}},"/v1/admin/merchants/{merchantId}/provider-configs/{id}":{"get":{"operationId":"adminProviderConfigsRetrieve","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant o config no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Obtener pasarela por ID","tags":["Admin · Provider Configs"]},"patch":{"description":"Si se envían `credentials`, **reemplazan** las anteriores (se re-encriptan).","operationId":"adminProviderConfigsUpdate","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProviderConfigDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderConfigDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant o config no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Actualizar pasarela","tags":["Admin · Provider Configs"]},"delete":{"operationId":"adminProviderConfigsDelete","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Eliminada."},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant o config no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Eliminar pasarela del comercio","tags":["Admin · Provider Configs"]}},"/v1/payment-intents":{"post":{"description":"Crea un intent de pago en estado `created`. El intent es la **fuente de verdad** del lifecycle (intent → attempt → refund). En esta fase no se invoca al provider aún (se hace en endpoints separados de captura/checkout).","operationId":"paymentIntentsCreate","parameters":[{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Token único del cliente para deduplicar requests dentro de 24h. Si se reenvía el mismo body+key, devuelve el mismo intent.","required":false,"schema":{"type":"string","example":"8c4ee2e6-1c4e-4f3b-9c7d-1234567890ab"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentIntentDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentIntentDto"}}}},"400":{"description":"Validación falló o `customerId` no pertenece al merchant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Idempotency-Key reusado con body distinto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear payment intent","tags":["Payment Intents"]},"get":{"description":"Paginación por cursor. Filtro opcional por `status`.","operationId":"paymentIntentsList","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["created","pending","processing","approved","rejected","expired","refunded","partially_refunded","cancelled"],"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentIntentListDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Listar payment intents","tags":["Payment Intents"]}},"/v1/payment-intents/{id}/confirm":{"post":{"description":"Invoca al provider ruteado (Dpago, dLocal, …) y crea un `PaymentAttempt`, actualiza el status del intent y emite el webhook correspondiente. Tres modalidades según el body:\n- `paymentLink: true` → link de pago nativo del provider (ej. Dpago `/links`); no requiere `platformId`. Devuelve `attempt.redirectUrl` (URL a compartir).\n- `platformId: \"18\"` → cobro directo con ese medio; devuelve `attempt.qr` (EMV para dibujar) y/o `attempt.redirectUrl`.\n- sin ninguno → el provider decide (hosted checkout); devuelve `attempt.redirectUrl` si aplica.","operationId":"paymentIntentsConfirm","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmPaymentIntentDto"}}}},"responses":{"200":{"description":"Intent confirmado (estado actual + attempt creado).","content":{"application/json":{"schema":{"type":"object","properties":{"intent":{"$ref":"#/components/schemas/PaymentIntentDto"},"attempt":{"$ref":"#/components/schemas/PaymentAttemptDto"}},"required":["intent","attempt"]}}}},"400":{"description":"Intent en status no confirmable, o falta provider config apto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Intent no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Confirmar payment intent (cobrar vía provider)","tags":["Payment Intents"]}},"/v1/payment-intents/{id}":{"get":{"operationId":"paymentIntentsRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentIntentDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener payment intent por ID","tags":["Payment Intents"]}},"/v1/payment-intents/{id}/sync":{"post":{"description":"Pull manual del estado al provider (útil cuando el webhook del provider no llegó). Actualiza attempt + intent y emite el webhook outbound correspondiente si hay cambio.","operationId":"paymentIntentsSync","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentIntentDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Intent no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Resincronizar intent contra el provider","tags":["Payment Intents"]}},"/v1/admin/billing/stats":{"get":{"description":"Agrega tx aprobadas/rechazadas, volumen por moneda y top 5 comercios. Si no se pasa `period`, usa el mes calendario actual (UTC).","operationId":"adminBillingGlobalStats","parameters":[{"name":"period","required":false,"in":"query","description":"Formato YYYY-MM (UTC).","schema":{"example":"2026-06","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlobalBillingStatsDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Métricas globales de la pasarela en un período","tags":["Admin · Billing"]}},"/v1/admin/merchants/{id}/billing/stats":{"get":{"operationId":"adminBillingMerchantStats","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"period","required":false,"in":"query","schema":{"example":"2026-06","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantBillingStatsDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Métricas del comercio en un período","tags":["Admin · Billing"]}},"/v1/admin/merchants/{merchantId}/pricing-plans":{"post":{"description":"Si ya existía un plan activo, se expira automáticamente con `validUntil = now` y el nuevo queda como único activo.","operationId":"adminPricingPlansCreate","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePricingPlanDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingPlanDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Crear plan de pricing para el comercio","tags":["Admin · Billing"]},"get":{"operationId":"adminPricingPlansList","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PricingPlanDto"}}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar planes del comercio (activo + histórico)","tags":["Admin · Billing"]}},"/v1/admin/merchants/{merchantId}/pricing-plans/active":{"get":{"operationId":"adminPricingPlansActive","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PricingPlanDto"},{"type":"null"}]}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Plan vigente (puede ser null si nunca se asignó)","tags":["Admin · Billing"]}},"/v1/admin/merchants/{merchantId}/pricing-plans/{planId}/expire":{"post":{"description":"Marca `active=false` y setea `validUntil=now`. Idempotente solo si ya estaba activo.","operationId":"adminPricingPlansExpire","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"planId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingPlanDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Merchant no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Plan ya expirado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Expirar el plan manualmente","tags":["Admin · Billing"]}},"/v1/admin/invoices":{"get":{"operationId":"adminInvoicesList","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["pending","paid","overdue","void"],"type":"string"}},{"name":"period","required":false,"in":"query","schema":{"example":"2026-06","type":"string"}},{"name":"merchantId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceListDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar facturas","tags":["Admin · Billing"]}},"/v1/admin/invoices/{id}":{"get":{"operationId":"adminInvoicesRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Factura no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Detalle de factura","tags":["Admin · Billing"]}},"/v1/admin/invoices/generate":{"post":{"description":"Genera la factura del período indicado para los comercios solicitados (o todos los activos si no se pasa merchantId). Idempotente: si ya existe `(merchantId, period)` se skipea.","operationId":"adminInvoicesGenerate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateInvoiceDto"}}}},"responses":{"201":{"description":"Resultado de la generación masiva.","content":{"application/json":{"schema":{"type":"object","properties":{"generated":{"type":"array","items":{"type":"object"}},"skipped":{"type":"array","items":{"type":"object","properties":{"merchantId":{"type":"string"},"reason":{"type":"string"}}}}}}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Generar facturas para un período","tags":["Admin · Billing"]}},"/v1/admin/invoices/{id}/mark-paid":{"patch":{"operationId":"adminInvoicesMarkPaid","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Factura no encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"409":{"description":"Factura ya estaba paid/void.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Marcar factura como pagada manualmente","tags":["Admin · Billing"]}},"/v1/checkout-sessions":{"post":{"description":"Crea una sesión de hosted checkout (URL con `publicToken`) y un `clientSecret` para drop-in SDK. Un payment intent solo puede tener una sesión asociada.","operationId":"checkoutSessionsCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutSessionDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedCheckoutSessionDto"}}}},"400":{"description":"Intent no pertenece al merchant o ya tiene sesión asociada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear checkout session","tags":["Checkout Sessions"]}},"/v1/checkout-sessions/{id}":{"get":{"description":"El `clientSecret` **no** se incluye en esta respuesta (solo se devuelve al crear).","operationId":"checkoutSessionsRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener checkout session por ID","tags":["Checkout Sessions"]}},"/v1/public/checkout-sessions/{publicToken}":{"get":{"description":"Endpoint público consumido por el hosted checkout. Devuelve únicamente información segura de mostrar al comprador (monto, moneda, descripción, nombre del merchant). Sesiones expiradas, bloqueadas o de merchants inactivos devuelven 404 uniforme.","operationId":"publicCheckoutSessionsRetrieve","parameters":[{"name":"publicToken","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCheckoutSessionDto"}}}},"404":{"description":"Sesión inexistente, expirada, bloqueada o merchant inactivo. Respuesta uniforme para evitar enumeración.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"summary":"Obtener checkout session por publicToken (sin auth)","tags":["Checkout Sessions · Public"]}},"/v1/public/checkout-sessions/{publicToken}/payment-methods":{"get":{"description":"Devuelve los medios de pago que el comprador puede elegir para esta sesión, según el provider ruteado. Cada uno trae su `platformId` para enviar en el confirm. Lista vacía ⇒ el provider no expone selección inline (se completa vía redirect/hosted checkout).","operationId":"publicCheckoutSessionsPaymentMethods","parameters":[{"name":"publicToken","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicPaymentMethodDto"}}}}},"404":{"description":"Sesión inexistente, expirada, bloqueada o merchant inactivo. Respuesta uniforme para evitar enumeración.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"summary":"Listar medios de pago disponibles (sin auth)","tags":["Checkout Sessions · Public"]}},"/v1/public/checkout-sessions/{publicToken}/confirm":{"post":{"description":"Confirma el payment intent asociado a esta sesión. Devuelve el estado resultante y eventualmente la URL de redirección del provider. Tras 5 intentos fallidos consecutivos, la sesión queda bloqueada y todos los endpoints públicos devuelven 404.","operationId":"publicCheckoutSessionsConfirm","parameters":[{"name":"publicToken","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmPublicCheckoutSessionDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCheckoutConfirmResultDto"}}}},"404":{"description":"Sesión inexistente, expirada, bloqueada o merchant inactivo. Respuesta uniforme para evitar enumeración.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"summary":"Confirmar pago desde hosted checkout","tags":["Checkout Sessions · Public"]}},"/v1/webhook-endpoints":{"post":{"description":"Registra una URL para recibir eventos firmados con HMAC-SHA256. El `secret` se devuelve **solo una vez** en esta respuesta.","operationId":"webhookEndpointsCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhookEndpointDto"}}}},"400":{"description":"Validación de body falló.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear webhook endpoint","tags":["Webhook Endpoints"]},"get":{"operationId":"webhookEndpointsList","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointListDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Listar webhook endpoints","tags":["Webhook Endpoints"]}},"/v1/webhook-endpoints/{id}":{"get":{"operationId":"webhookEndpointsRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener webhook endpoint","tags":["Webhook Endpoints"]},"patch":{"operationId":"webhookEndpointsUpdate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpointDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Actualizar webhook endpoint","tags":["Webhook Endpoints"]},"delete":{"operationId":"webhookEndpointsDelete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Eliminado."},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"La API key no tiene scope `full` o el merchant está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"No encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Eliminar webhook endpoint","tags":["Webhook Endpoints"]}},"/v1/providers":{"get":{"description":"Endpoint público (sin auth). Devuelve la lista completa de payment providers reconocidos por el gateway, con su estado de disponibilidad, países y métodos de pago. Pensado para que clientes (ERP, dashboards externos, SDKs) hidraten dropdowns sin hardcodear la lista. Los providers marcados con `available: false` están en roadmap y aún no aceptan tráfico productivo.","operationId":"providersList","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderCatalogResponseDto"}}}}},"summary":"Listar catálogo público de providers soportados","tags":["Providers"]}},"/v1/providers/{provider}/payment-methods":{"get":{"description":"Endpoint público (sin auth). Devuelve el catálogo de medios de pago de un provider para un país (ej. los `platformId` de Dpago: QR Ueno, Pix, tarjeta, etc.). Lo usa el ERP para poblar el selector de 'QR directo' en Nuevo Cobro y la app de cobradores.","operationId":"providerPaymentMethodsList","parameters":[{"name":"provider","required":true,"in":"path","schema":{"enum":["dlocal","bancard","pagopar","dinelco","dpago"],"type":"string"}},{"name":"country","required":false,"in":"query","schema":{"example":"PY","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderPaymentMethodsResponseDto"}}}}},"summary":"Listar medios de pago (platformIds) de un provider","tags":["Providers"]}},"/v1/webhooks/in/{provider}/{providerConfigId}":{"post":{"description":"Endpoint público sin auth de API key — el provider se autentica con su propia firma HMAC, verificada por el adapter. El body se procesa como **raw** para preservar la firma. Persistimos toda recepción (incluso inválidas) en `WebhookEventIn` para trazabilidad. Si la firma valida y el `providerPaymentId` matchea un attempt, se actualiza intent/attempt y se emite webhook al merchant.","operationId":"webhooksInboundReceive","parameters":[{"name":"provider","required":true,"in":"path","schema":{"type":"string"}},{"name":"providerConfigId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Procesado (o deduplicado).","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean","example":true},"eventId":{"type":"string","example":"wei_01HXYZABCDEFGHJKMNPQRSTV"},"deduped":{"type":"boolean","example":false}}}}}},"400":{"description":"Provider config no encontrada o provider mismatched.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Firma HMAC inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"summary":"Recibir webhook de provider","tags":["Webhooks · Inbound"]}},"/v1/refunds":{"post":{"description":"Reembolsa total o parcialmente el último attempt aprobado del intent. Si se omite `amount`, reembolsa el remanente. Llama al provider y, si la respuesta es `approved`, actualiza el intent a `partially_refunded` o `refunded`.","operationId":"refundsCreate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRefundDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundDto"}}}},"400":{"description":"Intent sin attempt aprobado, ya reembolsado totalmente, o monto > remanente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Intent no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Crear refund","tags":["Refunds"]},"get":{"operationId":"refundsList","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}},{"name":"intentId","required":false,"in":"query","description":"Filtrar por intent específico.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundListDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Listar refunds","tags":["Refunds"]}},"/v1/refunds/{id}":{"get":{"operationId":"refundsRetrieve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundDto"}}}},"401":{"description":"API key faltante, inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"403":{"description":"Merchant inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Refund no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"bearerAuth":[]}],"summary":"Obtener refund por ID","tags":["Refunds"]}},"/v1/admin/auth/login":{"post":{"operationId":"adminAuthLogin","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponseDto"}}}},"401":{"description":"Credenciales inválidas.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"summary":"Login de usuario admin","tags":["Admin · Auth"]}},"/v1/admin/auth/refresh":{"post":{"operationId":"adminAuthRefresh","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponseDto"}}}},"401":{"description":"Refresh inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"summary":"Renovar access token","tags":["Admin · Auth"]}},"/v1/admin/auth/me":{"get":{"operationId":"adminAuthMe","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserPublicDto"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Usuario actualmente autenticado","tags":["Admin · Auth"]}},"/v1/admin/merchants/{merchantId}/events/outbound":{"get":{"operationId":"adminEventsListOutbound","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["pending","delivered","failed","dead"],"type":"string"}},{"name":"endpointId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOutListDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar webhooks salientes del merchant","tags":["Admin · Events"]}},"/v1/admin/merchants/{merchantId}/events/outbound/{eventId}":{"get":{"operationId":"adminEventsRetrieveOutbound","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOutDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Event no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Detalle de webhook saliente","tags":["Admin · Events"]}},"/v1/admin/merchants/{merchantId}/events/outbound/{eventId}/retry":{"post":{"description":"Re-encola el evento en estado `pending`. Disponible para events `pending`, `failed` o `dead`.","operationId":"adminEventsRetryOutbound","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Reencolado."},"400":{"description":"Event ya delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"404":{"description":"Event no encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Forzar reintento manual de un webhook saliente","tags":["Admin · Events"]}},"/v1/admin/merchants/{merchantId}/events/inbound":{"get":{"operationId":"adminEventsListInbound","parameters":[{"name":"merchantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}},{"name":"provider","required":false,"in":"query","schema":{"enum":["dlocal","bancard","pagopar","dinelco","dpago"],"type":"string"}},{"name":"processed","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventInListDto"}}}},"401":{"description":"Token admin faltante o inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}},"429":{"description":"Rate limit excedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}},"security":[{"adminUserAuth":[]}],"summary":"Listar webhooks entrantes (de providers) asociados al merchant","tags":["Admin · Events"]}}},"info":{"title":"Novasis Pay","description":"Pasarela de pagos multi-tenant, multi-provider.\n\n## Autenticación\n\nHay **dos esquemas Bearer distintos**. El candado de cada endpoint indica cuál usar.\n\n### 🔑 `bearerAuth` — API key del merchant\n\n- **Formato:** `sk_test_…`, `sk_live_…`, `pk_test_…`, `pk_live_…`\n- **Header:** `Authorization: Bearer sk_test_xxxxxxxxxxxx`\n- **Cómo obtenerla:** un admin emite la primera key vía `POST /v1/admin/merchants/{merchantId}/api-keys`. El campo `secret` de la respuesta **solo se muestra una vez** — guardalo en password manager.\n- **Scopes:** `full` (todo) o `restricted` (no puede tocar provider-configs, webhook-endpoints ni refunds).\n- **Usar en:** todos los endpoints de comercio — `/payment-intents`, `/refunds`, `/customers`, `/provider-configs`, `/webhook-endpoints`, `/merchants/me`, etc.\n\n### 🛡️ `adminUserAuth` — JWT de usuario admin del gateway\n\n- **Formato:** JWT firmado con `ADMIN_JWT_SECRET`.\n- **Header:** `Authorization: Bearer <accessToken>`.\n- **Cómo obtenerlo:** `POST /v1/admin/auth/login` con email + password de un `admin_user`. Devuelve `accessToken` (15 min) y `refreshToken` (30 días).\n- **Renovación:** `POST /v1/admin/auth/refresh` con el `refreshToken`.\n- **Usar en:** todos los endpoints bajo `/v1/admin/*` — crear merchants, emitir API keys, inspeccionar eventos.\n\n### Errores comunes\n\n| Mensaje | Causa | Cómo arreglar |\n|---|---|---|\n| `Invalid API key format` | Mandaste `adm_…` o algo arbitrario donde se espera `sk_*` / `pk_*` | Usá la key del merchant, no el admin token |\n| `Missing Bearer token` | El header `Authorization` no llegó o no empieza con `Bearer ` | Verificá auth en el cliente HTTP |\n| `Invalid API key` | La key no existe o fue revocada | Emití una nueva con el admin |\n| `Merchant is not active` | El merchant está `suspended` o `disabled` | Reactivá el merchant vía admin |\n| `API key does not have permission for this action` (403) | Scope `restricted` intentando un endpoint sensible | Generá una key con `scope: \"full\"` |\n\n## Convenciones generales\n\n- **Versionado:** todas las rutas viven bajo `/v1`.\n- **IDs:** prefijo del recurso + ULID time-ordered (`mer_`, `pi_`, `pa_`, `re_`, `cs_`, `whe_`, `evt_`, `wei_`, `cus_`, `pcfg_`, `key_`).\n- **Idempotency:** endpoints de creación aceptan header `Idempotency-Key` (ventana 24h, mismo key + mismo merchant ⇒ devuelve el intent original).\n- **Paginación:** cursor-based. Respuesta `{ data: [...], nextCursor: string | null }`.\n- **Montos:** enteros en la unidad menor de la moneda (PYG es zero-decimal: `150000` = 150.000 Gs; USD: `12500` = USD 125,00).\n- **Errores:** formato uniforme `{ error: { code, message, details? } }` con `statusCode` HTTP estándar.\n\n## Webhooks\n\n- **Outbound:** firmamos cada evento con HMAC-SHA256 usando el `whsec_…` del endpoint del merchant. Headers: `X-Payments-Signature: t=<unix>,v1=<hex>`, `X-Payments-Event-Id`, `X-Payments-Event-Type`. Verificá la firma + ventana de 5 min para evitar replays.\n- **Inbound (de providers):** el adapter del provider verifica firma propia (ej. dLocal usa `X-Dlocalgo-Signature`). El body se preserva raw para que la firma sea reproducible.","version":"1.0.0","contact":{}},"tags":[],"servers":[{"url":"http://localhost:3010","description":"Local"}],"components":{"securitySchemes":{"bearerAuth":{"scheme":"bearer","bearerFormat":"sk_test_… / sk_live_… / pk_test_… / pk_live_…","type":"http","description":"API key del **merchant**. Se emite vía `POST /v1/admin/merchants/{merchantId}/api-keys` y se devuelve **una sola vez** en el campo `secret`. Usar en todos los endpoints de comercio (payment-intents, refunds, customers, provider-configs, webhook-endpoints, merchants/me)."},"adminUserAuth":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"JWT de usuario admin. Obtenelo vía `POST /v1/admin/auth/login` (email + password). Renovalo con `POST /v1/admin/auth/refresh`. Válido para endpoints bajo `/v1/admin/*`."}},"schemas":{"ApiErrorBody":{"type":"object","properties":{"code":{"type":"string","example":"validation_error","description":"Código corto y estable del error."},"message":{"type":"string","example":"email must be an email","description":"Mensaje legible para humanos. No usar para lógica de cliente."},"details":{"type":"object","description":"Detalles adicionales (campo a campo, IDs relacionados, etc.).","additionalProperties":true}},"required":["code","message"]},"ApiErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ApiErrorBody"}},"required":["error"]},"CreateMerchantDto":{"type":"object","properties":{"name":{"type":"string","example":"Acme S.A.","minLength":2,"maxLength":120},"email":{"type":"string","example":"billing@acme.com.py","format":"email"},"defaultCountry":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2 en mayúsculas.","minLength":2,"maxLength":2},"slug":{"type":"string","example":"acme-sa","description":"Slug opcional. Si no se envía, se deriva de `name`."}},"required":["name","email","defaultCountry"]},"MerchantDto":{"type":"object","properties":{"id":{"type":"string","example":"mer_01HXYZABCDEFGHJKMNPQRSTV"},"name":{"type":"string","example":"Acme S.A."},"slug":{"type":"string","example":"acme-sa"},"email":{"type":"string","example":"billing@acme.com.py"},"defaultCountry":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2."},"status":{"type":"string","enum":["active","suspended"],"example":"active"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","name","slug","email","defaultCountry","status","createdAt","updatedAt"]},"MerchantListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MerchantDto"}},"nextCursor":{"type":"object","nullable":true,"example":"mer_01HXYZABCDEFGHJKMNPQRSTV","description":"Cursor para la siguiente página. `null` si no hay más resultados."}},"required":["data"]},"UpdateMerchantDto":{"type":"object","properties":{"name":{"type":"string","example":"Acme S.A."},"email":{"type":"string","example":"billing@acme.com.py","format":"email"},"defaultCountry":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2 en mayúsculas."},"status":{"type":"string","enum":["active","suspended"],"example":"active"}}},"ApiKeyDto":{"type":"object","properties":{"id":{"type":"string","example":"key_01HXYZABCDEFGHJKMNPQRSTV"},"type":{"type":"string","enum":["secret","publishable"],"example":"secret"},"environment":{"type":"string","enum":["live","test"],"example":"test"},"prefix":{"type":"string","example":"sk_test_5f3a9b2c1d8e","description":"Primeros caracteres del valor de la key. Único y seguro de mostrar."},"scope":{"type":"string","enum":["full","restricted"],"example":"full"},"label":{"type":"object","nullable":true,"example":"Backend de producción"},"lastUsedAt":{"type":"object","nullable":true,"example":"2026-06-07T14:30:00.000Z"},"revokedAt":{"type":"object","nullable":true,"example":null},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","type","environment","prefix","scope","createdAt"]},"CreateApiKeyDto":{"type":"object","properties":{"type":{"type":"string","enum":["secret","publishable"],"example":"secret","description":"`secret` (`sk_*`) para uso server-to-server. `publishable` (`pk_*`) para clientes públicos (drop-in SDK)."},"environment":{"type":"string","enum":["live","test"],"example":"test"},"scope":{"type":"string","enum":["full","restricted"],"example":"full","description":"Default: `full`. Usar `restricted` para keys con permisos acotados."},"label":{"type":"string","example":"Backend de producción","description":"Etiqueta libre para identificar el uso de la key."}},"required":["type","environment"]},"CreatedApiKeyDto":{"type":"object","properties":{"id":{"type":"string","example":"key_01HXYZABCDEFGHJKMNPQRSTV"},"type":{"type":"string","enum":["secret","publishable"],"example":"secret"},"environment":{"type":"string","enum":["live","test"],"example":"test"},"prefix":{"type":"string","example":"sk_test_5f3a9b2c1d8e","description":"Primeros caracteres del valor de la key. Único y seguro de mostrar."},"scope":{"type":"string","enum":["full","restricted"],"example":"full"},"label":{"type":"object","nullable":true,"example":"Backend de producción"},"lastUsedAt":{"type":"object","nullable":true,"example":"2026-06-07T14:30:00.000Z"},"revokedAt":{"type":"object","nullable":true,"example":null},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"secret":{"type":"string","example":"sk_test_5f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e","description":"Valor completo de la API key. **Se devuelve UNA sola vez** en la creación. Guardalo de forma segura: no se puede recuperar después."}},"required":["id","type","environment","prefix","scope","createdAt","secret"]},"CreateApiKeyPairDto":{"type":"object","properties":{"environment":{"type":"string","enum":["live","test"],"example":"test"},"label":{"type":"string","example":"Conexión del ERP"}},"required":["environment"]},"CreatedApiKeyPairDto":{"type":"object","properties":{"secret":{"$ref":"#/components/schemas/CreatedApiKeyDto"},"publishable":{"$ref":"#/components/schemas/CreatedApiKeyDto"}},"required":["secret","publishable"]},"CreateCustomerDto":{"type":"object","properties":{"email":{"type":"string","example":"cliente@acme.com.py","format":"email"},"name":{"type":"string","example":"Juan Pérez"},"docType":{"type":"string","enum":["ruc","ci","dni","rut","cuit","cuil","passport","other"],"example":"ruc","description":"Tipo de documento. Usar `other` si ninguno aplica."},"docNumber":{"type":"string","example":"80012345-6"},"phone":{"type":"string","example":"+595981000111"},"country":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2."},"metadata":{"type":"object","description":"Metadata libre (key/value) para correlación con sistemas del merchant.","example":{"erpCustomerId":"C-00123"}}}},"CustomerDto":{"type":"object","properties":{"id":{"type":"string","example":"cus_01HXYZABCDEFGHJKMNPQRSTV"},"merchantId":{"type":"string","example":"mer_01HXYZABCDEFGHJKMNPQRSTV"},"email":{"type":"object","nullable":true,"example":"cliente@acme.com.py"},"name":{"type":"object","nullable":true,"example":"Juan Pérez"},"docType":{"type":"string","nullable":true,"enum":["ruc","ci","dni","rut","cuit","cuil","passport","other"],"example":"ruc"},"docNumber":{"type":"object","nullable":true,"example":"80012345-6"},"phone":{"type":"object","nullable":true,"example":"+595981000111"},"country":{"type":"object","nullable":true,"example":"PY"},"metadata":{"type":"object","nullable":true,"example":{"erpCustomerId":"C-00123"}},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","merchantId","createdAt","updatedAt"]},"CustomerListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomerDto"}},"nextCursor":{"type":"object","nullable":true,"example":null}},"required":["data"]},"UpdateCustomerDto":{"type":"object","properties":{"email":{"type":"string","example":"cliente@acme.com.py","format":"email"},"name":{"type":"string","example":"Juan Pérez"},"docType":{"type":"string","enum":["ruc","ci","dni","rut","cuit","cuil","passport","other"],"example":"ruc","description":"Tipo de documento. Usar `other` si ninguno aplica."},"docNumber":{"type":"string","example":"80012345-6"},"phone":{"type":"string","example":"+595981000111"},"country":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2."},"metadata":{"type":"object","description":"Metadata libre (key/value) para correlación con sistemas del merchant.","example":{"erpCustomerId":"C-00123"}}}},"CreateProviderConfigDto":{"type":"object","properties":{"provider":{"type":"string","enum":["dlocal","bancard","pagopar","dinelco","dpago"],"example":"dlocal"},"mode":{"type":"string","enum":["sandbox","live"],"example":"sandbox"},"country":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2 en mayúsculas."},"credentials":{"type":"object","description":"Credenciales del provider en texto plano. Se almacenan **encriptadas con AES-256-GCM** y nunca se devuelven. El shape depende del provider (ej. dLocal Go: `{ apiKey, secretKey }`).","example":{"apiKey":"demo_api_key","secretKey":"demo_secret_key"}},"fxStrategy":{"type":"string","enum":["reject_others","delegate_provider","freeze_own"],"example":"delegate_provider","description":"Manejo de monedas distintas a la base. `reject_others`: rechazar. `delegate_provider`: dejar al provider. `freeze_own`: fijar tipo de cambio propio."},"priority":{"type":"number","example":0,"description":"Prioridad de ruteo si hay múltiples configs aptas. Mayor = preferido."},"active":{"type":"boolean","example":true}},"required":["provider","mode","country","credentials"]},"ProviderConfigDto":{"type":"object","properties":{"id":{"type":"string","example":"pcfg_01HXYZABCDEFGHJKMNPQRSTV"},"merchantId":{"type":"string","example":"mer_01HXYZABCDEFGHJKMNPQRSTV"},"provider":{"type":"string","enum":["dlocal","bancard","pagopar","dinelco","dpago"],"example":"dlocal"},"mode":{"type":"string","enum":["sandbox","live"],"example":"sandbox"},"country":{"type":"string","example":"PY"},"fxStrategy":{"type":"string","enum":["reject_others","delegate_provider","freeze_own"],"example":"delegate_provider"},"priority":{"type":"number","example":0},"active":{"type":"boolean","example":true},"hasCredentials":{"type":"boolean","example":true,"description":"Indica si hay credenciales almacenadas. El valor de las credenciales **nunca** se devuelve por API."},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","merchantId","provider","mode","country","fxStrategy","priority","active","hasCredentials","createdAt","updatedAt"]},"ProviderConfigListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProviderConfigDto"}},"nextCursor":{"type":"object","nullable":true,"example":null}},"required":["data"]},"UpdateProviderConfigDto":{"type":"object","properties":{"credentials":{"type":"object","description":"Si se envía, **reemplaza completamente** las credenciales actuales (re-encriptadas).","example":{"apiKey":"new_api_key","secretKey":"new_secret_key"}},"fxStrategy":{"type":"string","enum":["reject_others","delegate_provider","freeze_own"]},"priority":{"type":"number","example":10},"active":{"type":"boolean","example":false}}},"PaymentAttemptDto":{"type":"object","properties":{"id":{"type":"string","example":"pa_01HXYZABCDEFGHJKMNPQRSTV"},"intentId":{"type":"string","example":"pi_01HXYZABCDEFGHJKMNPQRSTV"},"provider":{"type":"string","enum":["dlocal","bancard","pagopar","dinelco","dpago"],"example":"dlocal"},"providerPaymentId":{"type":"object","nullable":true,"example":"D-12345-67890"},"method":{"type":"string","enum":["card","cash_voucher","bank_transfer","pix","wallet","zimple","other"],"example":"card"},"platformId":{"type":"object","nullable":true,"example":"18","description":"Código del medio en el provider (ej. platformId de Dpago) usado en este intento."},"status":{"type":"string","enum":["pending","processing","approved","rejected","error"],"example":"processing"},"responseCode":{"type":"object","nullable":true,"example":"200"},"responseMessage":{"type":"object","nullable":true,"example":"Pending capture"},"redirectUrl":{"type":"object","nullable":true,"example":"https://pago.dpago.com/link?code=pl_864058fc4be979a2","description":"URL externa para completar/compartir el pago: hosted checkout del provider, link de pago nativo (ej. Dpago `/links`) o página de QR. Redirigí o compartí según el caso."},"qr":{"type":"string","nullable":true,"example":"00020101021220265898125204739953036005406100.05802PY5907UPAY...6304ABCD","description":"Payload EMV del QR a renderizar inline (ej. `qrInformation` de Dpago) cuando el medio devuelve QR en vez de redirección. Generá el QR a partir de este string. `null` si no aplica."},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","intentId","provider","method","status","createdAt"]},"PaymentIntentDto":{"type":"object","properties":{"id":{"type":"string","example":"pi_01HXYZABCDEFGHJKMNPQRSTV"},"merchantId":{"type":"string","example":"mer_01HXYZABCDEFGHJKMNPQRSTV"},"customerId":{"type":"object","nullable":true,"example":"cus_01HXYZABCDEFGHJKMNPQRSTV"},"externalReference":{"type":"object","nullable":true,"example":"ORDER-2026-0123"},"amount":{"type":"string","example":"150000.0000","description":"Decimal(20,4) serializado como string para evitar pérdida de precisión."},"currency":{"type":"string","example":"PYG"},"country":{"type":"string","example":"PY"},"description":{"type":"object","nullable":true,"example":"Pago de orden #0123"},"requestedProvider":{"type":"string","nullable":true,"enum":["dlocal","bancard","pagopar","dinelco","dpago"],"example":"dlocal"},"platformId":{"type":"object","nullable":true,"example":"18","description":"Medio de pago pre-seleccionado (código del provider, ej. platformId de Dpago)."},"status":{"type":"string","enum":["created","pending","processing","approved","rejected","expired","refunded","partially_refunded","cancelled"],"example":"created"},"metadata":{"type":"object","nullable":true,"example":{"orderId":"ORDER-2026-0123"}},"expiresAt":{"type":"object","nullable":true,"example":"2026-06-08T14:30:00.000Z"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","merchantId","amount","currency","country","status","createdAt","updatedAt"]},"CreatePaymentIntentDto":{"type":"object","properties":{"amount":{"type":"number","example":150000,"description":"Monto en la unidad menor de la moneda, salvo monedas sin decimales (PYG). Para PYG enviar enteros (`150000` = 150.000 Gs). Para USD enviar centavos (`12500` = USD 125,00)."},"currency":{"type":"string","example":"PYG","description":"ISO 4217, 3 letras mayúsculas."},"country":{"type":"string","example":"PY","description":"ISO 3166-1 alpha-2 del comprador."},"externalReference":{"type":"string","example":"ORDER-2026-0123","description":"Referencia externa del merchant (orden, factura, etc.). Único informativo."},"description":{"type":"string","example":"Pago de orden #0123"},"customerId":{"type":"string","example":"cus_01HXYZABCDEFGHJKMNPQRSTV","description":"ID de un customer previamente creado en este merchant."},"requestedProvider":{"type":"string","enum":["dlocal","bancard","pagopar","dinelco","dpago"],"description":"Forzar provider específico. Si se omite, el gateway rutea según prioridad."},"platformId":{"type":"string","example":"18","description":"Medio de pago pre-seleccionado, en el código propio del provider (ej. `platformId` de Dpago: 18 = QR Ueno). Si se envía, la confirmación cobra directo ese método (útil para pedir QR sin link). Se resuelve contra el catálogo `provider_payment_method`. Se puede sobrescribir al confirmar."},"expiresAt":{"type":"string","example":"2026-06-08T14:30:00.000Z","description":"Fecha ISO-8601 de expiración del intent. Default: configurado a nivel sistema."},"metadata":{"type":"object","example":{"orderId":"ORDER-2026-0123"}}},"required":["amount","currency","country"]},"PaymentIntentListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentIntentDto"}},"nextCursor":{"type":"object","nullable":true,"example":null}},"required":["data"]},"ConfirmPaymentIntentDto":{"type":"object","properties":{"providerConfigId":{"type":"string","example":"pcfg_01HXYZABCDEFGHJKMNPQRSTV","description":"Forzar una provider config específica. Si se omite, se rutea por (country, requestedProvider, priority)."},"platformId":{"type":"string","example":"18","description":"Medio de pago en el código del provider (ej. `platformId` de Dpago). Sobrescribe el guardado en el intent. Requerido por providers que exigen elegir el método antes de crear la transacción."},"paymentLink":{"type":"boolean","example":true,"description":"Pedir un LINK DE PAGO nativo del provider (ej. Dpago `/links`) en vez de una transacción directa. Con esto no hace falta `platformId`. Los providers sin link nativo lo ignoran."},"returnUrl":{"type":"string","example":"https://merchant.com/orders/123/success","description":"URL de retorno tras pago aprobado (sobrescribe la de checkout session si existe)."},"cancelUrl":{"type":"string","example":"https://merchant.com/orders/123/cancel"}}},"CurrencyVolumeDto":{"type":"object","properties":{"currency":{"type":"string","example":"PYG","description":"ISO 4217"},"approvedCount":{"type":"number","example":42},"approvedVolume":{"type":"string","example":"2500000.0000","description":"Decimal serializado como string para preservar precisión."}},"required":["currency","approvedCount","approvedVolume"]},"TopMerchantDto":{"type":"object","properties":{"merchantId":{"type":"string"},"merchantName":{"type":"string"},"approvedCount":{"type":"number","example":120},"approvedVolumePyg":{"type":"string","example":"8500000.0000","description":"Volumen aprobado en PYG (ranking por esta moneda)."}},"required":["merchantId","merchantName","approvedCount","approvedVolumePyg"]},"GlobalBillingStatsDto":{"type":"object","properties":{"period":{"type":"string","example":"2026-06"},"approvedCount":{"type":"number","example":320},"rejectedCount":{"type":"number","example":18},"previousApprovedCount":{"type":"number","example":295,"description":"Tx aprobadas del período anterior — sirve para calcular delta."},"activeMerchants":{"type":"number","example":12},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/CurrencyVolumeDto"}},"topMerchants":{"type":"array","items":{"$ref":"#/components/schemas/TopMerchantDto"}}},"required":["period","approvedCount","rejectedCount","previousApprovedCount","activeMerchants","volumes","topMerchants"]},"MerchantBillingStatsDto":{"type":"object","properties":{"merchantId":{"type":"string"},"period":{"type":"string","example":"2026-06","description":"Período YYYY-MM (UTC)."},"approvedCount":{"type":"number","example":42},"rejectedCount":{"type":"number","example":3},"pendingCount":{"type":"number","example":5,"description":"Intents en estados no terminales: created + pending + processing."},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/CurrencyVolumeDto"}}},"required":["merchantId","period","approvedCount","rejectedCount","pendingCount","volumes"]},"CreatePricingPlanDto":{"type":"object","properties":{"name":{"type":"string","example":"Plan Estándar PY 2026","description":"Etiqueta interna del plan (visible solo en admin)."},"monthlyFee":{"type":"number","example":150000,"description":"Fee mensual fijo (en la moneda del plan). 0 = sin fee mensual."},"monthlyFeeCurrency":{"type":"string","example":"PYG","default":"PYG"},"txFeeFixed":{"type":"number","example":500,"description":"Fee fijo por transacción aprobada (en la moneda del plan)."},"txFeePercentage":{"type":"number","example":0.3,"description":"Fee porcentual sobre monto aprobado. 0.3 = 0,3%."},"txFeeCurrency":{"type":"string","example":"PYG","default":"PYG"},"includedTxPerMonth":{"type":"number","example":200,"description":"Tx aprobadas incluidas en el fee mensual. A partir de este número, se cobra `overQuotaTxFee` por tx adicional."},"overQuotaTxFee":{"type":"number","example":800,"description":"Costo por tx que excede la cuota incluida."},"billingCycle":{"type":"string","enum":["monthly","quarterly","yearly"],"default":"monthly","description":"Frecuencia de facturación. `monthly` factura todos los meses; `quarterly` cada cierre de trimestre (mar/jun/sep/dic); `yearly` solo en diciembre. `monthlyFee` se multiplica por la cantidad de meses del ciclo."},"free":{"type":"boolean","default":false,"description":"Si es `true`, el plan no genera facturas (comercio bonificado o cortesía)."},"selfBillingEnabled":{"type":"boolean","default":false,"description":"Si es `true`, al generar la factura se crea un PaymentIntent en el merchant interno de Novasis para que el comercio pague el fee con su propio método. Requiere `SELF_BILLING_MERCHANT_ID` configurado."}},"required":["name","monthlyFee","monthlyFeeCurrency","txFeeFixed","txFeePercentage","txFeeCurrency"]},"PricingPlanDto":{"type":"object","properties":{"id":{"type":"string"},"merchantId":{"type":"string"},"name":{"type":"string"},"validFrom":{"type":"string","format":"date-time"},"validUntil":{"type":"object","format":"date-time","nullable":true},"monthlyFee":{"type":"string","example":"150000.0000"},"monthlyFeeCurrency":{"type":"string","example":"PYG"},"txFeeFixed":{"type":"string","example":"500.0000"},"txFeePercentage":{"type":"string","example":"0.3000"},"txFeeCurrency":{"type":"string","example":"PYG"},"includedTxPerMonth":{"type":"object","nullable":true,"example":200},"overQuotaTxFee":{"type":"object","nullable":true,"example":"800.0000"},"billingCycle":{"type":"string","enum":["monthly","quarterly","yearly"]},"free":{"type":"boolean"},"selfBillingEnabled":{"type":"boolean"},"active":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","merchantId","name","validFrom","validUntil","monthlyFee","monthlyFeeCurrency","txFeeFixed","txFeePercentage","txFeeCurrency","includedTxPerMonth","overQuotaTxFee","billingCycle","free","selfBillingEnabled","active","createdAt"]},"InvoiceItemDto":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["monthly_fee","tx_fee_fixed","tx_fee_percentage","over_quota_fee","adjustment"]},"description":{"type":"string","example":"Fee mensual"},"quantity":{"type":"number","example":1},"unitAmount":{"type":"string","example":"150000.0000"},"totalAmount":{"type":"string","example":"150000.0000"}},"required":["id","type","description","quantity","unitAmount","totalAmount"]},"InvoiceDto":{"type":"object","properties":{"id":{"type":"string"},"merchantId":{"type":"string"},"merchantName":{"type":"string","example":"Acme S.A."},"period":{"type":"string","example":"2026-06"},"monthlyFeeAmount":{"type":"string","example":"150000.0000"},"txFeesAmount":{"type":"string","example":"21000.0000"},"txCount":{"type":"number","example":42},"totalAmount":{"type":"string","example":"171000.0000"},"currency":{"type":"string","example":"PYG"},"status":{"type":"string","enum":["pending","paid","overdue","void"]},"issuedAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"paidAt":{"type":"object","format":"date-time","nullable":true},"selfBillingIntentId":{"type":"object","nullable":true,"description":"Si el plan tiene self-billing, ID del PaymentIntent creado para cobrar el fee vía Novasis Pay."},"items":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceItemDto"}}},"required":["id","merchantId","period","monthlyFeeAmount","txFeesAmount","txCount","totalAmount","currency","status","issuedAt","dueAt","paidAt","selfBillingIntentId","items"]},"InvoiceListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceDto"}},"nextCursor":{"type":"object","nullable":true}},"required":["data","nextCursor"]},"GenerateInvoiceDto":{"type":"object","properties":{"merchantId":{"type":"string","example":"mer_01h…","description":"Si se omite, se genera para todos los comercios activos con plan vigente."},"period":{"type":"string","example":"2026-05","description":"Período YYYY-MM a facturar. Default: mes calendario anterior al actual (UTC)."}}},"CreateCheckoutSessionDto":{"type":"object","properties":{"intentId":{"type":"string","example":"pi_01HXYZABCDEFGHJKMNPQRSTV","description":"ID del payment intent que esta sesión va a cobrar. Debe pertenecer al merchant autenticado."},"returnUrl":{"type":"string","example":"https://merchant.com/orders/123/success","description":"URL a la que el hosted checkout redirige tras pago aprobado."},"cancelUrl":{"type":"string","example":"https://merchant.com/orders/123/cancel","description":"URL a la que se redirige si el comprador cancela."},"expirationHours":{"type":"number","example":24,"description":"Horas hasta que la sesión expire. Default: `CHECKOUT_SESSION_DEFAULT_EXPIRATION_HOURS` (env)."},"uiConfig":{"type":"object","description":"Configuración de la página de pago. `locale`/`brandColor` para branding; `qrEmbedded: true` hace que el QR directo se dibuje embebido en el checkout en vez de redirigir a la página del provider.","example":{"locale":"es-PY","brandColor":"#1976d2","qrEmbedded":true}}},"required":["intentId"]},"CreatedCheckoutSessionDto":{"type":"object","properties":{"id":{"type":"string","example":"cs_01HXYZABCDEFGHJKMNPQRSTV"},"intentId":{"type":"string","example":"pi_01HXYZABCDEFGHJKMNPQRSTV"},"publicToken":{"type":"string","example":"f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c","description":"Token público URL-safe. Se usa para construir la URL del hosted checkout: `<CHECKOUT_BASE_URL>/c/<publicToken>`."},"url":{"type":"string","example":"https://checkout.novasispay.com/c/f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c","description":"URL completa lista para redirigir al comprador o abrir en iframe."},"status":{"type":"string","enum":["open","completed","expired","cancelled"],"example":"open"},"returnUrl":{"type":"object","nullable":true,"example":"https://merchant.com/orders/123/success"},"cancelUrl":{"type":"object","nullable":true,"example":"https://merchant.com/orders/123/cancel"},"uiConfig":{"type":"object","nullable":true,"example":{"locale":"es-PY"}},"failedAttempts":{"type":"number","example":0},"expiresAt":{"format":"date-time","type":"string","example":"2026-06-08T14:30:00.000Z"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"clientSecret":{"type":"string","example":"cs_secret_f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c","description":"Client secret para drop-in SDK. Se devuelve **solo en la creación**. Permite operar exclusivamente sobre este intent."}},"required":["id","intentId","publicToken","url","status","failedAttempts","expiresAt","createdAt","clientSecret"]},"CheckoutSessionDto":{"type":"object","properties":{"id":{"type":"string","example":"cs_01HXYZABCDEFGHJKMNPQRSTV"},"intentId":{"type":"string","example":"pi_01HXYZABCDEFGHJKMNPQRSTV"},"publicToken":{"type":"string","example":"f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c","description":"Token público URL-safe. Se usa para construir la URL del hosted checkout: `<CHECKOUT_BASE_URL>/c/<publicToken>`."},"url":{"type":"string","example":"https://checkout.novasispay.com/c/f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c","description":"URL completa lista para redirigir al comprador o abrir en iframe."},"status":{"type":"string","enum":["open","completed","expired","cancelled"],"example":"open"},"returnUrl":{"type":"object","nullable":true,"example":"https://merchant.com/orders/123/success"},"cancelUrl":{"type":"object","nullable":true,"example":"https://merchant.com/orders/123/cancel"},"uiConfig":{"type":"object","nullable":true,"example":{"locale":"es-PY"}},"failedAttempts":{"type":"number","example":0},"expiresAt":{"format":"date-time","type":"string","example":"2026-06-08T14:30:00.000Z"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","intentId","publicToken","url","status","failedAttempts","expiresAt","createdAt"]},"PublicCheckoutSessionDto":{"type":"object","properties":{"id":{"type":"string","example":"cs_01HXYZABCDEFGHJKMNPQRSTV"},"status":{"type":"string","enum":["open","completed","expired","cancelled"],"example":"open"},"expiresAt":{"format":"date-time","type":"string","example":"2026-06-08T14:30:00.000Z"},"amount":{"type":"string","example":"150000.0000"},"currency":{"type":"string","example":"PYG"},"country":{"type":"string","example":"PY"},"description":{"type":"object","nullable":true,"example":"Pago de orden #0123"},"merchantName":{"type":"string","example":"Acme S.A."},"uiConfig":{"type":"object","nullable":true,"example":{"locale":"es-PY"}},"returnUrl":{"type":"object","nullable":true,"example":"https://merchant.com/orders/123/success"},"cancelUrl":{"type":"object","nullable":true,"example":"https://merchant.com/orders/123/cancel"},"customer":{"type":"object","nullable":true,"description":"Datos del comprador cuando el merchant creó un Customer al iniciar el cobro. El hosted checkout los usa para prellenar el form.","example":{"email":"cliente@acme.com.py","name":"Juan Pérez","docNumber":"80012345-6","phone":"+595981000111","country":"PY"}}},"required":["id","status","expiresAt","amount","currency","country","merchantName"]},"PublicPaymentMethodDto":{"type":"object","properties":{"platformId":{"type":"string","example":"18","description":"Código del medio en el provider (ej. platformId de Dpago)."},"name":{"type":"string","example":"QR Ueno","description":"Nombre para mostrar."},"method":{"type":"string","enum":["card","cash_voucher","bank_transfer","pix","wallet","zimple","other"],"example":"other","description":"Método interno mapeado."}},"required":["platformId","name","method"]},"PublicCheckoutCustomerDto":{"type":"object","properties":{"email":{"type":"string","example":"comprador@example.com"},"name":{"type":"string","example":"Juan Pérez"},"docNumber":{"type":"string","example":"80012345-6"},"phone":{"type":"string","example":"+595981000000"},"country":{"type":"string","example":"PY"}}},"ConfirmPublicCheckoutSessionDto":{"type":"object","properties":{"customer":{"$ref":"#/components/schemas/PublicCheckoutCustomerDto"},"providerToken":{"type":"string","description":"Token opaco emitido por el SDK de tokenización del provider (ej. dLocal SmartFields). El backend nunca recibe PAN.","example":"tok_abc123…"},"platformId":{"type":"string","example":"18","description":"Medio de pago elegido por el comprador, en el código del provider (ej. `platformId` de Dpago). Los valores válidos salen del endpoint público de métodos de la sesión."}}},"PublicCheckoutConfirmResultDto":{"type":"object","properties":{"status":{"type":"string","example":"pending","description":"Estado del intent tras la confirmación."},"redirectUrl":{"type":"object","nullable":true,"description":"URL a la que redirigir/compartir para completar el pago (hosted del provider, página de QR o link nativo).","example":"https://pago.dpago.com/upay/qr?code=eyJ0cmFuc2FjdGlvbklkIjox…"},"qr":{"type":"string","nullable":true,"description":"Payload EMV del QR a renderizar inline cuando el medio elegido devuelve QR (ej. `qrInformation` de Dpago) en vez de redirección. Generá el QR a partir de este string. `null` si no aplica.","example":"00020101021220265898125204739953036005406100.05802PY5907UPAY...6304ABCD"},"returnUrl":{"type":"object","nullable":true,"description":"URL del merchant a la que volver si el pago terminó aprobado.","example":"https://merchant.com/orders/123/success"},"cancelUrl":{"type":"object","nullable":true,"description":"URL del merchant a la que volver si el comprador cancela.","example":"https://merchant.com/orders/123/cancel"}},"required":["status"]},"CreateWebhookEndpointDto":{"type":"object","properties":{"url":{"type":"string","example":"https://merchant.com/webhooks/payments","description":"URL HTTPS donde se entregarán los eventos. En producción se exige `https`."},"subscribedEvents":{"example":["payment_intent.succeeded","payment_intent.failed","checkout_session.expired"],"description":"Lista de eventos a los que se suscribe. Usar `*` para recibir todos. Ver catálogo en `/docs · Webhooks`.","type":"array","items":{"type":"string"}},"description":{"type":"string","example":"Producción · servidor de pedidos"}},"required":["url","subscribedEvents"]},"CreatedWebhookEndpointDto":{"type":"object","properties":{"id":{"type":"string","example":"whe_01HXYZABCDEFGHJKMNPQRSTV"},"merchantId":{"type":"string","example":"mer_01HXYZABCDEFGHJKMNPQRSTV"},"url":{"type":"string","example":"https://merchant.com/webhooks/payments"},"subscribedEvents":{"example":["payment_intent.succeeded","payment_intent.failed"],"type":"array","items":{"type":"string"}},"active":{"type":"boolean","example":true},"description":{"type":"object","nullable":true,"example":"Producción"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"secret":{"type":"string","example":"whsec_5f3a9b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a3b2c1d8e7f4a","description":"Secret HMAC para verificar firmas de webhooks. Se devuelve **una sola vez**. Usalo del lado del merchant para validar el header `X-Payments-Signature` (formato Stripe: `t=...,v1=...`)."}},"required":["id","merchantId","url","subscribedEvents","active","createdAt","updatedAt","secret"]},"WebhookEndpointDto":{"type":"object","properties":{"id":{"type":"string","example":"whe_01HXYZABCDEFGHJKMNPQRSTV"},"merchantId":{"type":"string","example":"mer_01HXYZABCDEFGHJKMNPQRSTV"},"url":{"type":"string","example":"https://merchant.com/webhooks/payments"},"subscribedEvents":{"example":["payment_intent.succeeded","payment_intent.failed"],"type":"array","items":{"type":"string"}},"active":{"type":"boolean","example":true},"description":{"type":"object","nullable":true,"example":"Producción"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","merchantId","url","subscribedEvents","active","createdAt","updatedAt"]},"WebhookEndpointListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpointDto"}},"nextCursor":{"type":"object","nullable":true,"example":null}},"required":["data"]},"UpdateWebhookEndpointDto":{"type":"object","properties":{"url":{"type":"string","example":"https://merchant.com/webhooks/payments-v2"},"subscribedEvents":{"example":["payment_intent.succeeded"],"type":"array","items":{"type":"string"}},"active":{"type":"boolean","example":false},"description":{"type":"string","example":"Producción"}}},"ProviderCatalogEntryDto":{"type":"object","properties":{"id":{"type":"string","enum":["dlocal","bancard","pagopar","dinelco","dpago"],"example":"dlocal","description":"Identificador interno del provider — el mismo valor que se usa en provider-configs y en `defaultProvider`."},"displayName":{"type":"string","example":"dLocal Go","description":"Nombre comercial para mostrar en UI."},"countries":{"example":["PY"],"description":"Países (ISO 3166-1 alpha-2) en los que el provider opera.","type":"array","items":{"type":"string"}},"paymentMethods":{"example":["card","transfer"],"description":"Métodos de pago soportados.","type":"array","items":{"type":"string"}},"modes":{"example":["sandbox","live"],"description":"Modos de operación disponibles.","type":"array","items":{"type":"string"}},"available":{"type":"boolean","example":true,"description":"Si el adapter está implementado y listo para uso productivo. Los providers con `available: false` figuran como roadmap."},"docsUrl":{"type":"object","example":"https://docs.dlocalgo.com/integration-api","description":"Link a la documentación oficial del provider para integradores.","nullable":true},"minAmount":{"type":"object","example":{"PYG":10000},"description":"Monto mínimo por moneda que el provider acepta, en la unidad principal (PYG en guaraníes). Los clientes (ej. el ERP) validan el monto del cobro contra este mínimo según el provider ruteado.","nullable":true,"additionalProperties":{"type":"number"}},"requiresPlatformId":{"type":"boolean","example":true,"description":"Si el provider exige elegir un medio de pago (`platformId`) para el cobro DIRECTO. Los clientes muestran el selector de método sólo cuando es true (ej. Dpago directo)."},"requiredCustomerFields":{"example":["email","name","docNumber","phone"],"description":"Campos del comprador que el provider necesita para un cobro DIRECTO (nombres canónicos del gateway). Los clientes validan estos campos antes de confirmar. Vacío ⇒ el provider los pide en su propio checkout (ej. dLocal hosted).","type":"array","items":{"type":"string"}},"supportsDirect":{"type":"boolean","example":true,"description":"Si soporta cobro DIRECTO (confirmar en el acto, ej. QR embebido)."},"supportsHosted":{"type":"boolean","example":true,"description":"Si soporta checkout HOSPEDADO de Novasis (link genérico con selector de método)."},"supportsNativeLink":{"type":"boolean","example":true,"description":"Si el provider ofrece un LINK DE PAGO nativo propio (ej. Dpago `/links`). Cuando es true, el modo 'link de pago' usa el link del provider en vez del checkout hospedado de Novasis."},"features":{"example":["qr"],"description":"Capacidades extra soportadas (ej. `qr`, `refund`).","type":"array","items":{"type":"string"}}},"required":["id","displayName","countries","paymentMethods","modes","available"]},"ProviderCatalogResponseDto":{"type":"object","properties":{"providers":{"type":"array","items":{"$ref":"#/components/schemas/ProviderCatalogEntryDto"}}},"required":["providers"]},"ProviderPaymentMethodDto":{"type":"object","properties":{"platformId":{"type":"string","example":"18","description":"Código del medio de pago en el provider (ej. `platformId` de Dpago)."},"name":{"type":"string","example":"QR Ueno","description":"Nombre legible del medio de pago."},"method":{"type":"string","enum":["card","cash_voucher","bank_transfer","pix","wallet","zimple","other"],"example":"wallet","description":"Método interno normalizado del gateway."}},"required":["platformId","name","method"]},"ProviderPaymentMethodsResponseDto":{"type":"object","properties":{"methods":{"type":"array","items":{"$ref":"#/components/schemas/ProviderPaymentMethodDto"}}},"required":["methods"]},"CreateRefundDto":{"type":"object","properties":{"intentId":{"type":"string","example":"pi_01HXYZABCDEFGHJKMNPQRSTV","description":"ID del payment intent a reembolsar. Se reembolsa contra el último attempt aprobado."},"amount":{"type":"number","example":50000,"description":"Monto a reembolsar en la unidad menor de la moneda (igual convención que `payment_intents.amount`). Si se omite, se reembolsa el total restante del attempt."},"reason":{"type":"string","example":"Cliente canceló la orden"}},"required":["intentId"]},"RefundDto":{"type":"object","properties":{"id":{"type":"string","example":"re_01HXYZABCDEFGHJKMNPQRSTV"},"attemptId":{"type":"string","example":"pa_01HXYZABCDEFGHJKMNPQRSTV"},"amount":{"type":"string","example":"50000.0000","description":"Decimal(20,4) serializado como string."},"reason":{"type":"object","nullable":true,"example":"Cliente canceló la orden"},"status":{"type":"string","enum":["pending","approved","rejected","error"],"example":"approved"},"providerRefundId":{"type":"object","nullable":true,"example":"R-99887766"},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","attemptId","amount","status","createdAt"]},"RefundListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RefundDto"}},"nextCursor":{"type":"object","nullable":true,"example":null}},"required":["data"]},"LoginDto":{"type":"object","properties":{"email":{"type":"string","example":"operador@novasis.com","format":"email"},"password":{"type":"string","example":"********","minLength":8,"maxLength":200}},"required":["email","password"]},"AdminUserPublicDto":{"type":"object","properties":{"id":{"type":"string","example":"adu_01jx…"},"email":{"type":"string","example":"operador@novasis.com"},"name":{"type":"string","example":"María Pereira"},"role":{"type":"string","enum":["superadmin","operator"],"example":"operator"}},"required":["id","email","name","role"]},"AuthResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT corto para usar como `Authorization: Bearer …` en cada request."},"refreshToken":{"type":"string","description":"Refresh token de larga duración (uso único)."},"expiresIn":{"type":"number","example":900,"description":"Vida del accessToken en segundos."},"user":{"$ref":"#/components/schemas/AdminUserPublicDto"}},"required":["accessToken","refreshToken","expiresIn","user"]},"RefreshDto":{"type":"object","properties":{"refreshToken":{"type":"string","description":"Refresh token devuelto por /admin/auth/login."}},"required":["refreshToken"]},"EventOutDto":{"type":"object","properties":{"id":{"type":"string","example":"evt_01HXYZABCDEFGHJKMNPQRSTV"},"endpointId":{"type":"string","example":"whe_01HXYZABCDEFGHJKMNPQRSTV"},"intentId":{"type":"object","nullable":true,"example":"pi_01HXYZABCDEFGHJKMNPQRSTV"},"eventType":{"type":"string","example":"payment_intent.succeeded"},"status":{"type":"string","enum":["pending","delivered","failed","dead"],"example":"delivered"},"attempts":{"type":"number","example":1},"lastAttemptAt":{"type":"object","nullable":true,"example":"2026-06-07T14:30:00.000Z"},"nextAttemptAt":{"type":"object","nullable":true,"example":null},"lastError":{"type":"object","nullable":true,"example":null},"deliveredAt":{"type":"object","nullable":true,"example":"2026-06-07T14:30:00.000Z"},"payload":{"type":"object","description":"Payload firmado enviado al endpoint."},"createdAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","endpointId","eventType","status","attempts","payload","createdAt"]},"EventOutListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EventOutDto"}},"nextCursor":{"type":"object","nullable":true}},"required":["data"]},"EventInDto":{"type":"object","properties":{"id":{"type":"string","example":"wei_01HXYZABCDEFGHJKMNPQRSTV"},"provider":{"type":"string","enum":["dlocal","bancard","pagopar","dinelco","dpago"],"example":"dlocal"},"attemptId":{"type":"object","nullable":true,"example":"pa_01HXYZABCDEFGHJKMNPQRSTV"},"signature":{"type":"object","nullable":true},"processed":{"type":"boolean","example":true},"processedAt":{"type":"object","nullable":true},"error":{"type":"object","nullable":true},"payload":{"type":"object"},"receivedAt":{"format":"date-time","type":"string","example":"2026-06-07T14:30:00.000Z"}},"required":["id","provider","processed","payload","receivedAt"]},"EventInListDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EventInDto"}},"nextCursor":{"type":"object","nullable":true}},"required":["data"]}}}}