feat: Add exchange rate conversion for balance calculations (#4534)

This commit is contained in:
Yang Luo
2025-11-21 22:13:26 +08:00
parent a48b1d0c73
commit 8002613398
6 changed files with 128 additions and 12 deletions

View File

@@ -589,7 +589,7 @@ func (org *Organization) GetInitScore() (int, error) {
}
}
func UpdateOrganizationBalance(owner string, name string, balance float64, isOrgBalance bool, lang string) error {
func UpdateOrganizationBalance(owner string, name string, balance float64, currency string, isOrgBalance bool, lang string) error {
organization, err := getOrganization(owner, name)
if err != nil {
return err
@@ -598,12 +598,19 @@ func UpdateOrganizationBalance(owner string, name string, balance float64, isOrg
return fmt.Errorf(i18n.Translate(lang, "auth:the organization: %s is not found"), fmt.Sprintf("%s/%s", owner, name))
}
// Convert the balance amount from transaction currency to organization's balance currency
balanceCurrency := organization.BalanceCurrency
if balanceCurrency == "" {
balanceCurrency = "USD"
}
convertedBalance := ConvertCurrency(balance, currency, balanceCurrency)
var columns []string
if isOrgBalance {
organization.OrgBalance = AddPrices(organization.OrgBalance, balance)
organization.OrgBalance = AddPrices(organization.OrgBalance, convertedBalance)
columns = []string{"org_balance"}
} else {
organization.UserBalance = AddPrices(organization.UserBalance, balance)
organization.UserBalance = AddPrices(organization.UserBalance, convertedBalance)
columns = []string{"user_balance"}
}

View File

@@ -207,7 +207,11 @@ func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp
}
if payment.IsRecharge {
err = UpdateUserBalance(payment.Owner, payment.User, payment.Price, "en")
currency := payment.Currency
if currency == "" {
currency = "USD"
}
err = UpdateUserBalance(payment.Owner, payment.User, payment.Price, currency, "en")
return payment, notifyResult, err
}

View File

@@ -323,16 +323,36 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
if provider.Type == "Dummy" {
payment.State = pp.PaymentStatePaid
err = UpdateUserBalance(user.Owner, user.Name, payment.Price, "en")
currency := payment.Currency
if currency == "" {
currency = "USD"
}
err = UpdateUserBalance(user.Owner, user.Name, payment.Price, currency, "en")
if err != nil {
return nil, nil, err
}
} else if provider.Type == "Balance" {
if product.Price > user.Balance {
// Convert product price to user's balance currency for comparison
productCurrency := product.Currency
if productCurrency == "" {
productCurrency = "USD"
}
userBalanceCurrency := user.BalanceCurrency
if userBalanceCurrency == "" {
// Get organization's balance currency as fallback
org, err := getOrganization("admin", user.Owner)
if err == nil && org != nil && org.BalanceCurrency != "" {
userBalanceCurrency = org.BalanceCurrency
} else {
userBalanceCurrency = "USD"
}
}
convertedPrice := ConvertCurrency(product.Price, productCurrency, userBalanceCurrency)
if convertedPrice > user.Balance {
return nil, nil, fmt.Errorf("insufficient user balance")
}
transaction.Amount = -transaction.Amount
err = UpdateUserBalance(user.Owner, user.Name, -product.Price, "en")
err = UpdateUserBalance(user.Owner, user.Name, -product.Price, productCurrency, "en")
if err != nil {
return nil, nil, err
}

View File

@@ -178,19 +178,24 @@ func (transaction *Transaction) GetId() string {
}
func updateBalanceForTransaction(transaction *Transaction, amount float64, lang string) error {
currency := transaction.Currency
if currency == "" {
currency = "USD"
}
if transaction.Tag == "Organization" {
// Update organization's own balance
return UpdateOrganizationBalance("admin", transaction.Owner, amount, true, lang)
return UpdateOrganizationBalance("admin", transaction.Owner, amount, currency, true, lang)
} else if transaction.Tag == "User" {
// Update user's balance
if transaction.User == "" {
return fmt.Errorf(i18n.Translate(lang, "general:User is required for User category transaction"))
}
if err := UpdateUserBalance(transaction.Owner, transaction.User, amount, lang); err != nil {
if err := UpdateUserBalance(transaction.Owner, transaction.User, amount, currency, lang); err != nil {
return err
}
// Update organization's user balance sum
return UpdateOrganizationBalance("admin", transaction.Owner, amount, false, lang)
return UpdateOrganizationBalance("admin", transaction.Owner, amount, currency, false, lang)
}
return nil
}

View File

@@ -1479,7 +1479,7 @@ func GenerateIdForNewUser(application *Application) (string, error) {
return res, nil
}
func UpdateUserBalance(owner string, name string, balance float64, lang string) error {
func UpdateUserBalance(owner string, name string, balance float64, currency string, lang string) error {
user, err := getUser(owner, name)
if err != nil {
return err
@@ -1487,7 +1487,21 @@ func UpdateUserBalance(owner string, name string, balance float64, lang string)
if user == nil {
return fmt.Errorf(i18n.Translate(lang, "general:The user: %s is not found"), fmt.Sprintf("%s/%s", owner, name))
}
user.Balance = AddPrices(user.Balance, balance)
// Convert the balance amount from transaction currency to user's balance currency
balanceCurrency := user.BalanceCurrency
if balanceCurrency == "" {
// Get organization's balance currency as fallback
org, err := getOrganization("admin", owner)
if err == nil && org != nil && org.BalanceCurrency != "" {
balanceCurrency = org.BalanceCurrency
} else {
balanceCurrency = "USD"
}
}
convertedBalance := ConvertCurrency(balance, currency, balanceCurrency)
user.Balance = AddPrices(user.Balance, convertedBalance)
_, err = UpdateUser(user.GetId(), user, []string{"balance"}, true)
return err
}

View File

@@ -16,6 +16,72 @@ package object
import "math"
// Fixed exchange rates (temporary implementation as per requirements)
// All rates represent how many units of the currency equal 1 USD
// Example: EUR: 0.92 means 1 USD = 0.92 EUR
var exchangeRates = map[string]float64{
"USD": 1.0,
"EUR": 0.92,
"GBP": 0.79,
"JPY": 149.50,
"CNY": 7.24,
"AUD": 1.52,
"CAD": 1.39,
"CHF": 0.88,
"HKD": 7.82,
"SGD": 1.34,
"INR": 83.12,
"KRW": 1319.50,
"BRL": 4.97,
"MXN": 17.09,
"ZAR": 18.15,
"RUB": 92.50,
"TRY": 32.15,
"NZD": 1.67,
"SEK": 10.35,
"NOK": 10.72,
"DKK": 6.87,
"PLN": 3.91,
"THB": 34.50,
"MYR": 4.47,
"IDR": 15750.00,
"PHP": 55.50,
"VND": 24500.00,
}
// GetExchangeRate returns the exchange rate from fromCurrency to toCurrency
func GetExchangeRate(fromCurrency, toCurrency string) float64 {
if fromCurrency == toCurrency {
return 1.0
}
// Default to USD if currency not found
fromRate, fromExists := exchangeRates[fromCurrency]
if !fromExists {
fromRate = 1.0
}
toRate, toExists := exchangeRates[toCurrency]
if !toExists {
toRate = 1.0
}
// Convert from source currency to USD, then from USD to target currency
// Example: EUR to JPY = (1/0.92) * 149.50 = USD/EUR * JPY/USD
return toRate / fromRate
}
// ConvertCurrency converts an amount from one currency to another using exchange rates
func ConvertCurrency(amount float64, fromCurrency, toCurrency string) float64 {
if fromCurrency == toCurrency {
return amount
}
rate := GetExchangeRate(fromCurrency, toCurrency)
converted := amount * rate
return math.Round(converted*1e8) / 1e8
}
func AddPrices(price1 float64, price2 float64) float64 {
res := price1 + price2
return math.Round(res*1e8) / 1e8