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

Operations on Stripe Payout objects.

A Payout represents money moving from your Stripe balance to a connected
bank account or debit card. Payouts are the outbound leg of money movement —
inbound money arrives via `Charge` / `PaymentIntent`, and you (or Stripe's
automatic schedule) trigger a `Payout` to settle the balance to a bank.

## Key behaviors

- Full CRUDL: `create/3`, `retrieve/3`, `update/4`, `list/3`, `stream!/3`.
- `cancel/4` and `reverse/4` both use the canonical
  `(client, id, params \\ %{}, opts \\ [])` shape per D-03. Both endpoints
  accept at least `expand`, and `reverse` additionally accepts `metadata`.
- The `trace_id` field decodes into a typed `%LatticeStripe.Payout.TraceId{}`
  — pattern-match on `payout.trace_id.status` to branch your reconciliation
  flow.
- Unknown future fields from Stripe land in `:extra` (F-001).
- `require_param!` validates the payout id pre-network on `retrieve`,
  `update`, `cancel`, and `reverse`.

## Expandable references

The following fields are typed as `binary() | map() | nil` because Stripe
returns an ID string by default and only inlines the expanded object when you
pass `expand: [...]`:

- `destination` — expand then cast via `LatticeStripe.BankAccount.cast/1` or
  `LatticeStripe.Card.cast/1` as appropriate.
- `balance_transaction` and `failure_balance_transaction` — expand then cast
  via `LatticeStripe.BalanceTransaction.from_map/1`.

Per D-05, these references stay polymorphic — the guide documents the
"expand then cast via expected module" idiom.

## Usage

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

    # Trigger a manual payout to the default external account
    {:ok, payout} = LatticeStripe.Payout.create(client, %{
      "amount" => 5000,
      "currency" => "usd"
    })

    # Retrieve and branch on trace_id availability
    {:ok, payout} = LatticeStripe.Payout.retrieve(client, "po_...")

    case payout.trace_id do
      %LatticeStripe.Payout.TraceId{status: "supported", value: trace} ->
        reconcile(trace)

      %LatticeStripe.Payout.TraceId{status: "pending"} ->
        # Stripe will populate trace_id asynchronously — listen for payout.updated
        :wait_for_webhook

      _ ->
        :skip
    end

    # Cancel a pending payout (no params needed)
    {:ok, _} = LatticeStripe.Payout.cancel(client, "po_...")

    # Cancel with expand for reconciliation
    {:ok, payout} =
      LatticeStripe.Payout.cancel(client, "po_...", %{
        "expand" => ["balance_transaction"]
      })

    # Reverse a completed payout with reconciliation metadata
    {:ok, reversed} =
      LatticeStripe.Payout.reverse(client, "po_...", %{
        "metadata" => %{"reason" => "customer_dispute"},
        "expand" => ["balance_transaction"]
      })

## Idempotency

`create`, `cancel`, and `reverse` are money-moving operations. `Client.request/2`
auto-generates an idempotency key for mutating requests and reuses it across
retries. Pass `idempotency_key:` in `opts` to set your own stable key.

## Stripe API Reference

- [Payouts](https://docs.stripe.com/api/payouts)
- [Cancel a payout](https://docs.stripe.com/api/payouts/cancel)
- [Reverse a payout](https://docs.stripe.com/api/payouts/reverse)

# `t`

```elixir
@type t() :: %LatticeStripe.Payout{
  amount: integer() | nil,
  application_fee: String.t() | nil,
  application_fee_amount: integer() | nil,
  arrival_date: integer() | nil,
  automatic: boolean() | nil,
  balance_transaction: LatticeStripe.BalanceTransaction.t() | String.t() | nil,
  created: integer() | nil,
  currency: String.t() | nil,
  description: String.t() | nil,
  destination:
    LatticeStripe.BankAccount.t() | LatticeStripe.Card.t() | String.t() | nil,
  extra: map(),
  failure_balance_transaction:
    LatticeStripe.BalanceTransaction.t() | String.t() | nil,
  failure_code: String.t() | nil,
  failure_message: String.t() | nil,
  id: String.t() | nil,
  livemode: boolean() | nil,
  metadata: map() | nil,
  method: atom() | String.t() | nil,
  object: String.t(),
  original_payout: String.t() | nil,
  reconciliation_status: String.t() | nil,
  reversed_by: String.t() | nil,
  source_type: String.t() | nil,
  statement_descriptor: String.t() | nil,
  status: atom() | String.t() | nil,
  trace_id: LatticeStripe.Payout.TraceId.t() | nil,
  type: atom() | String.t() | nil
}
```

A Stripe Payout object.

See the [Stripe Payout object](https://docs.stripe.com/api/payouts/object)
for field definitions.

# `cancel`

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

Cancels a pending Payout.

Sends `POST /v1/payouts/:id/cancel`. Only payouts with `status: "pending"`
can be canceled.

## Signature (D-03)

`cancel(client, id, params \\ %{}, opts \\ [])` — the `params` default is
mandatory. Stripe's `/cancel` endpoint accepts `expand`, so dropping `params`
would force a breaking change the first time a caller needs
`expand: ["balance_transaction"]`.

## Example

    {:ok, payout} = LatticeStripe.Payout.cancel(client, "po_...")

    {:ok, payout} =
      LatticeStripe.Payout.cancel(client, "po_...", %{
        "expand" => ["balance_transaction"]
      })

# `cancel!`

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

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

# `create`

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

Creates a new Payout.

Sends `POST /v1/payouts` with the given params.

## Parameters

- `client` — A `%LatticeStripe.Client{}` struct
- `params` — Payout attributes. Stripe requires `"amount"` and `"currency"`;
  LatticeStripe does not pre-validate — `Stripe` returns a `400` if missing.
- `opts` — Per-request overrides (e.g., `[idempotency_key: "..."]`,
  `[stripe_account: "acct_..."]`)

## Example

    {:ok, payout} =
      LatticeStripe.Payout.create(client, %{
        "amount" => 5000,
        "currency" => "usd",
        "method" => "instant",
        "source_type" => "card"
      })

# `create!`

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

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

# `from_map`

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

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

Maps every known Stripe Payout field explicitly. The nested `trace_id` map
is cast to a typed `%LatticeStripe.Payout.TraceId{}` struct. Unknown fields
land in `:extra` (F-001).

# `list`

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

Lists Payouts with optional filters.

Sends `GET /v1/payouts` and returns a paginated `%Response{data: %List{}}`
with typed `%Payout{}` items. All params are optional.

# `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 Payout by ID.

Sends `GET /v1/payouts/:id`. Raises `ArgumentError` (pre-network) if `id` is
`nil` or an empty string.

# `retrieve!`

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

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

# `reverse`

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

Reverses a Payout.

Sends `POST /v1/payouts/:id/reverse`. Only payouts with `status: "paid"` and
`type: "bank_account"` can be reversed.

## Signature (D-03)

`reverse(client, id, params \\ %{}, opts \\ [])` — the `params` default is
mandatory. Stripe's `/reverse` endpoint accepts `metadata` and `expand`.

## Example

    {:ok, reversed} =
      LatticeStripe.Payout.reverse(client, "po_...", %{
        "metadata" => %{"reason" => "customer_dispute"},
        "expand" => ["balance_transaction"]
      })

# `reverse!`

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

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

# `stream!`

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

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

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

# `update`

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

Updates a Payout by ID.

Sends `POST /v1/payouts/:id`. Note: Stripe only supports updating `metadata`
on a Payout.

# `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*
