Documentation
¶
Overview ¶
Package server provides the generic challenge-or-verify flow for Payment authentication on the server side.
Examples import this package by its bare name. The Tempo charge package uses the alias `charge`, so there is no conflict.
Package server provides the server-side MPP payment handler.
It implements the HTTP 402 challenge/credential flow: when a request arrives without payment credentials, it returns a 402 with a WWW-Authenticate challenge. When valid credentials are provided, it verifies them and returns a receipt.
Index ¶
- Constants
- func ChargeMiddleware(m *Mpp, params ChargeParams) func(http.Handler) http.Handler
- func ComposeMiddleware(configs ...ComposeConfig) func(http.Handler) http.Handler
- func ContextWithPayment(ctx context.Context, credential *mpp.Credential, receipt *mpp.Receipt) context.Context
- func CredentialFromContext(ctx context.Context) *mpp.Credential
- func DeferPaymentReceipt(w http.ResponseWriter, receipt *mpp.Receipt) (http.ResponseWriter, func())
- func DetectRealm() string
- func DetectSecretKey() (string, error)
- func ReadRequestBody(r *http.Request) ([]byte, error)
- func ReceiptFromContext(ctx context.Context) *mpp.Receipt
- func RequestBodyError(err error) *mpp.PaymentError
- func ScopeFromHTTPRequest(r *http.Request, route string) map[string]string
- func WriteChallenge(w http.ResponseWriter, challenge *mpp.Challenge, realm string)
- func WriteChallenges(w http.ResponseWriter, challenges []*mpp.Challenge, realm string)
- func WritePaymentError(w http.ResponseWriter, err error)
- func WritePaymentErrorWithChallenge(w http.ResponseWriter, err error, challenge *mpp.Challenge, realm string)
- func WritePaymentErrorWithChallenges(w http.ResponseWriter, err error, challenges []*mpp.Challenge, realm string)
- type BroadcastFunc
- type BroadcastingIntent
- type ChargeOffersBuilder
- type ChargeParams
- type ChargeRequestBuilder
- type ChargeResult
- type ComposeConfig
- type Intent
- type IntentHooks
- type Method
- type Mpp
- func (m *Mpp) BroadcastCredential(ctx context.Context, credential *mpp.Credential) (*mpp.Receipt, error)
- func (m *Mpp) Charge(ctx context.Context, params ChargeParams) (*ChargeResult, error)
- func (m *Mpp) CredentialHeader() string
- func (m *Mpp) Realm() string
- func (m *Mpp) RequiresAuth() bool
- func (m *Mpp) ValidateCredential(ctx context.Context, credential *mpp.Credential) (*Validation, error)
- func (m *Mpp) VerifyCredential(ctx context.Context, credential *mpp.Credential) (*mpp.Receipt, error)
- type Option
- type ValidateFunc
- type ValidatingIntent
- type Validation
- type VerifyFunc
- type VerifyParams
- type VerifyResult
Constants ¶
const MaxRequestBodyBytes = 4 << 20
MaxRequestBodyBytes bounds the body buffered for payment digest verification. Configure the HTTP server's read timeout separately to bound slow uploads.
Variables ¶
This section is empty.
Functions ¶
func ChargeMiddleware ¶
ChargeMiddleware creates an http.Handler middleware for the charge intent.
It calls Mpp.Charge with the provided ChargeParams, injects the incoming Authorization and Payment-Authorization headers automatically, returns a 402 challenge when payment is required, and stores the verified Credential and Receipt in the request context on success.
func ComposeMiddleware ¶
func ComposeMiddleware(configs ...ComposeConfig) func(http.Handler) http.Handler
ComposeMiddleware creates an http.Handler middleware that supports multiple payment methods on a single route.
When no credential is present, it fans out to every configured method and returns a merged 402 response with one WWW-Authenticate header per method. When a credential is present, it dispatches to the matching method by comparing the credential's echoed method, intent, and canonical request.
All configs must share the same realm. ComposeMiddleware panics if configs is empty, any Mpp is nil, or realms differ.
func ContextWithPayment ¶
func ContextWithPayment(ctx context.Context, credential *mpp.Credential, receipt *mpp.Receipt) context.Context
ContextWithPayment stores the verified payment objects on a request context.
func CredentialFromContext ¶
func CredentialFromContext(ctx context.Context) *mpp.Credential
CredentialFromContext extracts the Credential from the request context.
func DeferPaymentReceipt ¶ added in v0.4.0
func DeferPaymentReceipt(w http.ResponseWriter, receipt *mpp.Receipt) (http.ResponseWriter, func())
DeferPaymentReceipt returns a writer that adds the receipt only when a 2xx response status is committed. Call complete after the handler returns to commit an otherwise empty successful response.
func DetectRealm ¶
func DetectRealm() string
DetectRealm auto-detects the server realm from environment variables. It checks MPP_REALM, FLY_APP_NAME, HEROKU_APP_NAME, RAILWAY_PUBLIC_DOMAIN, RENDER_EXTERNAL_HOSTNAME, VERCEL_URL, and WEBSITE_HOSTNAME in that order. Returns "MPP Payment" if none are set. HOST and HOSTNAME are not read: container runtimes set HOSTNAME per replica, and the realm is bound into the challenge ID, so replicas would reject each other's credentials.
func DetectSecretKey ¶
DetectSecretKey reads the MPP_SECRET_KEY environment variable. Returns an error if it is not set.
func ReadRequestBody ¶ added in v0.2.0
ReadRequestBody reads and restores r.Body so middleware can verify body digests without consuming the body before the protected handler runs.
func ReceiptFromContext ¶
ReceiptFromContext extracts the Receipt from the request context.
func RequestBodyError ¶ added in v0.4.0
func RequestBodyError(err error) *mpp.PaymentError
RequestBodyError maps oversized bodies to HTTP 413 and other read failures to 400.
func ScopeFromHTTPRequest ¶ added in v0.2.0
ScopeFromHTTPRequest returns the framework scope bound into charge requests.
func WriteChallenge ¶
func WriteChallenge(w http.ResponseWriter, challenge *mpp.Challenge, realm string)
WriteChallenge serializes an initial 402 challenge response.
func WriteChallenges ¶ added in v0.4.0
func WriteChallenges(w http.ResponseWriter, challenges []*mpp.Challenge, realm string)
WriteChallenges serializes an initial 402 response offering every challenge, one WWW-Authenticate field value per challenge in presentation order.
func WritePaymentError ¶
func WritePaymentError(w http.ResponseWriter, err error)
WritePaymentError serializes MPP verification errors as problem details.
func WritePaymentErrorWithChallenge ¶ added in v0.4.0
func WritePaymentErrorWithChallenge(w http.ResponseWriter, err error, challenge *mpp.Challenge, realm string)
WritePaymentErrorWithChallenge serializes an MPP error with a fresh retry challenge.
func WritePaymentErrorWithChallenges ¶ added in v0.4.0
func WritePaymentErrorWithChallenges(w http.ResponseWriter, err error, challenges []*mpp.Challenge, realm string)
WritePaymentErrorWithChallenges serializes an MPP error with fresh retry challenges, one WWW-Authenticate field value per challenge in order.
Types ¶
type BroadcastFunc ¶ added in v0.4.0
type BroadcastFunc func( ctx context.Context, credential *mpp.Credential, request map[string]any, ) (*mpp.Receipt, error)
BroadcastFunc finalizes a validated credential and may mutate payment state.
type BroadcastingIntent ¶ added in v0.4.0
type BroadcastingIntent interface {
ValidatingIntent
Broadcast(ctx context.Context, credential *mpp.Credential, request map[string]any) (*mpp.Receipt, error)
}
BroadcastingIntent supports a split validation and settlement lifecycle.
Broadcast must revalidate the credential before performing its terminal payment operation. Verify remains the compatibility path for callers that do not use the split lifecycle explicitly.
type ChargeOffersBuilder ¶ added in v0.4.0
type ChargeOffersBuilder interface {
BuildChargeRequests(params ChargeParams) ([]map[string]any, error)
}
ChargeOffersBuilder lets a method issue several ordered charge offers for one charge, for example one per accepted currency. Charge issues one Challenge per returned request and verifies a Credential against the offer it echoes. When a method implements both interfaces, BuildChargeRequests takes precedence for Charge.
type ChargeParams ¶
type ChargeParams struct {
// EnableChallengeNonce opts into unique issuance after every replica has
// nonce-aware verification. Leave false during the initial rolling upgrade.
EnableChallengeNonce bool
// Authorization is the incoming Authorization header value.
Authorization string
// PaymentAuthorization is the incoming Payment-Authorization header value.
// Used when the handler was created with WithRequiresAuth(true).
PaymentAuthorization string
// Amount is the human-readable charge amount.
Amount string
// Currency overrides the method's default currency.
Currency string
// Recipient overrides the method's default recipient.
Recipient string
// ExternalID is copied into the request and echoed back in the Receipt.
ExternalID string
// Body is the actual request body to bind via the challenge digest. See
// mpp.BodyDigest for how non-byte, non-string values are digested.
Body any
// Expires overrides the default Challenge expiry.
Expires string
// Description is exposed in the server-generated Challenge.
Description string
// Splits adds Tempo split-payment transfers under methodDetails.splits.
Splits []tempo.SplitParams
// FeePayer requests the sponsored Tempo flow when the method supports it.
FeePayer bool
// FeePayerURL points at a remote fee-payer signer.
FeePayerURL string
// SupportedModes advertises the Tempo submission modes allowed for this challenge.
SupportedModes []tempo.ChargeMode
// ChainID overrides the method's default Tempo chain ID.
ChainID int
// Meta stores opaque Challenge metadata.
Meta map[string]string
// MppxScope binds framework route/resource/query context into the request.
MppxScope map[string]string
}
ChargeParams contains the parameters for a charge operation.
type ChargeRequestBuilder ¶
type ChargeRequestBuilder interface {
BuildChargeRequest(params ChargeParams) (map[string]any, error)
}
ChargeRequestBuilder lets a method provide a canonical request shape for the generic charge helper. Tempo uses this to normalize human amounts and nest method-specific fields under methodDetails.
type ChargeResult ¶
type ChargeResult struct {
// Challenge is returned when the client still needs to satisfy the payment.
// With several offers it is the offer the submitted Credential echoed, or
// the first offer otherwise.
Challenge *mpp.Challenge
// Challenges lists every offered Challenge in presentation order whenever
// Challenge is set. It contains a single element for single-offer methods.
Challenges []*mpp.Challenge
// Credential is the verified client credential on success.
Credential *mpp.Credential
// Receipt acknowledges a successfully verified payment.
Receipt *mpp.Receipt
}
ChargeResult is either a Challenge or a verified (Credential, Receipt) pair.
func (*ChargeResult) IsChallenge ¶
func (r *ChargeResult) IsChallenge() bool
IsChallenge returns true if the result is a 402 challenge.
type ComposeConfig ¶
type ComposeConfig struct {
Mpp *Mpp
Params ChargeParams
}
ComposeConfig pairs an Mpp instance with the ChargeParams for one payment method. Pass one or more of these to ComposeMiddleware to advertise multiple payment options on a single route.
type Intent ¶
type Intent interface {
Name() string
Verify(ctx context.Context, credential *mpp.Credential, request map[string]any) (*mpp.Receipt, error)
}
Intent is the interface for payment intents (server-side verification).
type IntentHooks ¶ added in v0.4.0
type IntentHooks struct {
// Verify configures the legacy combined verification path.
Verify VerifyFunc
// Validate configures the advisory phase of a split verification path.
Validate ValidateFunc
// Broadcast configures the settlement phase of a split verification path.
Broadcast BroadcastFunc
}
IntentHooks configures either a legacy Verify hook or split Validate and Broadcast hooks.
type Mpp ¶
type Mpp struct {
// contains filtered or unexported fields
}
Mpp is the server-side payment handler.
func New ¶
New creates an Mpp instance. It returns an error when secretKey is shorter than minimumSecretKeyBytes, since a weak secret yields forgeable, HMAC-bound challenge IDs.
func (*Mpp) BroadcastCredential ¶ added in v0.4.0
func (m *Mpp) BroadcastCredential(ctx context.Context, credential *mpp.Credential) (*mpp.Receipt, error)
BroadcastCredential validates and settles an issued credential.
func (*Mpp) Charge ¶
func (m *Mpp) Charge(ctx context.Context, params ChargeParams) (*ChargeResult, error)
Charge handles a charge intent with human-readable amounts.
Methods implementing ChargeOffersBuilder may offer several requests; Charge then returns one Challenge per offer and verifies a Credential against the offer it echoes.
func (*Mpp) CredentialHeader ¶ added in v0.4.0
CredentialHeader returns the HTTP field this handler expects for Payment credentials.
func (*Mpp) RequiresAuth ¶ added in v0.4.0
RequiresAuth reports whether Payment credentials use Payment-Authorization.
func (*Mpp) ValidateCredential ¶ added in v0.4.0
func (m *Mpp) ValidateCredential(ctx context.Context, credential *mpp.Credential) (*Validation, error)
ValidateCredential validates an issued credential without consuming or broadcasting it.
func (*Mpp) VerifyCredential ¶ added in v0.4.0
func (m *Mpp) VerifyCredential(ctx context.Context, credential *mpp.Credential) (*mpp.Receipt, error)
VerifyCredential is a backwards-compatible alias for BroadcastCredential.
type Option ¶ added in v0.4.0
type Option func(*Mpp)
Option configures an Mpp instance.
func WithRequiresAuth ¶ added in v0.4.0
WithRequiresAuth advertises header="Payment-Authorization" so Payment credentials leave Authorization available for application authentication.
type ValidateFunc ¶ added in v0.4.0
type ValidateFunc func( ctx context.Context, credential *mpp.Credential, request map[string]any, ) (*Validation, error)
ValidateFunc performs an advisory credential check without mutating payment state.
type ValidatingIntent ¶ added in v0.4.0
type ValidatingIntent interface {
Intent
Validate(ctx context.Context, credential *mpp.Credential, request map[string]any) (*Validation, error)
}
ValidatingIntent supports non-mutating credential validation.
Validate must not consume replay state, sign transactions, broadcast, or otherwise mutate payment state. Callers must use BroadcastCredential to accept a payment after validation.
type Validation ¶ added in v0.4.0
type Validation struct {
// Challenge is the server-issued challenge echoed by the credential.
Challenge mpp.ChallengeEcho
// Credential is the accepted credential.
Credential *mpp.Credential
// Details contains method-specific validation metadata.
Details map[string]any
// Intent is the validated payment intent.
Intent string
// Method is the validated payment method.
Method string
// Request is the challenge request bound to the credential.
Request map[string]any
// Source is the optional payer identity.
Source string
}
Validation describes a credential accepted without consuming or broadcasting it.
type VerifyFunc ¶ added in v0.4.0
type VerifyFunc func( ctx context.Context, credential *mpp.Credential, request map[string]any, ) (*mpp.Receipt, error)
VerifyFunc is the legacy combined credential verification hook.
type VerifyParams ¶
type VerifyParams struct {
// EnableChallengeNonce opts into unique issuance after every replica has
// nonce-aware verification. The default preserves legacy issuance for rollout.
EnableChallengeNonce bool
// Authorization is the incoming Authorization header value.
Authorization string
// Intent verifies Credentials for this request.
Intent Intent
// Request is the canonical request shape the Challenge binds to.
Request map[string]any
// Body is the actual request body bytes/string/object the digest binds to.
// When set, issued challenges include a body digest and submitted
// credentials must echo a digest matching this value. See mpp.BodyDigest
// for how non-byte, non-string values are digested.
Body any
// Realm is the expected server realm.
Realm string
// SecretKey signs and verifies Challenge IDs.
SecretKey string
// Method is the payment method token, for example "tempo".
Method string
// Description is copied into a newly issued Challenge.
Description string
// Meta stores opaque Challenge metadata.
Meta map[string]string
// Expires overrides the default Challenge expiry.
Expires string
// Header selects the HTTP field for the Payment credential. Empty defaults
// to Authorization. Payment-Authorization is advertised when set.
Header string
}
VerifyParams contains the parameters for VerifyOrChallenge.
type VerifyResult ¶
type VerifyResult struct {
// Challenge is returned when the server needs the client to pay first.
Challenge *mpp.Challenge
// Credential is the parsed client credential on success.
Credential *mpp.Credential
// Receipt acknowledges successful verification.
Receipt *mpp.Receipt
}
VerifyResult is either a Challenge or a verified (Credential, Receipt) pair.
func VerifyOrChallenge ¶
func VerifyOrChallenge(ctx context.Context, params VerifyParams) (*VerifyResult, error)
VerifyOrChallenge checks for a valid payment credential or generates a new challenge. Returns a Challenge if payment is required, or (Credential, Receipt) if verified.
Logic:
- If no authorization header, create new challenge
- Extract "Payment" scheme from Authorization header
- Parse credential
- Recompute expected challenge ID from echoed parameters via HMAC
- Constant-time compare IDs
- Verify echoed fields match (realm, method, intent name, request)
- Check expiry
- Verify body digest, if present
- Validate then broadcast split intents, or call legacy intent.Verify
- Return result
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package echoadapter provides Echo middleware for MPP-protected routes.
|
Package echoadapter provides Echo middleware for MPP-protected routes. |
|
Package fiberadapter provides Fiber middleware for MPP-protected routes.
|
Package fiberadapter provides Fiber middleware for MPP-protected routes. |
|
Package ginadapter provides Gin middleware for MPP-protected routes.
|
Package ginadapter provides Gin middleware for MPP-protected routes. |