{"openapi":"3.0.3","info":{"title":"Wallet mock W1–W6","version":"0.2.0","description":"Mock público de la hackatón para 8 grupos fijos. Selecciona el servidor de tu grupo. Estado persistente en SQLite por grupo; se borra con POST /control/reset. PEN en céntimos. Solo datos TEST. CORS habilitado."},"servers":[{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/1","description":"Grupo 1"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/2","description":"Grupo 2"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/3","description":"Grupo 3"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/4","description":"Grupo 4"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/5","description":"Grupo 5"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/6","description":"Grupo 6"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/7","description":"Grupo 7"},{"url":"https://bcp-wallet-mock.yefri.workers.dev/groups/8","description":"Grupo 8"}],"paths":{"/health":{"get":{"summary":"Salud","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}},"tags":["Servicio y grupos"],"description":"Comprueba disponibilidad y almacenamiento: SQLite en cloud y memoria en Python local. No modifica datos. El servicio raíz remoto incluye enlaces a grupos y OpenAPI; la URL del grupo informa su entorno.","operationId":"getHealth"}},"/scenarios":{"get":{"summary":"Escenarios disponibles","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scenarios"}}}}},"tags":["Servicio y grupos"],"description":"Lista las opciones exactas de scenario para KYC, pagos y alteraciones del reporte. No avanza escenarios ni modifica datos.","operationId":"getScenarios"}},"/openapi.json":{"get":{"summary":"Este contrato","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApi"}}}}},"tags":["Servicio y grupos"],"description":"Devuelve este contrato. En cloud, el contrato raíz ofrece ocho servidores y el contrato del grupo ofrece solo su URL. Importa el del grupo en Postman o Swagger.","operationId":"getOpenapi.Json"}},"/kyc/verifications":{"post":{"summary":"Crear wallet con KYC simulado","responses":{"201":{"description":"Creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Kyc"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Proveedor no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentType":{"type":"string","enum":["TEST"],"minLength":1,"maxLength":200,"description":"Solo TEST. No enviar documentos reales."},"document":{"type":"string","pattern":"^TEST-","minLength":1,"maxLength":200,"description":"Documento ficticio con prefijo TEST-."},"scenario":{"type":"string","enum":["APPROVED","REJECTED","MANUAL_REVIEW","UNAVAILABLE"],"minLength":1,"maxLength":200,"description":"Escenario del proveedor simulado. Predeterminado APPROVED.","default":"APPROVED"}},"required":["documentType","document"]},"example":{"documentType":"TEST","document":"TEST-001","scenario":"APPROVED"}}}},"tags":["W1 · Registro y activación"],"description":"Crea una verificación y una wallet con saldo cero. APPROVED, valor predeterminado, activa; REJECTED rechaza; MANUAL_REVIEW deja PENDING y abre soporte. UNAVAILABLE devuelve 503 KYC_UNAVAILABLE sin crear wallet. Solo documentType TEST y document con prefijo TEST-. No evita altas repetidas.","operationId":"postKycVerifications"}},"/kyc/verifications/{id}":{"get":{"summary":"Consultar KYC","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Kyc"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"tags":["W1 · Registro y activación"],"description":"Consulta la verificación por su ID kyc, distinto al wallet_id. Devuelve 404 NOT_FOUND si no existe en el grupo.","operationId":"getKycVerificationsByid"}},"/kyc/verifications/{id}/decision":{"post":{"summary":"Resolver revisión humana","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Kyc"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string","enum":["ACTIVE","REJECTED"],"minLength":1,"maxLength":200,"description":"Decisión final del operador sobre la verificación."}},"required":["decision"]},"example":{"decision":"ACTIVE"}}}},"tags":["W1 · Registro y activación"],"description":"Decide ACTIVE o REJECTED sobre una verificación PENDING, actualiza su wallet y resuelve su caso de soporte. Repetir la misma decisión devuelve 200; cambiar una decisión final devuelve 409 DECISION_CONFLICT. Decisión inválida: 400 INVALID_DECISION.","operationId":"postKycVerificationsByidDecision"}},"/wallets/{id}":{"get":{"summary":"Saldo contable, reservado y disponible","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"tags":["W1 · Registro y activación"],"description":"Consulta estado y saldos en céntimos. available = balance - reserved. Solo los pagos PENDING reservan saldo. wallet-demo empieza ACTIVE con 100000. Wallet inexistente: 404 NOT_FOUND.","operationId":"getWalletsByid"}},"/payments":{"post":{"summary":"Crear pago de contacto o comercio","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"202":{"description":"Pendiente; completar mediante evento","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"QR vencido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Proveedor no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"Respuesta perdida después de procesar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Clave única por wallet. Reutiliza la misma clave y contenido al reintentar."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"walletId":{"type":"string","minLength":1,"maxLength":200,"description":"ID de wallet. Para pagos con fondos usa wallet-demo."},"recipient":{"type":"string","enum":["contact-demo","merchant-demo"],"minLength":1,"maxLength":200,"description":"Destinatario externo de prueba."},"amountCents":{"type":"integer","minimum":1,"maximum":100000000,"description":"Importe en céntimos de PEN. 1500 = S/ 15.00."},"currency":{"type":"string","enum":["PEN"],"minLength":1,"maxLength":200,"description":"Solo PEN. Predeterminado PEN.","default":"PEN"},"scenario":{"type":"string","enum":["APPROVED","REJECTED","PENDING","DELAYED_APPROVAL","TIMEOUT_AFTER_PROCESSING","PROVIDER_ERROR"],"minLength":1,"maxLength":200,"description":"Escenario del proveedor simulado. Predeterminado APPROVED.","default":"APPROVED"},"qrId":{"type":"string","description":"ID del QR creado en este grupo. Opcional, valida comercio, monto y vigencia.","nullable":true}},"required":["walletId","recipient","amountCents"]},"example":{"walletId":"wallet-demo","recipient":"contact-demo","amountCents":1500,"currency":"PEN","scenario":"APPROVED"}}}},"tags":["W2 · Pago a contacto"],"description":"Crea una intención PENDING y reserva saldo; responde 202. Requiere Idempotency-Key de 1 a 200 caracteres, única por wallet. Misma clave y contenido devuelve 200 con el pago actual; contenido distinto devuelve 409 IDEMPOTENCY_CONFLICT. APPROVED y REJECTED generan evento; PENDING y DELAYED_APPROVAL esperan control manual. TIMEOUT_AFTER_PROCESSING devuelve 504 con paymentId, pero ya creó el pago y el proveedor lo aprobó. PROVIDER_ERROR devuelve 503 sin operación ni reserva. Confirma por callback o /resolve. Moneda predeterminada PEN, escenario APPROVED. 409 WALLET_NOT_ACTIVE o INSUFFICIENT_FUNDS; 404 por wallet, destinatario o QR inexistente; 410 QR_EXPIRED; 409 QR_PAYMENT_MISMATCH si difieren comercio o monto fijo. Solo se permiten contact-demo y merchant-demo. qrId es opcional: sin él, el pago es directo.","operationId":"postPayments"},"get":{"summary":"Historial filtrado por walletId","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}}}}}},"parameters":[{"in":"query","name":"walletId","schema":{"type":"string","default":"wallet-demo"},"description":"Wallet del historial, predeterminada wallet-demo."}],"tags":["W2 · Pago a contacto"],"description":"Lista el historial de una wallet. walletId es opcional y vale wallet-demo por defecto. Una wallet sin pagos o inexistente produce []. Sin paginación ni filtros de fechas. No lista todas las wallets del grupo.","operationId":"getPayments"}},"/payments/{id}":{"get":{"summary":"Consultar pago","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"tags":["W2 · Pago a contacto"],"description":"Consulta el estado local del pago. No consulta al proveedor ni confirma eventos. Pago inexistente: 404 NOT_FOUND.","operationId":"getPaymentsByid"}},"/payments/{id}/receipt":{"get":{"summary":"Comprobante de pago exitoso o reversado","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Receipt"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"tags":["W2 · Pago a contacto"],"description":"Obtiene el comprobante cuando el pago está SUCCEEDED o REVERSED. En PENDING o REJECTED devuelve 409 RECEIPT_NOT_READY. No genera PDF. Pago inexistente: 404.","operationId":"getPaymentsByidReceipt"}},"/provider/payments/{id}":{"get":{"summary":"Consultar fuente del proveedor","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderPayment"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"tags":["W2 · Pago a contacto"],"description":"Consulta el estado del proveedor simulado usando el mismo ID del pago. Puede diferir del estado local si falta el callback. No aplica el resultado a la wallet. ID inexistente: 404.","operationId":"getProviderPaymentsByid"}},"/reversals/{id}":{"get":{"summary":"Consultar reverso","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reversal"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"tags":["W5 · Fallas y reversos"],"description":"Consulta usando el ID rev del reverso. PENDING espera confirmación y SUCCEEDED significa saldo devuelto. No usar el ID pay del pago en esta ruta. Inexistente: 404.","operationId":"getReversalsByid"}},"/payments/{id}/resolve":{"post":{"summary":"Consultar proveedor y resolver o escalar","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"202":{"description":"Pendiente; completar mediante evento","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingResolution"}}}},"400":{"description":"JSON o campos inválidos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]},"example":{}}}},"tags":["W5 · Fallas y reversos"],"description":"Consulta al proveedor sin crear otro cobro. Si sigue PENDING devuelve 202 con escalated:true y abre un único caso de soporte. Si ya terminó, aplica el resultado y devuelve 200 con el pago. Repetir conserva el resultado. También puede completar un reverso pendiente. No requiere cuerpo ni Idempotency-Key. Pago inexistente: 404.","operationId":"postPaymentsByidResolve"}},"/payments/{id}/reversals":{"post":{"summary":"Reverso completo, único por pago","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reversal"}}}},"202":{"description":"Pendiente; completar mediante evento","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reversal"}}}},"400":{"description":"JSON o campos inválidos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]},"example":{}}}},"tags":["W5 · Fallas y reversos"],"description":"Solicita un reverso completo de un pago SUCCEEDED. Devuelve 202 con reverso PENDING, cambia el proveedor a REVERSED y crea el evento REVERSED. Hasta confirmar, la wallet conserva el cargo. Repetir devuelve 200 con el mismo reverso. Otro estado inicial: 409 PAYMENT_NOT_REVERSIBLE. Pago inexistente: 404. No requiere cuerpo ni Idempotency-Key.","operationId":"postPaymentsByidReversals"}},"/qrs":{"post":{"summary":"Generar payload QR de prueba","responses":{"201":{"description":"Creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Qr"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"merchantId":{"type":"string","enum":["merchant-demo"],"minLength":1,"maxLength":200,"description":"Comercio de prueba. Predeterminado merchant-demo."},"amountCents":{"type":"integer","minimum":1,"maximum":100000000,"nullable":true,"description":"Importe en céntimos de PEN. 1500 = S/ 15.00."},"expiresInSeconds":{"type":"integer","minimum":0,"maximum":86400,"default":300,"description":"Vigencia en segundos. 0 vence inmediatamente."}},"required":[]},"example":{"merchantId":"merchant-demo","amountCents":2200,"expiresInSeconds":300}}}},"tags":["W3 · Pago QR comercio"],"description":"Crea texto qrmock:ID para merchant-demo. Sin amountCents o con null, el monto es abierto. Con monto, el pago debe respetarlo. expiresInSeconds predeterminado 300, rango entero 0..86400; 0 vence inmediatamente. Comercio desconocido: 404 MERCHANT_NOT_FOUND; TTL inválido: 400 INVALID_TTL. No genera imagen ni valida EMVCo.","operationId":"postQrs"}},"/qrs/resolve":{"post":{"summary":"Resolver payload local","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolvedQr"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"QR vencido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"payload":{"type":"string","minLength":1,"maxLength":200,"description":"Texto qrmock:ID devuelto por POST /qrs."}},"required":["payload"]},"example":{"payload":"qrmock:REEMPLAZAR_ID_QR"}}}},"tags":["W3 · Pago QR comercio"],"description":"Resuelve el payload y devuelve comercio y monto, null si abierto. No crea pagos. 400 INVALID_QR por formato desconocido; 404 NOT_FOUND por ID desconocido; 410 QR_EXPIRED por vencimiento. Usa el ID devuelto al crear el QR como qrId del pago.","operationId":"postQrsResolve"}},"/callbacks/payments":{"post":{"summary":"Consumir evento conocido del proveedor local","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Callback"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventId":{"type":"string","minLength":1,"maxLength":200,"description":"ID evt de un evento devuelto por GET /control/events."}},"required":["eventId"]},"example":{"eventId":"REEMPLAZAR_ID_EVENTO"}}}},"tags":["W4 · Idempotencia y callbacks"],"description":"Entrega un evento existente mediante eventId. Primera entrega: duplicate:false y payment; repetición del mismo evento: solo duplicate:true. El éxito descuenta saldo y crea dos asientos; rechazo libera reserva; reverso devuelve saldo y compensa asientos. Una aprobación tardía de un pago ya REVERSED se acepta sin cambiar saldo ni estado. Evento inexistente: 404; transición incompatible: 409 INVALID_STATE_TRANSITION. No verifica firmas ni recibe payloads externos.","operationId":"postCallbacksPayments"}},"/control/events":{"get":{"summary":"Eventos con contador de entrega","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}}}}},"tags":["Controles del mock"],"description":"Lista eventos de todos los pagos del grupo, incluidos los entregados. Filtra en tu cliente por payment_id y target, y usa id como eventId. No hay envío automático de webhooks.","operationId":"getControlEvents"}},"/ledger":{"get":{"summary":"Asientos de partida doble","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LedgerEntry"}}}}}},"tags":["W6 · Conciliación"],"description":"Lista asientos de partida doble del grupo. Incluye dos asientos FUNDING de la semilla. PAYMENT resta a la wallet y suma a clearing; REVERSAL hace lo contrario. No modifica saldos.","operationId":"getLedger"}},"/support/cases":{"get":{"summary":"Casos abiertos y resueltos","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SupportCase"}}}}}},"tags":["W5 · Fallas y reversos"],"description":"Lista casos OPEN y RESOLVED por revisión KYC o resultado de pago pendiente. La decisión KYC o confirmación del pago resuelve el caso. No envía notificaciones externas ni ofrece edición directa.","operationId":"getSupportCases"}},"/settlements":{"get":{"summary":"Reporte simulado completo, sin corte diario","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProviderPayment"}}}}}},"tags":["W6 · Conciliación"],"description":"Devuelve el reporte completo del proveedor con las alteraciones configuradas en /control/settlements. Incluye pagos pendientes, rechazados y reversados. No concilia ni cambia la wallet.","operationId":"getSettlements"}},"/reconciliations/incidents":{"get":{"summary":"Historial de incidencias","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Incident"}}}}}},"tags":["W6 · Conciliación"],"description":"Lista incidencias OPEN y RESOLVED del último proceso de conciliación. La misma combinación de recurso y motivo conserva ID en ejecuciones posteriores. Solo POST /reconciliations recalcula su estado.","operationId":"getReconciliationsIncidents"}},"/control/events/{id}/deliver":{"post":{"summary":"Entregar o repetir evento local","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Callback"}}}},"400":{"description":"JSON o campos inválidos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]},"example":{}}}},"tags":["Controles del mock"],"description":"Entrega el evento por ID evt de la ruta. Equivale a POST /callbacks/payments con eventId. No requiere cuerpo. Repetir es seguro; 404 si no existe; 409 si la transición es incompatible.","operationId":"postControlEventsByidDeliver"}},"/control/provider/{id}/complete":{"post":{"summary":"Finalizar proveedor pendiente y encolar evento","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventCreated"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"ID del recurso nombrado en la ruta. En /control/provider usa el ID del pago."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["SUCCEEDED","REJECTED"],"minLength":1,"maxLength":200,"description":"Resultado del proveedor. Predeterminado SUCCEEDED."}},"required":[]},"example":{"status":"SUCCEEDED"}}}},"tags":["Controles del mock"],"description":"Finaliza un proveedor PENDING usando el ID pay del pago. status puede ser SUCCEEDED, valor predeterminado, o REJECTED. Devuelve eventId para entregar después. No confirma la wallet automáticamente. Si ya terminó: 409 PROVIDER_ALREADY_FINAL; estado inválido: 400 INVALID_STATUS; ID inexistente: 404.","operationId":"postControlProviderByidComplete"}},"/control/settlements":{"post":{"summary":"Alterar reporte sin modificar wallet ni ledger","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Configured"}}}},"400":{"description":"Entrada inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado, saldo o idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentId":{"type":"string","minLength":1,"maxLength":200,"description":"ID pay del pago; para ORPHAN usa un ID nuevo."},"mode":{"type":"string","enum":["MISSING","AMOUNT","DUPLICATE","ORPHAN","CLEAR"],"minLength":1,"maxLength":200,"description":"Alteración del reporte. CLEAR restaura la fila original."},"amountCents":{"type":"integer","minimum":1,"maximum":100000000,"description":"Importe en céntimos de PEN. 1500 = S/ 15.00. Obligatorio para AMOUNT y ORPHAN."}},"required":["paymentId","mode"]},"example":{"paymentId":"REEMPLAZAR_ID_PAGO","mode":"AMOUNT","amountCents":1600}}}},"tags":["Controles del mock"],"description":"Configura una sola alteración por paymentId, reemplazando la anterior. AMOUNT exige amountCents; MISSING omite fila; DUPLICATE duplica fila; los tres exigen pago existente. ORPHAN exige ID nuevo y amountCents, o 409 PROVIDER_ALREADY_EXISTS. CLEAR elimina la alteración aunque no exista. Modo inválido: 400 INVALID_MODE. No modifica proveedor, wallet ni ledger; solo el reporte.","operationId":"postControlSettlements"}},"/reconciliations":{"post":{"summary":"Comparar fuentes y actualizar incidencias","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reconciliation"}}}},"400":{"description":"JSON o campos inválidos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Solo cloud: GROUP_LIMIT_RESET_REQUIRED. Reinicia el grupo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]},"example":{}}}},"tags":["W6 · Conciliación"],"description":"Compara todos los pagos del grupo con reporte, ledger y saldos. Devuelve MATCHED con differences:[] o DIFFERENCES con incidencias OPEN. Detecta faltantes, duplicados, montos, estados, asientos y balances. Repetir no duplica incidencias. Corregir la causa y volver a ejecutar marca las anteriores RESOLVED. No mueve dinero ni corrige saldos. No requiere cuerpo.","operationId":"postReconciliations"}},"/control/reset":{"post":{"summary":"Borrar datos locales y reponer semilla","responses":{"200":{"description":"Consulta o repetición","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reset"}}}},"400":{"description":"JSON o campos inválidos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Cuerpo mayor a 65536 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]},"example":{}}}},"tags":["Controles del mock"],"description":"Borra TODOS los datos del grupo y repone wallet-demo con 100000 y dos asientos de apertura. Elimina KYC, pagos, QRs, claves, eventos, reversos, soporte e incidencias. Afecta a todos los integrantes del grupo. No afecta otros grupos ni cambia su URL. Sin cuerpo. Funciona incluso al alcanzar el límite cloud.","operationId":"postControlReset"}},"/groups":{"get":{"tags":["Servicio y grupos"],"summary":"Listar los ocho grupos y sus URLs fijas","servers":[{"url":"https://bcp-wallet-mock.yefri.workers.dev"}],"description":"Consulta la raíz del servicio, sin prefijo de grupo. Devuelve los ocho grupos fijos y sus contratos. No crea grupos ni modifica estado.","responses":{"200":{"description":"Lista de los ocho grupos.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["groupId","name","baseUrl","openapiUrl"],"properties":{"groupId":{"type":"integer","minimum":1,"maximum":8},"name":{"type":"string"},"baseUrl":{"type":"string"},"openapiUrl":{"type":"string"}}}}}}}}}},"/usage":{"get":{"tags":["Servicio y grupos"],"summary":"Uso acumulado del grupo por método, endpoint y código HTTP","description":"Solo cloud. Almacenamiento persistente por grupo. Las consultas de uso y logs y las solicitudes OPTIONS no se contabilizan. Los logs conservan las últimas 10000 solicitudes; los contadores son acumulados desde la instalación de esta versión.","responses":{"200":{"description":"Uso acumulado del grupo por método, endpoint y código HTTP","content":{"application/json":{"schema":{"type":"object","properties":{"groupId":{"type":"integer"},"totalRequests":{"type":"integer"},"errors":{"type":"integer"},"avgDurationMs":{"type":"number"},"endpoints":{"type":"array","items":{"type":"object"}}}}}}}}}},"/logs":{"get":{"tags":["Servicio y grupos"],"summary":"Últimas solicitudes del grupo, sin cuerpos ni cabeceras","description":"Solo cloud. Almacenamiento persistente por grupo. Las consultas de uso y logs y las solicitudes OPTIONS no se contabilizan. Los logs conservan las últimas 10000 solicitudes; los contadores son acumulados desde la instalación de esta versión.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"before","in":"query","description":"Cursor nextBefore de la página anterior.","schema":{"type":"integer","minimum":1}},{"name":"status","in":"query","schema":{"type":"integer","minimum":100,"maximum":599}},{"name":"method","in":"query","schema":{"type":"string"}},{"name":"endpoint","in":"query","description":"Ruta del contrato, por ejemplo /payments/{id}.","schema":{"type":"string"}}],"responses":{"200":{"description":"Últimas solicitudes del grupo, sin cuerpos ni cabeceras","content":{"application/json":{"schema":{"type":"object","properties":{"groupId":{"type":"integer"},"logs":{"type":"array","items":{"type":"object"}},"nextBefore":{"type":"integer","nullable":true},"retentionLimit":{"type":"integer"}}}}}},"400":{"description":"INVALID_LOG_FILTER"}}}},"/usage/all":{"get":{"tags":["Servicio y grupos"],"summary":"Comparar el uso de los ocho grupos","servers":[{"url":"https://bcp-wallet-mock.yefri.workers.dev"}],"responses":{"200":{"description":"Resumen global y desglose por grupo.","content":{"application/json":{"schema":{"type":"object","properties":{"totalRequests":{"type":"integer"},"errors":{"type":"integer"},"groups":{"type":"array","items":{"type":"object","properties":{"groupId":{"type":"integer"},"totalRequests":{"type":"integer"},"errors":{"type":"integer"},"avgDurationMs":{"type":"number"},"endpoints":{"type":"array","items":{"type":"object"}}}}}}}}}}}}}},"tags":[{"name":"Servicio y grupos","description":"Disponibilidad, contrato OpenAPI y URLs fijas de los ocho grupos."},{"name":"W1 · Registro y activación","description":"Crear cuentas de prueba, consultar KYC y resolver la revisión humana."},{"name":"W2 · Pago a contacto","description":"Crear pagos, consultar su resultado, historial y comprobante. También se usa al confirmar pagos QR."},{"name":"W3 · Pago QR comercio","description":"Generar y resolver QR de prueba antes de crear el pago en W2."},{"name":"W4 · Idempotencia y callbacks","description":"Entregar eventos o repetirlos sin duplicar cargos. Los reintentos de creación se prueban con Idempotency-Key en POST /payments de W2."},{"name":"W5 · Fallas y reversos","description":"Consultar resultados inciertos, simular reversos y revisar casos de soporte."},{"name":"W6 · Conciliación","description":"Comparar wallet, proveedor y ledger; consultar incidencias y asientos."},{"name":"Controles del mock","description":"Avanzar el proveedor, entregar eventos, alterar reportes y reiniciar los datos. Afectan solo al grupo actual."}],"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Código estable del error."},"processed":{"type":"boolean"},"paymentId":{"type":"string","description":"Pago creado aunque la respuesta sea 504."},"status":{"type":"string","enum":["PENDING","SUCCEEDED","REJECTED","REVERSED"]}},"required":["error"]},"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"storage":{"type":"string","enum":["memory","sqlite"]},"environment":{"type":"string"},"startedAt":{"type":"string"},"service":{"type":"string"},"groups":{"type":"string"},"openapi":{"type":"string"}},"required":["status","storage"]},"Scenarios":{"type":"object","properties":{"kyc":{"type":"array","items":{"type":"string"}},"payments":{"type":"array","items":{"type":"string"}},"settlements":{"type":"array","items":{"type":"string"}}},"required":["kyc","payments","settlements"]},"Kyc":{"type":"object","properties":{"id":{"type":"string","description":"ID de verificación KYC."},"wallet_id":{"type":"string","description":"ID de la wallet creada."},"scenario":{"type":"string","enum":["APPROVED","REJECTED","MANUAL_REVIEW"]},"status":{"type":"string","enum":["ACTIVE","PENDING","REJECTED"]}},"required":["id","wallet_id","scenario","status"]},"Wallet":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["ACTIVE","PENDING","REJECTED"]},"balance":{"type":"integer","description":"Saldo contable en céntimos."},"reserved":{"type":"integer","description":"Suma de pagos PENDING en céntimos."},"available":{"type":"integer","description":"balance menos reserved."},"currency":{"type":"string","enum":["PEN"]}},"required":["id","status","balance","reserved","available","currency"]},"Payment":{"type":"object","properties":{"id":{"type":"string"},"wallet_id":{"type":"string"},"recipient":{"type":"string","enum":["contact-demo","merchant-demo"]},"amount":{"type":"integer","description":"Importe entero en céntimos de PEN.","minimum":1,"maximum":100000000},"status":{"type":"string","enum":["PENDING","SUCCEEDED","REJECTED","REVERSED"]},"scenario":{"type":"string"},"idem":{"type":"string","description":"Clave de idempotencia recibida."},"fingerprint":{"description":"Dato interno. Array en Python, texto JSON del mismo array en cloud. No usar para construir la interfaz.","oneOf":[{"type":"array","items":{"nullable":true}},{"type":"string"}]}},"required":["id","wallet_id","recipient","amount","status","scenario","idem","fingerprint"]},"ProviderPayment":{"type":"object","properties":{"id":{"type":"string","description":"Mismo ID que el pago de la wallet."},"amount":{"type":"integer","description":"Importe entero en céntimos de PEN.","minimum":1,"maximum":100000000},"status":{"type":"string","enum":["PENDING","SUCCEEDED","REJECTED","REVERSED"]}},"required":["id","amount","status"]},"Receipt":{"type":"object","properties":{"receiptId":{"type":"string"},"paymentId":{"type":"string"},"amountCents":{"type":"integer","description":"Importe entero en céntimos de PEN.","minimum":1,"maximum":100000000},"currency":{"type":"string","enum":["PEN"]},"recipient":{"type":"string"},"status":{"type":"string","enum":["SUCCEEDED","REVERSED"]}},"required":["receiptId","paymentId","amountCents","currency","recipient","status"]},"Reversal":{"type":"object","properties":{"id":{"type":"string","description":"ID del reverso, distinto al ID del pago."},"payment_id":{"type":"string"},"status":{"type":"string","enum":["PENDING","SUCCEEDED"]}},"required":["id","payment_id","status"]},"PendingResolution":{"type":"object","properties":{"status":{"type":"string","enum":["PENDING"]},"escalated":{"type":"boolean","enum":[true]}},"required":["status","escalated"]},"Qr":{"type":"object","properties":{"id":{"type":"string"},"payload":{"type":"string","description":"Texto qrmock:ID para codificar en QR."},"amountCents":{"type":"integer","description":"Importe entero en céntimos de PEN.","minimum":1,"maximum":100000000,"nullable":true},"currency":{"type":"string","enum":["PEN"]}},"required":["id","payload","amountCents","currency"]},"ResolvedQr":{"type":"object","properties":{"merchantId":{"type":"string"},"merchantName":{"type":"string"},"amountCents":{"type":"integer","description":"Importe entero en céntimos de PEN.","minimum":1,"maximum":100000000,"nullable":true},"currency":{"type":"string","enum":["PEN"]}},"required":["merchantId","merchantName","amountCents","currency"]},"Callback":{"type":"object","properties":{"duplicate":{"type":"boolean"},"payment":{"$ref":"#/components/schemas/Payment"}},"required":["duplicate"]},"Event":{"type":"object","properties":{"id":{"type":"string","description":"Usar como eventId o en /control/events/{id}/deliver."},"payment_id":{"type":"string"},"target":{"type":"string","enum":["SUCCEEDED","REJECTED","REVERSED"]},"delivered":{"type":"integer","description":"0 antes de procesar; 1 después. Repetir no lo incrementa."}},"required":["id","payment_id","target","delivered"]},"LedgerEntry":{"type":"object","properties":{"id":{"type":"integer"},"operation":{"type":"string","description":"ID del pago o opening para la semilla."},"kind":{"type":"string","enum":["FUNDING","PAYMENT","REVERSAL"]},"account":{"type":"string","description":"ID de wallet, funding o clearing."},"amount":{"type":"integer","description":"Movimiento con signo en céntimos. Cada par suma cero."}},"required":["id","operation","kind","account","amount"]},"SupportCase":{"type":"object","properties":{"id":{"type":"string"},"resource":{"type":"string","description":"ID de KYC o pago que requiere atención."},"reason":{"type":"string","enum":["KYC_REVIEW","UNKNOWN_PAYMENT_RESULT","PROVIDER_PENDING"]},"status":{"type":"string","enum":["OPEN","RESOLVED"]}},"required":["id","resource","reason","status"]},"Incident":{"type":"object","properties":{"id":{"type":"string"},"payment_id":{"type":"string","description":"ID del recurso afectado. Puede ser una wallet para BALANCE_MISMATCH."},"reason":{"type":"string","enum":["DUPLICATE_PROVIDER","MISSING_WALLET","MISSING_PROVIDER","AMOUNT_MISMATCH","STATUS_MISMATCH","LEDGER_MISMATCH","LEDGER_UNBALANCED","BALANCE_MISMATCH"]},"status":{"type":"string","enum":["OPEN","RESOLVED"]}},"required":["id","payment_id","reason","status"]},"Reconciliation":{"type":"object","properties":{"status":{"type":"string","enum":["MATCHED","DIFFERENCES"]},"differences":{"type":"array","items":{"$ref":"#/components/schemas/Incident"}}},"required":["status","differences"]},"EventCreated":{"type":"object","properties":{"eventId":{"type":"string"}},"required":["eventId"]},"Configured":{"type":"object","properties":{"configured":{"type":"boolean"}},"required":["configured"]},"Reset":{"type":"object","properties":{"reset":{"type":"boolean"}},"required":["reset"]},"OpenApi":{"type":"object","properties":{"openapi":{"type":"string"},"info":{"type":"object"},"paths":{"type":"object"},"servers":{"type":"array","items":{"type":"object"}}},"required":["openapi","info","paths","servers"]}}}}