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

Operations on Stripe Transfer objects — the Connect separate-charge-and-transfer primitive.

A Transfer moves funds from your platform balance to a connected account.
See the [Stripe Transfer API](https://docs.stripe.com/api/transfers).

## Usage

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

    # Create a transfer
    {:ok, transfer} = LatticeStripe.Transfer.create(client, %{
      "amount" => 1000,
      "currency" => "usd",
      "destination" => "acct_1Nv0FGQ9RKHgCVdK"
    })

    # Retrieve
    {:ok, transfer} = LatticeStripe.Transfer.retrieve(client, "tr_...")

    # Update metadata
    {:ok, transfer} = LatticeStripe.Transfer.update(client, "tr_...", %{
      "metadata" => %{"internal_id" => "xfer_42"}
    })

    # List / stream
    {:ok, resp} = LatticeStripe.Transfer.list(client, %{"destination" => "acct_..."})

    client
    |> LatticeStripe.Transfer.stream!()
    |> Enum.take(500)

## Reversing a transfer: use `LatticeStripe.TransferReversal`

This module deliberately does **not** define `reverse/3` or `reverse/4`.
Phase 18 decision D-02 locks TransferReversal as a standalone top-level
module addressed by `(transfer_id, reversal_id)`. To reverse a transfer:

    {:ok, reversal} = LatticeStripe.TransferReversal.create(client, "tr_...", %{})

Mirrors stripe-java's own top-level `TransferReversal` class and the Phase 17
`AccountLink` / `LoginLink` precedent.

## Embedded reversals sublist decoding

The Stripe API returns an embedded (non-paginated) `reversals` sublist on
every Transfer:

    {
      "reversals": {
        "object": "list",
        "data": [ {...}, {...} ],
        "has_more": false,
        "url": "/v1/transfers/tr_.../reversals",
        "total_count": 2
      }
    }

`Transfer.from_map/1` decodes this specially:

- `transfer.reversals` becomes `[%LatticeStripe.TransferReversal{}, ...]`
  (a plain Elixir list, **not** a `%LatticeStripe.List{}` struct)
- The wrapper metadata (`has_more`, `url`, `total_count`) is preserved
  under `transfer.extra["reversals_meta"]` so no data is lost

## Idempotency for double-execution safety

`Transfer.create/3` is a money-moving operation. `LatticeStripe.Client.request/2`
already auto-generates idempotency keys for mutating requests and reuses them
across retries (Phase 2 RTRY-03). For at-least-once safety in your own failure
recovery loops, pass an explicit key:

    {:ok, transfer} =
      LatticeStripe.Transfer.create(client, params, idempotency_key: "my-xfer-42")

## No client-side validation

Per Phase 15 D5 / Phase 18 D-04, `Transfer.create/3` does **not** pre-validate
`amount`, `currency`, or `destination` beyond the standard CRUDL shape. Stripe's
own 400 errors surface as `{:error, %LatticeStripe.Error{type: :invalid_request_error}}`.

## Stripe API Reference

See the [Stripe Transfer API](https://docs.stripe.com/api/transfers).

# `t`

```elixir
@type t() :: %LatticeStripe.Transfer{
  amount: integer() | nil,
  amount_reversed: integer() | nil,
  balance_transaction: LatticeStripe.BalanceTransaction.t() | String.t() | nil,
  created: integer() | nil,
  currency: String.t() | nil,
  description: String.t() | nil,
  destination: LatticeStripe.Account.t() | String.t() | nil,
  destination_payment: LatticeStripe.Charge.t() | String.t() | nil,
  extra: map(),
  id: String.t() | nil,
  livemode: boolean() | nil,
  metadata: map() | nil,
  object: String.t(),
  reversals: [LatticeStripe.TransferReversal.t()],
  reversed: boolean() | nil,
  source_transaction: LatticeStripe.Charge.t() | String.t() | nil,
  source_type: String.t() | nil,
  transfer_group: String.t() | nil
}
```

A Stripe Transfer object.

`reversals` is a plain list of `%LatticeStripe.TransferReversal{}` structs
(NOT a `%LatticeStripe.List{}`). The original sublist wrapper metadata
(`has_more`, `url`, `total_count`) is stashed under
`extra["reversals_meta"]` by `from_map/1`.

See the [Stripe Transfer API](https://docs.stripe.com/api/transfers/object).

# `create`

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

Creates a new Transfer.

Sends `POST /v1/transfers`. No client-side validation of params (Stripe's 400
surfaces as `{:error, %Error{}}`).

# `create!`

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

Like `create/3` but raises on failure.

# `from_map`

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

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

Specially decodes the embedded `reversals` sublist into
`[%TransferReversal{}]` (a plain list, not a `%List{}`), and preserves the
wrapper metadata under `extra["reversals_meta"]`.

# `list`

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

Lists Transfers with optional filters.

# `list!`

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

Like `list/3` but raises on failure.

# `retrieve`

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

Retrieves a Transfer by ID.

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

# `retrieve!`

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

Like `retrieve/3` but raises on failure.

# `stream!`

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

Returns a lazy stream of all Transfers (auto-pagination).

# `update`

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

Updates a Transfer (typically only `metadata` / `description`).

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

# `update!`

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

Like `update/4` but raises on failure.

---

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