Multi-currency

This commit is contained in:
2025-03-23 17:58:12 +08:00
parent 90f451cf5a
commit 468cd655f8
9 changed files with 206 additions and 118 deletions

View File

@@ -50,7 +50,7 @@ func (v *Server) MakeTransaction(ctx context.Context, request *proto.MakeTransac
}
}
transaction, err := services.MakeTransaction(request.GetAmount(), request.GetRemark(), payerWallet, payeeWallet)
transaction, err := services.MakeTransaction(request.GetAmount(), request.GetRemark(), request.GetCurrency(), payerWallet, payeeWallet)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
@@ -77,7 +77,7 @@ func (v *Server) MakeTransactionWithAccount(ctx context.Context, request *proto.
}
}
transaction, err := services.MakeTransaction(request.GetAmount(), request.GetRemark(), payerWallet, payeeWallet)
transaction, err := services.MakeTransaction(request.GetAmount(), request.GetRemark(), request.GetCurrency(), payerWallet, payeeWallet)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}

View File

@@ -24,4 +24,5 @@ type Order struct {
Transaction *Transaction `json:"transaction"`
TransactionID *uint `json:"transaction_id"`
ClientID *uint `json:"client_id"`
Currency string `json:"currency"`
}

View File

@@ -10,12 +10,13 @@ import (
type Transaction struct {
cruda.BaseModel
Remark string `json:"remark"` // The usage of this transaction
Amount decimal.Decimal `json:"amount" type:"decimal(30,2);"`
Payer *Wallet `json:"payer" foreignKey:"PayerID"` // Who give the money
Payee *Wallet `json:"payee" foreignKey:"PayeeID"` // Who get the money
PayerID *uint `json:"payer_id"` // Leave this field as nil means pay from the system
PayeeID *uint `json:"payee_id"` // Leave this field as nil means pay to the system
Remark string `json:"remark"` // The usage of this transaction
Amount decimal.Decimal `json:"amount" type:"decimal(30,2);"`
Currency string `json:"currency" gorm:"default:'normal'"`
Payer *Wallet `json:"payer" foreignKey:"PayerID"` // Who give the money
Payee *Wallet `json:"payee" foreignKey:"PayeeID"` // Who get the money
PayerID *uint `json:"payer_id"` // Leave this field as nil means pay from the system
PayeeID *uint `json:"payee_id"` // Leave this field as nil means pay to the system
}
func (v *Transaction) ToTransactionInfo() *proto.TransactionInfo {

View File

@@ -9,9 +9,10 @@ import (
type Wallet struct {
cruda.BaseModel
Balance decimal.Decimal `json:"balance" sql:"type:decimal(30,2);"`
Password string `json:"password"`
AccountID uint `json:"account_id"`
Balance decimal.Decimal `json:"balance" sql:"type:decimal(30,2);"`
GoldenBalance decimal.Decimal `json:"golden_balance" sql:"type:decimal(30,2);"`
Password string `json:"password"`
AccountID uint `json:"account_id"`
}
func (v *Wallet) ToWalletInfo() *proto.WalletInfo {

View File

@@ -11,6 +11,7 @@ import (
"git.solsynth.dev/hypernet/wallet/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/wallet/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"golang.org/x/crypto/bcrypt"
)
@@ -34,12 +35,17 @@ func createOrder(c *fiber.Ctx) error {
Amount float64 `json:"amount" validate:"required"`
PayeeID *uint `json:"payee_id"`
PayerID *uint `json:"payer_id"`
Currency string `json:"currency" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
if !lo.Contains([]string{"normal", "golden"}, data.Currency) {
return fiber.NewError(fiber.StatusBadRequest, "invalid currency")
}
// Validating client
client, err := authkit.GetThirdClientByAlias(gap.Nx, data.ClientID, &data.ClientSecret)
if err != nil {
@@ -50,6 +56,7 @@ func createOrder(c *fiber.Ctx) error {
Status: models.OrderStatusPending,
Remark: data.Remark,
Amount: decimal.NewFromFloat(data.Amount),
Currency: data.Currency,
ClientID: &client.ID,
}
@@ -126,7 +133,7 @@ func payOrder(c *fiber.Ctx) error {
}
}
if tran, err := services.MakeTransaction(order.Amount.InexactFloat64(), order.Remark, payer, payee); err != nil {
if tran, err := services.MakeTransaction(order.Amount.InexactFloat64(), order.Remark, order.Currency, payer, payee); err != nil {
return fiber.NewError(fiber.StatusPaymentRequired, err.Error())
} else {
if err := database.C.Model(&order).Updates(&models.Order{
@@ -137,6 +144,7 @@ func payOrder(c *fiber.Ctx) error {
_, _ = services.MakeTransaction(
order.Amount.InexactFloat64(),
fmt.Sprintf("%s - #%d Refund", order.Remark, order.ID),
order.Currency,
payee,
payer,
)

View File

@@ -11,6 +11,7 @@ import (
"git.solsynth.dev/hypernet/wallet/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/wallet/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func listTransaction(c *fiber.Ctx) error {
@@ -77,12 +78,17 @@ func makeTransaction(c *fiber.Ctx) error {
Amount float64 `json:"amount" validate:"required"`
PayeeID *uint `json:"payee_id"`
PayerID *uint `json:"payer_id"`
Currency string `json:"currency" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
if !lo.Contains([]string{"normal", "golden"}, data.Currency) {
return fiber.NewError(fiber.StatusBadRequest, "invalid currency")
}
// Validating client
client, err := authkit.GetThirdClientByAlias(gap.Nx, data.ClientID, &data.ClientSecret)
if err != nil {
@@ -110,7 +116,7 @@ func makeTransaction(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "payee and payer cannot be both blank")
}
tran, err := services.MakeTransaction(data.Amount, data.Remark, payer, payee)
tran, err := services.MakeTransaction(data.Amount, data.Remark, data.Currency, payer, payee)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}

View File

@@ -12,13 +12,14 @@ import (
"github.com/shopspring/decimal"
)
func MakeTransaction(amount float64, remark string, payer, payee *models.Wallet) (models.Transaction, error) {
func MakeTransaction(amount float64, remark, currency string, payer, payee *models.Wallet) (models.Transaction, error) {
// Round amount to keep 2 decimal places
amount = math.Round(amount*100) / 100
transaction := models.Transaction{
Amount: decimal.NewFromFloat(amount),
Remark: remark,
Amount: decimal.NewFromFloat(amount),
Remark: remark,
Currency: currency,
}
if payer != nil {
if payer.Balance.LessThan(transaction.Amount) {
@@ -38,7 +39,12 @@ func MakeTransaction(amount float64, remark string, payer, payee *models.Wallet)
}
if payer != nil {
payer.Balance = payer.Balance.Sub(transaction.Amount)
switch currency {
case "golden":
payer.GoldenBalance = payer.GoldenBalance.Sub(transaction.Amount)
default:
payer.Balance = payer.Balance.Sub(transaction.Amount)
}
if err := tx.Model(payer).
Updates(&models.Wallet{Balance: payer.Balance}).Error; err != nil {
tx.Rollback()
@@ -46,7 +52,12 @@ func MakeTransaction(amount float64, remark string, payer, payee *models.Wallet)
}
}
if payee != nil {
payee.Balance = payee.Balance.Add(transaction.Amount)
switch currency {
case "golden":
payee.GoldenBalance = payee.GoldenBalance.Add(transaction.Amount)
default:
payee.Balance = payee.Balance.Add(transaction.Amount)
}
if err := tx.Model(payee).
Updates(&models.Wallet{Balance: payee.Balance}).Error; err != nil {
tx.Rollback()
@@ -63,10 +74,18 @@ func MakeTransaction(amount float64, remark string, payer, payee *models.Wallet)
Subtitle: transaction.Remark,
Body: fmt.Sprintf("%.2f SRC removed from your wallet. Your new balance is %.2f", amount, payer.Balance.InexactFloat64()),
Metadata: map[string]any{
"id": transaction.ID,
"amount": amount,
"balance": payer.Balance.InexactFloat64(),
"remark": transaction.Remark,
"id": transaction.ID,
"amount": amount,
"balance": (func() float64 {
switch currency {
case "golden":
return payer.GoldenBalance.InexactFloat64()
default:
return payer.Balance.InexactFloat64()
}
})(),
"remark": transaction.Remark,
"currency": currency,
},
Priority: 0,
})
@@ -78,10 +97,18 @@ func MakeTransaction(amount float64, remark string, payer, payee *models.Wallet)
Subtitle: transaction.Remark,
Body: fmt.Sprintf("%.2f SRC added to your wallet. Your new balance is %.2f", amount, payee.Balance.InexactFloat64()),
Metadata: map[string]any{
"id": transaction.ID,
"amount": amount,
"balance": payee.Balance.InexactFloat64(),
"remark": transaction.Remark,
"id": transaction.ID,
"amount": amount,
"balance": (func() float64 {
switch currency {
case "golden":
return payee.GoldenBalance.InexactFloat64()
default:
return payee.Balance.InexactFloat64()
}
})(),
"remark": transaction.Remark,
"currency": currency,
},
Priority: 0,
})