MPP.Client.Transport behaviour (mpp v0.6.1)

Copy Markdown View Source

Behaviour for client-side transports that bridge a protocol response/request pair and the MPP payment flow.

A Transport implementation knows how to:

  1. Detect whether a response signals "payment required" (HTTP 402, JSON-RPC error code -32042, etc.)
  2. Extract the challenges carried on that response
  3. Attach a decoded credential to a new request for retry

This is the transport-shaped glue that sits between the provider-side MPP.Client.PaymentProvider abstraction and whatever concrete client (Req, an MCP JSON-RPC client, etc.) is actually issuing requests.

Callbacks

Selection

select_challenge/2 is a small helper (not a callback) that picks the first challenge whose method/intent is supported by a MPP.Client.MultiProvider. This matches the baseline "first supported in server offer order" behaviour shared with the reference SDKs; ranking via Accept-Payment is layered on top later and is out of scope for the transport itself.

API Functions

FunctionArityDescriptionParam Kinds
select_challenge2Pick the first challenge whose method+intent is supported by a MultiProvider.challenges: value, multi: value

Summary

Callbacks

Extract the MPP.Challenge list carried on a payment-required response.

Return true if the response signals that a payment is required.

Return a new request with the given credential attached in transport-specific form.

Functions

Pick the first challenge whose method/intent is supported by the MultiProvider.

Callbacks

get_challenges(response)

@callback get_challenges(response :: term()) ::
  {:ok, [MPP.Challenge.t()]} | {:error, term()}

Extract the MPP.Challenge list carried on a payment-required response.

Returning {:ok, []} is not a valid outcome — an empty or unparseable challenge set should surface as {:error, reason} so callers can distinguish "402 with no Payment challenges" from "402 with N challenges".

payment_required?(response)

@callback payment_required?(response :: term()) :: boolean()

Return true if the response signals that a payment is required.

For HTTP, this is status == 402. For MCP/JSON-RPC, this is error.code == -32042.

set_credential(request, credential)

@callback set_credential(request :: term(), credential :: MPP.Credential.t()) :: term()

Return a new request with the given credential attached in transport-specific form.

For HTTP, this sets Authorization: Payment <base64url>. For MCP, this injects the credential into params._meta["org.paymentauth/credential"]. Each transport owns its wire format — the credential struct is passed in decoded form so the transport is free to serialise it however it needs to.

Functions

select_challenge(challenges, multi)

@spec select_challenge([MPP.Challenge.t()], MPP.Client.MultiProvider.t()) ::
  {:ok, MPP.Challenge.t()} | {:error, :no_supported_challenge}

Pick the first challenge whose method/intent is supported by the MultiProvider.

Preserves server offer order — the first supported challenge wins, matching MPP.Client.MultiProvider.pay/2's dispatch order. Callers that need preference-based ranking (via Accept-Payment) should layer that on top.

Returns {:error, :no_supported_challenge} if no challenge matches any provider, including the empty-list case.