# `LatticeStripe.Charge`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v2.2.2/lib/lattice_stripe/charge.ex#L1)

Stripe Charge objects — the result record of a payment attempt.

When a `LatticeStripe.PaymentIntent` is confirmed, Stripe creates a Charge that
captures the settled payment outcome. New integrations should start with
`LatticeStripe.PaymentIntent` for accepting payments; this module is for reading
and reconciling those result records after the fact.

## When to use this module

- **Connect platform fee reconciliation** — walk `balance_transaction.fee_details`
  after a destination charge settles to reconcile application fees.
- **Support and audit workflows** — `list/3`, `stream!/3`, and `search/3` to find
  charges by customer, date, or Stripe search query.
- **Post-hoc metadata** — `update/4` to attach or correct `metadata` and
  `description` on an existing charge without re-running payment flows.

## When not to use this module

- **Accept a payment** → `LatticeStripe.PaymentIntent.create/3`
- **Capture a PI-initiated charge** → `LatticeStripe.PaymentIntent.capture/4`
- **Cancel a payment** → `LatticeStripe.PaymentIntent.cancel/4`
- **Refund a charge** → `LatticeStripe.Refund.create/3`

## Usage

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

    # Retrieve a settled charge by id (expand balance_transaction for fee_details)
    {:ok, charge} =
      LatticeStripe.Charge.retrieve(client, "ch_3OoLqrJ...",
        expand: ["balance_transaction"]
      )

    # List charges with filters
    {:ok, resp} = LatticeStripe.Charge.list(client, %{"limit" => "20", "customer" => "cus_123"})

    # Stream all charges lazily (auto-pagination)
    client
    |> LatticeStripe.Charge.stream!()
    |> Stream.take(100)
    |> Enum.each(&process_charge/1)

    # Search charges (eventual consistency — new charges may lag)
    {:ok, resp} = LatticeStripe.Charge.search(client, "status:'succeeded' AND currency:'usd'")

    # Update metadata and description on an existing charge
    {:ok, charge} =
      LatticeStripe.Charge.update(client, "ch_3OoLqrJ...", %{
        "metadata" => %{"order_id" => "ord_456"},
        "description" => "Order #456"
      })

    # Capture an uncaptured legacy direct charge (not PI-initiated)
    {:ok, charge} = LatticeStripe.Charge.capture(client, "ch_3OoLqrJ...")

## Connect platform fee reconciliation

After a destination charge settles, platforms walk
`PaymentIntent.latest_charge -> Charge.balance_transaction -> fee_details` to
reconcile the application fee Stripe transferred into their platform balance.
The typed `%Charge{}` return gives IDE-friendly completion and typespec coverage
for that flow without forcing users to drop into `LatticeStripe.Client.request/2`.

    # Walk fee_details to find the application_fee entry (Connect platform fee)
    application_fees =
      charge.balance_transaction["fee_details"]
      |> Enum.filter(fn fd -> fd["type"] == "application_fee" end)

## SDK surface (intentionally omitted)

`LatticeStripe.Charge.create` at arity `3` will not be added. Charge is a
read/reconciliation resource, not a payment-initiation API. For direct server-side
payment initiation, use `LatticeStripe.PaymentIntent.create/3`:

    {:ok, intent} =
      LatticeStripe.PaymentIntent.create(client, %{
        "amount" => 4_999,
        "currency" => "usd",
        "payment_method" => "pm_card_visa",
        "confirm" => true
      })

A successful PaymentIntent creates the resulting Charge for reconciliation. Browser
and other client-SDK flows create the PaymentIntent server-side but confirm it with
Stripe.js or an equivalent client SDK; customer action or SCA may still be required.

## Security and Inspect

`Inspect` shows only `[id, object, amount, currency, status, captured, paid]`.
The following fields may contain customer PII and are **hidden** from inspect
output so they do not leak into application logs:

  * `billing_details` (email, name, phone, address)
  * `payment_method_details` (card last4, fingerprint, etc.)
  * `fraud_details`
  * `receipt_email`, `receipt_number`, `receipt_url`
  * `customer`, `payment_method`

If you explicitly access those fields on a `%Charge{}` struct, you own the
disclosure decision.

## Stripe API Reference

See the [Stripe Charge API](https://docs.stripe.com/api/charges/object) for the
full object reference.

# `t`

```elixir
@type t() :: %LatticeStripe.Charge{
  amount: integer() | nil,
  amount_captured: integer() | nil,
  amount_refunded: integer() | nil,
  application: String.t() | nil,
  application_fee: String.t() | nil,
  application_fee_amount: integer() | nil,
  balance_transaction: LatticeStripe.BalanceTransaction.t() | String.t() | nil,
  billing_details: map() | nil,
  captured: boolean() | nil,
  created: integer() | nil,
  currency: String.t() | nil,
  customer: LatticeStripe.Customer.t() | String.t() | nil,
  description: String.t() | nil,
  destination: LatticeStripe.Account.t() | String.t() | nil,
  extra: map(),
  failure_code: String.t() | nil,
  failure_message: String.t() | nil,
  fraud_details: map() | nil,
  id: String.t() | nil,
  invoice: LatticeStripe.Invoice.t() | String.t() | nil,
  livemode: boolean() | nil,
  metadata: map() | nil,
  object: String.t(),
  on_behalf_of: String.t() | nil,
  outcome: map() | nil,
  paid: boolean() | nil,
  payment_intent: LatticeStripe.PaymentIntent.t() | String.t() | nil,
  payment_method: LatticeStripe.PaymentMethod.t() | String.t() | nil,
  payment_method_details: map() | nil,
  receipt_email: String.t() | nil,
  receipt_number: String.t() | nil,
  receipt_url: String.t() | nil,
  refunded: boolean() | nil,
  refunds: map() | nil,
  review: String.t() | nil,
  source_transfer: LatticeStripe.Transfer.t() | String.t() | nil,
  statement_descriptor: String.t() | nil,
  statement_descriptor_suffix: String.t() | nil,
  status: atom() | String.t() | nil,
  transfer_data: map() | nil,
  transfer_group: String.t() | nil
}
```

A Stripe Charge object.

See the [Stripe Charge API](https://docs.stripe.com/api/charges/object) for field
definitions. Unknown fields are preserved in `:extra` (F-001).

# `capture`

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

Captures an uncaptured Charge.

Sends `POST /v1/charges/:id/capture` with optional params and returns
`{:ok, %Charge{}}`. Only applicable to Charges created via legacy direct-charge
flows that were not yet captured.

For charges created by PaymentIntent confirmation, use
`LatticeStripe.PaymentIntent.capture/4` instead — capturing a PI-initiated charge
through this function is not the supported path.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `id` - The Charge ID string
- `params` - Optional capture params (e.g., `%{"amount" => 1500}`)
- `opts` - Per-request overrides

## Returns

- `{:ok, %Charge{}}` on success
- `{:error, %LatticeStripe.Error{}}` on failure

# `capture!`

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

Like `capture/4` but raises `LatticeStripe.Error` on failure.

# `from_map`

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

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

Maps every known Stripe Charge field explicitly. Any unrecognized fields
are collected into `:extra` (F-001) so no data is silently lost when Stripe
adds new fields.

`from_map(nil)` returns `nil` for use with optional/nullable charge payloads.

# `list`

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

Lists Charges with optional filters.

Sends `GET /v1/charges` and returns `{:ok, %Response{data: %List{}}}` with
typed `%Charge{}` items.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `params` - Filter params (e.g., `%{"limit" => "10", "customer" => "cus_123"}`)
- `opts` - Per-request overrides

## Returns

- `{:ok, %Response{data: %List{data: [%Charge{}, ...]}}}` on success
- `{:error, %LatticeStripe.Error{}}` on failure

## Example

    {:ok, resp} = LatticeStripe.Charge.list(client, %{"limit" => "20"})
    Enum.each(resp.data.data, &IO.inspect/1)

# `list!`

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

Like `list/3` but raises `LatticeStripe.Error` on failure.

# `retrieve`

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

Retrieves a Charge by ID.

Sends `GET /v1/charges/:id` and returns `{:ok, %Charge{}}`.

Raises `ArgumentError` (pre-network) if `id` is `nil` or an empty string.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `id` - The Charge ID string (e.g., `"ch_3OoLqrJ..."`)
- `opts` - Per-request overrides. Supports `expand: ["balance_transaction", ...]`
  to inline expanded child objects.

## Returns

- `{:ok, %Charge{}}` on success
- `{:error, %LatticeStripe.Error{}}` on failure

## Example

    {:ok, charge} =
      LatticeStripe.Charge.retrieve(client, "ch_3OoLqrJ...",
        expand: ["balance_transaction"]
      )

    application_fees =
      charge.balance_transaction["fee_details"]
      |> Enum.filter(fn fd -> fd["type"] == "application_fee" end)

# `retrieve!`

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

Like `retrieve/3` but returns the bare `%Charge{}` on success and raises
`LatticeStripe.Error` on failure.

Also raises `ArgumentError` (pre-network) when `id` is `nil` or empty.

# `search`

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

Searches Charges using Stripe's search query language.

Sends `GET /v1/charges/search` with the query string and returns typed results.
Note: search results have eventual consistency — newly created Charges may not
appear immediately.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `query` - Stripe search query string (e.g., `"status:'succeeded' AND currency:'usd'"`)
- `opts` - Per-request overrides

## Returns

- `{:ok, %Response{data: %List{data: [%Charge{}, ...]}}}` on success
- `{:error, %LatticeStripe.Error{}}` on failure

## Example

    {:ok, resp} = LatticeStripe.Charge.search(client, "status:'succeeded'")

# `search!`

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

Like `search/3` but raises `LatticeStripe.Error` on failure.

# `search_stream!`

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

Returns a lazy stream of all Charges matching the search query (auto-pagination).

Emits individual `%Charge{}` structs, fetching additional search pages as needed.
Raises `LatticeStripe.Error` if any page fetch fails.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `query` - Stripe search query string
- `opts` - Per-request overrides

## Returns

An `Enumerable.t()` of `%Charge{}` structs.

# `stream!`

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

Returns a lazy stream of all Charges matching the given params (auto-pagination).

Emits individual `%Charge{}` structs, fetching additional pages as needed.
Raises `LatticeStripe.Error` if any page fetch fails.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `params` - Filter params (e.g., `%{"limit" => "100"}`)
- `opts` - Per-request overrides

## Returns

An `Enumerable.t()` of `%Charge{}` structs.

## Example

    client
    |> LatticeStripe.Charge.stream!()
    |> Stream.take(500)
    |> Enum.to_list()

# `update`

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

Updates a Charge by ID.

Sends `POST /v1/charges/:id` with the given params and returns `{:ok, %Charge{}}`.
Note: the Stripe API only supports updating the `metadata` and `description`
fields on a Charge.

## Parameters

- `client` - A `%LatticeStripe.Client{}` struct
- `id` - The Charge ID string
- `params` - Map of fields to update (only `"metadata"` and `"description"` are accepted by Stripe)
- `opts` - Per-request overrides

## Returns

- `{:ok, %Charge{}}` on success
- `{:error, %LatticeStripe.Error{}}` on failure

# `update!`

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

Like `update/4` but raises `LatticeStripe.Error` on failure.

---

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