EDA.Subscription (EDA v0.5.0-beta.2)

Copy Markdown View Source

Represents a Discord subscription — a user's recurring access to one or more SKUs.

Do not hardcode the status integers

Discord renumbered this enum on 2026-06-16: INACTIVE and ENDING swapped places. Code that compared status == 1 kept running and started meaning the opposite thing. Use status/1, which returns :active, :inactive or :ending.

Status is not a permission

A subscription's status says what will happen at the end of the period, not what the user may do right now. Discord is explicit that a subscription "can be ACTIVE outside its current period or INACTIVE within its current period" — a failed payment keeps it active while it retries, and a chargeback makes it inactive mid-period.

Entitlements decide access, not this. Read EDA.API.Entitlement for that question and treat the status as billing information.

Summary

Types

A subscription status name.

t()

Functions

Returns true if the subscription has been cancelled.

Adds a change to an existing changeset for this entity.

Creates a changeset for batching mutations to this entity.

Returns true if the SKUs will change at renewal.

Fetches one subscription for a SKU. Returns a t/0.

Lists a SKU's subscriptions as structs.

Returns true if the subscription will renew at the end of the current period.

Names the subscription's status.

Returns the integer Discord uses for a status name, or nil for an unknown name.

Types

status()

@type status() :: :active | :inactive | :ending | :unknown

A subscription status name.

t()

@type t() :: %EDA.Subscription{
  canceled_at: String.t() | nil,
  country: String.t() | nil,
  current_period_end: String.t() | nil,
  current_period_start: String.t() | nil,
  entitlement_ids: [String.t()] | nil,
  id: String.t() | nil,
  renewal_sku_ids: [String.t()] | nil,
  sku_ids: [String.t()] | nil,
  status: integer() | nil,
  user_id: String.t() | nil
}

Functions

canceled?(arg1)

@spec canceled?(t() | map()) :: boolean()

Returns true if the subscription has been cancelled.

Reads canceled_at, which is set as soon as the user cancels — the subscription is then :ending until the period runs out, and :inactive after.

Examples

iex> EDA.Subscription.canceled?(%EDA.Subscription{canceled_at: "2026-09-19T12:00:00Z"})
true

iex> EDA.Subscription.canceled?(%EDA.Subscription{})
false

change(cs, key, value)

Adds a change to an existing changeset for this entity.

changeset(entity)

@spec changeset(t()) :: EDA.Entity.Changeset.t()

Creates a changeset for batching mutations to this entity.

changing_plan?(raw)

@spec changing_plan?(t() | map()) :: boolean()

Returns true if the SKUs will change at renewal.

renewal_sku_ids is nil while nothing is changing, and carries the new list once the user has switched plan — an upgrade or downgrade that has not taken effect yet.

Examples

iex> EDA.Subscription.changing_plan?(%EDA.Subscription{sku_ids: ["1"], renewal_sku_ids: ["2"]})
true

iex> EDA.Subscription.changing_plan?(%EDA.Subscription{sku_ids: ["1"], renewal_sku_ids: nil})
false

fetch_subscription(sku_id, subscription_id)

@spec fetch_subscription(String.t() | integer(), String.t() | integer()) ::
  {:ok, t()} | {:error, term()}

Fetches one subscription for a SKU. Returns a t/0.

Named fetch_subscription/2 rather than fetch/2 because Access.fetch/2 owns that arity.

from_raw(raw)

@spec from_raw(map()) :: t()

list(sku_id, opts \\ [])

@spec list(
  String.t() | integer(),
  keyword()
) :: {:ok, [t()]} | {:error, term()}

Lists a SKU's subscriptions as structs.

Takes the same options as EDA.API.Subscription.list/2 — :user_id, :before, :after, :limit.

renewing?(subscription)

@spec renewing?(t() | map() | integer() | nil) :: boolean()

Returns true if the subscription will renew at the end of the current period.

Only :active renews. :ending is still running but will stop, which is the distinction worth acting on — it is the state to send a "your subscription ends on …" message about.

Examples

iex> EDA.Subscription.renewing?(%EDA.Subscription{status: 0})
true

iex> EDA.Subscription.renewing?(%EDA.Subscription{status: 2})
false

status(value)

@spec status(t() | map() | integer() | nil) :: status()

Names the subscription's status.

  • :active — running and scheduled to renew
  • :inactive — not running and not being charged
  • :ending — still running, but it will not renew

An integer Discord has since added comes back as :unknown rather than crashing a case.

Examples

iex> EDA.Subscription.status(%EDA.Subscription{status: 0})
:active

iex> EDA.Subscription.status(%{"status" => 2})
:ending

iex> EDA.Subscription.status(%EDA.Subscription{status: 99})
:unknown

iex> EDA.Subscription.status(nil)
:unknown

status_value(name)

@spec status_value(status()) :: integer() | nil

Returns the integer Discord uses for a status name, or nil for an unknown name.

Useful for filtering a list without writing the numbers down.

Examples

iex> EDA.Subscription.status_value(:ending)
2

iex> EDA.Subscription.status_value(:nonsense)
nil