Accrue.Billing.ChargeActions (accrue v0.2.0)

Copy Markdown View Source

Phase 3 Plan 06 charge / payment intent / setup intent write surface (BILL-20, BILL-21, BILL-22).

Ships three public entry points, all exposed on Accrue.Billing via defdelegate in Plan 01 Task 4:

  • charge/3 — atomic charge creation with SCA-safe tagged returns. Resolves payment method from opts[:payment_method] or the customer's default_payment_method association. Returns typed {:error, %Accrue.Error.NoDefaultPaymentMethod{}} when neither is set (BILL-21) — never silently falls back to the first attached PM (Cashier footgun, D3-58).
  • create_payment_intent/2 — thin wrapper with IntentResult.wrap/1 on the result.
  • create_setup_intent/2 — BILL-22 off-session card-on-file parallel.

Every mutation runs inside Accrue.Repo.transact/2 with an accrue_events row written in the same transaction (EVT-04 invariant).

Summary

Functions

Charges a customer a fixed amount of %Accrue.Money{}. Returns intent_result(Charge.t())

Raising variant of charge/3. Raises Accrue.ActionRequiredError on {:ok, :requires_action, pi}, re-raises typed errors otherwise.

Thin wrapper over Processor.create_payment_intent/2. Returns intent_result(map()) so SCA paths surface via {:ok, :requires_action, pi}.

BILL-22 off-session card-on-file parallel. Creates a SetupIntent with usage: "off_session" so the resulting PaymentMethod can be charged off-session later (e.g. subscription renewals with SCA pre-authorized). Returns intent_result(map()).

Functions

charge(billable_or_customer, amount, opts \\ [])

@spec charge(Accrue.Billing.Customer.t() | struct(), Accrue.Money.t(), keyword()) ::
  {:ok, Accrue.Billing.Charge.t()}
  | {:ok, :requires_action, map()}
  | {:error, term()}

Charges a customer a fixed amount of %Accrue.Money{}. Returns intent_result(Charge.t()):

  • {:ok, %Charge{}} — happy path
  • {:ok, :requires_action, pi} — SCA / 3DS required (BILL-20)
  • {:error, %Accrue.Error.NoDefaultPaymentMethod{}} — no PM resolved (BILL-21 — typed, loud, pattern-matchable; never silently falls back to "first attached PM")
  • {:error, other} — anything else

Options

  • :payment_method — processor-side payment method id (e.g. "pm_..."). If absent, resolves to the customer's default_payment_method via the schema association.
  • :description — charge description (string).
  • :operation_id — deterministic idempotency seed. Defaults to Accrue.Actor.current_operation_id!/0.

charge!(billable_or_customer, amount, opts \\ [])

Raising variant of charge/3. Raises Accrue.ActionRequiredError on {:ok, :requires_action, pi}, re-raises typed errors otherwise.

create_payment_intent(params, opts \\ [])

@spec create_payment_intent(
  map(),
  keyword()
) :: {:ok, map()} | {:ok, :requires_action, map()} | {:error, term()}

Thin wrapper over Processor.create_payment_intent/2. Returns intent_result(map()) so SCA paths surface via {:ok, :requires_action, pi}.

create_payment_intent!(params, opts \\ [])

@spec create_payment_intent!(
  map(),
  keyword()
) :: map()

Raising variant of create_payment_intent/2.

create_setup_intent(customer_or_billable, opts \\ [])

@spec create_setup_intent(
  Accrue.Billing.Customer.t() | struct(),
  keyword()
) :: {:ok, map()} | {:ok, :requires_action, map()} | {:error, term()}

BILL-22 off-session card-on-file parallel. Creates a SetupIntent with usage: "off_session" so the resulting PaymentMethod can be charged off-session later (e.g. subscription renewals with SCA pre-authorized). Returns intent_result(map()).

create_setup_intent!(customer_or_billable, opts \\ [])

@spec create_setup_intent!(
  Accrue.Billing.Customer.t() | struct(),
  keyword()
) :: map()

Raising variant of create_setup_intent/2.