# `LatticeStripe.Entitlements.ActiveEntitlement`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v2.2.2/lib/lattice_stripe/entitlements/active_entitlement.ex#L1)

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 {: .warning}
>
> 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](guides/entitlements.md) 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)

# `t`

```elixir
@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
}
```

# `from_map`

```elixir
@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`

```elixir
@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!`

```elixir
@spec list!(LatticeStripe.Client.t(), map(), keyword()) :: LatticeStripe.Response.t()
```

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

# `retrieve`

```elixir
@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!`

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

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

# `stream!`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
