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 ¶
- Constants
- Variables
- func AdvertisedCredentialHeader(header string) string
- func B64Decode(s string) (map[string]any, error)
- func ChallengeBoundJSONEqual(left, right any) bool
- func ConstantTimeEqual(a, b string) bool
- func ExtractAuthorizationScheme(header, scheme string) string
- func ExtractAuthorizationSchemeStrict(header, scheme string) (string, error)
- func FindPaymentAuthorization(header string) string
- func FindPaymentAuthorizationStrict(header string) (string, error)
- func FormatAuthenticate(c *Challenge, realm string) string
- func FormatAuthenticateStrict(c *Challenge, realm string) (string, error)
- func FormatAuthorization(c *Credential) string
- func FormatAuthorizationStrict(c *Credential) (string, error)
- func FormatPaymentReceipt(r *Receipt) string
- func FormatReceipt(r *Receipt) string
- func GenerateChallengeID(opts GenerateChallengeIDInput) string
- func GenerateChallengeIDWithError(opts GenerateChallengeIDInput) (string, error)
- func JSONEqual(left, right any) bool
- func ParseUnits(value string, decimals int) (int64, error)
- func SplitAuthenticate(header string) []string
- func TransformUnits(request map[string]any) (map[string]any, error)
- type Challenge
- func FromAuthenticate(header string) (*Challenge, error)
- func NewChallenge(secretKey, realm, method, intent string, request map[string]any, ...) *Challenge
- func NewChallengeWithError(secretKey, realm, method, intent string, request map[string]any, ...) (*Challenge, error)
- func ParseChallenge(header string) (*Challenge, error)
- func (c *Challenge) CredentialHeader() string
- func (c Challenge) MarshalJSON() ([]byte, error)
- func (c *Challenge) NewCredential(payload map[string]any, opts ...CredentialOption) *Credential
- func (c *Challenge) ToAuthenticate(realm string) string
- func (c *Challenge) ToAuthenticateStrict(realm string) (string, error)
- func (c *Challenge) ToEcho() ChallengeEcho
- func (c *Challenge) UnmarshalJSON(data []byte) error
- func (c *Challenge) Verify(secretKey, realm string) bool
- type ChallengeEcho
- type ChallengeOption
- type Credential
- type CredentialOption
- type ErrorType
- type GenerateChallengeIDInput
- type PaymentError
- func ErrBadRequest(reason string) *PaymentError
- func ErrCredentialReplayed(reason string) *PaymentError
- func ErrInternalPayment() *PaymentError
- func ErrInvalidChallenge(challengeID, reason string) *PaymentError
- func ErrInvalidPayload(reason string) *PaymentError
- func ErrMalformedCredential(reason string) *PaymentError
- func ErrMethodUnsupported(method string) *PaymentError
- func ErrPaymentExpired(expires string) *PaymentError
- func ErrPaymentInsufficient(reason string) *PaymentError
- func ErrPaymentRequired(realm, description string) *PaymentError
- func ErrVerificationFailed(reason string) *PaymentError
- type Receipt
- type ReceiptOption
Constants ¶
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.
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.
const (
SchemePayment = "Payment"
)
MPP HTTP authentication schemes.
Variables ¶
var BodyDigest = struct { Compute func(body any) string Verify func(digest string, body any) bool }{ Compute: computeDigest, Verify: verifyDigest, }
BodyDigest provides helpers for computing and verifying SHA-256 digests. Compute returns a digest string in the form "sha-256=<base64>". Verify recomputes the digest and compares using constant-time comparison.
A []byte, string or json.RawMessage body, or a value of a named string or byte-slice type, is digested as is. Any other value is digested as its json.Marshal output, not the raw request bytes, so pass the raw bytes to bind exactly what the client sent. Compute and Verify panic for a value json.Marshal cannot encode, such as a channel or function, and for an io.Reader such as http.Request.Body: read it and pass the bytes.
var ErrVerification = errors.New("payment verification failed")
ErrVerification is a sentinel error for payment verification failures.
var Expires = struct { Seconds func(n int) string Minutes func(n int) string Hours func(n int) string Days func(n int) string Weeks func(n int) string }{ Seconds: func(n int) string { return formatISO(time.Now().UTC().Add(time.Duration(n) * time.Second)) }, Minutes: func(n int) string { return formatISO(time.Now().UTC().Add(time.Duration(n) * time.Minute)) }, Hours: func(n int) string { return formatISO(time.Now().UTC().Add(time.Duration(n) * time.Hour)) }, Days: func(n int) string { return formatISO(time.Now().UTC().Add(time.Duration(n) * 24 * time.Hour)) }, Weeks: func(n int) string { return formatISO(time.Now().UTC().Add(time.Duration(n) * 7 * 24 * time.Hour)) }, }
Expires provides helpers for generating ISO 8601 expiration timestamps with millisecond precision and Z suffix.
Functions ¶
func AdvertisedCredentialHeader ¶ added in v0.4.0
AdvertisedCredentialHeader returns a challenge header parameter value, or empty for the implicit Authorization default.
func B64Decode ¶
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
ChallengeBoundJSONEqual compares values using the RFC 8785 representation used to bind challenge requests and opaque values into their HMAC IDs.
func ConstantTimeEqual ¶
ConstantTimeEqual returns true if a and b are equal, using constant-time comparison to avoid timing side-channels.
func ExtractAuthorizationScheme ¶
ExtractAuthorizationScheme returns the first authorization value that matches the requested scheme from a possibly merged Authorization header.
func ExtractAuthorizationSchemeStrict ¶ added in v0.2.0
ExtractAuthorizationSchemeStrict returns a single authorization value that matches scheme, or an error if the header includes multiple matching values.
func FindPaymentAuthorization ¶
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
FindPaymentAuthorizationStrict returns the Payment credential from an Authorization header, or an error if more than one Payment credential exists.
func FormatAuthenticate ¶
FormatAuthenticate formats a Challenge as an authentication header value.
Output format: Payment id="...", realm="...", method="...", intent="...", request="..."
func FormatAuthenticateStrict ¶ added in v0.1.2
FormatAuthenticateStrict formats a Challenge as an authentication header value. It rejects values that cannot be safely represented in quoted auth-params, and empty required auth-params (id, realm, method, intent, request), which would otherwise be omitted and yield a challenge ParseChallenge rejects.
func FormatAuthorization ¶
func FormatAuthorization(c *Credential) string
FormatAuthorization formats a Credential as an Authorization header value.
Output format: Payment <base64url-json>
func FormatAuthorizationStrict ¶ added in v0.4.0
func FormatAuthorizationStrict(c *Credential) (string, error)
FormatAuthorizationStrict formats a credential and enforces the wire size limit.
func FormatPaymentReceipt ¶
FormatPaymentReceipt formats a Receipt as a Payment-Receipt header value.
Output format: <base64url-json>
func FormatReceipt ¶
FormatReceipt formats a Receipt as a receipt header value.
func GenerateChallengeID ¶
func GenerateChallengeID(opts GenerateChallengeIDInput) string
GenerateChallengeID produces an HMAC-SHA256 challenge ID from the given inputs. 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 GenerateChallengeIDWithError ¶ added in v0.4.0
func GenerateChallengeIDWithError(opts GenerateChallengeIDInput) (string, error)
GenerateChallengeIDWithError produces an HMAC-SHA256 challenge ID and rejects ambiguous fields, invalid advertised credential headers and request values that JCS cannot represent exactly.
func ParseUnits ¶
ParseUnits converts a human-readable decimal string to base units. For example, ParseUnits("1.5", 6) returns 1500000. Returns an error if value is not a valid decimal, is negative, would produce fractional base units, or decimals is negative.
func SplitAuthenticate ¶
SplitAuthenticate splits a potentially merged authentication header value into individual scheme values while preserving commas inside quoted auth-param values.
func TransformUnits ¶
TransformUnits transforms request amounts from human-readable to base units. If a "decimals" key is present in the request map, it converts the "amount" field (and optionally "suggestedDeposit") from human-readable decimal strings to base unit strings, then removes the "decimals" key. If no "decimals" key is present, the request is returned unchanged.
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 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 FromAuthenticate ¶
FromAuthenticate parses an authentication header value into a Challenge.
func NewChallenge ¶
func NewChallenge(secretKey, realm, method, intent string, request map[string]any, opts ...ChallengeOption) *Challenge
NewChallenge creates a new Challenge with an HMAC-bound ID. The secretKey and realm are used to compute the ID via GenerateChallengeID.
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. It rejects invalid advertised credential headers and request values that JCS cannot represent without changing their numeric value.
func ParseChallenge ¶
ParseChallenge parses a Payment challenge from a WWW-Authenticate header value.
func (*Challenge) CredentialHeader ¶ added in v0.4.0
CredentialHeader returns the HTTP field a client must use for the payment credential. Challenges that omit header default to Authorization.
func (Challenge) MarshalJSON ¶
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) ToAuthenticate ¶
ToAuthenticate formats this Challenge as an authentication header value.
func (*Challenge) ToAuthenticateStrict ¶ added in v0.1.2
ToAuthenticateStrict formats this Challenge as an authentication header value and rejects values that cannot be safely represented in quoted auth-params.
func (*Challenge) ToEcho ¶
func (c *Challenge) ToEcho() ChallengeEcho
ToEcho returns the ChallengeEcho representation suitable for inclusion in a Credential.
func (*Challenge) UnmarshalJSON ¶
UnmarshalJSON accepts the standard JSON challenge shape and computes the cached RequestB64 field used by header serialization.
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 FromAuthorization ¶
func FromAuthorization(header string) (*Credential, error)
FromAuthorization parses an Authorization header value into a Credential.
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) ToAuthorization ¶
func (c *Credential) ToAuthorization() string
ToAuthorization formats this Credential as an Authorization header value.
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.
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" )
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 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.
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 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 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) 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"`
Extra map[string]any `json:"extra,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 (not nested
// under "extra"). The tag is "-" because serialization is handled
// explicitly by FormatPaymentReceipt, not via struct marshaling.
Extensions map[string]any `json:"-"`
}
Receipt represents a server-issued payment receipt sent via the Payment-Receipt header.
func FromPaymentReceipt ¶
FromPaymentReceipt parses a Payment-Receipt header value into a Receipt.
func ParsePaymentReceipt ¶
ParsePaymentReceipt parses a Payment-Receipt header value into a Receipt.
Expected format: <base64url-json>
func ParseReceipt ¶
ParseReceipt parses a receipt header value into a Receipt.
func Success ¶
func Success(method, reference string, opts ...ReceiptOption) *Receipt
Success creates a new successful Receipt with the given method, reference, and options.
func (*Receipt) ToPaymentReceipt ¶
ToPaymentReceipt formats this Receipt as a Payment-Receipt header value.
type ReceiptOption ¶
type ReceiptOption func(*Receipt)
ReceiptOption configures optional fields when creating a Receipt.
func WithExternalID ¶
func WithExternalID(id string) ReceiptOption
WithExternalID sets the external transaction ID on a Receipt.
func WithExtra ¶
func WithExtra(extra map[string]any) ReceiptOption
WithExtra sets extra metadata 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.