Suscripciones
KUTI le cobra a tu cliente cada periodo, sobre su Yape afiliado. Monto fijo o monto que tú envías cada periodo.
Creas la suscripción una vez y KUTI cobra cada periodo sin que tu cliente intervenga. Cada periodo es un cobro normal: aparece en GET /payment-intents con metadata.subscription_id y metadata.billing_period, y emite payment.succeeded.
Monto fijo
- 1
Crea la suscripción
POST /subscriptions con el cliente, el monto y la frecuencia.
- 2
Primer periodo
Si tu cliente ya tiene su Yape afiliado, se debita en el acto y queda ACTIVE. Si no, nace INCOMPLETE y latest_cycle.checkout_url trae el enlace para que afilie y pague.
- 3
Periodos siguientes
KUTI debita solo, a la hora de charge_time (hora de Perú). Recibes subscription.payment_succeeded.
Monto variable
Para cobros por consumo: con billing_mode: "variable" tú envías el monto de cada periodo. Se cobra a periodo vencido.
- 1
Crea la suscripción
Sin amount. No se cobra nada. Si tu cliente aún no tiene Yape afiliado, setup_url trae el enlace para que lo afilie sin pagar.
- 2
Llega la fecha
Recibes subscription.amount_required con el periodo en latest_cycle.billing_period.
- 3
Envía el monto
POST /subscriptions/{id}/charges con el total. KUTI lo debita. También puedes enviarlo antes, sin esperar el aviso.
Estados
| status | Qué significa |
|---|---|
| INCOMPLETE | Creada. Falta que tu cliente afilie su Yape (y, en monto fijo, pague el primer periodo). |
| ACTIVE | Al día. Cobra sola. |
| PAST_DUE | Hay un periodo sin pagar. KUTI reintenta y tu cliente puede pagarlo por el enlace. |
| PAUSED | La detuviste. No genera periodos ni reintenta. |
| CANCELLED | Cancelada. Es final. |
| COMPLETED | Llegó a su end_date. Es final. |
Cuando un cobro falla
Recibes subscription.payment_failed. En latest_cycle vienen el motivo (last_failure_code), el número de intento (attempts) y cuándo se reintenta (next_attempt_at). Mientras tanto, latest_cycle.checkout_url es un enlace para que tu cliente pague ese periodo con cualquier método.
| last_failure_code | Qué pasó |
|---|---|
| insufficient_funds | Sin saldo en Yape. Se reintenta. |
| payment_method_revoked | Tu cliente quitó la afiliación. No se reintenta sobre ese medio. |
| payment_method_required | No hay un Yape afiliado sobre el cual cobrar. |
| amount_exceeds_method_limit | El monto pasa el tope del método. |
| temporarily_unavailable | Falla temporal. Se reintenta. |
Reintentos
retry_policy decide cuándo se vuelve a intentar. Por defecto: 1, 3 y 5 días después, y si no se logra la suscripción queda PAST_DUE esperando el pago por el enlace.
{
"retry_policy": {
"interval_days": [1, 3, 5],
"on_exhausted": "past_due"
}
}- interval_days: días entre un intento y el siguiente. Máximo 5 reintentos y 30 días en total. [] = no reintentar.
- on_exhausted: past_due (sigue viva esperando el pago) o cancel (se cancela).
- POST /subscriptions/{id}/retry debita ahora el periodo más antiguo sin pagar.
- KUTI no cobra entre la 1:00 y las 3:00 a.m. (hora de Perú).
Eventos
El data de todos lleva la suscripción completa (data.subscription), igual que GET /subscriptions/{id}, incluido latest_cycle.
| Evento | Cuándo |
|---|---|
| subscription.created | Se creó. status dice si ya cobra (ACTIVE) o espera a tu cliente (INCOMPLETE). |
| subscription.activated | Empezó a correr: se pagó el primer periodo o tu cliente afilió su Yape. |
| subscription.payment_succeeded | Se cobró un periodo, por débito o por el enlace. |
| subscription.payment_failed | Un débito se denegó o no hay medio sobre el cual cobrar. |
| subscription.amount_required | Monto variable: llegó la fecha y falta el monto del periodo. |
| subscription.period_skipped | Monto variable: nunca enviaste el monto y el periodo se saltó. |
| subscription.updated | La editaste. Rige desde el próximo periodo. |
| subscription.paused / .resumed | La pausaste o la reanudaste. |
| subscription.cancelled / .completed | Se canceló o llegó a su end_date. |
switch (event.type) {
case "subscription.payment_succeeded":
// Extiende el acceso hasta el próximo periodo
break;
case "subscription.payment_failed": {
const cycle = event.data.subscription.latest_cycle;
// cycle.last_failure_code, cycle.next_attempt_at, cycle.checkout_url
break;
}
case "subscription.amount_required": {
const sub = event.data.subscription;
// Calcula el consumo de sub.latest_cycle.billing_period y envíalo:
// POST /subscriptions/{sub.id}/charges { amount }
break;
}
}Siguiente
- Crear suscripción — POST /subscriptions
- Enviar el monto — monto variable
- Medios de pago guardados — cómo se afilia tu cliente