Ir al contenido

Suscripciones

KUTI le cobra a tu cliente cada periodo, sobre su Yape afiliado. Monto fijo o monto que tú envías cada periodo.

Hoy disponible solo en modo prueba (kuti_test_…). En producción el método YAPE todavía no se ofrece: te avisaremos en el changelog cuando esté activo.

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.

KUTI cobra y te avisa. No corta tu servicio: tú decides qué hacer cuando un pago falla.

Monto fijo

  1. 1

    Crea la suscripción

    POST /subscriptions con el cliente, el monto y la frecuencia.

  2. 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. 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. 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. 2

    Llega la fecha

    Recibes subscription.amount_required con el periodo en latest_cycle.billing_period.

  3. 3

    Envía el monto

    POST /subscriptions/{id}/charges con el total. KUTI lo debita. También puedes enviarlo antes, sin esperar el aviso.

Si no envías el monto, KUTI te avisa 3 veces (una cada 24 horas) y luego salta el periodo sin cobrar: subscription.period_skipped.

Estados

statusQué significa
INCOMPLETECreada. Falta que tu cliente afilie su Yape (y, en monto fijo, pague el primer periodo).
ACTIVEAl día. Cobra sola.
PAST_DUEHay un periodo sin pagar. KUTI reintenta y tu cliente puede pagarlo por el enlace.
PAUSEDLa detuviste. No genera periodos ni reintenta.
CANCELLEDCancelada. Es final.
COMPLETEDLlegó 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_codeQué pasó
insufficient_fundsSin saldo en Yape. Se reintenta.
payment_method_revokedTu cliente quitó la afiliación. No se reintenta sobre ese medio.
payment_method_requiredNo hay un Yape afiliado sobre el cual cobrar.
amount_exceeds_method_limitEl monto pasa el tope del método.
temporarily_unavailableFalla 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_policyjson
{
  "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.

EventoCuándo
subscription.createdSe creó. status dice si ya cobra (ACTIVE) o espera a tu cliente (INCOMPLETE).
subscription.activatedEmpezó a correr: se pagó el primer periodo o tu cliente afilió su Yape.
subscription.payment_succeededSe cobró un periodo, por débito o por el enlace.
subscription.payment_failedUn débito se denegó o no hay medio sobre el cual cobrar.
subscription.amount_requiredMonto variable: llegó la fecha y falta el monto del periodo.
subscription.period_skippedMonto variable: nunca enviaste el monto y el periodo se saltó.
subscription.updatedLa editaste. Rige desde el próximo periodo.
subscription.paused / .resumedLa pausaste o la reanudaste.
subscription.cancelled / .completedSe canceló o llegó a su end_date.
En tu webhookjs
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