X402.Client.Hooks behaviour (X402 v0.9.0)

Copy Markdown View Source

Behaviour for lifecycle hooks around client payment creation.

X402.Client.build_payment/3 runs the callbacks in this order:

  1. before_payment/2 — after a requirements entry has been selected and before anything is signed. Continue with {:cont, context} (the context's :requirements may be replaced, for example to pin a different accepts entry) or abort with {:halt, reason}, which surfaces as {:error, {:hook_halted, :before_payment, reason}}.
  2. after_payment/2 — once the PaymentPayload has been signed, assembled and enriched. Continue with {:cont, context}; the context's :payload may be replaced and becomes the returned payload.
  3. on_payment_failure/2 — when signing or enrichment fails after before_payment/2 has run. Continue with {:cont, context} (the context's :error may be replaced) or recover with {:recover, payload}, which returns {:ok, payload} instead.

These mirror the onBeforePaymentCreation, onAfterPaymentCreation, and onPaymentCreationFailure hooks of the reference TypeScript client. Selection failures (:no_acceptable_requirements) happen before any hook runs and never reach on_payment_failure/2.

A callback that raises or exits yields {:error, {:hook_callback_failed, callback, reason}}; one that returns anything outside its contract yields {:error, {:hook_invalid_return, callback, value}}.

Hooks are invoked in the process that calls X402.Client.build_payment/3 (through X402.Client.Finch.request/3 or X402.MCP.Client.call/3, the request process). X402.Client.Hooks.Default is the no-op implementation used when no :hooks option is given.

Example

defmodule MyApp.PaymentHooks do
  @behaviour X402.Client.Hooks

  @impl true
  def before_payment(context, _metadata) do
    case context.requirements["amount"] do
      amount when amount > "1000000" -> {:halt, :too_expensive}
      _amount -> {:cont, context}
    end
  end

  @impl true
  def after_payment(context, _metadata) do
    Logger.info("paying #{context.requirements["amount"]}")
    {:cont, context}
  end

  @impl true
  def on_payment_failure(context, _metadata), do: {:cont, context}
end

Summary

Types

Return type for after_payment/2.

Return type for before_payment/2.

Hook callback identifier.

Hook execution errors returned by X402.Client.build_payment/3.

Lifecycle callback metadata passed to hooks.

Return type for on_payment_failure/2.

Callbacks

Runs after the payment payload has been built.

Runs after requirements selection and before the payment is signed.

Runs when building the payment payload fails.

Functions

Validates that a value is a module implementing X402.Client.Hooks.

Types

after_result()

@type after_result() :: {:cont, X402.Client.Hooks.Context.t()}

Return type for after_payment/2.

before_result()

@type before_result() :: {:cont, X402.Client.Hooks.Context.t()} | {:halt, term()}

Return type for before_payment/2.

callback_name()

@type callback_name() :: :before_payment | :after_payment | :on_payment_failure

Hook callback identifier.

hook_error()

@type hook_error() ::
  {:hook_halted, callback_name(), term()}
  | {:hook_callback_failed, callback_name(), term()}
  | {:hook_invalid_return, callback_name(), term()}

Hook execution errors returned by X402.Client.build_payment/3.

metadata()

@type metadata() :: %{
  operation: :build_payment,
  hook_module: module(),
  scheme: String.t() | nil,
  network: String.t() | nil
}

Lifecycle callback metadata passed to hooks.

on_failure_result()

@type on_failure_result() ::
  {:cont, X402.Client.Hooks.Context.t()} | {:recover, map()}

Return type for on_payment_failure/2.

Callbacks

after_payment(t, metadata)

@callback after_payment(X402.Client.Hooks.Context.t(), metadata()) :: after_result()

Runs after the payment payload has been built.

before_payment(t, metadata)

@callback before_payment(X402.Client.Hooks.Context.t(), metadata()) :: before_result()

Runs after requirements selection and before the payment is signed.

on_payment_failure(t, metadata)

@callback on_payment_failure(X402.Client.Hooks.Context.t(), metadata()) ::
  on_failure_result()

Runs when building the payment payload fails.

Functions

validate_module(module)

(since 0.9.0)
@spec validate_module(term()) :: {:ok, module()} | {:error, String.t()}

Validates that a value is a module implementing X402.Client.Hooks.

Designed for NimbleOptions custom validation.

Examples

iex> X402.Client.Hooks.validate_module(X402.Client.Hooks.Default)
{:ok, X402.Client.Hooks.Default}

iex> X402.Client.Hooks.validate_module(Enum)
{:error, "expected a module implementing X402.Client.Hooks"}