# `LatticeStripe.Client`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v0.2.0/lib/lattice_stripe/client.ex#L1)

The main entry point for making Stripe API requests.

`Client` is a plain struct (no GenServer, no global state) that holds all
configuration for a Stripe integration. Create one at application startup
and pass it explicitly to every API call.

## Quick Start

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

    request = %LatticeStripe.Request{method: :get, path: "/v1/customers/cus_123"}
    {:ok, customer} = LatticeStripe.Client.request(client, request)

## Multiple Clients

You can run multiple clients with different keys simultaneously — useful for
Stripe Connect platforms managing sub-accounts:

    platform_client = LatticeStripe.Client.new!(api_key: "sk_live_platform", finch: MyApp.Finch)
    connect_client = LatticeStripe.Client.new!(
      api_key: "sk_live_platform",
      finch: MyApp.Finch,
      stripe_account: "acct_connected_account"
    )

## Per-Request Overrides

Pass `opts` in a `Request` struct to override client defaults for a single call:

    request = %LatticeStripe.Request{
      method: :post,
      path: "/v1/charges",
      params: %{amount: 1000, currency: "usd", source: "tok_visa"},
      opts: [
        idempotency_key: "charge-unique-key-123",
        stripe_account: "acct_connected",
        timeout: 10_000
      ]
    }

# `t`

```elixir
@type t() :: %LatticeStripe.Client{
  api_key: String.t(),
  api_version: String.t(),
  base_url: String.t(),
  finch: atom(),
  json_codec: module(),
  max_retries: non_neg_integer(),
  retry_strategy: module(),
  stripe_account: String.t() | nil,
  telemetry_enabled: boolean(),
  timeout: pos_integer(),
  transport: module()
}
```

A configured LatticeStripe client.

Created via `new!/1` or `new/1`. Pass this struct to every API call.
It is a plain struct with no process state — safe to share across processes.

- `api_key` - Stripe secret key (`sk_test_...` or `sk_live_...`)
- `finch` - Name of the Finch pool started in your supervision tree
- `stripe_account` - Connected account ID for Stripe Connect platforms, or `nil`
- `base_url` - Stripe API base URL (default: `"https://api.stripe.com"`)
- `api_version` - Stripe API version header (default: `"2026-03-25.dahlia"`)
- `transport` - Transport module implementing `LatticeStripe.Transport`
- `json_codec` - JSON codec module implementing `LatticeStripe.Json`
- `retry_strategy` - Retry strategy module implementing `LatticeStripe.RetryStrategy`
- `timeout` - Default request timeout in milliseconds (default: `30_000`)
- `max_retries` - Max retry attempts after initial failure (default: `2`)
- `telemetry_enabled` - Whether to emit telemetry events (default: `true`)

# `new`

```elixir
@spec new(keyword()) :: {:ok, t()} | {:error, NimbleOptions.ValidationError.t()}
```

Creates a new `%Client{}` struct, returning `{:ok, client}` or `{:error, error}`.

Like `new!/1` but returns a result tuple instead of raising.

## Example

    case LatticeStripe.Client.new(api_key: "sk_test_...", finch: MyApp.Finch) do
      {:ok, client} -> client
      {:error, error} -> raise error
    end

# `new!`

```elixir
@spec new!(keyword()) :: t()
```

Creates a new `%Client{}` struct, raising on invalid options.

Validates options using `LatticeStripe.Config.validate!/1`. Raises
`NimbleOptions.ValidationError` with a descriptive message if any option
is invalid or a required option is missing.

## Required Options

- `:api_key` - Your Stripe API key (e.g., `"sk_test_..."`)
- `:finch` - Name atom of a running Finch pool (e.g., `MyApp.Finch`)

## Optional Options

See `LatticeStripe.Config` for the full schema with defaults and documentation.

## Example

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

# `request`

```elixir
@spec request(t(), LatticeStripe.Request.t()) ::
  {:ok, LatticeStripe.Response.t()} | {:error, LatticeStripe.Error.t()}
```

Dispatches a `Request` through the client's configured transport with automatic retries.

Builds the full request with all required headers, encodes params, calls
the transport, decodes the response JSON, and returns either `{:ok, map}`
on success or `{:error, %Error{}}` on failure.

POST requests automatically get an `idk_ltc_`-prefixed UUID v4 idempotency key
to make retries safe. The same key is reused across all retry attempts.
User-provided `:idempotency_key` in `opts` takes precedence over auto-generation.

Wraps the transport call(s) in a `:telemetry.span/3` for observability (unless
`telemetry_enabled: false` on the client). Per-retry events are emitted as
`[:lattice_stripe, :request, :retry]`.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `request` - A `%LatticeStripe.Request{}` struct

## Returns

- `{:ok, %LatticeStripe.Response{}}` - Response struct wrapping decoded data with metadata.
  `data` is a `%LatticeStripe.List{}` for list/search endpoints, or a plain map for singular resources.
- `{:error, %LatticeStripe.Error{}}` - Structured error from 4xx/5xx or transport failure

# `request!`

```elixir
@spec request!(t(), LatticeStripe.Request.t()) :: LatticeStripe.Response.t()
```

Like `request/2`, but raises `LatticeStripe.Error` on failure.

Retries are attempted first. Only raises after all retries are exhausted.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `request` - A `%LatticeStripe.Request{}` struct

## Returns

- `%LatticeStripe.Response{}` on success (raises `LatticeStripe.Error` on failure)
- Raises `LatticeStripe.Error` on failure (after retries exhausted)

---

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