Manejar pagos fallidos
Qué pasa cuando el pago de un cliente falla y cómo puede reactivar su suscripción desde el Portal del Cliente.
Cuando falla un pago de renovación, la suscripción pasa al estado past_due e ingresa al proceso de reintentos (dunning). El cliente mantiene el servicio durante esta ventana de gracia mientras Commet reintenta el cobro. Los clientes pueden reactivar antes desde el Portal del Cliente reintentando el pago o actualizando su tarjeta.
Qué pasa cuando un pago falla
- La suscripción cambia al estado
past_due - El recibo fallido se marca como
outstanding - El cliente mantiene el servicio: los eventos de uso y de asientos siguen funcionando (el uso se acumula como deuda)
- El cliente recibe una notificación por email
- Commet reintenta el cobro según un calendario fijo (dunning)
Si un reintento tiene éxito, la suscripción vuelve a active. Si todos los reintentos fallan, la suscripción se cancela y el recibo se marca como uncollectible.
Calendario de reintentos (dunning)
Los reintentos se ejecutan el día 1, el día 3 y el día 5 después de la falla original (3 reintentos). Tras el último reintento fallido, la suscripción se cancela.
Consultar el estado de la suscripción
const { data } = await commet.subscriptions.getActive({ customerId: 'user_123' })
if (data.status === 'past_due') {
// Pago fallido — invita al cliente a reactivar desde el portal
}response = commet.subscriptions.get_active(customer_id='user_123')
if response.data['status'] == 'past_due':
# Pago fallido — invita al cliente a reactivar desde el portal
passresult, err := client.Subscriptions.GetActive(ctx, &commet.GetActiveSubscriptionParams{CustomerID: "user_123"})
if result.Data.Status == "past_due" {
// Pago fallido — invita al cliente a reactivar desde el portal
}ApiResponse<Subscription> result = commet.subscriptions().getActive(GetActiveSubscriptionParams.builder("user_123").build());
if ("past_due".equals(result.getData().getStatus())) {
// Pago fallido — invita al cliente a reactivar desde el portal
}$result = $commet->subscriptions->getActive('user_123');
if ($result->data['status'] === 'past_due') {
// Pago fallido — invita al cliente a reactivar desde el portal
}curl "https://commet.co/api/subscriptions/active?customerId=user_123" \
-H "x-api-key: $COMMET_API_KEY"Restringir el acceso según el estado
Commet sigue dando servicio a los clientes en past_due durante la ventana de dunning: los eventos de uso y de asientos siguen funcionando. Tú decides si restringes tu propio producto cuando la suscripción está en past_due. Para dar acceso solo mientras el cobro está sano, trata active y trialing como los estados con acceso:
const { data } = await commet.subscriptions.getActive({ customerId: 'user_123' })
const hasAccess = data.status === 'active' || data.status === 'trialing'response = commet.subscriptions.get_active(customer_id='user_123')
has_access = response.data['status'] in ('active', 'trialing')result, err := client.Subscriptions.GetActive(ctx, &commet.GetActiveSubscriptionParams{CustomerID: "user_123"})
status := result.Data.Status
hasAccess := status == "active" || status == "trialing"ApiResponse<Subscription> result = commet.subscriptions().getActive(GetActiveSubscriptionParams.builder("user_123").build());
String status = result.getData().getStatus();
boolean hasAccess = "active".equals(status) || "trialing".equals(status);$result = $commet->subscriptions->getActive('user_123');
$hasAccess = in_array($result->data['status'], ['active', 'trialing'], true);curl "https://commet.co/api/subscriptions/active?customerId=user_123" \
-H "x-api-key: $COMMET_API_KEY"Recuperar una suscripción de forma programática
El SDK expone tres primitivas de recuperación del lado del servidor (SDK v7.2.0, @commet/node). Para suscripciones en past_due, todas operan sobre el mismo recibo de renovación outstanding: ninguna lo anula. reactivate además reactiva suscripciones canceled.
Reintentar el cobro de servidor a servidor
reactivate cobra el método de pago guardado de la suscripción. Funciona tanto en suscripciones past_due como canceled, con efectos distintos:
past_due: reintenta el mismo recibo de renovación pendiente. El ancla de facturación se mantiene fija. Si tiene éxito, la suscripción vuelve aactivey se emitepayment.recovered.canceled: genera un recibo nuevo, reinicia el ancla del período de facturación a ahora y cobra la tarjeta guardada. Si tiene éxito, la suscripción vuelve aactivey se emitesubscription.reactivated. Requiere que el plan siga disponible en la moneda de la suscripción; de lo contrario devuelvePLAN_UNAVAILABLE(422).
const { data } = await commet.subscriptions.reactivate({ id: 'sub_123' })
// data.retryInitiated === trueSi el cobro se rechaza o no hay tarjeta guardada, la respuesta devuelve un recoveryUrl en los detalles del error: una página hospedada donde el cliente agrega una tarjeta nueva y paga. Esto importa para suscripciones canceladas por dunning: llegaron a canceled precisamente porque la tarjeta guardada seguía fallando.
Enviar un enlace de recuperación al cliente
createRecoveryLink devuelve un enlace hospedado y firmado para que el cliente pague la renovación pendiente por su cuenta. Entrégalo a través de tu propio email, SMS o dashboard. El enlace permanece válido hasta que el cobro se paga o la suscripción deja de estar en past_due.
const { data } = await commet.subscriptions.createRecoveryLink({ id: 'sub_123' })
// data.url → página de pago hospedada
// data.token → token firmado incrustado en la URLActualizar el método de pago
updatePaymentMethod devuelve un checkout hospedado donde el cliente actualiza el método de pago predeterminado de la suscripción.
const { data } = await commet.subscriptions.updatePaymentMethod({
id: 'sub_123',
successUrl: 'https://yourapp.com/billing',
})
// redirect(data.checkoutUrl)Reactivación desde el Portal del Cliente
Los clientes en past_due ven su suscripción en el Portal del Cliente con un botón Reactivar suscripción. Pueden elegir:
- Reintentar con su tarjeta actual — útil cuando la falla fue temporal (fondos insuficientes que ya están disponibles, una retención bancaria que se liberó).
- Actualizar su método de pago — ingresar una nueva tarjeta vía Stripe y reintentar en el mismo paso.
Un reintento exitoso liquida el recibo pendiente, devuelve la suscripción a active y emite un evento payment.recovered. Los reintentos están limitados a 3 por día por cliente.
Invitar a actualizar el pago
Redirige a los clientes al Portal del Cliente para reactivar:
const portal = await commet.portal.getUrl({ customerId: 'user_123' })
redirect(portal.data.portalUrl)portal = commet.portal.get_url(customer_id='user_123')
redirect(portal.data['portal_url'])portal, err := client.Portal.GetURL(ctx, &commet.GetPortalURLParams{
CustomerID: "user_123",
})
// redirect(portal.Data.PortalURL)ApiResponse<PortalSession> portal = commet.portal().getUrl("user_123", null, null);
// redirect(portal.getData().getPortalUrl())$portal = $commet->portal->getUrl(customerId: 'user_123');
redirect($portal->data['portalUrl']);curl -X POST https://commet.co/api/portal/request-access \
-H "x-api-key: $COMMET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"customerId": "user_123"}'Relacionado
- Recibos y ciclos de cobro — Tipos de recibo y momento del cobro
- Administrar suscripciones — Crear y administrar suscripciones de clientes
- Portal del Cliente — Portal de autogestión de cobros y pagos para clientes
¿Cómo está esta guía?