package payments import ( "context" "database/sql" "errors" "fmt" "time" "github.com/google/uuid" ) // railAvailability reads a rail's operational availability. A missing row is fail-open: enabled with // no message, so payments stay on unless an operator explicitly disabled the rail. func (s *Store) railAvailability(ctx context.Context, rail string) (RailAvailability, error) { var a RailAvailability err := s.db.QueryRowContext(ctx, `SELECT enabled, message_ru, message_en FROM payments.rail_status WHERE rail = $1`, rail). Scan(&a.Enabled, &a.MessageRU, &a.MessageEN) if errors.Is(err, sql.ErrNoRows) { return RailAvailability{Enabled: true}, nil } if err != nil { return RailAvailability{}, fmt.Errorf("payments: read rail status %q: %w", rail, err) } return a, nil } // allRailStatus reads every stored rail-status row for the admin editor, keyed by rail. Rails with // no row are absent (the caller fills them with the fail-open enabled default). func (s *Store) allRailStatus(ctx context.Context) (map[string]RailAvailability, error) { rows, err := s.db.QueryContext(ctx, `SELECT rail, enabled, message_ru, message_en FROM payments.rail_status`) if err != nil { return nil, fmt.Errorf("payments: read rail statuses: %w", err) } defer rows.Close() out := make(map[string]RailAvailability) for rows.Next() { var rail string var a RailAvailability if err := rows.Scan(&rail, &a.Enabled, &a.MessageRU, &a.MessageEN); err != nil { return nil, fmt.Errorf("payments: scan rail status: %w", err) } out[rail] = a } return out, rows.Err() } // setRailStatus upserts a rail's operational status (admin). func (s *Store) setRailStatus(ctx context.Context, rail string, a RailAvailability, now time.Time) error { if _, err := s.db.ExecContext(ctx, `INSERT INTO payments.rail_status (rail, enabled, message_ru, message_en, updated_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (rail) DO UPDATE SET enabled = $2, message_ru = $3, message_en = $4, updated_at = $5`, rail, a.Enabled, a.MessageRU, a.MessageEN, now); err != nil { return fmt.Errorf("payments: set rail status %q: %w", rail, err) } return nil } // purchaseOverride reads an account's purchase override; a missing row is OverrideDefault. func (s *Store) purchaseOverride(ctx context.Context, accountID uuid.UUID) (PurchaseOverride, error) { var allow bool err := s.db.QueryRowContext(ctx, `SELECT allow FROM payments.account_payment_override WHERE account_id = $1`, accountID).Scan(&allow) if errors.Is(err, sql.ErrNoRows) { return OverrideDefault, nil } if err != nil { return OverrideDefault, fmt.Errorf("payments: read purchase override %s: %w", accountID, err) } if allow { return OverrideAllow, nil } return OverrideDeny, nil } // setPurchaseOverride upserts an account's override, or deletes the row for OverrideDefault (the // default state is the absence of a row). func (s *Store) setPurchaseOverride(ctx context.Context, accountID uuid.UUID, ov PurchaseOverride, now time.Time) error { if ov == OverrideDefault { if _, err := s.db.ExecContext(ctx, `DELETE FROM payments.account_payment_override WHERE account_id = $1`, accountID); err != nil { return fmt.Errorf("payments: clear purchase override %s: %w", accountID, err) } return nil } if _, err := s.db.ExecContext(ctx, `INSERT INTO payments.account_payment_override (account_id, allow, updated_at) VALUES ($1, $2, $3) ON CONFLICT (account_id) DO UPDATE SET allow = $2, updated_at = $3`, accountID, ov == OverrideAllow, now); err != nil { return fmt.Errorf("payments: set purchase override %s: %w", accountID, err) } return nil }