Ecto schema for the accrue_subscriptions table.
Stores the local projection of a processor subscription (e.g. Stripe
sub_xxx). Phase 3 upgrades :status to an Ecto.Enum over the
canonical Stripe subscription status set (BILL-05, D3-03) and adds
the cancel-at-period-end + pause_collection fields needed for the
full lifecycle state machine.
Predicates (BILL-05)
Never gate on raw .status access. The predicates defined in this
module are the canonical way to ask "is this subscription X?" — raw
access is lint-time forbidden by Accrue.Credo.NoRawStatusAccess.
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
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 D4-02 dunning sweeper is allowed to ask the processor
facade to move it to a terminal action.
Webhook-path changeset (D3-17). 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 Plan 04 subscribe/3 to surface SCA/3DS action-required to the caller.
Canonical list of subscription statuses (D3-03, 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 per D3-03.
@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 (BILL-15,
D4-02) 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 D4-02 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 (BILL-15).
@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 (D3-17). 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 versions)
and the modern pause_collection map (D3-03).
@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 Plan 04 subscribe/3 to surface SCA/3DS action-required to the caller.
@spec statuses() :: [atom()]
Canonical list of subscription statuses (D3-03, 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.