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 FormatChallenge(c *Challenge) (string, error)
- func FormatCredential(c *Credential) (string, error)
- func FormatReceiptWithError(r *Receipt) (string, error)
- 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 SplitAuthenticate(header string) []string
- func VerifyBodyDigest(digest string, body []byte) bool
- type Challenge
- 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) ToEcho() ChallengeEcho
- func (c *Challenge) UnmarshalJSON(data []byte) error
- 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
- 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 ErrVerification = errors.New("payment verification failed")
ErrVerification is a sentinel error for payment verification failures.
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 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 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 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 SplitAuthenticate ¶
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
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 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) 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) 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 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
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"`
// 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 ¶
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.
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.