Behaviour for pluggable payment method verification.
A payment method connects the MPP protocol to a real payment system
(Stripe, Tempo, x402/EVM, etc.). Each method module implements three
callbacks that the MPP.Plug middleware uses during the 402 handshake:
method_name/0— lowercase identifier for protocol headersverify/2— verify a credential payload against a charge intentchallenge_method_details/1— (optional) add method-specific fields to challenges
Usage
defmodule MyApp.Payments.Stripe do
use MPP.Method
@impl MPP.Method
def method_name, do: "stripe"
@impl MPP.Method
def verify(payload, charge) do
# payload is the credential's method-specific proof map
# charge is the validated MPP.Intents.Charge struct
spt = Map.fetch!(payload, "spt")
# ... verify with Stripe API ...
{:ok, MPP.Receipt.new(method: "stripe", reference: "pi_abc123")}
end
@impl MPP.Method
def challenge_method_details(_charge) do
%{"networkId" => "...", "paymentMethodTypes" => ["card"]}
end
endDesign
Intent = Schema, Method = Implementation. All methods share the same
MPP.Intents.Charge struct — methods only handle verification. The payload
argument is the raw map from the credential (method-specific proof), not the
full MPP.Credential struct. Methods don't need to know about protocol framing.
Summary
Callbacks
Returns method-specific fields to include in the challenge request.
Returns the lowercase payment method name (e.g., "stripe", "tempo").
Validates method_config at init time. Raises on missing required keys.
Verifies a payment credential payload against a charge intent.
Callbacks
@callback challenge_method_details(charge :: MPP.Intents.Charge.t()) :: map() | nil
Returns method-specific fields to include in the challenge request.
Called when generating a 402 challenge. The returned map is merged into
charge.method_details so the client knows method-specific requirements
(e.g., Stripe's networkId and paymentMethodTypes).
The default implementation returns nil (no extra fields).
@callback method_name() :: String.t()
Returns the lowercase payment method name (e.g., "stripe", "tempo").
Used in the challenge method field and the receipt method field.
Must be a stable, lowercase string identifier.
@callback validate_config!(config :: map()) :: :ok
Validates method_config at init time. Raises on missing required keys.
Called during MPP.Plug init for each method entry. Use this to
fail fast on misconfiguration (e.g., missing Stripe secret key or
network ID) rather than discovering it at request time.
The default implementation is a no-op (all configs accepted).
@callback verify(payload :: map(), charge :: MPP.Intents.Charge.t()) :: {:ok, MPP.Receipt.t()} | {:error, MPP.Errors.t()}
Verifies a payment credential payload against a charge intent.
Arguments
payload— method-specific proof map from the credential (e.g.,%{"spt" => "spt_..."}for Stripe)charge— theMPP.Intents.Chargestruct from the challenge, containing amount, currency, recipient, etc.
Returns
{:ok, MPP.Receipt.t()}— payment verified successfully{:error, MPP.Errors.t()}— verification failed with a typed RFC 9457 error