Ecto schema for the accrue_subscriptions table.
Stores the local projection of a Stripe subscription. :status is an
Ecto.Enum over the full Stripe subscription status set, and the schema
includes cancel_at_period_end, pause_collection, and other lifecycle
fields needed to answer all common billing questions locally.
Use the predicates, not raw .status
Do not gate business logic on direct comparisons to .status. Raw
status checks are easy to get wrong — for example, a subscription with
cancel_at_period_end: true still has status :active, and an
:incomplete_expired subscription is just as terminated as a
:canceled one.
The predicates in this module capture those edge cases correctly:
trialing?/1active?/1— includes:trialingpast_due?/1—:past_dueor:unpaidcanceled?/1—:canceled,:incomplete_expired, or anyended_atcanceling?/1—:active+cancel_at_period_end+ future period endpaused?/1— legacy:pausedstatus OR non-nilpause_collection
For database queries over multiple subscriptions, the matching fragments
are in Accrue.Billing.Query.
Summary
Functions
True if the subscription counts as "active" for entitlement purposes.
True if the subscription has terminated.
True if the subscription is :active with cancel_at_period_end set and
the current period end is still in the future (cancel_at_period_end cancel
hasn't taken effect yet).
Builds a changeset for creating or updating a Subscription.
Returns the dunning-terminal status atom (:unpaid or :canceled)
if the subscription has reached a dunning-exhaustion state. Returns
nil otherwise.
True if the subscription is in the narrow :past_due retry window
where the dunning sweeper is allowed to ask the processor facade
to move it to a terminal action.
Webhook-path changeset. Skips user-path validation guards so out-of-order webhook events can settle arbitrary state without the state-machine check failing on an otherwise-valid transition.
True if the subscription is past due or unpaid (dunning territory).
True if the subscription is paused.
Extracts a pre-hydrated PaymentIntent from data.latest_invoice.payment_intent,
used by subscribe/3 to surface SCA/3DS action-required to the caller.
Canonical list of subscription statuses (Stripe's 8 values).
True if the subscription is currently in a trial.
Types
@type t() :: %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() }
Functions
@spec active?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription counts as "active" for entitlement purposes.
Includes :trialing — a customer in a trial period has full access.
@spec canceled?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription has terminated.
:canceled, :incomplete_expired, or any row with a non-nil ended_at.
@spec canceling?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription is :active with cancel_at_period_end set and
the current period end is still in the future (cancel_at_period_end cancel
hasn't taken effect yet).
@spec changeset( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Builds a changeset for creating or updating a Subscription.
@spec dunning_exhausted_status( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: :unpaid | :canceled | nil
Returns the dunning-terminal status atom (:unpaid or :canceled)
if the subscription has reached a dunning-exhaustion state. Returns
nil otherwise.
Used by the customer.subscription.updated webhook reducer to
detect terminal transitions without raw .status access.
@spec dunning_sweepable?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription is in the narrow :past_due retry window
where the dunning sweeper is allowed to ask the processor facade
to move it to a terminal action.
Strictly :past_due — does NOT include :unpaid. An :unpaid
subscription has already reached its terminal state (whether via
Stripe-native termination or a prior sweep) and must not be swept
again.
@spec force_status_changeset( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Webhook-path changeset. Skips user-path validation guards so out-of-order webhook events can settle arbitrary state without the state-machine check failing on an otherwise-valid transition.
@spec past_due?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription is past due or unpaid (dunning territory).
@spec paused?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription is paused.
Covers both the legacy :paused status (used by earlier Stripe API
versions) and the modern pause_collection map returned by current
Stripe versions.
@spec pending_intent( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: map() | nil
Extracts a pre-hydrated PaymentIntent from data.latest_invoice.payment_intent,
used by subscribe/3 to surface SCA/3DS action-required to the caller.
@spec statuses() :: [atom()]
Canonical list of subscription statuses (Stripe's 8 values).
@spec trialing?( %Accrue.Billing.Subscription{ __meta__: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), cancel_at: term(), cancel_at_period_end: term(), canceled_at: term(), current_period_end: term(), current_period_start: term(), customer: term(), customer_id: term(), data: term(), discount_id: term(), dunning_sweep_attempted_at: term(), ended_at: term(), id: term(), inserted_at: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), past_due_since: term(), pause_behavior: term(), pause_collection: term(), paused_at: term(), processor: term(), processor_id: term(), status: term(), subscription_items: term(), trial_end: term(), trial_start: term(), updated_at: term() } | map() ) :: boolean()
True if the subscription is currently in a trial.