# `Accrue.Billing.PaymentMethodActions`
[🔗](https://github.com/szTheory/accrue/blob/accrue-v1.0.0/lib/accrue/billing/payment_method_actions.ex#L1)

Payment method write surface.

Ships four public entry points, all exposed via `defdelegate` on
`Accrue.Billing`:

  * `attach_payment_method/3` — attaches a processor-side payment
    method to a customer, with **fingerprint dedup**. If an existing
    PaymentMethod row for the same `(customer_id, fingerprint)`
    exists, the processor-side duplicate is detached and the existing
    row is returned with `existing?: true`. A concurrent race that
    slips past the application-level check hits the
    `accrue_payment_methods_customer_fingerprint_idx` partial unique
    index and is rescued via `Ecto.ConstraintError`.
  * `detach_payment_method/2` — detaches on the processor and deletes
    the local row.
  * `set_default_payment_method/3` — asserts `pm.customer_id ==
    customer.id` (strict attachment check) and raises
    `Accrue.Error.NotAttached` otherwise. Never silently wires a
    foreign PM as a customer default.
  * `list_payment_methods/2` — read-only listing of processor-side
    payment methods for the customer's Stripe customer id. Optional
    keyword filters are validated with `NimbleOptions` before the
    processor call.

## `list_payment_methods/2` options

| Key | Type | Notes |
|-----|------|-------|
| `type` | string | Stripe `type` filter (e.g. `"card"`). |
| `limit` | pos_integer | Page size for Stripe list. |
| `starting_after` | string | Pagination cursor. |
| `ending_before` | string | Pagination cursor. |
| `operation_id` | string | Dropped before the wire; reserved for parity with write paths. |

Empty `[]` is always valid. Additional host-visible filters can extend
this schema in a minor release without changing the arity.

# `attach_payment_method`

```elixir
@spec attach_payment_method(Accrue.Billing.Customer.t(), String.t(), keyword()) ::
  {:ok, Accrue.Billing.PaymentMethod.t()} | {:error, term()}
```

Attaches a processor-side payment method to a customer with fingerprint
dedup. Returns `{:ok, %PaymentMethod{}}`; the `existing?: true`
virtual flag is set when dedup hit an existing row.

# `attach_payment_method!`

```elixir
@spec attach_payment_method!(Accrue.Billing.Customer.t(), String.t(), keyword()) ::
  Accrue.Billing.PaymentMethod.t()
```

Raising variant of `attach_payment_method/3`.

# `detach_payment_method`

```elixir
@spec detach_payment_method(
  Accrue.Billing.PaymentMethod.t(),
  keyword()
) :: {:ok, Accrue.Billing.PaymentMethod.t()} | {:error, term()}
```

Detaches a payment method from its customer on the processor and
deletes the local row in the same transaction.

# `detach_payment_method!`

```elixir
@spec detach_payment_method!(
  Accrue.Billing.PaymentMethod.t(),
  keyword()
) :: Accrue.Billing.PaymentMethod.t()
```

Raising variant of `detach_payment_method/2`.

# `list_payment_methods`

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

Lists payment methods attached to the customer on the processor (Stripe
truth — not a local cache projection).

# `list_payment_methods!`

```elixir
@spec list_payment_methods!(
  Accrue.Billing.Customer.t(),
  keyword()
) :: map()
```

Raising variant of `list_payment_methods/2`.

# `set_default_payment_method`

```elixir
@spec set_default_payment_method(
  Accrue.Billing.Customer.t(),
  Accrue.Billing.PaymentMethod.t(),
  keyword()
) :: {:ok, Accrue.Billing.Customer.t()} | {:error, term()}
```

Sets a payment method as the customer's default. Raises
`Accrue.Error.NotAttached` if `pm.customer_id != customer.id` —
strict attachment check, never silently wires a foreign PM as
default.

# `set_default_payment_method!`

```elixir
@spec set_default_payment_method!(
  Accrue.Billing.Customer.t(),
  Accrue.Billing.PaymentMethod.t(),
  keyword()
) :: Accrue.Billing.Customer.t()
```

Raising variant of `set_default_payment_method/3`.

---

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