All packsSaaS Billing + Tax CoreSolve
stripe dunning past due grace period access control
Access level as a computed function, not a cron job
A past-due account needs a grace window before losing access -- computing accessLevel() at request time from state and a timestamp removes the cron job entirely (D9).
What goes wrong
A payment failure shouldn't cut off access instantly -- most billing systems give a grace period before downgrading a past-due account to read-only or blocked. Building that as a scheduled job that walks every account and flips a status field adds a whole failure mode (the job doesn't run, runs twice, or runs against stale data) to something that's actually a pure function of two timestamps.
Why agents get it wrong
"Run a daily job to check for overdue accounts" is the first design that comes to mind for anything time-based, because that's how the same problem is usually solved outside of billing -- but it means the access decision now depends on whether a cron job fired recently, which is one more thing that can silently drift from the truth.
What SaaS Billing + Tax Core pre-decides
D9's accessLevel(state, pastDueSince, now) is a pure function: ACTIVE is full access; PAST_DUE is read-only while now is inside a 14-day grace window from pastDueSince, else blocked; everything else is blocked. Computing it at request time removes the cron job and its failure modes entirely -- Stripe's own Smart Retries (configured in the dashboard) handles the actual payment-recovery attempts, and the app never re-implements retry cadence.
Source: ARCHITECTURE.md D9