# `LatticeStripe.SubscriptionItem`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v1.1.0/lib/lattice_stripe/subscription_item.ex#L1)

Operations on Stripe Subscription Item objects.

A SubscriptionItem is a single plan/price line on a Subscription. Manipulating
items lets you add products, change quantities, and swap prices on a live
subscription without re-creating it.

This module is at the flat top-level namespace (`LatticeStripe.SubscriptionItem`)
per Phase 1 D-17, matching `LatticeStripe.Customer`, `LatticeStripe.Invoice`,
and friends.

## Listing requires a subscription

`list/3` and `stream!/3` require the `"subscription"` param — unfiltered
SubscriptionItem listing is an antipattern and produces confusing results
(it returns items across all subscriptions, which is rarely what you want).
Passing `%{}` raises `ArgumentError` immediately.

## Usage-based billing note

Legacy Usage Records (`/v1/subscription_items/:id/usage_records`) are being
deprecated by Stripe in favor of the Billing Meters API (`/v1/billing/meters`,
`/v1/billing/meter_events`). Meters are not yet wired into LatticeStripe —
see roadmap BILL-07. For new integrations, use Billing Meters directly via
raw HTTP until that phase ships.

## Proration

All mutations (`create/3`, `update/4`, `delete/3`/`delete/4`) run the
`Billing.Guards.check_proration_required/2` guard before dispatching. If
your client sets `require_explicit_proration: true`, you MUST pass
`"proration_behavior"` in the params.

## Stripe API Reference

See the [Stripe Subscription Items API](https://docs.stripe.com/api/subscription_items).

# `t`

```elixir
@type t() :: %LatticeStripe.SubscriptionItem{
  billing_thresholds: map() | nil,
  created: integer() | nil,
  discounts: list() | nil,
  extra: map(),
  id: String.t() | nil,
  metadata: map() | nil,
  object: String.t(),
  plan: map() | nil,
  price: map() | nil,
  proration_behavior: String.t() | nil,
  quantity: integer() | nil,
  subscription: String.t() | nil,
  tax_rates: list() | nil
}
```

A Stripe Subscription Item object.

# `create`

```elixir
@spec create(LatticeStripe.Client.t(), map(), keyword()) ::
  {:ok, t()} | {:error, LatticeStripe.Error.t()}
```

Creates a new SubscriptionItem.

Sends `POST /v1/subscription_items`. Runs the proration guard before dispatching.

## Parameters

- `client` - `%LatticeStripe.Client{}`
- `params` - Map with at least `"subscription"` and `"price"`. Common keys:
  `"subscription"`, `"price"`, `"quantity"`, `"proration_behavior"`
- `opts` - Per-request overrides (e.g., `[idempotency_key: "..."]`)

## Returns

- `{:ok, %SubscriptionItem{}}` on success
- `{:error, %LatticeStripe.Error{}}` on failure or guard rejection

# `create!`

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

Like `create/3` but raises on failure.

# `delete`

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

Deletes a SubscriptionItem.

Sends `DELETE /v1/subscription_items/:id` with optional params such as
`"clear_usage"` and `"proration_behavior"`. Runs the proration guard before
dispatching.

The 3-arity form delegates to 4-arity with empty params.

# `delete`

```elixir
@spec delete(LatticeStripe.Client.t(), String.t(), map(), keyword()) ::
  {:ok, t()} | {:error, LatticeStripe.Error.t()}
```

# `delete!`

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

Like `delete/3` but raises on failure.

# `delete!`

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

Like `delete/4` but raises on failure.

# `from_map`

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

Converts a decoded Stripe API map to a `%SubscriptionItem{}` struct.

Returns `nil` when given `nil`.

**`id` is always preserved.** stripity_stripe had a well-known bug where
nested SubscriptionItems inside Subscription responses lost their id
(see issue #208), making programmatic updates impossible. This decoder
covers that case explicitly via the round-trip unit tests.

# `list`

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

Lists SubscriptionItems filtered by subscription.

**Requires** `"subscription"` in params. Raises `ArgumentError` otherwise
(OQ-2: unfiltered listing is an antipattern).

# `list!`

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

Like `list/3` but raises on failure.

# `retrieve`

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

Retrieves a SubscriptionItem by ID.

# `retrieve!`

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

Like `retrieve/3` but raises on failure.

# `stream!`

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

Lazy stream of SubscriptionItems for a given subscription.

**Requires** `"subscription"` in params.

# `update`

```elixir
@spec update(LatticeStripe.Client.t(), String.t(), map(), keyword()) ::
  {:ok, t()} | {:error, LatticeStripe.Error.t()}
```

Updates a SubscriptionItem.

Runs the proration guard before dispatching.

# `update!`

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

Like `update/4` but raises on failure.

---

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