MPP.Method behaviour (mpp v0.6.0)

Copy Markdown View Source

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:

  1. method_name/0 — lowercase identifier for protocol headers
  2. verify/2 — verify a credential payload against a charge intent
  3. challenge_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
end

Design

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

challenge_method_details(charge)

(optional)
@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).

method_name()

@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.

validate_config!(config)

(optional)
@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).

verify(payload, charge)

@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 — the MPP.Intents.Charge struct 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