# `Accrue.Connect`
[🔗](https://github.com/szTheory/accrue/blob/accrue-v0.3.0/lib/accrue/connect.ex#L1)

Stripe Connect domain facade.

Wraps the `Accrue.Processor` Connect callbacks with:

  * `with_account/2` — process-dictionary scoped block that threads a
    `stripe_account` id through every nested processor call via the
    `:accrue_connected_account_id` key. This is the same key
    `Accrue.Processor.Stripe.resolve_stripe_account/1` reads, and the
    bundled Oban middleware restores it across the enqueue → perform boundary.
  * `create_account/2..list_accounts/1` dual bang/tuple facade
    (mirrors `Accrue.BillingPortal.Session`).
  * Local projection upsert via `Accrue.Connect.Projection.decompose/1`
    + `Accrue.Connect.Account.changeset/2`, wrapped in a single
    `Accrue.Repo.transact/1` block with `Accrue.Events.record_multi/3`
    so the state mutation + audit row commit atomically.

Soft-delete semantics: `delete_account/2` tombstones the local row
via `deauthorized_at` rather than hard-deleting it (audit-friendly).

# `create_account`

```elixir
@spec create_account(
  map() | keyword(),
  keyword()
) :: {:ok, Accrue.Connect.Account.t()} | {:error, term()}
```

Creates a new connected account through the configured processor,
then upserts the local `accrue_connect_accounts` row and records an
`"connect.account.created"` event in the same transaction.

## Options

See `@create_schema` in the module source for the full NimbleOptions
schema. `:type` is required (no default — host explicitly picks
`:standard`/`:express`/`:custom`).

# `create_account!`

```elixir
@spec create_account!(
  map() | keyword(),
  keyword()
) :: Accrue.Connect.Account.t()
```

Bang variant of `create_account/2`. Raises on failure.

# `create_account_link`

```elixir
@spec create_account_link(
  Accrue.Connect.Account.t() | String.t(),
  keyword()
) :: {:ok, Accrue.Connect.AccountLink.t()} | {:error, term()}
```

Creates a Stripe Connect Account Link for hosted onboarding or
account-update flows.

Accepts either an `%Account{}` struct, a bare `"acct_..."` binary,
or a map with an `:account` key. `:return_url` and `:refresh_url`
are required per `@account_link_schema`.

Returns `{:ok, %Accrue.Connect.AccountLink{}}` on success. The
returned struct masks its `:url` field in `Inspect` output — treat
the URL as a short-lived bearer credential and redirect the user
immediately.

## Options

- `:return_url` (required) — where Stripe redirects on completion
- `:refresh_url` (required) — where Stripe redirects if the link expires
- `:type` — `"account_onboarding"` (default) or `"account_update"`
- `:collect` — `"currently_due"` (default) or `"eventually_due"`

# `create_account_link!`

```elixir
@spec create_account_link!(
  Accrue.Connect.Account.t() | String.t(),
  keyword()
) :: Accrue.Connect.AccountLink.t()
```

Bang variant of `create_account_link/2`. Raises on failure.

# `create_login_link`

```elixir
@spec create_login_link(
  Accrue.Connect.Account.t() | String.t(),
  keyword()
) :: {:ok, Accrue.Connect.LoginLink.t()} | {:error, term()}
```

Creates a Stripe Express dashboard Login Link for a connected account.

**Only Express accounts are supported.** Standard and Custom
accounts are rejected locally before reaching the processor to
avoid leaking "acct_X is Standard" via a Stripe 400 error payload.
The local row is consulted first; on a miss the
account is retrieved from the processor.

Returns `{:ok, %Accrue.Connect.LoginLink{}}` on success. The
returned struct masks its `:url` field in `Inspect` output — treat
the URL as a short-lived bearer credential and redirect the user
immediately.

# `create_login_link!`

```elixir
@spec create_login_link!(
  Accrue.Connect.Account.t() | String.t(),
  keyword()
) :: Accrue.Connect.LoginLink.t()
```

Bang variant of `create_login_link/2`. Raises on failure.

# `current_account_id`

```elixir
@spec current_account_id() :: String.t() | nil
```

Reads the currently-scoped connected account id from the pdict (or `nil`).

# `delete_account`

```elixir
@spec delete_account(
  String.t(),
  keyword()
) :: {:ok, Accrue.Connect.Account.t()} | {:error, term()}
```

Deletes a connected account through the processor and tombstones the
local row via `deauthorized_at` (soft delete — audit trail
is never hard-deleted).

# `delete_account!`

```elixir
@spec delete_account!(
  String.t(),
  keyword()
) :: Accrue.Connect.Account.t()
```

Bang variant of `delete_account/2`.

# `delete_account_id`

```elixir
@spec delete_account_id() :: :ok
```

Clears the connected-account scope from the process dictionary.

# `destination_charge`

```elixir
@spec destination_charge(
  map() | keyword(),
  keyword()
) :: {:ok, Accrue.Billing.Charge.t()} | {:error, term()}
```

Creates a Stripe Connect **destination charge**.

A destination charge is a single platform-scoped charge whose
`transfer_data.destination` points at a connected account. Stripe
handles the platform-to-connected-account settlement on your behalf;
you do not need to issue a separate `Transfer`. An optional
`:application_fee_amount` (pre-computed via
`Accrue.Connect.platform_fee/2`) is forwarded to Stripe verbatim.

**This call is always PLATFORM-scoped.** The `Stripe-Account` header
is explicitly unset regardless of any `with_account/2` scope the
caller may be inside — Pitfall 2 would otherwise cause Stripe to 400.

## Required parameters

  * `:amount` — `%Accrue.Money{}` gross charge amount
  * `:destination` — `%Accrue.Connect.Account{}` struct or
    `"acct_..."` binary
  * `:customer` — `%Accrue.Billing.Customer{}` struct

## Optional parameters

  * `:application_fee_amount` — `%Accrue.Money{}` platform fee.
    Compute via `Accrue.Connect.platform_fee/2` and pass through —
    fees are caller-injected (never auto-applied).
  * `:description`, `:metadata`, `:statement_descriptor`
  * `:payment_method` — processor payment method id

Returns `{:ok, %Accrue.Billing.Charge{}}` on success. The local
charge row is persisted and bundled with the resolved destination
account via the charge's `data` jsonb field.

## Examples

    {:ok, fee} = Accrue.Connect.platform_fee(Accrue.Money.new(10_000, :usd))

    {:ok, %Accrue.Billing.Charge{} = charge} =
      Accrue.Connect.destination_charge(
        %{
          amount: Accrue.Money.new(10_000, :usd),
          destination: connected_account,
          customer: customer
        },
        application_fee_amount: fee,
        payment_method: "pm_..."
      )

# `destination_charge!`

```elixir
@spec destination_charge!(
  map() | keyword(),
  keyword()
) :: Accrue.Billing.Charge.t()
```

Bang variant of `destination_charge/2`. Raises on failure.

# `fetch_account`

```elixir
@spec fetch_account(
  String.t(),
  keyword()
) :: {:ok, Accrue.Connect.Account.t()} | {:error, term()}
```

Local-first fetch: returns the persisted `%Account{}` row by stripe
account id, falling back to `retrieve_account/2` on miss (which upserts
the local row as a side-effect).

# `fetch_account!`

```elixir
@spec fetch_account!(
  String.t(),
  keyword()
) :: Accrue.Connect.Account.t()
```

Bang variant of `fetch_account/2`.

# `list_accounts`

```elixir
@spec list_accounts(keyword()) :: {:ok, map()} | {:error, term()}
```

Lists connected accounts through the processor (pass-through).

# `platform_fee`

```elixir
@spec platform_fee(
  Accrue.Money.t(),
  keyword()
) :: {:ok, Accrue.Money.t()} | {:error, Exception.t()}
```

Computes a platform fee as a pure `Accrue.Money` value.

**Caller-inject semantics.** This helper returns the computed fee; it
does NOT auto-apply the value to any charge or transfer. Callers thread
the result into `application_fee_amount:` on their own charge/transfer
calls so the fee line is always auditable at the call site.

See `Accrue.Connect.PlatformFee` for the full computation order and
clamp semantics. Defaults come from the `:platform_fee` sub-key of the
`:connect` config (`Accrue.Config.get!(:connect)`), which ships with
Stripe's standard 2.9% baseline and no fixed/min/max.

## Examples

    iex> {:ok, fee} = Accrue.Connect.platform_fee(
    ...>   Accrue.Money.new(10_000, :usd),
    ...>   percent: Decimal.new("2.9"),
    ...>   fixed: Accrue.Money.new(30, :usd)
    ...> )
    iex> fee
    %Accrue.Money{amount_minor: 320, currency: :usd}

# `platform_fee!`

```elixir
@spec platform_fee!(
  Accrue.Money.t(),
  keyword()
) :: Accrue.Money.t()
```

Bang variant of `platform_fee/2`. Raises on validation failure.

# `put_account_id`

```elixir
@spec put_account_id(String.t() | nil) :: :ok
```

Writes the connected-account scope to the process dictionary without
restoring afterwards. Used by `Accrue.Plug.PutConnectedAccount` and
the bundled Oban middleware, where the scope lifetime matches the
request/job lifetime rather than a lexical block.

# `reject_account`

```elixir
@spec reject_account(String.t(), String.t(), keyword()) ::
  {:ok, Accrue.Connect.Account.t()} | {:error, term()}
```

Rejects a connected account through the processor. `reason` is a
bare string per the Stripe API (e.g. `"fraud"`, `"terms_of_service"`).

# `reject_account!`

```elixir
@spec reject_account!(String.t(), String.t(), keyword()) :: Accrue.Connect.Account.t()
```

Bang variant of `reject_account/3`.

# `resolve_account_id`

```elixir
@spec resolve_account_id(Accrue.Connect.Account.t() | String.t() | nil) ::
  String.t() | nil
```

Normalizes a caller-supplied account reference to a bare stripe account
id string. Accepts `%Account{}`, a binary, or `nil` (returns `nil` —
caller-side authorization is the host's responsibility).

# `retrieve_account`

```elixir
@spec retrieve_account(
  String.t(),
  keyword()
) :: {:ok, Accrue.Connect.Account.t()} | {:error, term()}
```

Retrieves a connected account through the processor and upserts the
local row (force_status_changeset path — out-of-order webhooks can
arrive before first retrieve). Returns `{:ok, %Account{}}`.

# `retrieve_account!`

```elixir
@spec retrieve_account!(
  String.t(),
  keyword()
) :: Accrue.Connect.Account.t()
```

Bang variant of `retrieve_account/2`.

# `separate_charge_and_transfer`

```elixir
@spec separate_charge_and_transfer(
  map() | keyword(),
  keyword()
) ::
  {:ok, %{charge: Accrue.Billing.Charge.t(), transfer: map()}}
  | {:error, term()}
```

Creates a **separate charge and transfer** flow.

Two distinct API calls:

  1. `Processor.create_charge/2` on the PLATFORM (no `Stripe-Account`
     header, no `transfer_data`). Charges the customer against the
     platform balance.
  2. `Processor.create_transfer/2` to the connected account,
     `source_transaction` linked to the charge above, moving a
     caller-specified amount from the platform balance to the
     connected-account balance.

Use this flow when the platform needs explicit control over the
transfer step — e.g. delayed transfers, split destinations, or
transfers that are not a fixed percentage of the gross.

Returns `{:ok, %{charge: %Charge{}, transfer: map()}}` on success.

If the transfer step fails after the charge succeeded, returns
`{:error, {:transfer_failed, %Charge{}, error}}` so callers can
reconcile — the charge row is persisted but no transfer exists.

## Required parameters

  * `:amount` — `%Money{}` gross charge amount
  * `:customer` — `%Accrue.Billing.Customer{}` struct
  * `:destination` — connected account
  * `:transfer_amount` — `%Money{}` amount to forward (platform keeps
    the difference as its fee; Accrue does NOT compute this for you —
    caller-inject semantics)

# `separate_charge_and_transfer!`

```elixir
@spec separate_charge_and_transfer!(
  map() | keyword(),
  keyword()
) :: %{charge: Accrue.Billing.Charge.t(), transfer: map()}
```

Bang variant of `separate_charge_and_transfer/2`. Raises on failure.

# `transfer`

```elixir
@spec transfer(
  map() | keyword(),
  keyword()
) :: {:ok, map()} | {:error, term()}
```

Creates a standalone **Transfer** from the platform balance to a
connected account.

This is the bare Transfers API — a thin wrapper over
`Processor.create_transfer/2`. Use when you need to manually move
funds outside a charge flow (e.g. revenue share payouts from an
accumulated platform balance).

Accrue does not ship a dedicated `accrue_connect_transfers` table;
each successful call appends a
`connect.transfer` row to `accrue_events` via `Accrue.Events.record/1`.

Returns `{:ok, map()}` (the bare processor response).

## Required parameters

  * `:amount` — `%Money{}`
  * `:destination` — connected account

# `transfer!`

```elixir
@spec transfer!(
  map() | keyword(),
  keyword()
) :: map()
```

Bang variant of `transfer/2`. Raises on failure.

# `update_account`

```elixir
@spec update_account(String.t(), map(), keyword()) ::
  {:ok, Accrue.Connect.Account.t()} | {:error, term()}
```

Updates a connected account through the processor. Nested params
(`capabilities:`, `settings: %{payouts: %{schedule: ...}}`) are
forwarded verbatim to Stripe.

# `update_account!`

```elixir
@spec update_account!(String.t(), map(), keyword()) :: Accrue.Connect.Account.t()
```

Bang variant of `update_account/3`.

# `with_account`

```elixir
@spec with_account(Accrue.Connect.Account.t() | String.t() | nil, (-&gt; result)) ::
  result
when result: var
```

Runs `fun` with the connected-account scope set in the process
dictionary, restoring the prior value (or clearing it) in an `after`
block even if `fun` raises. Mirrors `Accrue.Stripe.with_api_version/2`.

Accepts a stripe account id string, a `%Accrue.Connect.Account{}`
struct, or `nil` (nil clears any existing scope for the block's
lifetime — useful for temporarily stepping back to platform scope
from inside a nested block).

---

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