LatticeStripe.Subscription (LatticeStripe v1.1.0)

Copy Markdown View Source

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 for the full object reference and available parameters.

Summary

Types

t()

A Stripe Subscription object.

Functions

Cancels a Subscription.

Like cancel/3 but raises on failure.

Like cancel/4 but raises on failure.

Creates a new Subscription.

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

Lists Subscriptions with optional filters.

Like list/3 but raises on failure.

Resumes a paused Subscription.

Like resume/3 but raises on failure.

Retrieves a Subscription by ID.

Like retrieve/3 but raises on failure.

Searches Subscriptions.

Like search/3 but raises on failure.

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

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

Updates a Subscription by ID.

Types

t()

@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 for field definitions.

Functions

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

@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(client, id, params, opts)

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

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

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

Like cancel/3 but raises on failure.

cancel!(client, id, params, opts)

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

Like cancel/4 but raises on failure.

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

@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!(client, params \\ %{}, opts \\ [])

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

Like create/3 but raises on failure.

from_map(map)

@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(client, params \\ %{}, opts \\ [])

@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!(client, params \\ %{}, opts \\ [])

Like list/3 but raises on failure.

pause_collection(client, id, behavior, params \\ %{}, opts \\ [])

@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!(client, id, behavior, params \\ %{}, opts \\ [])

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

Like pause_collection/5 but raises on failure.

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

@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!(client, id, opts \\ [])

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

Like resume/3 but raises on failure.

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

@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!(client, id, opts \\ [])

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

Like retrieve/3 but raises on failure.

search(client, params, opts \\ [])

@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

Search results may not reflect changes made within the last ~1 second.

search!(client, params, opts \\ [])

Like search/3 but raises on failure.

search_stream!(client, params, opts \\ [])

@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

Search results may not reflect changes made within the last ~1 second.

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

@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(client, id, params, opts \\ [])

@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!(client, id, params, opts \\ [])

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

Like update/4 but raises on failure.