Todas las fechas internas del motor se normalizan en UTC. El cálculo de ciclo es
determinístico: depende únicamente de la fecha de inicio y de la regla de recurrencia (
recurrence),
sin reloj externo. En transport (body/query), las fechas siempre viajan en ISO 8601 con
offset — consulta Convenciones.La regla de recurrencia
Cada suscripción tiene unarecurrence (heredada del precio del plan o informada inline). Ella es la
que define el tamaño del ciclo y el punto de alineación de las facturas.
Multiplicador de la unidad. Entero positivo. Ej.:
interval=3 + unit=month → cada
3 meses.Unidad del ciclo. Uno de:
day, week, month, year.Cómo se alinea el ciclo a un punto de referencia. Uno de:
subscription_start,
day_of_month, end_of_month. Detallado a continuación.Obligatorio cuando
anchor='day_of_month'. Día del mes, rango de 1 a 31. Ignorado
en las demás anclas.Cuándo cobrar dentro del ciclo:
prepaid (inicio) o postpaid (fin). Detallado en
Prepaid vs Postpaid.Anclas
El ancla decide en qué fecha cae el fin del ciclo actual — que es también el inicio del siguiente ciclo y, por defecto, la fecha de la próxima factura.subscription_start — se alinea por la fecha de inicio
subscription_start — se alinea por la fecha de inicio
El siguiente ciclo es simplemente
Para los meses cortos aplica el clamp de fin de mes (ver abajo): inicio el 31/ene
→ el siguiente fin cae el 28/feb (o 29/feb en año bisiesto). A diferencia de
inicio + intervalo. Preserva la hora (hora/min/seg) de la
fecha de inicio.Ejemplo (unit=month, interval=1, inicio el 15/ene/2026 10:00):| Ciclo | Inicio | Fin (= próxima factura) |
|---|---|---|
| 1 | 15/ene 10:00 | 15/feb 10:00 |
| 2 | 15/feb 10:00 | 15/mar 10:00 |
| 3 | 15/mar 10:00 | 15/abr 10:00 |
day_of_month, el
subscription_start no re-ancla en el día original: cada paso avanza desde el fin del
ciclo anterior (ya clampado), así que después del 28/feb el ciclo siguiente es el 28/mar, luego el 28/abr
— el día “se encoge” al mes más corto que atraviesa y no vuelve a 31.day_of_month — el día X de cada mes (con clamp)
day_of_month — el día X de cada mes (con clamp)
Cobra siempre en el día
Es decir: si compras el día 5 con ancla en el día 10, no se cobra el día 10 del mismo mes —
se posterga al mes siguiente.
anchorDay. La primera factura recurrente cae al menos un
intervalo adelante — nunca en el mes corriente de la suscripción. Esto evita ciclos cortos al
arranque.Ejemplo (unit=month, interval=1, anchorDay=10):| Inicio de la suscripción | Fin del 1er ciclo (1ª factura recurrente) |
|---|---|
| 05/abr | 10/may |
| 19/abr | 10/may |
| 10/abr (ya en el día del ancla) | 10/may |
Si
anchorDay es mayor que el número de días del mes objetivo, el motor ajusta al
último día del mes (compatible con Stripe). anchorDay=31 en febrero se convierte en 28/feb
(o 29/feb en año bisiesto). Y como la alineación re-ancla en el día original en cada
paso, no hay drift: después del 28/feb (31 clampado), el ciclo siguiente vuelve al
31/mar.end_of_month — siempre el último día del mes
end_of_month — siempre el último día del mes
Cobra siempre en el último día de cada mes del ciclo, sin importar su duración.Ejemplo (
Al igual que
unit=month, interval=1, inicio el 10/ene):| Ciclo | Fin (= próxima factura) |
|---|---|
| 1 | 28/feb (o 29/feb bisiesto) |
| 2 | 31/mar |
| 3 | 30/abr |
day_of_month, la primera factura recurrente cae un intervalo adelante, nunca
en el mes de inicio.Prepaid vs Postpaid
collectionTiming define en qué momento, dentro del ciclo, ocurre el cobro.
prepaid (predeterminado)
Cobra al inicio de cada período. Es el modelo clásico de SaaS/streaming: pagas
por el período que está comenzando.
postpaid
Cobra al final de cada período. Modelo de consumo/utility: pagas por el período que
ya transcurrió.
collectionTiminga nivel de la suscripción —prepaid(default) opostpaid.collectionTimingdentro de la propiarecurrence— cuando está ausente, asumeprepaid(retrocompatibilidad con precios antiguos que no tenían el campo).
La ausencia del campo siempre significa
prepaid. Si no defines nada, tus suscripciones
cobran al inicio del ciclo.Generación de facturas: just_in_time vs upfront
invoiceGenerationMode controla cuántas facturas se crean y cuándo.
| Modo | Cuándo nacen las facturas | Para qué sirve |
|---|---|---|
just_in_time (predeterminado) | 1 factura por ciclo, generada por el programador en cada cambio | Suscripciones “infinitas” (SaaS/streaming) y contratos sin necesidad de ver ciclos futuros |
upfront | Todas las maxCycles facturas en el momento de la creación, cada una con dueAt distinto | Contratos finitos de plazo fijo (EAD, fraccionamiento) donde el comerciante quiere previsibilidad de flujo de caja |
just_in_time, el número total de ciclos está limitado por maxCycles (cuando se informa);
sin maxCycles, la suscripción se considera perpetua y el programador continúa generando una
factura por ciclo indefinidamente.
Trial (período de prueba)
trialSpec define un período inicial sin cobro.
Duración del trial en días. Entero
>= 0.Si el trial requiere un medio de pago adjunto para comenzar.
trialing y no se cobra ninguna factura. Al
final del trial, el motor genera la primera factura y la suscripción entra en cobro normal.
El trialSpec a nivel de la suscripción tiene precedencia sobre el trial derivado de ítems
basados en plan (que proviene del precio). Utiliza el override a nivel de la suscripción cuando el trial
se decide fuera del plan — por ejemplo, checkout inline con ítems sin precio asociado.
chargeLeadTimeDays — anticipación del cobro
chargeLeadTimeDays es la anticipación, en días, entre el momento en que la factura es disparada
para cobro (chargeAt — cuando la factura se abre y el boleto/PIX se crea en el PSP) y su
vencimiento (dueAt):
Rango aceptado: 0 a 30 días. Cuando se omite, se deriva automáticamente del tipo del medio
de pago predeterminado (
defaultPaymentMethodRef.type).| Medio de pago | Lead time predeterminado | Por qué |
|---|---|---|
card | 0 días | Cobro instantáneo |
pix | 1 día | Tiempo para enviar el QR Code al cliente |
boleto | 2 días | Tiempo de la red bancaria para registrar el boleto |
other | 0 días | Sin anticipación |
La regla de resolución es: el valor explícito siempre prevalece; de lo contrario, se deriva del tipo del medio de
pago; si no hay ninguno de los dos, el predeterminado es 0. Se persiste en la suscripción como
chargeLeadTimeDays.dueAt), con lead time 2, es
disparada para cobro el 18/jun (chargeAt) — dando 2 días para que el boleto circule
antes del vencimiento.
Dunning — qué ocurre cuando el cobro falla
Cuando un cobro recurrente falla, el motor entra en dunning (régimen de incumplimiento): decide si reprograma un nuevo intento o si agota los intentos y aplica la política final. El comportamiento es configurable por comerciante mediante BillingSettings.Configuración (BillingSettings)
| Campo | Default | Rango / valores | Significado |
|---|---|---|---|
retryIntervalsDays | [3, 5, 7] | array de enteros >= 0 (hasta 10) | Espera, en días, antes de cada reintento. El índice extrapolado reutiliza el último intervalo |
maxRetries | 3 | 0 a 10 | Número máximo de reintentos antes de agotar |
dunningFinalPolicy | mark_unpaid | mark_unpaid o cancel | Qué hacer al agotar el dunning |
hardDeclineCategories | ['hard_decline', 'authentication_required'] | subconjunto de soft_decline, hard_decline, insufficient_funds, authentication_required, other | Categorías de fallo que saltan el retry y van directamente a la política final |
Cómo funciona la régimen
El conteo sigue el modelo de Stripe: el intento nº 1 es el cobro inicial; los reintentos comienzan en 2. Por lo tanto,retryCountSoFar = attemptNumber − 1.
El cobro falla
La factura sale de
open y va a past_due. El intento se registra como failed con
la categoría de fallo.¿Aún hay reintentos?
Si
retryCountSoFar < maxRetries y el fallo no es hard decline, el motor programa el
próximo intento: nextRetryAt = momento del fallo + retryIntervalsDays[índice]. La
suscripción permanece activa y la factura continúa en past_due.Dunning agotado
Se agota cuando
retryCountSoFar >= maxRetries o el fallo cae en una categoría de
hardDeclineCategories (que salta el retry de inmediato). Entonces nextRetryAt queda nulo y la
política final se aplica.La transición predeterminada de la suscripción cuando el cobro falla es
active/cobrando →
past_due (en reintento) → unpaid (dunning agotado con política mark_unpaid).
Con la política cancel, el desenlace final es canceled en lugar de unpaid.retryIntervalsDays=[3,5,7], maxRetries=3,
dunningFinalPolicy=mark_unpaid), cobro inicial fallando el 01/jun:
| Intento | attemptNumber | retryCountSoFar | Fecha | Resultado |
|---|---|---|---|---|
| Inicial | 1 | 0 | 01/jun | Fallo → past_due, programa retry +3d |
| Retry 1 | 2 | 1 | 04/jun | Fallo → programa retry +5d |
| Retry 2 | 3 | 2 | 09/jun | Fallo → programa retry +7d |
| Retry 3 | 4 | 3 | 16/jun | Fallo → retryCountSoFar (3) >= maxRetries (3): agotado → suscripción pasa a unpaid |
incomplete, trialing, active, past_due,
unpaid, paused, canceled, completed) consulta
Suscripciones. Para los códigos de error de llamadas a la API
consulta Errores.
Ver también
Suscripciones
Crear y gestionar suscripciones — request completo con
recurrence, trialSpec,
collectionTiming y demás campos.Planes y precios
De dónde se hereda la
recurrence cuando la suscripción está basada en un plan.Facturas
Ciclo de vida de cada factura —
open, past_due, paid y los campos chargeAt/dueAt.Visión general de suscripciones
Conceptos del módulo de recurrencia de Z2Pay.

