LatticeStripe.Entitlements.ActiveEntitlement (LatticeStripe v2.0.0)

Copy Markdown View Source

A customer's currently-active entitlement to a feature.

Wire object entitlements.active_entitlement, ids prefixed ent_, served from the canonical list path /v1/entitlements/active_entitlements. One active entitlement means one customer currently has access to one feature, because they bought a Product that feature is attached to.

There is no entitled? helper — gate locally, fail closed

This module deliberately ships no entitled?-style per-request predicate, and one will not be added. An authorization check that makes a network call fails open under network partition: the call times out, the caller has no answer, and the pragmatic fallback is to let the request through — granting a customer access to something they did not buy.

Do this instead. Reconcile a customer's entitlements with stream!/3 (or list/3 when you genuinely only want one page), persist the result to a local store keyed on lookup_key, and gate against that local store on every request. When the local store is stale beyond your freshness budget, fail closed — deny access and re-reconcile — rather than reaching for Stripe on the authorization path. A local gate is fast, is available when Stripe is not, and has a failure mode you choose rather than one the network chooses for you.

See Entitlements for the end-to-end story.

Listing a customer's entitlements

The read surface is list/3 (one page), stream!/3 (every page, lazily), and retrieve/3 (one entitlement by id), each with the usual bang twin where one applies.

customer is a required filter — Stripe has no account-wide active-entitlement list, and both list/3 and stream!/3 raise ArgumentError before any network call if it is missing. That pre-network guard checks presence, not emptiness: a customer key whose value is "" or nil passes the guard and fails at Stripe instead.

Stripe's limit defaults to 10 and maxes at 100, so a single list/3 call silently returns a partial set for any customer with more than ten active entitlements — and a truncated set makes a paying customer look unentitled. stream!/3 is therefore the reconciler's entry point: it follows has_more across every page and raises rather than quietly returning a short list.

Each entitlement carries its own lookup_key, mirroring the feature's. That is the field a local gate keys on, and it is present without expanding feature — so the common reconciliation read needs no expand param at all.

Relationship to other feature surfaces

The feature field decodes to a LatticeStripe.Entitlements.Feature — the entitlement feature definition (wire object entitlements.feature, ids prefixed feat_) — when Stripe expands it, and stays the bare feat_ id string when it does not. It is not LatticeStripe.Product.Feature, which is the product attachment (wire object product_feature, ids prefixed prodft_).

Usage

{:ok, resp} =
  LatticeStripe.Entitlements.ActiveEntitlement.list(client, %{"customer" => "cus_123"})

keys = Enum.map(resp.data.data, & &1.lookup_key)

# Every page, not just the first:
keys =
  client
  |> LatticeStripe.Entitlements.ActiveEntitlement.stream!(%{"customer" => "cus_123"})
  |> Enum.map(& &1.lookup_key)

Summary

Functions

Decode a Stripe-shaped string-keyed map into an %ActiveEntitlement{}.

List a customer's active entitlements.

Retrieve a single active entitlement by id.

Returns a lazy stream of all of a customer's active entitlements (auto-pagination).

Types

t()

@type t() :: %LatticeStripe.Entitlements.ActiveEntitlement{
  extra: map(),
  feature: LatticeStripe.Entitlements.Feature.t() | String.t() | nil,
  id: String.t() | nil,
  livemode: boolean() | nil,
  lookup_key: String.t() | nil,
  object: String.t() | nil
}

Functions

from_map(entitlement)

@spec from_map(map() | t() | nil) :: t() | nil

Decode a Stripe-shaped string-keyed map into an %ActiveEntitlement{}.

The expandable feature field becomes a LatticeStripe.Entitlements.Feature when Stripe expanded it, and passes through unchanged (the bare feat_ id string) when it did not.

Idempotent: applied to an already-decoded struct it returns it unchanged, and from_map(nil) returns nil. Unknown top-level keys land in :extra.

list(client, params \\ %{}, opts \\ [])

@spec list(LatticeStripe.Client.t(), map(), keyword()) ::
  {:ok, LatticeStripe.Response.t()} | {:error, LatticeStripe.Error.t()}

List a customer's active entitlements.

params must contain "customer". The guard raises ArgumentError before any network call and checks key presence, not value emptiness.

Supports Stripe's limit (default 10, max 100), starting_after, ending_before, and expand (for example ["data.feature"]).

list!(client, params \\ %{}, opts \\ [])

Bang variant of list/3. Raises LatticeStripe.Error on failure.

retrieve(client, id, opts \\ [])

@spec retrieve(LatticeStripe.Client.t(), String.t(), keyword()) ::
  {:ok, t()} | {:error, LatticeStripe.Error.t()}

Retrieve a single active entitlement by id.

The id is the ent_-prefixed identifier from a list/3 or stream!/3 result, or from an entitlements.active_entitlement_summary webhook payload.

retrieve!(client, id, opts \\ [])

@spec retrieve!(LatticeStripe.Client.t(), String.t(), keyword()) :: t()

Bang variant of retrieve/3. Raises LatticeStripe.Error on failure.

stream!(client, params \\ %{}, opts \\ [])

@spec stream!(LatticeStripe.Client.t(), map(), keyword()) :: Enumerable.t()

Returns a lazy stream of all of a customer's active entitlements (auto-pagination).

Emits individual %ActiveEntitlement{} structs, following has_more and fetching each subsequent page as the stream is consumed. Raises LatticeStripe.Error if any page fetch fails, so a partial enumeration surfaces as an error rather than as a short list.

params must contain "customer", and the guard raises ArgumentError at call time — before the stream is stepped — so the failure lands at the call site rather than at the first Enum step.

Consume it with Enum.to_list/1 when you intend to hold every entitlement in memory, or bound it with Stream.take/2 when you do not:

client
|> LatticeStripe.Entitlements.ActiveEntitlement.stream!(%{"customer" => "cus_123"})
|> Stream.take(50)
|> Enum.to_list()

There is no non-bang stream/3 twin — a lazy stream cannot return an error tuple at construction time for a failure that happens pages later.