OneShot

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

3 of 3 runs passedOne decision inside SaaS Billing + Tax Core (nextjs-prisma-postgres).

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

Coming soon — $149Full receipt and details for SaaS Billing + Tax Core

Related problems