Behaviour for client-side transports that bridge a protocol response/request pair and the MPP payment flow.
A Transport implementation knows how to:
- Detect whether a response signals "payment required" (HTTP 402, JSON-RPC
error code
-32042, etc.) - Extract the challenges carried on that response
- 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
payment_required?/1— plain boolean check on a responseget_challenges/1— parse the response into a challenge listset_credential/2— return a new request with the credential attached in a transport-specific way
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
via the optional :accept_payment keyword to select_challenge/3.
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
select_challenge | 3 | Pick the first challenge whose method+intent is supported by a MultiProvider. | challenges: value, multi: value, opts: 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 supported challenge, optionally ranked by Accept-Payment.
Callbacks
@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".
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.
@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
@spec select_challenge([MPP.Challenge.t()], MPP.Client.MultiProvider.t(), keyword()) :: {:ok, MPP.Challenge.t()} | {:error, :no_supported_challenge}
Pick the first supported challenge, optionally ranked by Accept-Payment.
When :accept_payment is provided, challenges are reordered by client
preferences before the first MultiProvider-supported match is chosen.
Malformed preference lists are ignored (baseline server-offer order).
Returns {:error, :no_supported_challenge} if no challenge matches any
provider, including the empty-list case.