PayFanout API reference
    Preparing search index...

    Class SubscriptionManager

    Index
    • Immediate cancel stops everything now (the rest of the paid window is forfeit — no refund is initiated; use refundPayment separately if owed). atPeriodEnd lets the paid window run out, then ends without charging.

      Parameters

      • id: string
      • options: { atPeriodEnd?: boolean } = {}

      Returns Promise<SubscriptionRecord>

    • THE cron entry point — run it every few minutes/hours from the host's scheduler. Idempotent and crash-safe: renewal idempotency keys are derived from (subscription id, period, attempt), so a crashed run that re-charges after the store missed an update dedupes at the PSP.

      Prefers store.listDue (host-indexed batches) when implemented, falling back to per-status list() scans (active, trialing, past_due); either way every candidate's due-ness is re-checked here before any charge.

      Concurrent runs are safe for MONEY (charges converge at the PSP) but not for events (at-least-once, see onEvent) — hosts wanting single-run semantics should hold a lock around this call.

      Parameters

      • Optionalat: string | Date

      Returns Promise<ChargeDueResult>

    • Halts billing without ending the subscription: the cron skips paused records and dunning stops (nextRetryAt is cleared; failedAttempts and any pendingRenewal survive untouched — an unresolved renewal still resolves via resolvePendingRenewal, but a paused record is never re-charged). Pausing a paused record is a no-op; canceled records cannot pause.

      Parameters

      • id: string

      Returns Promise<SubscriptionRecord>

    • Applies the final outcome of a renewal charge that resolved as "processing" (async rails). Wire it to the host's payment webhook ingress (payment.succeeded / payment.failed with a matching pspPaymentId) — until it runs, the subscription is frozen: chargeDueSubscriptions never charges on top of an unresolved renewal. Replay-safe: re-resolving an already-applied success is a no-op. Resolving never reactivates: a record paused (or canceled) in the meantime keeps its status — a success still advances the paid-through window (the money moved), a failure is recorded without entering dunning.

      Parameters

      • id: string
      • outcome: {
            error?: { code?: UnifiedErrorCode; message?: string };
            pspPaymentId?: string;
            status: "succeeded" | "failed";
        }
        • Optionalerror?: { code?: UnifiedErrorCode; message?: string }
        • OptionalpspPaymentId?: string

          From the webhook — guards against applying a different payment's outcome.

        • status: "succeeded" | "failed"

      Returns Promise<SubscriptionRecord>

    • Reactivates a paused subscription. Still paid through -> just "active", nothing is charged. Lapsed -> ONE immediate charge (occurrence "recurring", the caller's idempotencyKey — retry a storage failure with the SAME key so the PSP replays instead of re-charging) re-anchors the billing cycle at the resume instant. A failed charge leaves the record paused with lastError — no dunning — and throws. A charge resolving as "processing" freezes the still-paused record under pendingRenewal: resolve it, then resume again (paid through by then, so no second charge).

      Parameters

      • id: string
      • options: { idempotencyKey: string }

      Returns Promise<SubscriptionRecord>

    • Plan/instrument changes apply from the NEXT period (no proration — the already-paid window stands). Changing the token also clears dunning: a fresh card deserves a fresh chance at the next renewal. Emits "subscription.updated" (renewals never do).

      Parameters

      • id: string
      • updates: {
            metadata?: Record<string, string>;
            plan?: SubscriptionPlan;
            savedPaymentMethodToken?: string;
        }

      Returns Promise<SubscriptionRecord>