package economy import ( "context" "database/sql" "errors" "fmt" ) // ارزها. const ( CurrencyCoin = "coin" CurrencyTicket = "ticket" ) var ( ErrInsufficient = errors.New("insufficient balance") ErrBadCurrency = errors.New("invalid currency") ) // column نام ستون موجودی را برای یک ارز برمی‌گرداند. func column(currency string) (string, error) { switch currency { case CurrencyCoin: return "coins", nil case CurrencyTicket: return "tickets", nil } return "", ErrBadCurrency } // adjustTx موجودی را داخل یک تراکنش تغییر داده و در دفتر ثبت می‌کند. // برای delta منفی، اگر موجودی کافی نباشد ErrInsufficient برمی‌گرداند (بدون تغییر). func adjustTx(ctx context.Context, tx *sql.Tx, userID int64, currency string, delta int64, reason, ref string) error { col, err := column(currency) if err != nil { return err } var res sql.Result if delta < 0 { // فقط در صورت کفایت موجودی کم کن (اتمیک) res, err = tx.ExecContext(ctx, fmt.Sprintf("UPDATE users SET %s = %s + ? WHERE id = ? AND %s >= ?", col, col, col), delta, userID, -delta) } else { res, err = tx.ExecContext(ctx, fmt.Sprintf("UPDATE users SET %s = %s + ? WHERE id = ?", col, col), delta, userID) } if err != nil { return err } n, _ := res.RowsAffected() if n == 0 { return ErrInsufficient } _, err = tx.ExecContext(ctx, "INSERT INTO wallet_tx (user_id, amount, reason, ref, currency) VALUES (?, ?, ?, ?, ?)", userID, delta, reason, ref, currency) return err } // Adjust موجودی را در یک تراکنش مستقل تغییر می‌دهد. func (s *Service) Adjust(ctx context.Context, userID int64, currency string, delta int64, reason, ref string) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() if err := adjustTx(ctx, tx, userID, currency, delta, reason, ref); err != nil { return err } return tx.Commit() }