paywall

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2025 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrResourceNotConfigured = errors.New("paywall: resource not configured")

ErrResourceNotConfigured indicates the requested resource lacks pricing metadata.

View Source
var ErrStripeSessionPending = errors.New("paywall: stripe session pending")

ErrStripeSessionPending indicates a Stripe session is still awaiting webhook confirmation.

Functions

func InterpolateMemo

func InterpolateMemo(template, resourceID string) string

InterpolateMemo replaces template placeholders with actual values.

func ResourceIDFromContext

func ResourceIDFromContext(ctx context.Context) (string, bool)

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

type ResourceResolver func(*http.Request) (string, error)

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

func (s *Service) ConsumeNonce(ctx context.Context, nonceID string) error

ConsumeNonce marks a nonce as consumed (one-time use).

func (*Service) CreateNonce

func (s *Service) CreateNonce(ctx context.Context, nonce storage.AdminNonce) error

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

func (s *Service) DenyRefund(ctx context.Context, refundID string) error

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

func (s *Service) GenerateQuote(ctx context.Context, resourceID, couponCode string) (Quote, error)

GenerateQuote builds a paywall quote for the resource with optional coupon.

func (*Service) GetCartQuote

func (s *Service) GetCartQuote(ctx context.Context, cartID string) (storage.CartQuote, error)

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

func (s *Service) GetProduct(ctx context.Context, productID string) (products.Product, error)

GetProduct retrieves a product by ID.

func (*Service) GetRefundQuote

func (s *Service) GetRefundQuote(ctx context.Context, refundID string) (storage.RefundQuote, error)

GetRefundQuote retrieves an existing refund quote by ID.

func (*Service) HasPaymentBeenProcessed

func (s *Service) HasPaymentBeenProcessed(ctx context.Context, signature string) (bool, error)

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

func (s *Service) InterpolateMemo(template, resourceID string) string

InterpolateMemo wraps the standalone InterpolateMemo function. Exposed as a method on Service for use by HTTP handlers.

func (*Service) ListPendingRefunds

func (s *Service) ListPendingRefunds(ctx context.Context) ([]storage.RefundQuote, error)

ListPendingRefunds returns all unprocessed refund quotes. This is used by admin to review pending refund requests.

func (*Service) ListProducts

func (s *Service) ListProducts(ctx context.Context) ([]products.Product, error)

ListProducts returns all active products from the repository (uses cache if enabled).

func (*Service) Middleware

func (s *Service) Middleware(resolver ResourceResolver) func(http.Handler) http.Handler

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.

type SubscriptionInfo

type SubscriptionInfo struct {
	ID               string    `json:"id"`
	Status           string    `json:"status"`
	CurrentPeriodEnd time.Time `json:"currentPeriodEnd"`
}

SubscriptionInfo contains subscription details when access is granted via subscription.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL