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

Operations on Stripe Subscription objects.

A Subscription represents a customer's recurring charge against one or more
Prices. Subscriptions drive Stripe's billing engine: they create Invoices on
schedule, handle proration when items change, and transition through a
well-defined lifecycle.

## Lifecycle

```
incomplete
     |
     v
incomplete_expired        (if first payment fails permanently)
     |
trialing --> active --> past_due --> unpaid
               |           |
             paused       canceled
```

State transitions are driven by Stripe's internal billing engine, not by
SDK calls. **Always drive your application state from webhook events**, not
from SDK responses — an SDK response reflects the state at the moment of the
call, but Stripe may transition the subscription a moment later (trial
ending, payment failing, dunning retries, scheduled cancellation, etc.).

Wire `customer.subscription.updated`, `customer.subscription.deleted`,
`invoice.payment_failed`, and `invoice.payment_succeeded` into your webhook
handler via `LatticeStripe.Webhook`.

## Proration

When changing a subscription's items (swapping a price, changing quantity,
adding/removing items), Stripe prorates charges by default. If your client
was configured with `require_explicit_proration: true`, you MUST pass
`"proration_behavior"` either at the top level of `params`, inside
`"subscription_details"`, or inside any element of the `"items"` array.

Valid values: `"create_prorations"` (default), `"always_invoice"`, `"none"`.

## Pause collection

Use `pause_collection/5` to temporarily pause automatic invoice collection.
The `behavior` atom is guarded at the function head — only `:keep_as_draft`,
`:mark_uncollectible`, and `:void` are accepted. Any other value raises
`FunctionClauseError` at compile-time (for literals) or runtime.

## Telemetry

Subscription CRUD piggybacks on the general `[:lattice_stripe, :request, *]`
events emitted by `Client.request/2`. No subscription-specific telemetry
events are emitted — subscription state transitions belong to webhook
handlers, not the SDK layer.

## Stripe API Reference

See the [Stripe Subscriptions API](https://docs.stripe.com/api/subscriptions)
for the full object reference and available parameters.

# `t`

```elixir
@type t() :: %LatticeStripe.Subscription{
  application: String.t() | nil,
  application_fee_percent: number() | nil,
  automatic_tax: LatticeStripe.Invoice.AutomaticTax.t() | nil,
  billing_cycle_anchor: integer() | nil,
  billing_thresholds: map() | nil,
  cancel_at: integer() | nil,
  cancel_at_period_end: boolean() | nil,
  canceled_at: integer() | nil,
  cancellation_details:
    LatticeStripe.Subscription.CancellationDetails.t() | nil,
  collection_method: String.t() | nil,
  created: integer() | nil,
  currency: String.t() | nil,
  current_period_end: integer() | nil,
  current_period_start: integer() | nil,
  customer: String.t() | nil,
  days_until_due: integer() | nil,
  default_payment_method: String.t() | nil,
  default_source: String.t() | nil,
  default_tax_rates: list() | nil,
  description: String.t() | nil,
  discount: map() | nil,
  discounts: list() | nil,
  ended_at: integer() | nil,
  extra: map(),
  id: String.t() | nil,
  invoice_settings: map() | nil,
  items: [LatticeStripe.SubscriptionItem.t()] | map() | nil,
  latest_invoice: String.t() | nil,
  livemode: boolean() | nil,
  metadata: map() | nil,
  next_pending_invoice_item_interval: map() | nil,
  object: String.t(),
  on_behalf_of: String.t() | nil,
  pause_collection: LatticeStripe.Subscription.PauseCollection.t() | nil,
  payment_settings: map() | nil,
  pending_invoice_item_interval: map() | nil,
  pending_setup_intent: String.t() | nil,
  pending_update: map() | nil,
  plan: map() | nil,
  quantity: integer() | nil,
  schedule: String.t() | nil,
  start_date: integer() | nil,
  status: String.t() | nil,
  test_clock: String.t() | nil,
  transfer_data: map() | nil,
  trial_end: integer() | nil,
  trial_settings: LatticeStripe.Subscription.TrialSettings.t() | nil,
  trial_start: integer() | nil
}
```

A Stripe Subscription object.

See the [Stripe Subscription API](https://docs.stripe.com/api/subscriptions/object)
for field definitions.

# `cancel`

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

Cancels a Subscription.

Sends `DELETE /v1/subscriptions/:id` with optional pass-through params such
as `"prorate"`, `"invoice_now"`, and `"cancellation_details"`.

The 3-arity form is a convenience for `cancel(client, id, %{}, opts)`.

# `cancel`

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

# `cancel!`

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

Like `cancel/3` but raises on failure.

# `cancel!`

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

Like `cancel/4` but raises on failure.

# `create`

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

Creates a new Subscription.

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

## Parameters

- `client` - `%LatticeStripe.Client{}`
- `params` - Map of subscription attributes. Common keys:
  - `"customer"` - Customer ID (required)
  - `"items"` - List of `%{"price" => "price_..."}` maps
- `opts` - Per-request overrides (e.g., `[idempotency_key: "..."]`)

## Returns

- `{:ok, %Subscription{}}` 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.

# `from_map`

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

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

Decodes nested typed structs:
- `automatic_tax` → `%LatticeStripe.Invoice.AutomaticTax{}`
- `pause_collection` → `%LatticeStripe.Subscription.PauseCollection{}`
- `cancellation_details` → `%LatticeStripe.Subscription.CancellationDetails{}`
- `trial_settings` → `%LatticeStripe.Subscription.TrialSettings{}`
- `items.data` → `[%LatticeStripe.SubscriptionItem{}]` (id preserved — regression guard against stripity_stripe #208)

Unknown top-level fields are collected into `:extra`.

# `list`

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

Lists Subscriptions with optional filters.

Sends `GET /v1/subscriptions`.

# `list!`

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

Like `list/3` but raises on failure.

# `pause_collection`

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

Pauses collection on a Subscription.

Dispatches to `update/4` with `"pause_collection"` merged into params. The
`behavior` is a compile-time atom — only `:keep_as_draft`,
`:mark_uncollectible`, and `:void` are accepted. Any other atom raises
`FunctionClauseError`.

## Example

    Subscription.pause_collection(client, "sub_123", :keep_as_draft)

    # With custom resumes_at:
    Subscription.pause_collection(client, "sub_123", :void, %{
      "pause_collection" => %{"resumes_at" => 1_800_000_000}
    })

Note: Stripe has no dedicated pause endpoint — this helper is a thin wrapper
around `update/4`. We expose it under the exact field name (`pause_collection`)
rather than a generic `pause/4` so IDE autocomplete and Stripe docs align.

# `pause_collection!`

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

Like `pause_collection/5` but raises on failure.

# `resume`

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

Resumes a paused Subscription.

Sends `POST /v1/subscriptions/:id/resume`.

# `resume!`

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

Like `resume/3` but raises on failure.

# `retrieve`

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

Retrieves a Subscription by ID.

Sends `GET /v1/subscriptions/:id`.

# `retrieve!`

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

Like `retrieve/3` but raises on failure.

# `search`

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

Searches Subscriptions.

Sends `GET /v1/subscriptions/search`. Requires `"query"` in params.

> #### Eventual Consistency {: .warning}
>
> Search results may not reflect changes made within the last ~1 second.

# `search!`

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

Like `search/3` but raises on failure.

# `search_stream!`

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

Returns a lazy stream of all Subscriptions matching a search query.

Requires `"query"` in params. Emits individual `%Subscription{}` structs via
auto-pagination; raises `LatticeStripe.Error` if any page fetch fails
mid-stream.

The `!` suffix here does not pair with a tuple-returning `search_stream/3`
counterpart — there is none. Elixir Streams are inherently eager across
pages and cannot return `{:ok, _} | {:error, _}` for mid-stream failures,
so the only sensible behavior is to raise. This matches the convention used
by `LatticeStripe.Invoice.search_stream!/3` and
`LatticeStripe.Checkout.Session.search_stream!/3`.

> #### Eventual Consistency {: .warning}
>
> Search results may not reflect changes made within the last ~1 second.

# `stream!`

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

Returns a lazy stream of all Subscriptions matching the given params.

Auto-paginates via `LatticeStripe.List.stream!/2`. Raises on fetch failure.

# `update`

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

Updates a Subscription by ID.

Sends `POST /v1/subscriptions/:id`. 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*
