storage

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: 21 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// CleanupInterval is how often the cleanup goroutine runs to remove expired records.
	CleanupInterval = 1 * time.Hour
)
View Source
const (
	// DefaultQueryTimeout is the maximum time allowed for database queries.
	// This prevents queries from hanging indefinitely and causing cascading failures.
	DefaultQueryTimeout = 5 * time.Second
)
View Source
const NonceTTL = 5 * time.Minute

NonceTTL is the time-to-live for admin nonces (5 minutes).

Variables

View Source
var ErrCartExpired = errors.New("storage: cart quote expired")

ErrCartExpired is returned when attempting to use an expired cart quote.

View Source
var ErrNotFound = errors.New("storage: not found")

ErrNotFound is returned when a requested entity is missing from the store.

View Source
var ErrRefundExpired = errors.New("storage: refund expired")

ErrRefundExpired is returned when a refund quote has passed its expiration time.

Functions

func GenerateCartID

func GenerateCartID() (string, error)

GenerateCartID creates a cryptographically random cart identifier.

func GenerateNonceID

func GenerateNonceID() (string, error)

GenerateNonceID creates a new random nonce ID.

func GenerateRefundID

func GenerateRefundID() (string, error)

GenerateRefundID creates a cryptographically random refund identifier.

Types

type AdminNonce

type AdminNonce struct {
	ID         string     // Unique nonce identifier (UUID)
	Purpose    string     // What action this nonce is for (e.g., "list-pending-refunds")
	CreatedAt  time.Time  // When nonce was created
	ExpiresAt  time.Time  // When nonce expires
	ConsumedAt *time.Time // When nonce was consumed (nil if not yet consumed)
}

AdminNonce represents a one-time-use nonce for admin signature replay protection. Each nonce can only be consumed once and expires after a TTL.

func (AdminNonce) IsConsumed

func (n AdminNonce) IsConsumed() bool

IsConsumed returns true if this nonce has been used.

func (AdminNonce) IsExpiredAt

func (n AdminNonce) IsExpiredAt(now time.Time) bool

IsExpiredAt returns true if this nonce has passed its expiration time at the given moment.

type ArchivalConfig

type ArchivalConfig struct {
	Enabled         bool          // Enable automatic archival (default: false)
	RetentionPeriod time.Duration // How long to keep payment signatures (default: 90 days)
	RunInterval     time.Duration // How often to run archival (default: 24 hours)
}

ArchivalConfig holds configuration for automatic payment signature archival.

func DefaultArchivalConfig

func DefaultArchivalConfig() ArchivalConfig

DefaultArchivalConfig returns sensible defaults for signature archival.

type ArchivalService

type ArchivalService struct {
	// contains filtered or unexported fields
}

ArchivalService automatically archives old payment signatures on a schedule.

func NewArchivalService

func NewArchivalService(store Store, config ArchivalConfig, metricsCollector *metrics.Metrics, logger zerolog.Logger) *ArchivalService

NewArchivalService creates a new archival service.

func (*ArchivalService) RunNow

func (s *ArchivalService) RunNow() error

RunNow immediately runs an archival pass (useful for testing or manual triggers).

func (*ArchivalService) Start

func (s *ArchivalService) Start()

Start begins the archival service background loop.

func (*ArchivalService) Stop

func (s *ArchivalService) Stop()

Stop gracefully stops the archival service.

type CartItem

type CartItem struct {
	ResourceID string            // Resource ID from paywall config
	Quantity   int64             // Number of this item
	Price      money.Money       // Price per unit (locked at quote time)
	Metadata   map[string]string // Per-item custom metadata
}

CartItem represents a single item in a cart quote.

type CartQuote

type CartQuote struct {
	ID           string            // Unique cart ID (cart_abc123...)
	Items        []CartItem        // All items in the cart
	Total        money.Money       // Total price (sum of all items)
	Metadata     map[string]string // Cart-level metadata (user_id, campaign, etc.)
	CreatedAt    time.Time         // When quote was generated
	ExpiresAt    time.Time         // When quote becomes invalid
	WalletPaidBy string            // Set after payment verification (for idempotency)
}

CartQuote represents a temporary cart with locked prices and expiration.

func (*CartQuote) IsExpiredAt

func (c *CartQuote) IsExpiredAt(now time.Time) bool

IsExpiredAt returns true if the cart quote has passed its expiration time at the given moment.

type FileStore

type FileStore struct {
	// contains filtered or unexported fields
}

FileStore implements Store using JSON file storage.

func NewFileStore

func NewFileStore(filePath string) (*FileStore, error)

NewFileStore creates a new file-backed store.

⚠️ PRODUCTION WARNING: FileStore is NOT safe for production use! This storage backend should ONLY be used for local development and testing. For production deployments, use PostgreSQL or MongoDB instead.

Reasons FileStore is unsuitable for production:

  1. No horizontal scaling support (multiple instances corrupt data)
  2. Race conditions at high concurrency (>100 req/sec)
  3. 5-second flush interval creates data loss risk
  4. No ACID guarantees (partial writes corrupt database)
  5. Single point of failure (file corruption = total loss)

See docs/PRODUCTION.md for production deployment guide.

func (*FileStore) ArchiveOldPayments

func (s *FileStore) ArchiveOldPayments(_ context.Context, olderThan time.Time) (int64, error)

ArchiveOldPayments deletes payment transactions older than the specified time. This prevents unbounded growth of the file store while maintaining replay protection for recent transactions (e.g., last 90 days).

Returns the number of archived (deleted) records.

func (*FileStore) CleanupExpiredNonces

func (s *FileStore) CleanupExpiredNonces(_ context.Context) (int64, error)

CleanupExpiredNonces deletes expired admin nonces from the file store. This prevents unbounded growth of the file store.

Returns the number of deleted nonces.

func (*FileStore) Close

func (s *FileStore) Close() error

Close closes the file store.

func (*FileStore) ConsumeNonce

func (s *FileStore) ConsumeNonce(_ context.Context, nonceID string) error

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

func (*FileStore) CreateNonce

func (s *FileStore) CreateNonce(_ context.Context, nonce AdminNonce) error

CreateNonce stores a new admin nonce for replay protection.

func (*FileStore) DeleteRefundQuote

func (s *FileStore) DeleteRefundQuote(ctx context.Context, refundID string) error

DeleteRefundQuote removes a refund quote by ID.

func (*FileStore) DeleteWebhook

func (s *FileStore) DeleteWebhook(ctx context.Context, webhookID string) error

DeleteWebhook removes webhook from queue (admin operation).

func (*FileStore) DequeueWebhooks

func (s *FileStore) DequeueWebhooks(ctx context.Context, limit int) ([]PendingWebhook, error)

DequeueWebhooks retrieves webhooks ready for delivery.

func (*FileStore) EnqueueWebhook

func (s *FileStore) EnqueueWebhook(ctx context.Context, webhook PendingWebhook) (string, error)

EnqueueWebhook adds a webhook to the delivery queue.

func (*FileStore) GetCartQuote

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

GetCartQuote retrieves a cart quote by ID.

func (*FileStore) GetCartQuotes

func (s *FileStore) GetCartQuotes(ctx context.Context, cartIDs []string) ([]CartQuote, error)

GetCartQuotes retrieves multiple cart quotes using individual queries.

func (*FileStore) GetPayment

func (s *FileStore) GetPayment(_ context.Context, signature string) (PaymentTransaction, error)

GetPayment retrieves a payment transaction by signature. Returns the original payment record showing which resource it was used for.

func (*FileStore) GetRefundQuote

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

GetRefundQuote retrieves a refund quote by ID.

func (*FileStore) GetRefundQuoteByOriginalPurchaseID

func (s *FileStore) GetRefundQuoteByOriginalPurchaseID(ctx context.Context, originalPurchaseID string) (RefundQuote, error)

GetRefundQuoteByOriginalPurchaseID retrieves a refund quote by original purchase ID (transaction signature). This enforces the one-refund-per-signature limit.

func (*FileStore) GetWebhook

func (s *FileStore) GetWebhook(ctx context.Context, webhookID string) (PendingWebhook, error)

GetWebhook retrieves a webhook by ID (for admin UI).

func (*FileStore) HasCartAccess

func (s *FileStore) HasCartAccess(ctx context.Context, cartID, wallet string) bool

HasCartAccess checks if a cart is paid by the wallet.

func (*FileStore) HasPaymentBeenProcessed

func (s *FileStore) HasPaymentBeenProcessed(_ context.Context, signature string) (bool, error)

HasPaymentBeenProcessed checks if a transaction signature has EVER been used. Returns true if signature exists for ANY resource (prevents cross-resource replay).

func (*FileStore) ListPendingRefunds

func (s *FileStore) ListPendingRefunds(_ context.Context) ([]RefundQuote, error)

ListPendingRefunds returns all unprocessed refund quotes.

func (*FileStore) ListWebhooks

func (s *FileStore) ListWebhooks(ctx context.Context, status WebhookStatus, limit int) ([]PendingWebhook, error)

ListWebhooks lists webhooks with optional status filter (for admin UI).

func (*FileStore) MarkCartPaid

func (s *FileStore) MarkCartPaid(ctx context.Context, cartID, wallet string) error

MarkCartPaid marks a cart as paid.

func (*FileStore) MarkRefundProcessed

func (s *FileStore) MarkRefundProcessed(ctx context.Context, refundID, processedBy, signature string) error

MarkRefundProcessed marks a refund as completed.

func (*FileStore) MarkWebhookFailed

func (s *FileStore) MarkWebhookFailed(ctx context.Context, webhookID string, errorMsg string, nextAttemptAt time.Time) error

MarkWebhookFailed records failed attempt and schedules retry (or moves to DLQ if exhausted).

func (*FileStore) MarkWebhookProcessing

func (s *FileStore) MarkWebhookProcessing(ctx context.Context, webhookID string) error

MarkWebhookProcessing updates webhook status to prevent duplicate processing.

func (*FileStore) MarkWebhookSuccess

func (s *FileStore) MarkWebhookSuccess(ctx context.Context, webhookID string) error

MarkWebhookSuccess marks webhook as successfully delivered and removes from queue.

func (*FileStore) RecordPayment

func (s *FileStore) RecordPayment(_ context.Context, tx PaymentTransaction) error

RecordPayment saves a verified payment transaction for replay protection. CRITICAL: Signature is globally unique - once used, cannot be reused for any resource. Returns error if signature already exists (concurrent replay attack).

func (*FileStore) RecordPayments

func (s *FileStore) RecordPayments(ctx context.Context, txs []PaymentTransaction) error

RecordPayments saves multiple payment transactions using individual operations.

func (*FileStore) RetryWebhook

func (s *FileStore) RetryWebhook(ctx context.Context, webhookID string) error

RetryWebhook resets webhook to pending state for manual retry (admin operation).

func (*FileStore) SaveCartQuote

func (s *FileStore) SaveCartQuote(ctx context.Context, quote CartQuote) error

SaveCartQuote persists or updates a cart quote.

func (*FileStore) SaveCartQuotes

func (s *FileStore) SaveCartQuotes(ctx context.Context, quotes []CartQuote) error

SaveCartQuotes stores multiple cart quotes using individual operations.

func (*FileStore) SaveRefundQuote

func (s *FileStore) SaveRefundQuote(ctx context.Context, quote RefundQuote) error

SaveRefundQuote persists or updates a refund quote.

func (*FileStore) SaveRefundQuotes

func (s *FileStore) SaveRefundQuotes(ctx context.Context, quotes []RefundQuote) error

SaveRefundQuotes stores multiple refund quotes using individual operations.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore is an in-memory Store implementation suitable for tests and single-instance deployments.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore constructs a MemoryStore and starts background cleanup.

func (*MemoryStore) ArchiveOldPayments

func (m *MemoryStore) ArchiveOldPayments(_ context.Context, olderThan time.Time) (int64, error)

ArchiveOldPayments deletes payment transactions older than the specified time. For MemoryStore, this is primarily for testing - memory stores are ephemeral.

func (*MemoryStore) CleanupExpiredNonces

func (m *MemoryStore) CleanupExpiredNonces(_ context.Context) (int64, error)

CleanupExpiredNonces deletes expired admin nonces. For MemoryStore, this returns the count of deleted nonces.

func (*MemoryStore) Close

func (m *MemoryStore) Close() error

Close implements the Store interface by calling Stop.

func (*MemoryStore) ConsumeNonce

func (m *MemoryStore) ConsumeNonce(_ context.Context, nonceID string) error

ConsumeNonce marks a nonce as consumed (one-time use). Returns error if nonce doesn't exist, is already consumed, or has expired.

func (*MemoryStore) CreateNonce

func (m *MemoryStore) CreateNonce(_ context.Context, nonce AdminNonce) error

CreateNonce stores a new admin nonce for replay protection. Nonce must be unique and not already exist.

func (*MemoryStore) DeleteRefundQuote

func (m *MemoryStore) DeleteRefundQuote(_ context.Context, refundID string) error

DeleteRefundQuote removes a refund quote by ID.

func (*MemoryStore) DeleteWebhook

func (m *MemoryStore) DeleteWebhook(ctx context.Context, webhookID string) error

DeleteWebhook removes webhook from queue (admin operation).

func (*MemoryStore) DequeueWebhooks

func (m *MemoryStore) DequeueWebhooks(ctx context.Context, limit int) ([]PendingWebhook, error)

DequeueWebhooks retrieves webhooks ready for delivery.

func (*MemoryStore) EnqueueWebhook

func (m *MemoryStore) EnqueueWebhook(ctx context.Context, webhook PendingWebhook) (string, error)

EnqueueWebhook adds a webhook to the delivery queue.

func (*MemoryStore) GetCartQuote

func (m *MemoryStore) GetCartQuote(_ context.Context, cartID string) (CartQuote, error)

GetCartQuote retrieves a cart quote by ID. Returns ErrNotFound if cart doesn't exist, ErrCartExpired if expired.

func (*MemoryStore) GetCartQuotes

func (m *MemoryStore) GetCartQuotes(_ context.Context, cartIDs []string) ([]CartQuote, error)

GetCartQuotes retrieves multiple cart quotes by ID. Returns a slice with found quotes - missing or expired carts are skipped (partial results).

func (*MemoryStore) GetPayment

func (m *MemoryStore) GetPayment(_ context.Context, signature string) (PaymentTransaction, error)

GetPayment retrieves a payment transaction by signature. Returns the original payment showing which resource it was used for.

func (*MemoryStore) GetRefundQuote

func (m *MemoryStore) GetRefundQuote(_ context.Context, refundID string) (RefundQuote, error)

GetRefundQuote retrieves a refund quote by ID. NOTE: Refund requests never expire - they remain pending until approved or denied by admin.

func (*MemoryStore) GetRefundQuoteByOriginalPurchaseID

func (m *MemoryStore) GetRefundQuoteByOriginalPurchaseID(_ context.Context, originalPurchaseID string) (RefundQuote, error)

GetRefundQuoteByOriginalPurchaseID retrieves a refund quote by original purchase ID (transaction signature). This enforces the one-refund-per-signature limit.

func (*MemoryStore) GetWebhook

func (m *MemoryStore) GetWebhook(ctx context.Context, webhookID string) (PendingWebhook, error)

GetWebhook retrieves a webhook by ID (for admin UI).

func (*MemoryStore) HasCartAccess

func (m *MemoryStore) HasCartAccess(_ context.Context, cartID, wallet string) bool

HasCartAccess checks if a wallet has already paid for a cart.

func (*MemoryStore) HasPaymentBeenProcessed

func (m *MemoryStore) HasPaymentBeenProcessed(_ context.Context, signature string) (bool, error)

HasPaymentBeenProcessed checks if a transaction signature has EVER been used. Returns true if signature exists for ANY resource (prevents cross-resource replay).

func (*MemoryStore) ListPendingRefunds

func (m *MemoryStore) ListPendingRefunds(_ context.Context) ([]RefundQuote, error)

ListPendingRefunds returns all unprocessed refund quotes.

func (*MemoryStore) ListWebhooks

func (m *MemoryStore) ListWebhooks(ctx context.Context, status WebhookStatus, limit int) ([]PendingWebhook, error)

ListWebhooks lists webhooks with optional status filter (for admin UI).

func (*MemoryStore) MarkCartPaid

func (m *MemoryStore) MarkCartPaid(_ context.Context, cartID, wallet string) error

MarkCartPaid records the wallet that paid for a cart (for idempotency).

func (*MemoryStore) MarkRefundProcessed

func (m *MemoryStore) MarkRefundProcessed(_ context.Context, refundID, processedBy, signature string) error

MarkRefundProcessed marks a refund as completed with the transaction signature.

func (*MemoryStore) MarkWebhookFailed

func (m *MemoryStore) MarkWebhookFailed(ctx context.Context, webhookID string, errorMsg string, nextAttemptAt time.Time) error

MarkWebhookFailed records failed attempt and schedules retry (or moves to DLQ if exhausted).

func (*MemoryStore) MarkWebhookProcessing

func (m *MemoryStore) MarkWebhookProcessing(ctx context.Context, webhookID string) error

MarkWebhookProcessing updates webhook status to prevent duplicate processing.

func (*MemoryStore) MarkWebhookSuccess

func (m *MemoryStore) MarkWebhookSuccess(ctx context.Context, webhookID string) error

MarkWebhookSuccess marks webhook as successfully delivered and removes from queue.

func (*MemoryStore) RecordPayment

func (m *MemoryStore) RecordPayment(_ context.Context, tx PaymentTransaction) error

RecordPayment saves a verified payment transaction for replay protection. CRITICAL: Signature is the sole key - prevents cross-resource replay attacks. Returns error if signature already exists (concurrent replay attack).

func (*MemoryStore) RecordPayments

func (m *MemoryStore) RecordPayments(_ context.Context, txs []PaymentTransaction) error

RecordPayments saves multiple verified payment transactions in a single operation. CRITICAL: ALL signatures must be globally unique - batch fails if ANY signature exists. This is atomic - either all succeed or none are stored (fail fast on first duplicate).

func (*MemoryStore) RetryWebhook

func (m *MemoryStore) RetryWebhook(ctx context.Context, webhookID string) error

RetryWebhook resets webhook to pending state for manual retry (admin operation).

func (*MemoryStore) SaveCartQuote

func (m *MemoryStore) SaveCartQuote(_ context.Context, quote CartQuote) error

SaveCartQuote stores a cart quote with automatic expiration.

func (*MemoryStore) SaveCartQuotes

func (m *MemoryStore) SaveCartQuotes(_ context.Context, quotes []CartQuote) error

SaveCartQuotes stores multiple cart quotes in a single operation. All quotes are validated before any are stored (atomic batch).

func (*MemoryStore) SaveRefundQuote

func (m *MemoryStore) SaveRefundQuote(_ context.Context, quote RefundQuote) error

SaveRefundQuote persists or updates a refund quote.

func (*MemoryStore) SaveRefundQuotes

func (m *MemoryStore) SaveRefundQuotes(_ context.Context, quotes []RefundQuote) error

SaveRefundQuotes stores multiple refund quotes in a single operation. All quotes are validated before any are stored (atomic batch).

func (*MemoryStore) Stop

func (m *MemoryStore) Stop()

Stop gracefully stops the cleanup goroutine.

type MongoDBStore

type MongoDBStore struct {
	// contains filtered or unexported fields
}

MongoDBStore implements Store using MongoDB.

func NewMongoDBStore

func NewMongoDBStore(connectionString, database string) (*MongoDBStore, error)

NewMongoDBStore creates a new MongoDB-backed store.

func (*MongoDBStore) ArchiveOldPayments

func (s *MongoDBStore) ArchiveOldPayments(ctx context.Context, olderThan time.Time) (int64, error)

ArchiveOldPayments deletes payment transactions older than the specified time. This prevents unbounded growth of the payment_transactions collection while maintaining replay protection for recent transactions (e.g., last 90 days).

Returns the number of archived (deleted) records.

func (*MongoDBStore) CleanupExpiredNonces

func (s *MongoDBStore) CleanupExpiredNonces(ctx context.Context) (int64, error)

CleanupExpiredNonces deletes expired admin nonces from the database. This prevents unbounded growth of the admin_nonces collection.

Returns the number of deleted nonces.

func (*MongoDBStore) Close

func (s *MongoDBStore) Close() error

Close closes the database connection.

func (*MongoDBStore) ConsumeNonce

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

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

func (*MongoDBStore) CreateNonce

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

CreateNonce stores a new admin nonce for replay protection.

func (*MongoDBStore) DeleteRefundQuote

func (s *MongoDBStore) DeleteRefundQuote(ctx context.Context, refundID string) error

DeleteRefundQuote removes a refund quote by ID.

func (*MongoDBStore) DeleteWebhook

func (s *MongoDBStore) DeleteWebhook(ctx context.Context, webhookID string) error

DeleteWebhook removes webhook from queue (admin operation).

func (*MongoDBStore) DequeueWebhooks

func (s *MongoDBStore) DequeueWebhooks(ctx context.Context, limit int) ([]PendingWebhook, error)

DequeueWebhooks retrieves webhooks ready for delivery.

func (*MongoDBStore) EnqueueWebhook

func (s *MongoDBStore) EnqueueWebhook(ctx context.Context, webhook PendingWebhook) (string, error)

EnqueueWebhook adds a webhook to the delivery queue.

func (*MongoDBStore) GetCartQuote

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

GetCartQuote retrieves a cart quote by ID.

func (*MongoDBStore) GetCartQuotes

func (s *MongoDBStore) GetCartQuotes(ctx context.Context, cartIDs []string) ([]CartQuote, error)

GetCartQuotes retrieves multiple cart quotes using MongoDB $in operator (single query).

func (*MongoDBStore) GetPayment

func (s *MongoDBStore) GetPayment(ctx context.Context, signature string) (PaymentTransaction, error)

GetPayment retrieves a payment transaction by signature. Returns the original payment record showing which resource it was used for.

func (*MongoDBStore) GetRefundQuote

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

GetRefundQuote retrieves a refund quote by ID.

func (*MongoDBStore) GetRefundQuoteByOriginalPurchaseID

func (s *MongoDBStore) GetRefundQuoteByOriginalPurchaseID(ctx context.Context, originalPurchaseID string) (RefundQuote, error)

GetRefundQuoteByOriginalPurchaseID retrieves a refund quote by original purchase ID (transaction signature). This enforces the one-refund-per-signature limit.

func (*MongoDBStore) GetWebhook

func (s *MongoDBStore) GetWebhook(ctx context.Context, webhookID string) (PendingWebhook, error)

GetWebhook retrieves a webhook by ID (for admin UI).

func (*MongoDBStore) HasCartAccess

func (s *MongoDBStore) HasCartAccess(ctx context.Context, cartID, wallet string) bool

HasCartAccess checks if a cart is paid by the wallet.

func (*MongoDBStore) HasPaymentBeenProcessed

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

HasPaymentBeenProcessed checks if a transaction signature has EVER been used. Returns true if signature exists for ANY resource (prevents cross-resource replay).

func (*MongoDBStore) ListPendingRefunds

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

ListPendingRefunds returns all unprocessed refund quotes.

func (*MongoDBStore) ListWebhooks

func (s *MongoDBStore) ListWebhooks(ctx context.Context, status WebhookStatus, limit int) ([]PendingWebhook, error)

ListWebhooks lists webhooks with optional status filter (for admin UI).

func (*MongoDBStore) MarkCartPaid

func (s *MongoDBStore) MarkCartPaid(ctx context.Context, cartID, wallet string) error

MarkCartPaid marks a cart as paid.

func (*MongoDBStore) MarkRefundProcessed

func (s *MongoDBStore) MarkRefundProcessed(ctx context.Context, refundID, processedBy, signature string) error

MarkRefundProcessed marks a refund as completed.

func (*MongoDBStore) MarkWebhookFailed

func (s *MongoDBStore) MarkWebhookFailed(ctx context.Context, webhookID string, errorMsg string, nextAttemptAt time.Time) error

MarkWebhookFailed records failed attempt and schedules retry (or moves to DLQ if exhausted).

func (*MongoDBStore) MarkWebhookProcessing

func (s *MongoDBStore) MarkWebhookProcessing(ctx context.Context, webhookID string) error

MarkWebhookProcessing updates webhook status to prevent duplicate processing.

func (*MongoDBStore) MarkWebhookSuccess

func (s *MongoDBStore) MarkWebhookSuccess(ctx context.Context, webhookID string) error

MarkWebhookSuccess marks webhook as successfully delivered and removes from queue.

func (*MongoDBStore) RecordPayment

func (s *MongoDBStore) RecordPayment(ctx context.Context, tx PaymentTransaction) error

RecordPayment saves a verified payment transaction for replay protection. CRITICAL: Signature is globally unique - once used, cannot be reused for any resource. Returns error if signature already exists (concurrent replay attack).

func (*MongoDBStore) RecordPayments

func (s *MongoDBStore) RecordPayments(ctx context.Context, txs []PaymentTransaction) error

RecordPayments saves multiple payment transactions using MongoDB bulk operations. Note: Uses ordered=true to maintain atomic failure on duplicate signatures.

func (*MongoDBStore) RetryWebhook

func (s *MongoDBStore) RetryWebhook(ctx context.Context, webhookID string) error

RetryWebhook resets webhook to pending state for manual retry (admin operation).

func (*MongoDBStore) SaveCartQuote

func (s *MongoDBStore) SaveCartQuote(ctx context.Context, quote CartQuote) error

SaveCartQuote persists or updates a cart quote.

func (*MongoDBStore) SaveCartQuotes

func (s *MongoDBStore) SaveCartQuotes(ctx context.Context, quotes []CartQuote) error

SaveCartQuotes stores multiple cart quotes using MongoDB bulk operations.

func (*MongoDBStore) SaveRefundQuote

func (s *MongoDBStore) SaveRefundQuote(ctx context.Context, quote RefundQuote) error

SaveRefundQuote persists or updates a refund quote.

func (*MongoDBStore) SaveRefundQuotes

func (s *MongoDBStore) SaveRefundQuotes(ctx context.Context, quotes []RefundQuote) error

SaveRefundQuotes stores multiple refund quotes using MongoDB bulk operations.

type PaymentTransaction

type PaymentTransaction struct {
	Signature  string            // Transaction signature (unique ID, globally unique)
	ResourceID string            // Resource that was purchased
	Wallet     string            // Wallet that made the payment
	Amount     money.Money       // Amount paid
	CreatedAt  time.Time         // When transaction was verified
	Metadata   map[string]string // Additional metadata
}

PaymentTransaction represents a verified payment transaction. Used for replay protection - ensures each transaction signature is only used ONCE globally.

type PaymentTransactionStore

type PaymentTransactionStore interface {
	// RecordPayment saves a verified payment transaction.
	// The signature must be globally unique - attempting to record the same signature
	// twice (even for different resources) should fail or be silently ignored.
	RecordPayment(ctx context.Context, tx PaymentTransaction) error

	// HasPaymentBeenProcessed checks if a transaction signature has EVER been used.
	// Returns true if the signature exists for ANY resource (not just the specified one).
	// This prevents cross-resource replay attacks.
	HasPaymentBeenProcessed(ctx context.Context, signature string) (bool, error)

	// GetPayment retrieves a payment transaction by signature.
	// Returns the original payment record, which shows which resource it was used for.
	GetPayment(ctx context.Context, signature string) (PaymentTransaction, error)
}

PaymentTransactionStore defines the interface for payment transaction persistence. CRITICAL: This is used for replay protection to ensure each signature is only used ONCE, regardless of which resource it's being used for. Once a signature is consumed for any resource, it CANNOT be reused for any other resource.

type PendingWebhook

type PendingWebhook struct {
	ID            string            `json:"id"`            // Unique webhook identifier (webhook_123...)
	URL           string            `json:"url"`           // Destination URL
	Payload       json.RawMessage   `json:"payload"`       // JSON payload to send
	Headers       map[string]string `json:"headers"`       // HTTP headers
	EventType     string            `json:"eventType"`     // "payment" or "refund"
	Status        WebhookStatus     `json:"status"`        // Current status
	Attempts      int               `json:"attempts"`      // Number of delivery attempts
	MaxAttempts   int               `json:"maxAttempts"`   // Maximum retry attempts (e.g., 5)
	LastError     string            `json:"lastError"`     // Error from last attempt
	LastAttemptAt time.Time         `json:"lastAttemptAt"` // When last attempt was made
	NextAttemptAt time.Time         `json:"nextAttemptAt"` // When next attempt should be made
	CreatedAt     time.Time         `json:"createdAt"`     // When webhook was created
	CompletedAt   *time.Time        `json:"completedAt"`   // When webhook was successfully delivered or failed permanently
}

PendingWebhook represents a webhook waiting for delivery or retry. This struct is persisted to the database to ensure delivery across server restarts.

func (PendingWebhook) IsFinallyFailed

func (w PendingWebhook) IsFinallyFailed() bool

IsFinallyFailed returns true if the webhook has exhausted all retries.

func (PendingWebhook) IsReadyForDelivery

func (w PendingWebhook) IsReadyForDelivery() bool

IsReadyForDelivery returns true if the webhook should be processed now.

type PostgresStore

type PostgresStore struct {
	// contains filtered or unexported fields
}

PostgresStore implements Store using PostgreSQL.

func NewPostgresStore

func NewPostgresStore(connectionString string, poolConfig config.PostgresPoolConfig) (*PostgresStore, error)

NewPostgresStore creates a new PostgreSQL-backed store.

func NewPostgresStoreWithDB

func NewPostgresStoreWithDB(db *sql.DB) (*PostgresStore, error)

NewPostgresStoreWithDB creates a PostgreSQL-backed store using an existing connection pool. This allows sharing a single connection pool across multiple stores/repositories.

func (*PostgresStore) ArchiveOldPayments

func (s *PostgresStore) ArchiveOldPayments(ctx context.Context, olderThan time.Time) (int64, error)

ArchiveOldPayments deletes payment transactions older than the specified time. This prevents unbounded growth of the payment_transactions table while maintaining replay protection for recent transactions (e.g., last 90 days).

Returns the number of archived (deleted) records.

func (*PostgresStore) CleanupExpiredNonces

func (s *PostgresStore) CleanupExpiredNonces(ctx context.Context) (int64, error)

CleanupExpiredNonces deletes expired admin nonces from the database. This prevents unbounded growth of the admin_nonces table.

Returns the number of deleted nonces.

func (*PostgresStore) Close

func (s *PostgresStore) Close() error

Close closes the database connection.

func (*PostgresStore) ConsumeNonce

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

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

func (*PostgresStore) CreateNonce

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

CreateNonce stores a new admin nonce for replay protection.

func (*PostgresStore) DeleteRefundQuote

func (s *PostgresStore) DeleteRefundQuote(ctx context.Context, refundID string) error

DeleteRefundQuote removes a refund quote by ID.

func (*PostgresStore) DeleteWebhook

func (s *PostgresStore) DeleteWebhook(ctx context.Context, webhookID string) error

DeleteWebhook removes webhook from queue (admin operation).

func (*PostgresStore) DequeueWebhooks

func (s *PostgresStore) DequeueWebhooks(ctx context.Context, limit int) ([]PendingWebhook, error)

DequeueWebhooks retrieves webhooks ready for delivery.

func (*PostgresStore) EnqueueWebhook

func (s *PostgresStore) EnqueueWebhook(ctx context.Context, webhook PendingWebhook) (string, error)

EnqueueWebhook adds a webhook to the delivery queue.

func (*PostgresStore) GetCartQuote

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

GetCartQuote retrieves a cart quote by ID.

func (*PostgresStore) GetCartQuotes

func (s *PostgresStore) GetCartQuotes(ctx context.Context, cartIDs []string) ([]CartQuote, error)

GetCartQuotes retrieves multiple cart quotes by IDs in a single query. Returns found quotes - missing or expired carts are skipped (partial results).

func (*PostgresStore) GetPayment

func (s *PostgresStore) GetPayment(ctx context.Context, signature string) (PaymentTransaction, error)

GetPayment retrieves a payment transaction by signature. Returns the original payment record showing which resource it was used for.

func (*PostgresStore) GetRefundQuote

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

GetRefundQuote retrieves a refund quote by ID.

func (*PostgresStore) GetRefundQuoteByOriginalPurchaseID

func (s *PostgresStore) GetRefundQuoteByOriginalPurchaseID(ctx context.Context, originalPurchaseID string) (RefundQuote, error)

GetRefundQuoteByOriginalPurchaseID retrieves a refund quote by original purchase ID (transaction signature). This enforces the one-refund-per-signature limit.

func (*PostgresStore) GetWebhook

func (s *PostgresStore) GetWebhook(ctx context.Context, webhookID string) (PendingWebhook, error)

GetWebhook retrieves a webhook by ID (for admin UI).

func (*PostgresStore) HasCartAccess

func (s *PostgresStore) HasCartAccess(ctx context.Context, cartID, wallet string) bool

HasCartAccess checks if a cart is paid by the wallet.

func (*PostgresStore) HasPaymentBeenProcessed

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

HasPaymentBeenProcessed checks if a transaction signature has EVER been used. Returns true if signature exists for ANY resource (prevents cross-resource replay).

func (*PostgresStore) ListPendingRefunds

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

ListPendingRefunds returns all unprocessed refund quotes.

func (*PostgresStore) ListWebhooks

func (s *PostgresStore) ListWebhooks(ctx context.Context, status WebhookStatus, limit int) ([]PendingWebhook, error)

ListWebhooks lists webhooks with optional status filter (for admin UI).

func (*PostgresStore) MarkCartPaid

func (s *PostgresStore) MarkCartPaid(ctx context.Context, cartID, wallet string) error

MarkCartPaid marks a cart as paid.

func (*PostgresStore) MarkRefundProcessed

func (s *PostgresStore) MarkRefundProcessed(ctx context.Context, refundID, processedBy, signature string) error

MarkRefundProcessed marks a refund as completed.

func (*PostgresStore) MarkWebhookFailed

func (s *PostgresStore) MarkWebhookFailed(ctx context.Context, webhookID string, errorMsg string, nextAttemptAt time.Time) error

MarkWebhookFailed records failed attempt and schedules retry (or moves to DLQ if exhausted).

func (*PostgresStore) MarkWebhookProcessing

func (s *PostgresStore) MarkWebhookProcessing(ctx context.Context, webhookID string) error

MarkWebhookProcessing updates webhook status to prevent duplicate processing.

func (*PostgresStore) MarkWebhookSuccess

func (s *PostgresStore) MarkWebhookSuccess(ctx context.Context, webhookID string) error

MarkWebhookSuccess marks webhook as successfully delivered and removes from queue.

func (*PostgresStore) RecordPayment

func (s *PostgresStore) RecordPayment(ctx context.Context, tx PaymentTransaction) error

RecordPayment saves a verified payment transaction for replay protection. CRITICAL: Signature is globally unique - once used, cannot be reused for any resource. Returns error if signature was already used (concurrent request won the race).

func (*PostgresStore) RecordPayments

func (s *PostgresStore) RecordPayments(ctx context.Context, txs []PaymentTransaction) error

RecordPayments saves multiple verified payment transactions in a single batch operation. CRITICAL: ALL signatures must be globally unique - batch fails if ANY signature already exists. Uses multi-row INSERT for optimal performance (single database round-trip).

func (*PostgresStore) RetryWebhook

func (s *PostgresStore) RetryWebhook(ctx context.Context, webhookID string) error

RetryWebhook resets webhook to pending state for manual retry (admin operation).

func (*PostgresStore) SaveCartQuote

func (s *PostgresStore) SaveCartQuote(ctx context.Context, quote CartQuote) error

SaveCartQuote persists or updates a cart quote.

func (*PostgresStore) SaveCartQuotes

func (s *PostgresStore) SaveCartQuotes(ctx context.Context, quotes []CartQuote) error

SaveCartQuotes stores multiple cart quotes in a single batch operation. Uses multi-row INSERT for optimal performance (single database round-trip).

func (*PostgresStore) SaveRefundQuote

func (s *PostgresStore) SaveRefundQuote(ctx context.Context, quote RefundQuote) error

SaveRefundQuote persists or updates a refund quote.

func (*PostgresStore) SaveRefundQuotes

func (s *PostgresStore) SaveRefundQuotes(ctx context.Context, quotes []RefundQuote) error

SaveRefundQuotes stores multiple refund quotes in a single batch operation. Uses multi-row INSERT for optimal performance (single database round-trip).

func (*PostgresStore) WithTableNames

func (s *PostgresStore) WithTableNames(paymentTransactions, adminNonces, cartQuotes, refundQuotes, webhookQueue string) *PostgresStore

WithTableNames sets custom table names (for schema_mapping support). After setting table names, it recreates tables with the new names.

type RefundQuote

type RefundQuote struct {
	ID                 string
	OriginalPurchaseID string // Reference to original purchase (resource ID, cart ID, session ID)
	RecipientWallet    string // Wallet receiving the refund
	Amount             money.Money
	Reason             string
	Metadata           map[string]string
	CreatedAt          time.Time
	ExpiresAt          time.Time
	ProcessedBy        string // Wallet that executed the refund
	ProcessedAt        *time.Time
	Signature          string // Transaction signature
}

RefundQuote represents a generated refund quote with payment details.

func (*RefundQuote) IsExpiredAt

func (r *RefundQuote) IsExpiredAt(now time.Time) bool

IsExpiredAt returns true if the refund quote's transaction execution window has passed at the given moment. This means the blockhash is expired and the transaction cannot be executed. NOTE: This does NOT mean the refund request is deleted - it remains in storage and can be re-quoted or denied by an admin.

func (*RefundQuote) IsProcessed

func (r *RefundQuote) IsProcessed() bool

IsProcessed returns true if the refund has been completed.

type Store

type Store interface {
	// Single-record cart operations
	SaveCartQuote(ctx context.Context, quote CartQuote) error
	GetCartQuote(ctx context.Context, cartID string) (CartQuote, error)
	MarkCartPaid(ctx context.Context, cartID, wallet string) error
	HasCartAccess(ctx context.Context, cartID, wallet string) bool

	// Batch cart operations (for bulk imports, analytics, admin dashboards)
	// SaveCartQuotes stores multiple quotes efficiently (atomic: all succeed or all fail).
	// GetCartQuotes retrieves multiple quotes in a single query (partial results: skips missing/expired).
	SaveCartQuotes(ctx context.Context, quotes []CartQuote) error
	GetCartQuotes(ctx context.Context, cartIDs []string) ([]CartQuote, error)

	// Single-record refund operations
	SaveRefundQuote(ctx context.Context, quote RefundQuote) error
	GetRefundQuote(ctx context.Context, refundID string) (RefundQuote, error)
	GetRefundQuoteByOriginalPurchaseID(ctx context.Context, originalPurchaseID string) (RefundQuote, error)
	ListPendingRefunds(ctx context.Context) ([]RefundQuote, error)
	MarkRefundProcessed(ctx context.Context, refundID, processedBy, signature string) error
	DeleteRefundQuote(ctx context.Context, refundID string) error

	// Batch refund operations (for bulk refund processing)
	SaveRefundQuotes(ctx context.Context, quotes []RefundQuote) error

	// Single-record payment transaction tracking for replay protection
	// CRITICAL: Signatures are globally unique - once used for any resource, they cannot be reused
	RecordPayment(ctx context.Context, tx PaymentTransaction) error
	HasPaymentBeenProcessed(ctx context.Context, signature string) (bool, error)
	GetPayment(ctx context.Context, signature string) (PaymentTransaction, error)

	// Batch payment operations (for bulk settlement jobs, batch imports)
	// CRITICAL: All signatures must be globally unique - batch fails if any signature exists
	RecordPayments(ctx context.Context, txs []PaymentTransaction) error

	// Payment archival for database cleanup
	// Archives old payment signatures beyond the retention period to prevent unbounded growth
	ArchiveOldPayments(ctx context.Context, olderThan time.Time) (int64, error) // Returns count of archived records

	// Nonce management for admin signature replay protection
	// CRITICAL: Each nonce can only be used once - prevents signature replay attacks
	CreateNonce(ctx context.Context, nonce AdminNonce) error
	ConsumeNonce(ctx context.Context, nonceID string) error // Returns error if already consumed or not found

	// Admin nonce cleanup for database maintenance
	CleanupExpiredNonces(ctx context.Context) (int64, error) // Returns count of deleted nonces

	// Webhook queue operations for persistent webhook delivery
	// EnqueueWebhook adds a webhook to the delivery queue (returns webhook ID)
	EnqueueWebhook(ctx context.Context, webhook PendingWebhook) (string, error)
	// DequeueWebhooks retrieves webhooks ready for delivery (up to limit, ordered by next attempt time)
	DequeueWebhooks(ctx context.Context, limit int) ([]PendingWebhook, error)
	// MarkWebhookProcessing updates webhook status to prevent duplicate processing
	MarkWebhookProcessing(ctx context.Context, webhookID string) error
	// MarkWebhookSuccess marks webhook as successfully delivered and removes from queue
	MarkWebhookSuccess(ctx context.Context, webhookID string) error
	// MarkWebhookFailed records failed attempt and schedules retry (or moves to DLQ if exhausted)
	MarkWebhookFailed(ctx context.Context, webhookID string, errorMsg string, nextAttemptAt time.Time) error
	// GetWebhook retrieves a webhook by ID (for admin UI)
	GetWebhook(ctx context.Context, webhookID string) (PendingWebhook, error)
	// ListWebhooks lists webhooks with optional status filter (for admin UI)
	ListWebhooks(ctx context.Context, status WebhookStatus, limit int) ([]PendingWebhook, error)
	// RetryWebhook resets webhook to pending state for manual retry (admin operation)
	RetryWebhook(ctx context.Context, webhookID string) error
	// DeleteWebhook removes webhook from queue (admin operation)
	DeleteWebhook(ctx context.Context, webhookID string) error

	Close() error
}

Store captures the persistence requirements for paywall state.

Batch Operations

The Store interface provides batch operations for improved performance:

  • SaveCartQuotes / GetCartQuotes: Bulk cart operations (100x faster for bulk imports)
  • SaveRefundQuotes: Bulk refund creation (for batch refund processing)
  • RecordPayments: Bulk payment recording (for settlement jobs)

PostgreSQL implementation uses prepared statements and bulk queries. MongoDB/FileStore implementations use loop-based fallbacks (can be optimized later).

func NewStore

func NewStore(cfg StoreConfig) (Store, error)

NewStore creates a Store instance based on the provided configuration.

func NewStoreWithDB

func NewStoreWithDB(cfg StoreConfig, sharedDB *sql.DB) (Store, error)

NewStoreWithDB creates a Store instance with an optional shared database pool. If sharedDB is provided (non-nil) for postgres backends, it will be used instead of creating a new connection. Pass nil to create a new connection pool.

type StoreConfig

type StoreConfig struct {
	Backend         string // "memory", "postgres", "mongodb", or "file"
	PostgresURL     string
	MongoDBURL      string
	MongoDBDatabase string
	FilePath        string
	PostgresPool    config.PostgresPoolConfig // PostgreSQL connection pool settings
	CartQuoteTTL    time.Duration             // How long cart quotes remain valid
	RefundQuoteTTL  time.Duration             // How long refund quotes remain valid
	CleanupInterval time.Duration             // How often to clean up expired quotes

	// Schema mapping (table names for Postgres, collection names for MongoDB)
	PaymentTransactionsTableName string // Default: "payment_transactions"
	AdminNoncesTableName         string // Default: "admin_nonces"
	CartQuotesTableName          string // Default: "cart_quotes"
	RefundQuotesTableName        string // Default: "refund_quotes"
	WebhookQueueTableName        string // Default: "webhook_queue"
}

StoreConfig holds storage backend configuration.

type WebhookStatus

type WebhookStatus string

WebhookStatus represents the current state of a webhook in the queue.

const (
	WebhookStatusPending    WebhookStatus = "pending"    // Waiting for delivery
	WebhookStatusProcessing WebhookStatus = "processing" // Currently being delivered
	WebhookStatusFailed     WebhookStatus = "failed"     // Failed after all retries (DLQ)
	WebhookStatusSuccess    WebhookStatus = "success"    // Successfully delivered
)

Jump to

Keyboard shortcuts

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