Documentation
¶
Index ¶
- Variables
- func InterpolateMemo(template, resourceID string) string
- func ResourceIDFromContext(ctx context.Context) (string, bool)
- func SelectCouponsForPayment(ctx context.Context, couponRepo coupons.Repository, productID string, ...) []coupons.Coupon
- func StackCouponsOnMoney(originalPrice money.Money, applicableCoupons []coupons.Coupon, ...) (money.Money, error)
- type AuthorizationResult
- type CartItem
- type CartQuoteItem
- type CartQuoteRequest
- type CartQuoteResponse
- type CouponScope
- type CryptoQuote
- type Quote
- type RefundQuoteRequest
- type RefundQuoteResponse
- type ResourceResolver
- type Service
- func (s *Service) Authorize(ctx context.Context, ...) (AuthorizationResult, error)
- func (s *Service) AuthorizeWithWallet(ctx context.Context, ...) (AuthorizationResult, error)
- func (s *Service) ConsumeNonce(ctx context.Context, nonceID string) error
- func (s *Service) CreateNonce(ctx context.Context, nonce storage.AdminNonce) error
- func (s *Service) CreateRefundRequest(ctx context.Context, req RefundQuoteRequest) (storage.RefundQuote, error)
- func (s *Service) DenyRefund(ctx context.Context, refundID string) error
- func (s *Service) GenerateCartQuote(ctx context.Context, req CartQuoteRequest) (CartQuoteResponse, error)
- func (s *Service) GenerateQuote(ctx context.Context, resourceID, couponCode string) (Quote, error)
- func (s *Service) GetCartQuote(ctx context.Context, cartID string) (storage.CartQuote, error)
- func (s *Service) GetPayment(ctx context.Context, signature string) (storage.PaymentTransaction, error)
- func (s *Service) GetProduct(ctx context.Context, productID string) (products.Product, error)
- func (s *Service) GetRefundQuote(ctx context.Context, refundID string) (storage.RefundQuote, error)
- func (s *Service) HasPaymentBeenProcessed(ctx context.Context, signature string) (bool, error)
- func (s *Service) InterpolateMemo(template, resourceID string) string
- func (s *Service) ListPendingRefunds(ctx context.Context) ([]storage.RefundQuote, error)
- func (s *Service) ListProducts(ctx context.Context) ([]products.Product, error)
- func (s *Service) Middleware(resolver ResourceResolver) func(http.Handler) http.Handler
- func (s *Service) RegenerateRefundQuote(ctx context.Context, refundID string) (RefundQuoteResponse, error)
- func (s *Service) ResourceDefinition(ctx context.Context, resourceID string) (config.PaywallResource, error)
- func (s *Service) ResourceDefinitionByStripePriceID(ctx context.Context, stripePriceID string) (config.PaywallResource, error)
- func (s *Service) SetSubscriptionChecker(checker SubscriptionChecker)
- type SettlementResponse
- type StripeOption
- type SubscriptionChecker
- type SubscriptionInfo
Constants ¶
This section is empty.
Variables ¶
var ErrResourceNotConfigured = errors.New("paywall: resource not configured")
ErrResourceNotConfigured indicates the requested resource lacks pricing metadata.
var ErrStripeSessionPending = errors.New("paywall: stripe session pending")
ErrStripeSessionPending indicates a Stripe session is still awaiting webhook confirmation.
Functions ¶
func InterpolateMemo ¶
InterpolateMemo replaces template placeholders with actual values.
func ResourceIDFromContext ¶
ResourceIDFromContext retrieves the resolved resource identifier.
func SelectCouponsForPayment ¶
func SelectCouponsForPayment( ctx context.Context, couponRepo coupons.Repository, productID string, paymentMethod coupons.PaymentMethod, manualCoupon *coupons.Coupon, scope CouponScope, ) []coupons.Coupon
SelectCouponsForPayment is a unified coupon selector supporting all scopes. Handles catalog (product-specific), checkout (site-wide), and all (Stripe) coupon selection.
func StackCouponsOnMoney ¶
func StackCouponsOnMoney(originalPrice money.Money, applicableCoupons []coupons.Coupon, roundingMode money.RoundingMode) (money.Money, error)
StackCouponsOnMoney applies multiple coupons to a Money amount using proper integer arithmetic. Coupons are applied in optimal order: 1. All percentage discounts are applied first (multiplicatively stacked) 2. All fixed-amount discounts are summed and applied at the end This ensures maximum discount for the customer and matches existing float64 behavior.
Example:
Price: $10.00, Coupons: [10% off, 20% off, $1 off, $0.50 off] Step 1: Apply 10%: $10.00 * 0.9 = $9.00 Step 2: Apply 20%: $9.00 * 0.8 = $7.20 Step 3: Apply $1 + $0.50 = $1.50 off: $7.20 - $1.50 = $5.70
All arithmetic is done using int64 atomic units to avoid floating-point errors. The roundingMode parameter controls how fractional cents are rounded.
Types ¶
type AuthorizationResult ¶
type AuthorizationResult struct {
Granted bool
Method string
Wallet string
Quote *Quote
Settlement *SettlementResponse
Subscription *SubscriptionInfo // Present when access granted via subscription
}
AuthorizationResult captures the outcome of an access attempt.
func AuthorizationFromContext ¶
func AuthorizationFromContext(ctx context.Context) (AuthorizationResult, bool)
AuthorizationFromContext retrieves the authorization result for logging or auditing.
type CartItem ¶
type CartItem struct {
ResourceID string `json:"resource"`
Quantity int64 `json:"quantity"`
PriceAmount float64 `json:"priceAmount"` // Price per unit (after catalog coupons)
OriginalPrice float64 `json:"originalPrice"` // Original price before any discounts
Token string `json:"token"` // Token symbol
Description string `json:"description,omitempty"`
AppliedCoupons []string `json:"appliedCoupons,omitempty"` // Catalog coupons applied to this item
}
CartItem represents an item in the quote response.
type CartQuoteItem ¶
type CartQuoteItem struct {
ResourceID string `json:"resource"` // Resource ID from paywall config
Quantity int64 `json:"quantity"` // Number of this item
Metadata map[string]string `json:"metadata,omitempty"` // Per-item custom metadata
}
CartQuoteItem represents a single item in a cart quote request.
type CartQuoteRequest ¶
type CartQuoteRequest struct {
Items []CartQuoteItem `json:"items"`
Metadata map[string]string `json:"metadata,omitempty"` // Cart-level metadata (user_id, campaign, etc.)
CouponCode string `json:"couponCode,omitempty"` // Optional coupon code to apply discount
}
CartQuoteRequest represents a request to generate a quote for multiple items.
type CartQuoteResponse ¶
type CartQuoteResponse struct {
CartID string `json:"cartId"` // Unique cart identifier
Quote *CryptoQuote `json:"quote"` // x402 requirement for the cart total (unwrapped)
Items []CartItem `json:"items"` // Itemized breakdown
TotalAmount float64 `json:"totalAmount"` // Final total after all discounts
Metadata map[string]string `json:"metadata,omitempty"` // Cart metadata including coupon info
ExpiresAt time.Time `json:"expiresAt"` // When this cart quote expires
}
CartQuoteResponse contains the generated quote for a cart.
type CouponScope ¶
type CouponScope int
CouponScope defines the scope of coupon selection for different payment phases.
const ( // ScopeAll selects all coupons regardless of AppliesAt (used for Stripe payments). ScopeAll CouponScope = iota // ScopeCatalog selects only catalog-level coupons (product-specific). ScopeCatalog // ScopeCheckout selects only checkout-level coupons (site-wide). ScopeCheckout )
type CryptoQuote ¶
type CryptoQuote struct {
// x402 standard fields
Scheme string `json:"scheme"`
Network string `json:"network"`
MaxAmountRequired string `json:"maxAmountRequired"` // in atomic units
Resource string `json:"resource"`
Description string `json:"description"`
MimeType string `json:"mimeType"`
OutputSchema any `json:"outputSchema,omitempty"`
PayTo string `json:"payTo"`
MaxTimeoutSeconds int `json:"maxTimeoutSeconds"`
Asset string `json:"asset"`
Extra any `json:"extra,omitempty"`
}
CryptoQuote models the x402 paymentRequirements following the official spec. Reference: https://github.com/coinbase/x402
type Quote ¶
type Quote struct {
ResourceID string
ExpiresAt time.Time
Stripe *StripeOption
Crypto *CryptoQuote
}
Quote contains the pricing metadata shared with the caller.
type RefundQuoteRequest ¶
type RefundQuoteRequest struct {
OriginalPurchaseID string `json:"originalPurchaseId"` // Reference to original purchase
RecipientWallet string `json:"recipientWallet"` // Wallet to receive the refund
Amount float64 `json:"amount"` // Amount to refund
Token string `json:"token"` // Token symbol
Reason string `json:"reason,omitempty"` // Optional reason
Metadata map[string]string `json:"metadata,omitempty"` // Optional metadata
}
RefundQuoteRequest represents a request to generate a refund quote.
type RefundQuoteResponse ¶
type RefundQuoteResponse struct {
RefundID string `json:"refundId"` // Unique refund identifier
Quote *CryptoQuote `json:"quote"` // x402 requirement for the refund
ExpiresAt time.Time `json:"expiresAt"` // When this refund quote expires
}
RefundQuoteResponse contains the generated refund quote.
type ResourceResolver ¶
ResourceResolver extracts the paywall resource identifier from the request.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service orchestrates paywall pricing, quotes, and authorization.
func NewService ¶
func NewService(cfg *config.Config, store storage.Store, verifier x402.Verifier, notifier callbacks.Notifier, repository products.Repository, couponRepo coupons.Repository, metricsCollector *metrics.Metrics) *Service
NewService constructs a paywall service.
func (*Service) Authorize ¶
func (s *Service) Authorize(ctx context.Context, resourceID, stripeSessionID, paymentHeader, couponCode string) (AuthorizationResult, error)
Authorize attempts to grant access using Stripe or x402 proof headers.
func (*Service) AuthorizeWithWallet ¶
func (s *Service) AuthorizeWithWallet(ctx context.Context, resourceID, stripeSessionID, paymentHeader, couponCode, wallet string) (AuthorizationResult, error)
AuthorizeWithWallet attempts to grant access, checking subscription status if wallet is provided.
func (*Service) ConsumeNonce ¶
ConsumeNonce marks a nonce as consumed (one-time use).
func (*Service) CreateNonce ¶
CreateNonce stores a new admin nonce for replay protection.
func (*Service) CreateRefundRequest ¶
func (s *Service) CreateRefundRequest(ctx context.Context, req RefundQuoteRequest) (storage.RefundQuote, error)
CreateRefundRequest creates a refund request without generating an x402 quote. The quote is generated later when admin approves the refund via RegenerateRefundQuote. This is the correct flow: user requests → admin reviews → admin approves → quote generated → admin executes.
func (*Service) DenyRefund ¶
DenyRefund deletes a pending refund quote, effectively denying the refund request. Only unprocessed refunds can be denied. Returns ErrNotFound if the refund doesn't exist.
func (*Service) GenerateCartQuote ¶
func (s *Service) GenerateCartQuote(ctx context.Context, req CartQuoteRequest) (CartQuoteResponse, error)
GenerateCartQuote creates a quote for multiple items with locked prices.
func (*Service) GenerateQuote ¶
GenerateQuote builds a paywall quote for the resource with optional coupon.
func (*Service) GetCartQuote ¶
GetCartQuote retrieves an existing cart quote by ID.
func (*Service) GetPayment ¶
func (s *Service) GetPayment(ctx context.Context, signature string) (storage.PaymentTransaction, error)
GetPayment retrieves payment transaction details by signature. This is used for refund wallet validation to ensure refunds go to the original payer.
func (*Service) GetProduct ¶
GetProduct retrieves a product by ID.
func (*Service) GetRefundQuote ¶
GetRefundQuote retrieves an existing refund quote by ID.
func (*Service) HasPaymentBeenProcessed ¶
HasPaymentBeenProcessed checks if a payment signature has been processed by this server. This is used for refund request validation to ensure refunds can only be requested for actual payments.
func (*Service) InterpolateMemo ¶
InterpolateMemo wraps the standalone InterpolateMemo function. Exposed as a method on Service for use by HTTP handlers.
func (*Service) ListPendingRefunds ¶
ListPendingRefunds returns all unprocessed refund quotes. This is used by admin to review pending refund requests.
func (*Service) ListProducts ¶
ListProducts returns all active products from the repository (uses cache if enabled).
func (*Service) Middleware ¶
Middleware enforces paywall checks before calling the downstream handler.
func (*Service) RegenerateRefundQuote ¶
func (s *Service) RegenerateRefundQuote(ctx context.Context, refundID string) (RefundQuoteResponse, error)
RegenerateRefundQuote generates a fresh x402 quote for an existing refund request. This is used when the original quote expires (blockhash becomes stale after 15 min).
func (*Service) ResourceDefinition ¶
func (s *Service) ResourceDefinition(ctx context.Context, resourceID string) (config.PaywallResource, error)
ResourceDefinition resolves the pricing config for a resource ID. Accepts a context for cancellation and timeout propagation.
func (*Service) ResourceDefinitionByStripePriceID ¶
func (s *Service) ResourceDefinitionByStripePriceID(ctx context.Context, stripePriceID string) (config.PaywallResource, error)
ResourceDefinitionByStripePriceID resolves a resource by reverse-looking up its Stripe price ID. This is used for coupon validation when clients submit priceId-only cart items. Returns ErrResourceNotConfigured if no product matches the given price ID.
func (*Service) SetSubscriptionChecker ¶
func (s *Service) SetSubscriptionChecker(checker SubscriptionChecker)
SetSubscriptionChecker sets the subscription checker for access verification. This is optional - if not set, subscription-based access control is disabled.
type SettlementResponse ¶
type SettlementResponse struct {
Success bool `json:"success"`
Error *string `json:"error"`
TxHash *string `json:"txHash"`
NetworkID *string `json:"networkId"`
}
SettlementResponse communicates blockchain transaction details to the client. Sent via X-PAYMENT-RESPONSE header following x402 specification. Reference: https://github.com/coinbase/x402
type StripeOption ¶
type StripeOption struct {
PriceID string
AmountCents int64
Currency string
Description string
Metadata map[string]string
}
StripeOption exposes fiat checkout metadata.
type SubscriptionChecker ¶
type SubscriptionChecker interface {
// HasAccess checks if a wallet has active subscription access to a product.
HasAccess(ctx context.Context, wallet, productID string) (bool, *subscriptions.Subscription, error)
}
SubscriptionChecker provides subscription access verification.