Pago de servicios (Billers)

MODO PRUEBA

Cobra CFE, agua, telefonía, TV y recargas con el mismo flujo de tres pasos: consulta el catálogo, consulta el adeudo de una referencia (sin mover dinero), y confirma el pago del servicio. Útil para un POS que quiere ofrecer "pago de servicios" en el mismo mostrador donde ya cobra con tarjeta o SPEI.

1. Catálogo de billers

bash
curl -s https://api.winal.com.mx/v1/billers \
  -H "Authorization: Bearer $SK"
200 · respuesta real
{
  "object": "list",
  "data": [
    { "object": "biller", "code": "agua_cdmx", "name": "Sistema de Aguas de la Ciudad de México (SACMEX)", "category": "agua", "reference_label": "Cuenta de agua (8 a 10 dígitos)", "active": true },
    { "object": "biller", "code": "cfe", "name": "CFE (Comisión Federal de Electricidad)", "category": "luz", "reference_label": "Número de servicio (10 a 12 dígitos)", "active": true },
    { "object": "biller", "code": "telcel_recarga", "name": "Recarga Telcel", "category": "recarga", "reference_label": "Número celular a 10 dígitos", "active": true },
    { "object": "biller", "code": "telmex", "name": "Telmex", "category": "telefonia", "reference_label": "Número telefónico o de contrato (10 dígitos)", "active": true },
    { "object": "biller", "code": "izzi", "name": "izzi Telecom", "category": "tv", "reference_label": "Número de cuenta izzi (10 a 12 dígitos)", "active": true }
  ]
}

Filtra con ?category= (luz, agua, gas, telefonia, tv, recarga, gobierno) — hoy el catálogo de Sim solo sembró los cinco de arriba; gas y gobierno son categorías válidas del enum sin ningún biller activo todavía. Es un catálogo global (sin variación por tenant): agregar un biller nuevo es trabajo de Winal, no algo que el comercio configure.

2. Consulta de adeudo (inquiry)

POST /v1/billers/{code}/inquiry no mueve dinero — solo pregunta cuánto debe una referencia. Úsalo para mostrarle el monto al pagador antes de cobrarle.

bash
curl -s https://api.winal.com.mx/v1/billers/cfe/inquiry \
  -H "Authorization: Bearer $SK" \
  -H "Content-Type: application/json" \
  -d '{ "reference": "1234567890" }'
200 · respuesta real
{
  "object": "biller_inquiry",
  "biller_code": "cfe",
  "biller_name": "CFE (Comisión Federal de Electricidad)",
  "reference": "1234567890",
  "amount_due_minor": 118700,
  "currency": "MXN",
  "service_holder_name": "Cliente simulado (ref. 1234567890)",
  "due_date": "2026-07-18T06:47:41.852307+00:00"
}

reference debe cumplir el formato que declara reference_label del biller (p. ej. 10-12 dígitos para cfe). En Sim, el adeudo es determinista por referencia (la misma referencia siempre da el mismo amount_due_minor), lo que te deja escribir pruebas repetibles.

3. Pago del servicio

POST /v1/service-payments confirma el pago ante el biller. Exige Idempotency-Key (UUID) — este endpoint valida el header él mismo, con el mismo mensaje que el resto de la API. Si omites amount_minor, se cobra el adeudo vigente tal cual; si lo mandas, debe coincidir exactamente con el adeudo (fase 0 no admite pagos parciales).

bash
curl -s https://api.winal.com.mx/v1/service-payments \
  -H "Authorization: Bearer $SK" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "biller_code": "cfe", "reference": "1234567890" }'
201 · respuesta real (pagado)
{
  "id": "3f490a8e-630e-4a4b-ba4e-81be735c0c14",
  "object": "service_payment",
  "biller_code": "cfe",
  "reference": "1234567890",
  "amount_minor": 118700,
  "currency": "MXN",
  "status": "paid",
  "provider_ref": "SIMBILL-7188545cf02c4532b9681371fbe806b3",
  "created_at": "2026-07-08T06:47:46.857935+00:00",
  "updated_at": "2026-07-08T06:47:46.863537+00:00"
}

Si ya cobraste al pagador con un payment_intent propio (p. ej. le cobraste por tarjeta en tu POS y ahora pagas el servicio con ese dinero), manda payment_intent_id para enlazar ambos registros — es un campo de correlación libre, sin validar contra Payments (Billers no referencia otros módulos).

El biller puede rechazar la confirmación
Aunque la referencia tenga adeudo válido, el biller puede rechazar el pago al confirmarlo (cuenta cancelada, adeudo ya cubierto por otro medio, etc.). Esto no es un error HTTP — el procedimiento se completó, el rechazo es el desenlace: 201 con status: "failed" y failure_reason con el detalle, mismo criterio que un cargo declinado por el emisor en Payments.
{ "id": "...", "object": "service_payment", "status": "failed",
  "failure_reason": "El biller 'Telmex' rechazó la confirmación del pago para la referencia '...' (simulado)." }

Estados de un service_payment

EstadoQué significa
pendingRegistrado, aún no confirmado ante el biller (transitorio).
paidEl biller confirmó el pago del servicio.
failedEl biller rechazó la confirmación — ver failure_reason.

Idempotencia

Repetir la misma Idempotency-Key con el mismo cuerpo devuelve la misma fila (mismo 201, sin volver a confirmar ante el biller). Con un cuerpo distinto bajo la misma llave, 409 service_payment.idempotency_conflict.

Errores de billers / service-payments

HTTPcodeCausa
400biller.missing_referenceFalta reference en el inquiry.
404biller.not_foundcode no existe o está inactivo.
400biller.invalid_referencereference no cumple el formato del biller (ver reference_label).
404biller.reference_not_foundFormato válido pero la cuenta no existe para ese biller.
400service_payment.missing_fieldsFalta biller_code o reference.
400idempotency_key_requiredFalta el header o no es un UUID válido.
409service_payment.idempotency_conflictMisma llave, cuerpo distinto.
400service_payment.amount_mismatchamount_minor enviado no coincide con el adeudo vigente.
404service_payment.not_foundEl id no existe (o es de otro tenant).

Un agregador real es negocio regulado

Todo lo de arriba corre hoy contra el simulador de billers (adeudos deterministas, sin llamada de red real). Conectar un agregador real de pago de servicios es, en México, actividad regulada: cobrar y dispersar fondos de terceros por esta vía puede requerir estructurarse a través de un agregador ya autorizado para no romper "sin custodia de fondos" (ADR-0001). Es una decisión de producto/legal pendiente del dueño, no una limitación técnica del código — el puerto que reemplaza al simulador (IBillerGateway) ya está listo para recibir un adaptador real sin tocar nada de lo documentado arriba.

Endpoints

MétodoRutaNotas
GET/v1/billers?category= opcional.
POST/v1/billers/{code}/inquiryNo mueve dinero; no exige Idempotency-Key.
POST/v1/service-paymentsRequiere Idempotency-Key.
GET/v1/service-payments?limit= opcional (default 100, máx. 500).
GET/v1/service-payments/{id}404 service_payment.not_found si no existe.