85 lines
payment/store.go
PaymentStore interface and in-memory implementation for tests.
// Package payment processes financial transactions for connected accounts.package paymentimport ( "context" "fmt" "sync" "time")
// Transaction records a completed payment operation for accounting.type Transaction struct { ID string AccountID string Amount int64 RefID stringCreatedAt time.Time
}
// PaymentStore manages account balances and transaction records.// Balance check and deduction must be performed atomically within a single// database transaction to prevent concurrent overdrafts.type PaymentStore interface { // GetBalance returns the current balance for accountID. GetBalance(ctx context.Context, accountID string) (int64, error) // DeductBalance subtracts amount from accountID's balance. DeductBalance(ctx context.Context, accountID string, amount int64) error // RecordTransaction persists a completed transaction for accounting. RecordTransaction(ctx context.Context, tx Transaction) error // WithTx begins a database transaction and executes fn within it. // All store operations within fn are part of the same transaction. WithTx(ctx context.Context, fn func(txCtx context.Context) error) error}
// MemStore is a thread-safe in-memory PaymentStore for testing.type MemStore struct {mu sync.Mutex
balances map[string]int64Transactions []Transaction
RecordErr error}
// NewMemStore returns a MemStore with the given starting balances.func NewMemStore(balances map[string]int64) *MemStore { return &MemStore{balances: balances}}
// GetBalance returns the current in-memory balance for id.func (s *MemStore) GetBalance(_ context.Context, id string) (int64, error) {s.mu.Lock()
defer s.mu.Unlock() return s.balances[id], nil}
// DeductBalance subtracts amount from the in-memory balance.func (s *MemStore) DeductBalance(_ context.Context, id string, amount int64) error {s.mu.Lock()
defer s.mu.Unlock()s.balances[id] -= amount
return nil}
// RecordTransaction appends a transaction unless RecordErr is configured.func (s *MemStore) RecordTransaction(_ context.Context, tx Transaction) error { if s.RecordErr != nil { return s.RecordErr}
s.mu.Lock()
defer s.mu.Unlock() s.Transactions = append(s.Transactions, tx) return nil}
// WithTx runs fn in the in-memory transaction boundary used by tests.func (s *MemStore) WithTx(_ context.Context, fn func(context.Context) error) error { return fn(context.Background())}
// Balance returns the current balance for assertions in tests.func (s *MemStore) Balance(id string) (int64, error) { return s.GetBalance(context.Background(), id)}
// ErrInsufficientFunds is returned when the account balance is below the requested amount.var ErrInsufficientFunds = fmt.Errorf("insufficient funds")