# `LatticeStripe.BillingPortal.Session`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v1.1.0/lib/lattice_stripe/billing_portal/session.ex#L1)

Operations on Stripe Billing Portal Session objects.

The Stripe Customer Portal is a hosted UI that lets customers manage their
subscriptions and billing details — update payment methods, cancel or change
subscriptions, download invoices, and update billing information. Your app
creates a portal session and redirects the customer to the returned URL;
Stripe handles the rest and redirects back to your `return_url` when the
customer is done.

## Creating a portal session

`create/3` is the only operation exposed by the Stripe API — portal sessions
cannot be retrieved, listed, updated, or deleted.

The `"customer"` param is required. All other params are optional:

- `"return_url"` — URL to redirect the customer after they are done in the portal.
  Should be an absolute HTTPS URL you control.
- `"flow_data"` — Deep-links the customer into a specific flow instead of the
  default portal homepage. See `LatticeStripe.BillingPortal.Session.FlowData`
  for the full schema and per-flow required sub-fields.
- `"configuration"` — A `bpc_*` Billing Portal configuration ID. In v1.1,
  portal configuration is managed via the Stripe Dashboard;
  `LatticeStripe.BillingPortal.Configuration` is planned for v1.2+.
- `"locale"` — Override the portal language (e.g. `"en"`, `"fr"`, `"auto"`).
- `"on_behalf_of"` — Connect account ID when creating a portal session for a
  connected account. See the `stripe_account:` opt for per-request Connect routing.

## Flow types (deep-link into a specific portal view)

The `"flow_data"` param accepts four `"type"` values:

- `"subscription_cancel"` — requires `flow_data.subscription_cancel.subscription`
- `"subscription_update"` — requires `flow_data.subscription_update.subscription`
- `"subscription_update_confirm"` — requires `flow_data.subscription_update_confirm.subscription`
  AND `flow_data.subscription_update_confirm.items` (non-empty list)
- `"payment_method_update"` — no required sub-fields

A pre-flight guard validates these shapes pre-network and raises
`ArgumentError` with an actionable message if required sub-fields are missing.
See `LatticeStripe.BillingPortal.Session.FlowData` for the full nested struct schema.

## Examples

    client = LatticeStripe.Client.new!(api_key: "sk_live_...", finch: MyApp.Finch)

    # Basic portal session (customer lands on default portal homepage)
    {:ok, session} = LatticeStripe.BillingPortal.Session.create(client, %{
      "customer" => "cus_123",
      "return_url" => "https://example.com/account"
    })
    redirect_to(conn, session.url)

    # Deep-link into subscription cancellation flow
    {:ok, session} = LatticeStripe.BillingPortal.Session.create(client, %{
      "customer" => "cus_123",
      "return_url" => "https://example.com/account",
      "flow_data" => %{
        "type" => "subscription_cancel",
        "subscription_cancel" => %{"subscription" => "sub_abc"}
      }
    })

    # Deep-link into payment method update flow
    {:ok, session} = LatticeStripe.BillingPortal.Session.create(client, %{
      "customer" => "cus_123",
      "flow_data" => %{"type" => "payment_method_update"}
    })

    # Connect platform: create a portal session on behalf of a connected account
    {:ok, session} = LatticeStripe.BillingPortal.Session.create(
      client,
      %{"customer" => "cus_123"},
      stripe_account: "acct_connect_123"
    )

## Security note — the `:url` field

`session.url` is a single-use, short-lived (~5 minutes) authenticated redirect
that grants the customer full access to their portal session. It is a bearer
credential for the portal scope — treat it like a password.

LatticeStripe masks `:url` (and `:flow`) from default `Inspect` output to prevent
accidental leaks via `Logger`, APM agents, crash dumps, or telemetry handlers.
Access the URL directly when redirecting: `session.url`.

To inspect all fields including `:url` and `:flow` during debugging:

    IO.inspect(session, structs: false)
    # or access directly:
    session.url
    session.flow

## Portal configuration

Portal configuration (branding, allowed features, default behavior) is managed
via the Stripe Dashboard in v1.1. `LatticeStripe.BillingPortal.Configuration`
is planned for v1.2+. Pass a `bpc_*` configuration ID in `params["configuration"]`
to select a specific portal configuration at session creation time.

## Stripe API Reference

See the [Stripe Billing Portal Session API](https://docs.stripe.com/api/customer_portal/sessions)
for the full object reference and available parameters.

# `t`

```elixir
@type t() :: %LatticeStripe.BillingPortal.Session{
  configuration: String.t() | nil,
  created: integer() | nil,
  customer: String.t() | nil,
  extra: map(),
  flow: LatticeStripe.BillingPortal.Session.FlowData.t() | nil,
  id: String.t() | nil,
  livemode: boolean() | nil,
  locale: String.t() | nil,
  object: String.t() | nil,
  on_behalf_of: String.t() | nil,
  return_url: String.t() | nil,
  url: String.t() | nil
}
```

# `create`

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

Create a Stripe Billing Portal Session.

Returns `{:ok, %Session{url: url}}` on success. The `url` is a single-use,
short-lived (~5 minutes) authenticated redirect — redirect the customer to it
immediately. Do not cache or log it.

## Required params

- `"customer"` — The Stripe customer ID (`cus_*`) whose portal session to create.

## Optional params

- `"return_url"` — Absolute HTTPS URL to redirect the customer after the portal session.
- `"flow_data"` — Deep-link into a specific flow. See module docs for flow type details.
  Omit to render the default portal homepage.
- `"configuration"` — Billing Portal configuration ID (`bpc_*`). Defaults to the
  account default configured in the Stripe Dashboard.
- `"locale"` — Portal language override (`"en"`, `"fr"`, `"auto"`, etc.).
- `"on_behalf_of"` — Connect account ID for platform-to-connected-account sessions.

## Options

- `stripe_account:` — Connect per-request account routing. Adds `Stripe-Account` header.

## Examples

    {:ok, session} = Session.create(client, %{
      "customer" => "cus_123",
      "return_url" => "https://example.com/account"
    })

    # With flow deep-link
    {:ok, session} = Session.create(client, %{
      "customer" => "cus_123",
      "flow_data" => %{
        "type" => "subscription_cancel",
        "subscription_cancel" => %{"subscription" => "sub_abc"}
      }
    })

    # Connect platform routing
    {:ok, session} = Session.create(client, %{"customer" => "cus_123"},
      stripe_account: "acct_connect_123"
    )

## Raises

- `ArgumentError` — immediately (pre-network) when `"customer"` is missing
- `ArgumentError` — immediately (pre-network) when `"flow_data"` is present but
  has an unknown `"type"` or is missing required sub-fields

# `create!`

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

Bang variant of `create/3`. Returns `%Session{}` on success, raises
`LatticeStripe.Error` on API failure.

# `from_map`

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

Decode a Stripe-shaped string-keyed map into a `%Session{}`.

The `"flow"` sub-object is decoded into `%FlowData{}` via `FlowData.from_map/1`.
Unknown top-level keys land in `:extra`.

Returns `nil` when given `nil`.

---

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