Help us shape the future of Go! Take the 2026 Go Developer Survey

mpp

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT, Apache-2.0, MIT Imports: 18 Imported by: 1

Documentation

Overview

Package mpp implements the Machine Payments Protocol (MPP) types and header serialization for Go. It provides Challenge, Credential, and Receipt types along with authentication and receipt header parsing and formatting helpers.

Index

Examples

Constants

View Source
const (
	HeaderAcceptPayment          = "Accept-Payment"
	HeaderAuthorization          = "Authorization"
	HeaderPaymentAuthorization   = "Payment-Authorization"
	HeaderPaymentReceipt         = "Payment-Receipt"
	HeaderPaymentSession         = "Payment-Session"
	HeaderPaymentSessionSnapshot = "Payment-Session-Snapshot"
	HeaderWWWAuthenticate        = "WWW-Authenticate"
)

MPP HTTP header names.

View Source
const (
	IntentCharge  = "charge"
	IntentSession = "session"
)

MPP payment intent names.

View Source
const (
	HintPaymentRequired     = "" /* 167-byte string literal not displayed */
	HintMalformedCredential = "" /* 171-byte string literal not displayed */
	HintMethodUnsupported   = HintPaymentRequired
)

Default hints for error types that support them.

View Source
const (
	// MCPPaymentRequiredCode reports that a tool call requires payment.
	MCPPaymentRequiredCode = -32042
	// MCPPaymentVerificationFailedCode reports that a submitted Credential was rejected.
	MCPPaymentVerificationFailedCode = -32043
	// MCPInvalidParamsCode reports a malformed Credential or payload.
	MCPInvalidParamsCode = -32602
	// MCPInternalErrorCode reports a server-side payment processing failure.
	MCPInternalErrorCode = -32603
)

MCP JSON-RPC error codes for payment failures, matching mppx's Mcp module.

View Source
const (
	// MCPCredentialMetaKey carries the Credential in a tool call's params._meta.
	MCPCredentialMetaKey = "org.paymentauth/credential"
	// MCPPaymentRequiredMetaKey carries MCPPaymentRequiredData in a tool result's _meta.
	MCPPaymentRequiredMetaKey = "org.paymentauth/payment-required"
	// MCPReceiptMetaKey carries the MCPReceipt in a tool result's _meta.
	MCPReceiptMetaKey = "org.paymentauth/receipt"
)

MCP _meta keys for payment data.

View Source
const (
	SchemePayment = "Payment"
)

MPP HTTP authentication schemes.

Variables

View Source
var ErrVerification = errors.New("payment verification failed")

ErrVerification is a sentinel error for payment verification failures.

Functions

func AdvertisedCredentialHeader added in v0.4.0

func AdvertisedCredentialHeader(header string) string

AdvertisedCredentialHeader returns a challenge header parameter value, or empty for the implicit Authorization default.

func B64Decode

func B64Decode(s string) (map[string]any, error)

B64Decode decodes a base64url (no padding) string into a map.

Numbers decode as json.Number rather than float64. The challenge ID is an HMAC over the re-encoded request, so a decode that cannot reproduce the bytes it was given cannot reproduce the ID either: float64 carries 53 bits of mantissa, and any JSON integer above 2^53 comes back rounded, making a challenge the server itself issued fail to verify.

func ChallengeBoundJSONEqual added in v0.4.0

func ChallengeBoundJSONEqual(left, right any) bool

ChallengeBoundJSONEqual compares values using the RFC 8785 representation used to bind challenge requests and opaque values into their HMAC IDs.

func CheckExpires added in v0.5.1

func CheckExpires(expires string, now time.Time) error

CheckExpires checks a challenge's expires parameter at now, as mppx's Expires.assert does. It returns an invalid-challenge PaymentError when expires is empty or malformed, and ErrPaymentExpired when now is at or after it: a challenge is no longer valid at its exact expiry instant.

func ComputeBodyDigest added in v0.5.1

func ComputeBodyDigest(body []byte) string

ComputeBodyDigest returns the SHA-256 digest of body in the form "sha-256=<base64>", the value of a challenge's digest parameter. Pass the raw request bytes so the digest binds exactly what the client sent.

func ConstantTimeEqual

func ConstantTimeEqual(a, b string) bool

ConstantTimeEqual returns true if a and b are equal, using constant-time comparison to avoid timing side-channels.

func ExpiresIn added in v0.5.1

func ExpiresIn(d time.Duration) string

ExpiresIn returns the RFC 3339 timestamp d from now, in UTC with millisecond precision and a Z suffix, as mppx's Expires helpers emit. Use it for a challenge's expires parameter:

mpp.WithExpires(mpp.ExpiresIn(5 * time.Minute))
Example
package main

import (
	"fmt"
	"time"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	expires := mpp.ExpiresIn(5 * time.Minute)

	// Issue a challenge with mpp.WithExpires(expires); verify it later.
	fmt.Println(mpp.CheckExpires(expires, time.Now()))
	fmt.Println(mpp.CheckExpires(expires, time.Now().Add(10*time.Minute)) != nil)
}
Output:
<nil>
true

func ExtractAuthorizationScheme

func ExtractAuthorizationScheme(header, scheme string) string

ExtractAuthorizationScheme returns the first authorization value that matches the requested scheme from a possibly merged Authorization header.

func ExtractAuthorizationSchemeStrict added in v0.2.0

func ExtractAuthorizationSchemeStrict(header, scheme string) (string, error)

ExtractAuthorizationSchemeStrict returns a single authorization value that matches scheme, or an error if the header includes multiple matching values.

func FindPaymentAuthorization

func FindPaymentAuthorization(header string) string

FindPaymentAuthorization returns the Payment credential from an Authorization header. It tolerates comma-separated schemes and ignores non-Payment values.

func FindPaymentAuthorizationStrict added in v0.2.0

func FindPaymentAuthorizationStrict(header string) (string, error)

FindPaymentAuthorizationStrict returns the Payment credential from an Authorization header, or an error if more than one Payment credential exists.

func FormatChallenge added in v0.5.1

func FormatChallenge(c *Challenge) (string, error)

FormatChallenge formats c as a WWW-Authenticate header value, the inverse of ParseChallenge:

Payment id="...", realm="...", method="...", intent="...", request="..."

It writes c.Realm, the realm the challenge ID binds. It rejects a nil challenge, values that cannot be safely represented in quoted auth-params, an invalid advertised credential header, empty required auth-params (id, realm, method, intent, request), which would otherwise be omitted and yield a challenge ParseChallenge rejects, a method that is not a valid method name (lowercase ASCII letters), a RequestB64 that is not base64url-encoded JSON, and a header over the size limit.

Example
package main

import (
	"fmt"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	challenge, err := mpp.NewChallengeWithError(
		"server-secret", "api.example.com", "tempo", mpp.IntentCharge,
		map[string]any{"amount": "1000000", "currency": "0x20c0000000000000000000006a37DA5C996874BE"},
	)
	if err != nil {
		panic(err)
	}
	header, err := mpp.FormatChallenge(challenge)
	if err != nil {
		panic(err)
	}
	fmt.Println(header)
}
Output:
Payment id="qEJM3P0jKiqImybGyCeUMeHy2beC0JTnqS_TTde14lY", realm="api.example.com", method="tempo", intent="charge", request="eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDIwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDZhMzdEQTVDOTk2ODc0QkUifQ"

func FormatCredential added in v0.5.1

func FormatCredential(c *Credential) (string, error)

FormatCredential formats c as an Authorization (or Payment-Authorization) header value, the inverse of ParseCredential:

Payment <base64url-json>

It returns an error for a nil credential, for a credential ParseCredential would reject because its challenge echo lacks id, method, intent or request, its method is not a valid method name, its advertised credential header is not a valid header name or its payload is nil (payload must be a JSON object), for values JSON cannot encode, and for a header over the size limit.

func FormatReceiptWithError added in v0.5.1

func FormatReceiptWithError(r *Receipt) (string, error)

FormatReceiptWithError formats r as a Payment-Receipt header value, the inverse of ParseReceipt:

<base64url-json>

It returns an error for a nil receipt, for a receipt ParseReceipt would reject because its status is not "success", its reference or method is missing, its method is not a valid method name or the encoded header exceeds the size limit, and for Extensions values JSON cannot encode.

func GenerateChallengeIDWithError added in v0.4.0

func GenerateChallengeIDWithError(opts GenerateChallengeIDInput) (string, error)

GenerateChallengeIDWithError produces an HMAC-SHA256 challenge ID from the given inputs and rejects ambiguous fields, invalid advertised credential headers and request values that JCS cannot represent exactly. The HMAC is computed over pipe-delimited fields:

realm|method|intent|request_b64|expires|digest|opaque_b64

Challenges that advertise a credential header insert it immediately before the final opaque slot. Authorization is the implicit default and is never included, preserving the legacy binding. The result is base64url-encoded without padding.

func JSONEqual

func JSONEqual(left, right any) bool

JSONEqual compares two JSON-like values using Go's stable JSON encoding.

func MCPErrorCode added in v0.5.0

func MCPErrorCode(err error) int

MCPErrorCode maps a payment error to its MCP JSON-RPC error code. A nil error or a payment-required error maps to MCPPaymentRequiredCode, malformed credentials and invalid payloads to MCPInvalidParamsCode, internal payment errors and non-payment errors to MCPInternalErrorCode, and every other payment error to MCPPaymentVerificationFailedCode.

func ParseAmount added in v0.5.1

func ParseAmount(value string, decimals int) (*big.Int, error)

ParseAmount converts a human-readable decimal string to base units. For example, ParseAmount("1.5", 6) returns 1500000. The result is unbounded, so 18-decimal token amounts do not overflow. It returns an error if value is empty, negative or not a plain decimal (digits with an optional single "."), if it would produce fractional base units, or if decimals is negative.

func ParseExpires added in v0.5.1

func ParseExpires(s string) (time.Time, error)

ParseExpires parses an expires timestamp in the RFC 3339 format challenges use, with or without fractional seconds.

func SplitAuthenticate

func SplitAuthenticate(header string) []string

SplitAuthenticate splits a potentially merged authentication header value into individual scheme values while preserving commas inside quoted auth-param values.

func VerifyBodyDigest added in v0.5.1

func VerifyBodyDigest(digest string, body []byte) bool

VerifyBodyDigest reports whether digest is the ComputeBodyDigest of body, using constant-time comparison.

Types

type Challenge

type Challenge struct {
	ID          string         `json:"id"`
	Method      string         `json:"method"`
	Intent      string         `json:"intent"`
	Request     map[string]any `json:"request,omitempty"`
	Realm       string         `json:"realm,omitempty"`
	RequestB64  string         `json:"requestB64,omitempty"`
	Digest      string         `json:"digest,omitempty"`
	Expires     string         `json:"expires,omitempty"`
	Description string         `json:"description,omitempty"`
	// Opaque is the decoded opaque auth-param: server-defined metadata that
	// clients echo unchanged. mppx calls it meta; set it with WithMeta.
	Opaque map[string]string `json:"opaque,omitempty"`

	// Header is the HTTP field for the Payment credential. Empty means the
	// implicit default, Authorization. When set, it is advertised as the
	// challenge header parameter and bound into the challenge ID.
	Header string `json:"header,omitempty"`
	// contains filtered or unexported fields
}

Challenge represents a server-issued payment challenge sent via the WWW-Authenticate header.

func ChallengesFromResponse added in v0.5.1

func ChallengesFromResponse(resp *http.Response) ([]*Challenge, error)

ChallengesFromResponse parses every Payment challenge of a 402 response, in order, as mppx's Challenge.fromResponseList does. It reads every WWW-Authenticate field, splits values that merge several challenges, and ignores other authentication schemes. It returns an error when resp is nil or not a 402, when it carries no Payment challenge, or when one fails to parse.

func NewChallengeWithError added in v0.4.0

func NewChallengeWithError(secretKey, realm, method, intent string, request map[string]any, opts ...ChallengeOption) (*Challenge, error)

NewChallengeWithError creates a new Challenge with an HMAC-bound ID computed from secretKey over realm and the other challenge parameters (see GenerateChallengeIDWithError). It rejects invalid advertised credential headers and request values that JCS cannot represent without changing their numeric value. Check the ID of an echoed challenge with Challenge.VerifyID, and its Realm against the realm you expect.

Example
package main

import (
	"fmt"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	challenge, err := mpp.NewChallengeWithError(
		"server-secret", "api.example.com", "tempo", mpp.IntentCharge,
		map[string]any{"amount": "1000000"},
		mpp.WithExpires("2030-01-01T00:00:00.000Z"),
	)
	if err != nil {
		panic(err)
	}
	fmt.Println(challenge.VerifyID("server-secret"))

	// The ID binds every parameter, including the realm.
	moved := *challenge
	moved.Realm = "other.example.com"
	fmt.Println(moved.VerifyID("server-secret"))
}
Output:
true
false

func ParseChallenge

func ParseChallenge(header string) (*Challenge, error)

ParseChallenge parses a Payment challenge from a WWW-Authenticate header value.

Example
package main

import (
	"fmt"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	header := "Payment id=\"abc\", realm=\"api.example.com\", method=\"tempo\", intent=\"charge\", request=\"eyJhbW91bnQiOiIxMDAwMDAwIn0\""
	challenge, err := mpp.ParseChallenge(header)
	if err != nil {
		panic(err)
	}
	fmt.Println(challenge.Realm, challenge.Method, challenge.Intent, challenge.Request["amount"])
}
Output:
api.example.com tempo charge 1000000

func (*Challenge) CredentialHeader added in v0.4.0

func (c *Challenge) CredentialHeader() string

CredentialHeader returns the HTTP field a client must use for the payment credential. Challenges that omit header default to Authorization.

func (Challenge) MarshalJSON

func (c Challenge) MarshalJSON() ([]byte, error)

MarshalJSON emits the standard JSON challenge shape with a decoded request object, while keeping RequestB64 as an internal cache for header formatting.

func (*Challenge) NewCredential

func (c *Challenge) NewCredential(payload map[string]any, opts ...CredentialOption) *Credential

NewCredential builds a Credential from a Challenge and payment payload.

func (*Challenge) ToEcho

func (c *Challenge) ToEcho() ChallengeEcho

ToEcho returns the ChallengeEcho representation suitable for inclusion in a Credential.

func (*Challenge) UnmarshalJSON

func (c *Challenge) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the standard JSON challenge shape and computes the cached RequestB64 field used by header serialization.

func (*Challenge) VerifyID added in v0.5.1

func (c *Challenge) VerifyID(secretKey string) bool

VerifyID reports whether the challenge ID is the HMAC that secretKey binds to the challenge's parameters, including c.Realm. It uses constant-time comparison. A parsed challenge whose issuer encoded opaque non-canonically is verified against that encoding, as it is echoed.

VerifyID only proves that the holder of secretKey issued the ID for c.Realm; it does not check that c.Realm is a realm you serve. A server that shares secretKey across realms must also compare c.Realm with its own expected realm, or a challenge issued for another realm verifies here.

type ChallengeEcho

type ChallengeEcho struct {
	ID      string            `json:"id"`
	Realm   string            `json:"realm,omitempty"`
	Method  string            `json:"method"`
	Intent  string            `json:"intent"`
	Request string            `json:"request,omitempty"`
	Expires string            `json:"expires,omitempty"`
	Digest  string            `json:"digest,omitempty"`
	Opaque  map[string]string `json:"opaque,omitempty"`
	Header  string            `json:"header,omitempty"`
	// contains filtered or unexported fields
}

ChallengeEcho is the subset of a Challenge echoed back in a Credential.

type ChallengeOption

type ChallengeOption func(*challengeConfig)

ChallengeOption configures optional fields when creating a new Challenge.

func WithDescription

func WithDescription(desc string) ChallengeOption

WithDescription sets the human-readable description on a Challenge.

func WithDigest

func WithDigest(digest string) ChallengeOption

WithDigest sets the body digest on a Challenge.

func WithExpires

func WithExpires(expires string) ChallengeOption

WithExpires sets the expiration timestamp on a Challenge.

func WithHeader added in v0.4.0

func WithHeader(header string) ChallengeOption

WithHeader sets the HTTP field that must carry the Payment credential. Authorization is the implicit default and is not advertised on the wire.

func WithMeta

func WithMeta(meta map[string]string) ChallengeOption

WithMeta sets opaque metadata key-value pairs on a Challenge.

type Credential

type Credential struct {
	Challenge ChallengeEcho  `json:"challenge"`
	Payload   map[string]any `json:"payload,omitempty"`
	Source    string         `json:"source,omitempty"`
}

Credential represents a client-submitted payment proof. It is sent in Authorization by default, or in Payment-Authorization when the challenge advertised that header.

func ParseCredential

func ParseCredential(header string) (*Credential, error)

ParseCredential parses a Payment credential from an Authorization header value.

Expected format: Payment <base64url-json> The JSON payload contains: challenge (echo), payload, and optional source.

func (Credential) MarshalJSON

func (c Credential) MarshalJSON() ([]byte, error)

MarshalJSON emits the ergonomic JSON credential shape with a decoded challenge request object instead of the internal base64url string.

func (*Credential) UnmarshalJSON

func (c *Credential) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a credential with either an object request or the raw base64url echo string and normalizes it into the wire-centric ChallengeEcho.

type CredentialOption

type CredentialOption func(*Credential)

CredentialOption configures optional fields when creating a Credential.

func WithCredentialSource

func WithCredentialSource(source string) CredentialOption

WithCredentialSource sets the optional payer identity on a Credential.

type ErrorType

type ErrorType string

ErrorType identifies a machine-readable MPP problem type.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	err := fmt.Errorf("charge: %w", mpp.ErrMalformedCredential("invalid base64url"))

	fmt.Println(errors.Is(err, mpp.ErrorTypeMalformedCredential))
	fmt.Println(errors.Is(err, mpp.ErrorTypePaymentExpired))

	var pe *mpp.PaymentError
	if errors.As(err, &pe) {
		fmt.Println(pe.Status, pe.Title)
	}
}
Output:
true
false
402 Malformed Credential
const (
	ErrorTypePaymentRequired     ErrorType = "https://paymentauth.org/problems/payment-required"
	ErrorTypeMalformedCredential ErrorType = "https://paymentauth.org/problems/malformed-credential"
	ErrorTypeInvalidChallenge    ErrorType = "https://paymentauth.org/problems/invalid-challenge"
	ErrorTypeVerificationFailed  ErrorType = "https://paymentauth.org/problems/verification-failed"
	ErrorTypePaymentExpired      ErrorType = "https://paymentauth.org/problems/payment-expired"
	ErrorTypeInvalidPayload      ErrorType = "https://paymentauth.org/problems/invalid-payload"
	ErrorTypeBadRequest          ErrorType = "https://paymentauth.org/problems/bad-request"
	ErrorTypePaymentInsufficient ErrorType = "https://paymentauth.org/problems/payment-insufficient"
	ErrorTypeMethodUnsupported   ErrorType = "https://paymentauth.org/problems/method-unsupported"
	ErrorTypeInternalPayment     ErrorType = "https://paymentauth.org/problems/internal-payment-error"
	// ErrorTypePaymentActionRequired reports a payment that needs further
	// action by the payer, such as a Stripe PaymentIntent in requires_action.
	ErrorTypePaymentActionRequired ErrorType = "https://paymentauth.org/problems/payment-action-required"
	// ErrorTypePaymentOutcomeUnknown reports a credential that was sent but
	// whose payment may or may not have settled. Do not blindly retry.
	ErrorTypePaymentOutcomeUnknown ErrorType = "https://paymentauth.org/problems/payment-outcome-unknown"
)

func (ErrorType) Error added in v0.5.1

func (t ErrorType) Error() string

Error returns the problem type URI, so an ErrorType can be the target of errors.Is: errors.Is(err, ErrorTypeMalformedCredential) reports whether err wraps a PaymentError of that type.

func (ErrorType) Status

func (t ErrorType) Status() int

Status returns the default HTTP status for the error type.

func (ErrorType) Title

func (t ErrorType) Title() string

Title returns the default RFC 9457 title for the error type.

type GenerateChallengeIDInput

type GenerateChallengeIDInput struct {
	SecretKey string
	Realm     string
	Method    string
	Intent    string
	Request   map[string]any
	Expires   string
	Digest    string
	Opaque    map[string]string
	Header    string
}

GenerateChallengeIDInput holds the inputs for HMAC-based challenge ID generation.

type MCPPaymentRequiredData added in v0.5.0

type MCPPaymentRequiredData struct {
	// HTTPStatus is the HTTP status the same failure would have, such as 402.
	HTTPStatus int `json:"httpStatus"`
	// Challenges lists the offered Challenges in presentation order.
	Challenges []*Challenge `json:"challenges"`
	// Problem holds RFC 9457 problem details when a Credential was rejected.
	Problem map[string]any `json:"problem,omitempty"`
}

MCPPaymentRequiredData is the data of an MCP payment error, or the value of MCPPaymentRequiredMetaKey in a tool result.

type MCPReceipt added in v0.5.0

type MCPReceipt struct {
	Receipt
	// ChallengeID is the ID of the Challenge the Credential answered.
	ChallengeID string
}

MCPReceipt is a Receipt carried in MCP tool result metadata, bound to the Challenge it settles.

func (MCPReceipt) MarshalJSON added in v0.5.0

func (r MCPReceipt) MarshalJSON() ([]byte, error)

MarshalJSON emits the canonical receipt fields plus challengeId.

func (*MCPReceipt) UnmarshalJSON added in v0.5.0

func (r *MCPReceipt) UnmarshalJSON(data []byte) error

UnmarshalJSON validates the receipt fields and reads challengeId.

type PaymentError

type PaymentError struct {
	Type   ErrorType `json:"type"`
	Title  string    `json:"title"`
	Status int       `json:"status"`
	Detail string    `json:"detail"`
	Hint   string    `json:"hint,omitempty"`
	// Details contains safe machine-readable error metadata.
	Details map[string]any `json:"details,omitempty"`
	// contains filtered or unexported fields
}

PaymentError represents an MPP error using RFC 9457 Problem Details.

Match its problem type through wrapping with errors.Is and an ErrorType:

if errors.Is(err, mpp.ErrorTypePaymentExpired) { ... }

errors.As(err, &pe) reaches the PaymentError itself.

func ErrBadRequest

func ErrBadRequest(reason string) *PaymentError

ErrBadRequest returns a generic 400 error.

func ErrCredentialReplayed added in v0.4.0

func ErrCredentialReplayed(reason string) *PaymentError

ErrCredentialReplayed returns a 403 verification-failed error for a credential whose payment was already used: consumed by an earlier request or claimed by a concurrent one. The payment itself was valid, so the response carries no retry challenge and auto-paying clients do not pay again. The problem type stays verification-failed; only the status differs.

func ErrInternalPayment added in v0.4.0

func ErrInternalPayment() *PaymentError

ErrInternalPayment returns a 500 error for unexpected payment processing failures. Its detail is fixed so internal error messages are never exposed.

func ErrInvalidChallenge

func ErrInvalidChallenge(challengeID, reason string) *PaymentError

ErrInvalidChallenge returns a 402 error for invalid or tampered challenges.

func ErrInvalidPayload

func ErrInvalidPayload(reason string) *PaymentError

ErrInvalidPayload returns a 402 error for invalid payment payloads.

func ErrMalformedCredential

func ErrMalformedCredential(reason string) *PaymentError

ErrMalformedCredential returns a 402 error for unparseable credentials.

func ErrMethodUnsupported

func ErrMethodUnsupported(method string) *PaymentError

ErrMethodUnsupported returns a 400 error for unsupported payment methods.

func ErrPaymentActionRequired added in v0.5.1

func ErrPaymentActionRequired(reason string) *PaymentError

ErrPaymentActionRequired returns a 402 error for a payment that requires further action by the payer. reason may be empty. It matches mppx's PaymentActionRequiredError.

func ErrPaymentExpired

func ErrPaymentExpired(expires string) *PaymentError

ErrPaymentExpired returns a 402 error for expired payment challenges.

func ErrPaymentInsufficient

func ErrPaymentInsufficient(reason string) *PaymentError

ErrPaymentInsufficient returns a 402 error for insufficient payment amounts.

func ErrPaymentOutcomeUnknown added in v0.5.1

func ErrPaymentOutcomeUnknown(challengeID string) *PaymentError

ErrPaymentOutcomeUnknown returns a 402 error for a credential that was sent for challengeID without a definitive result, so the payment may have settled. It matches mppx's PaymentOutcomeUnknownError. The server middleware writers send it without retry challenges, and client.Transport returns such a response instead of paying again.

func ErrPaymentRequired

func ErrPaymentRequired(realm, description string) *PaymentError

ErrPaymentRequired returns a 402 Payment Required error.

func ErrVerificationFailed

func ErrVerificationFailed(reason string) *PaymentError

ErrVerificationFailed returns a 402 error for failed payment verification.

func (*PaymentError) Error

func (e *PaymentError) Error() string

func (*PaymentError) Is added in v0.5.1

func (e *PaymentError) Is(target error) bool

Is reports whether target is the ErrorType of e, so errors.Is(err, ErrorTypeVerificationFailed) matches a wrapped PaymentError of that type.

func (*PaymentError) ProblemDetails

func (e *PaymentError) ProblemDetails(challengeID string) map[string]any

ProblemDetails returns an RFC 9457 problem details map, optionally including a challengeID if non-empty.

func (*PaymentError) Unwrap

func (e *PaymentError) Unwrap() error

Unwrap returns the cause attached with WithCause, if any, so errors.Is and errors.As reach it. Otherwise verification errors unwrap to ErrVerification.

func (*PaymentError) WithCause added in v0.4.0

func (e *PaymentError) WithCause(cause error) *PaymentError

WithCause returns a copy of e that unwraps to cause. The cause is not part of the problem details sent to clients.

type Receipt

type Receipt struct {
	Status         string    `json:"status"`
	Timestamp      time.Time `json:"timestamp"`
	Reference      string    `json:"reference"`
	Method         string    `json:"method,omitempty"`
	ExternalID     string    `json:"externalId,omitempty"`
	SubscriptionID string    `json:"subscriptionId,omitempty"`
	// FundingCurrency is the currency actually debited to fund the payment,
	// when the payment method knows it.
	FundingCurrency string `json:"fundingCurrency,omitempty"`

	// Extensions holds method-defined top-level receipt fields beyond the
	// base set. draft-ietf-httpauth-payment §5.3 states "Payment method
	// specifications MAY define additional fields for receipts", and the
	// canonical mppx schema preserves such unknown fields across
	// parse/serialize round trips rather than discarding them. These are
	// flattened to the top level of the Payment-Receipt JSON. A top-level
	// "extra" field is an ordinary extension, kept as Extensions["extra"],
	// except that an empty "extra" object is dropped. The tag is "-" because
	// serialization is handled explicitly by FormatReceiptWithError, not via
	// struct marshaling. Set it with WithExtensions.
	Extensions map[string]any `json:"-"`
}

Receipt represents a server-issued payment receipt sent via the Payment-Receipt header.

func ParseReceipt

func ParseReceipt(header string) (*Receipt, error)

ParseReceipt parses a Payment-Receipt header value into a Receipt. It requires status "success", reference, method and an RFC 3339 timestamp, and keeps method-defined top-level fields in Receipt.Extensions.

Expected format: <base64url-json>

Example
package main

import (
	"fmt"
	"time"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	header, err := mpp.FormatReceiptWithError(&mpp.Receipt{
		Status:    "success",
		Method:    "tempo",
		Reference: "0x9f3c",
		Timestamp: time.Date(2026, 1, 29, 12, 0, 0, 0, time.UTC),
	})
	if err != nil {
		panic(err)
	}
	receipt, err := mpp.ParseReceipt(header)
	if err != nil {
		panic(err)
	}
	fmt.Println(receipt.Method, receipt.Reference, receipt.Timestamp.Format(time.RFC3339))
}
Output:
tempo 0x9f3c 2026-01-29T12:00:00Z

func ReceiptFromResponse added in v0.5.1

func ReceiptFromResponse(resp *http.Response) (*Receipt, error)

ReceiptFromResponse parses the Payment-Receipt header of resp, as mppx's Receipt.fromResponse does. It returns an error when resp is nil, the header is missing, or ParseReceipt rejects it.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/tempoxyz/mpp-go/pkg/mpp"
)

func main() {
	header, err := mpp.FormatReceiptWithError(mpp.Success("tempo", "0x9f3c"))
	if err != nil {
		panic(err)
	}
	resp := &http.Response{StatusCode: http.StatusOK, Header: http.Header{}}
	resp.Header.Set(mpp.HeaderPaymentReceipt, header)

	receipt, err := mpp.ReceiptFromResponse(resp)
	if err != nil {
		panic(err)
	}
	fmt.Println(receipt.Status, receipt.Reference)
}
Output:
success 0x9f3c

func Success

func Success(method, reference string, opts ...ReceiptOption) *Receipt

Success creates a new successful Receipt with the given method, reference, and options.

type ReceiptOption

type ReceiptOption func(*Receipt)

ReceiptOption configures optional fields when creating a Receipt.

func WithExtensions added in v0.5.1

func WithExtensions(extensions map[string]any) ReceiptOption

WithExtensions sets method-defined top-level fields on a Receipt. Keys of base receipt fields (status, timestamp, reference, method, externalId, subscriptionId) are ignored when formatting.

func WithExternalID

func WithExternalID(id string) ReceiptOption

WithExternalID sets the external transaction ID on a Receipt.

func WithFundingCurrency added in v0.4.0

func WithFundingCurrency(currency string) ReceiptOption

WithFundingCurrency sets the currency actually debited to fund the payment.

func WithSubscriptionID added in v0.4.0

func WithSubscriptionID(id string) ReceiptOption

WithSubscriptionID sets the server-issued subscription ID on a Receipt.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL