Composable Ecto.Query fragments mirroring the
Accrue.Billing.Subscription predicates.
Every predicate in Accrue.Billing.Subscription has a matching query
fragment here so you can filter subscriptions in the database with the
same semantics as the in-memory predicates. Prefer these fragments over
direct .status comparisons in where clauses — the predicates on
Accrue.Billing.Subscription are the correct way to check subscription
state, as direct comparisons miss edge cases like cancel_at_period_end
and ended_at that the predicates cover.
All functions accept an optional queryable (default
Accrue.Billing.Subscription) and compose via |>:
import Ecto.Query
from(s in Subscription, where: s.customer_id == ^id)
|> Accrue.Billing.Query.active()
|> Repo.all()
Summary
Functions
Subscriptions counted as active (includes :trialing).
Subscriptions that are terminated (:canceled, :incomplete_expired, or any ended_at).
Subscriptions that are :active with cancel_at_period_end set and a
period end still in the future — i.e. the cancel hasn't landed yet.
Subscriptions eligible for a dunning sweep tick: strictly
:past_due, with past_due_since older than the grace window, and
with no prior dunning_sweep_attempted_at stamp.
Subscriptions whose lifecycle grants entitlement: active/trialing, not paused, not ended.
Entitlement candidates including :past_due rows for the grace overlay.
Subscriptions currently in an active dunning campaign (anchor column is non-nil). Composable — pipe after any Subscription query to add the campaign-active predicate.
Subscriptions that are past due or unpaid (dunning territory).
Subscriptions that are paused (legacy :paused status or non-nil pause_collection).
Subscriptions currently in trial.
Functions
@spec active(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions counted as active (includes :trialing).
@spec canceled(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions that are terminated (:canceled, :incomplete_expired, or any ended_at).
@spec canceling(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions that are :active with cancel_at_period_end set and a
period end still in the future — i.e. the cancel hasn't landed yet.
@spec dunning_sweep_candidates(pos_integer(), Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions eligible for a dunning sweep tick: strictly
:past_due, with past_due_since older than the grace window, and
with no prior dunning_sweep_attempted_at stamp.
@spec entitling(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions whose lifecycle grants entitlement: active/trialing, not paused, not ended.
The database twin of Accrue.Billing.Subscription.entitling?/1 — the
rows this fragment returns are exactly those for which entitling?/1
is true. Beyond active/1's status set it adds is_nil(s.pause_collection)
(the SQL twin of paused?/1's non-nil pause_collection head, closing
the status: :active + pause_collection fail-open gap) and
is_nil(s.ended_at) (the SQL twin of canceled?/1's terminal ended_at
override). It deliberately does NOT add the legacy :paused status
OR-clause that paused/1 carries, because active/1's status set
already excludes :paused.
Distinct from active/1, which keeps its status-only semantics for
other callers (e.g. the dunning sweeper and projections).
@spec entitling_with_grace_candidates(Ecto.Queryable.t()) :: Ecto.Query.t()
Entitlement candidates including :past_due rows for the grace overlay.
The grace-widen twin of entitling/1: it adds :past_due (and ONLY
:past_due — never :unpaid, which is dunning-terminal) to the status
set while keeping both is_nil(s.pause_collection) and
is_nil(s.ended_at) guards. This widens the read just enough to surface
candidate :past_due rows; the per-row grace-window cutoff check stays in
Elixir (Accrue.Entitlements.PastDueGrace.within_grace?/2) because the
clock comparison must be test-driven, so this fragment deliberately does
NOT do any cutoff math.
Used only by Accrue.Entitlements.Resolver.LocalMap.fold_active/1 when
Accrue.Config.past_due_grace/0 is enabled; the :none default path uses
the leaner entitling/1 instead (zero query change).
@spec in_active_dunning_campaign(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions currently in an active dunning campaign (anchor column is non-nil). Composable — pipe after any Subscription query to add the campaign-active predicate.
@spec past_due(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions that are past due or unpaid (dunning territory).
@spec paused(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions that are paused (legacy :paused status or non-nil pause_collection).
@spec trialing(Ecto.Queryable.t()) :: Ecto.Query.t()
Subscriptions currently in trial.