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 CheckExpires(expires string, now time.Time) error
- func ComputeBodyDigest(body []byte) string
- func ConstantTimeEqual(a, b string) bool
- func ExpiresIn(d time.Duration) string
- 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) stringdeprecated
- func FormatAuthenticateStrict(c *Challenge, realm string) (string, error)deprecated
- func FormatAuthorization(c *Credential) stringdeprecated
- func FormatAuthorizationStrict(c *Credential) (string, error)deprecated
- func FormatChallenge(c *Challenge) (string, error)
- func FormatCredential(c *Credential) (string, error)
- func FormatPaymentReceipt(r *Receipt) stringdeprecated
- func FormatReceipt(r *Receipt) stringdeprecated
- func FormatReceiptWithError(r *Receipt) (string, error)
- func GenerateChallengeID(opts GenerateChallengeIDInput) stringdeprecated
- func GenerateChallengeIDWithError(opts GenerateChallengeIDInput) (string, error)
- func JSONEqual(left, right any) bool
- func MCPErrorCode(err error) int
- func ParseAmount(value string, decimals int) (*big.Int, error)
- func ParseExpires(s string) (time.Time, error)
- func ParseUnits(value string, decimals int) (int64, error)deprecated
- func SplitAuthenticate(header string) []string
- func TransformUnits(request map[string]any) (map[string]any, error)deprecated
- func VerifyBodyDigest(digest string, body []byte) bool
- type Challenge
- func ChallengesFromResponse(resp *http.Response) ([]*Challenge, error)
- func FromAuthenticate(header string) (*Challenge, error)deprecated
- func NewChallenge(secretKey, realm, method, intent string, request map[string]any, ...) *Challengedeprecated
- 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) stringdeprecated
- func (c *Challenge) ToAuthenticateStrict(realm string) (string, error)deprecated
- func (c *Challenge) ToEcho() ChallengeEcho
- func (c *Challenge) UnmarshalJSON(data []byte) error
- func (c *Challenge) Verify(secretKey, realm string) booldeprecated
- func (c *Challenge) VerifyID(secretKey string) bool
- type ChallengeEcho
- type ChallengeOption
- type Credential
- type CredentialOption
- type ErrorType
- type GenerateChallengeIDInput
- type MCPPaymentRequiredData
- type MCPReceipt
- 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 ErrPaymentActionRequired(reason string) *PaymentError
- func ErrPaymentExpired(expires string) *PaymentError
- func ErrPaymentInsufficient(reason string) *PaymentError
- func ErrPaymentOutcomeUnknown(challengeID string) *PaymentError
- func ErrPaymentRequired(realm, description string) *PaymentError
- func ErrVerificationFailed(reason string) *PaymentError
- type Receipt
- func FromPaymentReceipt(header string) (*Receipt, error)deprecated
- func ParsePaymentReceipt(header string) (*Receipt, error)deprecated
- func ParseReceipt(header string) (*Receipt, error)
- func ReceiptFromResponse(resp *http.Response) (*Receipt, error)
- func Success(method, reference string, opts ...ReceiptOption) *Receipt
- type ReceiptOption
Examples ¶
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 ( IntentCharge = "charge" IntentSession = "session" )
MPP payment intent 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 ( // 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.
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.
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.
Deprecated: Use ComputeBodyDigest and VerifyBodyDigest instead, which take the raw body bytes and never panic.
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 ExpiresIn(time.Duration(n) * time.Second) }, Minutes: func(n int) string { return ExpiresIn(time.Duration(n) * time.Minute) }, Hours: func(n int) string { return ExpiresIn(time.Duration(n) * time.Hour) }, Days: func(n int) string { return ExpiresIn(time.Duration(n) * 24 * time.Hour) }, Weeks: func(n int) string { return ExpiresIn(time.Duration(n) * 7 * 24 * time.Hour) }, }
Expires provides helpers for generating ISO 8601 expiration timestamps with millisecond precision and Z suffix.
Deprecated: Use ExpiresIn instead, such as ExpiresIn(5 * time.Minute).
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 CheckExpires ¶ added in v0.5.1
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
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 ¶
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
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 ¶
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
deprecated
FormatAuthenticate formats a Challenge as an authentication header value with the given realm. It returns an empty string when c cannot be formatted.
Output format: Payment id="...", realm="...", method="...", intent="...", request="..."
Deprecated: Use FormatChallenge instead, which writes c.Realm (the realm the challenge ID binds) and returns formatting errors.
func FormatAuthenticateStrict
deprecated
added in
v0.1.2
FormatAuthenticateStrict formats a Challenge as an authentication header value with the given realm, rejecting what FormatChallenge rejects.
Deprecated: Use FormatChallenge instead, which writes c.Realm, the realm the challenge ID binds.
func FormatAuthorization
deprecated
func FormatAuthorization(c *Credential) string
FormatAuthorization formats a Credential as an Authorization header value. It returns an empty string when c cannot be formatted.
Output format: Payment <base64url-json>
Deprecated: Use FormatCredential instead, which returns formatting errors.
func FormatAuthorizationStrict
deprecated
added in
v0.4.0
func FormatAuthorizationStrict(c *Credential) (string, error)
FormatAuthorizationStrict formats a credential and enforces the wire size limit.
Deprecated: Use FormatCredential instead.
func FormatChallenge ¶ added in v0.5.1
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 FormatPaymentReceipt
deprecated
func FormatReceipt
deprecated
func FormatReceiptWithError ¶ added in v0.5.1
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 GenerateChallengeID
deprecated
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. It panics where GenerateChallengeIDWithError returns an error.
Deprecated: Use GenerateChallengeIDWithError instead.
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 MCPErrorCode ¶ added in v0.5.0
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
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
ParseExpires parses an expires timestamp in the RFC 3339 format challenges use, with or without fractional seconds.
func ParseUnits
deprecated
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, decimals is negative, or the result overflows int64.
Deprecated: Use ParseAmount instead, which does not overflow for 18-decimal tokens.
func SplitAuthenticate ¶
SplitAuthenticate splits a potentially merged authentication header value into individual scheme values while preserving commas inside quoted auth-param values.
func TransformUnits
deprecated
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.
Deprecated: Convert amounts with ParseAmount when building the request.
func VerifyBodyDigest ¶ added in v0.5.1
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
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 FromAuthenticate
deprecated
func NewChallenge
deprecated
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 GenerateChallengeIDWithError. It panics where NewChallengeWithError returns an error.
Deprecated: Use NewChallengeWithError instead.
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 ¶
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
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
deprecated
ToAuthenticate formats this Challenge as an authentication header value with the given realm, which the challenge ID does not bind unless it equals c.Realm. It returns an empty string when c cannot be formatted.
Deprecated: Use FormatChallenge instead, which writes c.Realm and returns formatting errors.
func (*Challenge) ToAuthenticateStrict
deprecated
added in
v0.1.2
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.
func (*Challenge) Verify
deprecated
Verify checks whether the challenge ID matches the expected HMAC for the given secretKey and realm. Uses constant-time comparison.
Deprecated: Use VerifyID together with a c.Realm == realm check. VerifyID binds the ID to the challenge's own Realm and does not compare it with the realm you expect, so the check is still the caller's.
func (*Challenge) VerifyID ¶ added in v0.5.1
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 FromAuthorization
deprecated
func FromAuthorization(header string) (*Credential, error)
FromAuthorization parses an Authorization header value into a Credential.
Deprecated: Use ParseCredential instead.
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
deprecated
func (c *Credential) ToAuthorization() string
ToAuthorization formats this Credential as an Authorization header value. It returns an empty string when c cannot be formatted.
Deprecated: Use FormatCredential instead, which returns formatting errors.
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
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.
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"`
// Extra is a nested "extra" object, which no other MPP SDK emits.
//
// Deprecated: Use Extensions instead, which holds method-defined fields
// at the top level of the receipt as mppx and mpp-rs do.
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 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 FromPaymentReceipt
deprecated
func ParsePaymentReceipt
deprecated
func ParseReceipt ¶
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
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.
func (*Receipt) ToPaymentReceipt
deprecated
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 WithExtra
deprecated
func WithExtra(extra map[string]any) ReceiptOption
WithExtra sets extra metadata on a Receipt.
Deprecated: Use WithExtensions instead.
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.