Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 878d5006f0 |
+5
-5
@@ -41,17 +41,17 @@ type (
|
|||||||
UserId int64 `json:"user_id" validate:"required"`
|
UserId int64 `json:"user_id" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Balance *int64 `json:"balance"`
|
Balance int64 `json:"balance"`
|
||||||
Commission *int64 `json:"commission"`
|
Commission int64 `json:"commission"`
|
||||||
ReferralPercentage uint8 `json:"referral_percentage"`
|
ReferralPercentage uint8 `json:"referral_percentage"`
|
||||||
OnlyFirstPurchase *bool `json:"only_first_purchase"`
|
OnlyFirstPurchase *bool `json:"only_first_purchase"`
|
||||||
GiftAmount *int64 `json:"gift_amount"`
|
GiftAmount int64 `json:"gift_amount"`
|
||||||
Telegram int64 `json:"telegram"`
|
Telegram int64 `json:"telegram"`
|
||||||
ReferCode string `json:"refer_code"`
|
ReferCode string `json:"refer_code"`
|
||||||
RefererId *int64 `json:"referer_id"`
|
RefererId int64 `json:"referer_id"`
|
||||||
Enable *bool `json:"enable"`
|
Enable *bool `json:"enable"`
|
||||||
IsAdmin *bool `json:"is_admin"`
|
IsAdmin *bool `json:"is_admin"`
|
||||||
Remark *string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
UpdateUserNotifySettingRequest {
|
UpdateUserNotifySettingRequest {
|
||||||
UserId int64 `json:"user_id" validate:"required"`
|
UserId int64 `json:"user_id" validate:"required"`
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- Rollback: re-deduct commission for users with pending (status=0) withdrawals.
|
||||||
|
-- This re-applies the OLD behaviour where commission is deducted on application.
|
||||||
|
-- Only run this if you are rolling back to the old code; do NOT run against
|
||||||
|
-- the new code or commission will be double-deducted on approval.
|
||||||
|
|
||||||
|
UPDATE `user` u
|
||||||
|
JOIN (
|
||||||
|
SELECT user_id, COALESCE(SUM(amount), 0) AS pending_total
|
||||||
|
FROM user_withdrawal
|
||||||
|
WHERE status = 0
|
||||||
|
GROUP BY user_id
|
||||||
|
) p ON u.id = p.user_id
|
||||||
|
SET u.commission = u.commission - p.pending_total
|
||||||
|
WHERE p.pending_total > 0;
|
||||||
|
|
||||||
|
-- Remove the migration log entries written by the up migration.
|
||||||
|
DELETE FROM system_log
|
||||||
|
WHERE type = 3
|
||||||
|
AND content LIKE '%migration: refund pending withdrawal commission (HIF-22)%';
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
-- Migration: refund commission for existing pending (status=0) withdrawals
|
||||||
|
--
|
||||||
|
-- Under the old logic, commission was deducted when a withdrawal was submitted.
|
||||||
|
-- Under the new logic, commission is only deducted on approval.
|
||||||
|
-- This migration refunds the deducted amounts back to each user so that
|
||||||
|
-- the system is in a consistent state before the new code is deployed.
|
||||||
|
--
|
||||||
|
-- Idempotency: the UPDATE only touches rows whose commission would need
|
||||||
|
-- to increase, and each execution produces the same result because
|
||||||
|
-- COALESCE(SUM(amount),0) is deterministic given the same pending set.
|
||||||
|
-- Running this script multiple times is safe only if no new pending
|
||||||
|
-- withdrawals are created between runs; deploy new code immediately after.
|
||||||
|
|
||||||
|
-- Step 1: refund commission for all users with pending withdrawals.
|
||||||
|
UPDATE `user` u
|
||||||
|
JOIN (
|
||||||
|
SELECT user_id, COALESCE(SUM(amount), 0) AS pending_total
|
||||||
|
FROM user_withdrawal
|
||||||
|
WHERE status = 0
|
||||||
|
GROUP BY user_id
|
||||||
|
) p ON u.id = p.user_id
|
||||||
|
SET u.commission = u.commission + p.pending_total
|
||||||
|
WHERE p.pending_total > 0;
|
||||||
|
|
||||||
|
-- Step 2: write a migration log entry for each refunded user.
|
||||||
|
INSERT INTO system_log (type, date, object_id, content, created_at)
|
||||||
|
SELECT
|
||||||
|
3 AS type,
|
||||||
|
DATE(NOW()) AS date,
|
||||||
|
p.user_id AS object_id,
|
||||||
|
JSON_OBJECT(
|
||||||
|
'type', 99,
|
||||||
|
'amount', p.pending_total,
|
||||||
|
'order_no', '',
|
||||||
|
'timestamp', UNIX_TIMESTAMP(NOW()) * 1000,
|
||||||
|
'note', 'migration: refund pending withdrawal commission (HIF-22)'
|
||||||
|
) AS content,
|
||||||
|
NOW() AS created_at
|
||||||
|
FROM (
|
||||||
|
SELECT user_id, COALESCE(SUM(amount), 0) AS pending_total
|
||||||
|
FROM user_withdrawal
|
||||||
|
WHERE status = 0
|
||||||
|
GROUP BY user_id
|
||||||
|
HAVING pending_total > 0
|
||||||
|
) p;
|
||||||
@@ -46,13 +46,13 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
||||||
if req.Balance != nil && userInfo.Balance != *req.Balance {
|
if userInfo.Balance != req.Balance {
|
||||||
change := *req.Balance - userInfo.Balance
|
change := req.Balance - userInfo.Balance
|
||||||
balanceLog := log.Balance{
|
balanceLog := log.Balance{
|
||||||
Type: log.BalanceTypeAdjust,
|
Type: log.BalanceTypeAdjust,
|
||||||
Amount: change,
|
Amount: change,
|
||||||
OrderNo: "",
|
OrderNo: "",
|
||||||
Balance: *req.Balance,
|
Balance: req.Balance,
|
||||||
Timestamp: time.Now().UnixMilli(),
|
Timestamp: time.Now().UnixMilli(),
|
||||||
}
|
}
|
||||||
content, _ := balanceLog.Marshal()
|
content, _ := balanceLog.Marshal()
|
||||||
@@ -66,14 +66,14 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
userInfo.Balance = *req.Balance
|
userInfo.Balance = req.Balance
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.GiftAmount != nil && userInfo.GiftAmount != *req.GiftAmount {
|
if userInfo.GiftAmount != req.GiftAmount {
|
||||||
change := *req.GiftAmount - userInfo.GiftAmount
|
change := req.GiftAmount - userInfo.GiftAmount
|
||||||
if change != 0 {
|
if change != 0 {
|
||||||
var changeType uint16
|
var changeType uint16
|
||||||
if userInfo.GiftAmount < *req.GiftAmount {
|
if userInfo.GiftAmount < req.GiftAmount {
|
||||||
changeType = log.GiftTypeIncrease
|
changeType = log.GiftTypeIncrease
|
||||||
} else {
|
} else {
|
||||||
changeType = log.GiftTypeReduce
|
changeType = log.GiftTypeReduce
|
||||||
@@ -81,7 +81,7 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
|||||||
giftLog := log.Gift{
|
giftLog := log.Gift{
|
||||||
Type: changeType,
|
Type: changeType,
|
||||||
Amount: change,
|
Amount: change,
|
||||||
Balance: *req.GiftAmount,
|
Balance: req.GiftAmount,
|
||||||
Remark: "Admin adjustment",
|
Remark: "Admin adjustment",
|
||||||
Timestamp: time.Now().UnixMilli(),
|
Timestamp: time.Now().UnixMilli(),
|
||||||
}
|
}
|
||||||
@@ -96,27 +96,23 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
userInfo.GiftAmount = *req.GiftAmount
|
userInfo.GiftAmount = req.GiftAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Commission != nil && *req.Commission != userInfo.Commission {
|
if req.Commission != userInfo.Commission {
|
||||||
remark := ""
|
if isWithdrawalScene(req.Remark) {
|
||||||
if req.Remark != nil {
|
|
||||||
remark = *req.Remark
|
|
||||||
}
|
|
||||||
if isWithdrawalScene(remark) {
|
|
||||||
logWithdrawalGuard(l.Logger, userInfo.Id)
|
logWithdrawalGuard(l.Logger, userInfo.Id)
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "commission overwrite is blocked in withdrawal scene")
|
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "commission overwrite is blocked in withdrawal scene")
|
||||||
}
|
}
|
||||||
change := *req.Commission - userInfo.Commission
|
change := req.Commission - userInfo.Commission
|
||||||
if err = l.svcCtx.UserModel.UpdateCommission(l.ctx, userInfo.Id, change, tx); err != nil {
|
if err = l.svcCtx.UserModel.UpdateCommission(l.ctx, userInfo.Id, change, tx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = logicCommon.WriteCommissionLog(tx, userInfo.Id, log.CommissionTypeAdjust, change, ""); err != nil {
|
if err = logicCommon.WriteCommissionLog(tx, userInfo.Id, log.CommissionTypeAdjust, change, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
userInfo.Commission = *req.Commission
|
userInfo.Commission = req.Commission
|
||||||
}
|
}
|
||||||
if req.Avatar != "" {
|
if req.Avatar != "" {
|
||||||
userInfo.Avatar = req.Avatar
|
userInfo.Avatar = req.Avatar
|
||||||
@@ -124,17 +120,15 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
|||||||
if req.ReferCode != "" {
|
if req.ReferCode != "" {
|
||||||
userInfo.ReferCode = req.ReferCode
|
userInfo.ReferCode = req.ReferCode
|
||||||
}
|
}
|
||||||
if req.RefererId != nil {
|
userInfo.RefererId = req.RefererId
|
||||||
userInfo.RefererId = *req.RefererId
|
|
||||||
}
|
|
||||||
if req.Enable != nil {
|
if req.Enable != nil {
|
||||||
userInfo.Enable = req.Enable
|
userInfo.Enable = req.Enable
|
||||||
}
|
}
|
||||||
if req.IsAdmin != nil {
|
if req.IsAdmin != nil {
|
||||||
userInfo.IsAdmin = req.IsAdmin
|
userInfo.IsAdmin = req.IsAdmin
|
||||||
}
|
}
|
||||||
if req.Remark != nil {
|
if req.Remark != "" {
|
||||||
userInfo.Remark = *req.Remark
|
userInfo.Remark = req.Remark
|
||||||
}
|
}
|
||||||
if req.OnlyFirstPurchase != nil {
|
if req.OnlyFirstPurchase != nil {
|
||||||
userInfo.OnlyFirstPurchase = req.OnlyFirstPurchase
|
userInfo.OnlyFirstPurchase = req.OnlyFirstPurchase
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import (
|
|||||||
"github.com/perfect-panel/server/pkg/xerr"
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
func approveWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdrawalID int64) error {
|
func approveWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdrawalID int64) error {
|
||||||
return svcCtx.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
var approvedUserID int64
|
||||||
|
err := svcCtx.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
withdrawal, err := logicCommon.LoadPendingWithdrawalForUpdate(ctx, tx, withdrawalID)
|
withdrawal, err := logicCommon.LoadPendingWithdrawalForUpdate(ctx, tx, withdrawalID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "withdrawal status invalid" {
|
if err.Error() == "withdrawal status invalid" {
|
||||||
@@ -24,6 +26,16 @@ func approveWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdraw
|
|||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "load withdrawal failed: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "load withdrawal failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock user row and verify sufficient balance before deducting.
|
||||||
|
var u usermodel.User
|
||||||
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Where("id = ?", withdrawal.UserId).First(&u).Error; err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "load user failed: %v", err)
|
||||||
|
}
|
||||||
|
if u.Commission < withdrawal.Amount {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.UserCommissionNotEnough), "user %d has insufficient commission balance", withdrawal.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
if err := tx.Model(&usermodel.Withdrawal{}).
|
if err := tx.Model(&usermodel.Withdrawal{}).
|
||||||
Where("id = ? AND status = 0", withdrawalID).
|
Where("id = ? AND status = 0", withdrawalID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
@@ -33,17 +45,31 @@ func approveWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdraw
|
|||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "approve withdrawal failed: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "approve withdrawal failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduct commission atomically inside the transaction.
|
||||||
|
if err := tx.Model(&usermodel.User{}).
|
||||||
|
Where("id = ?", withdrawal.UserId).
|
||||||
|
UpdateColumn("commission", gorm.Expr("commission - ?", withdrawal.Amount)).Error; err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "deduct commission failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := logicCommon.WriteCommissionLog(tx, withdrawal.UserId, log.CommissionTypeWithdraw, withdrawal.Amount, ""); err != nil {
|
if err := logicCommon.WriteCommissionLog(tx, withdrawal.UserId, log.CommissionTypeWithdraw, withdrawal.Amount, ""); err != nil {
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "write commission log failed: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "write commission log failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
approvedUserID = withdrawal.UserId
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err == nil && approvedUserID > 0 {
|
||||||
|
_ = svcCtx.UserModel.ClearUserCache(ctx, &usermodel.User{Id: approvedUserID})
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func rejectWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdrawalID int64, reason string) error {
|
func rejectWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdrawalID int64, reason string) error {
|
||||||
reason = strings.TrimSpace(reason)
|
reason = strings.TrimSpace(reason)
|
||||||
return svcCtx.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return svcCtx.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
withdrawal, err := logicCommon.LoadPendingWithdrawalForUpdate(ctx, tx, withdrawalID)
|
_, err := logicCommon.LoadPendingWithdrawalForUpdate(ctx, tx, withdrawalID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "withdrawal status invalid" {
|
if err.Error() == "withdrawal status invalid" {
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.WithdrawalStatusInvalid), "withdrawal %d already processed", withdrawalID)
|
return errors.Wrapf(xerr.NewErrCode(xerr.WithdrawalStatusInvalid), "withdrawal %d already processed", withdrawalID)
|
||||||
@@ -51,23 +77,14 @@ func rejectWithdrawal(ctx context.Context, svcCtx *svc.ServiceContext, withdrawa
|
|||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "load withdrawal failed: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "load withdrawal failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Model(&usermodel.Withdrawal{}).
|
// Commission was NOT deducted at application time under the new logic,
|
||||||
|
// so rejection requires no refund — only a status update.
|
||||||
|
return tx.Model(&usermodel.Withdrawal{}).
|
||||||
Where("id = ? AND status = 0", withdrawalID).
|
Where("id = ? AND status = 0", withdrawalID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"status": 2,
|
"status": 2,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
}).Error; err != nil {
|
}).Error
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "reject withdrawal failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := svcCtx.UserModel.UpdateCommission(ctx, withdrawal.UserId, withdrawal.Amount, tx); err != nil {
|
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "refund commission failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := logicCommon.WriteCommissionLog(tx, withdrawal.UserId, log.CommissionTypeWithdrawReject, withdrawal.Amount, ""); err != nil {
|
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "write commission log failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
logicCommon "github.com/perfect-panel/server/internal/logic/common"
|
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
@@ -38,48 +36,39 @@ func (l *CommissionWithdrawLogic) CommissionWithdraw(req *types.CommissionWithdr
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Commission < req.Amount {
|
// Sum all pending (status=0) withdrawals to compute available balance.
|
||||||
logger.Errorf("User %d has insufficient commission balance: %.2f, requested: %.2f", u.Id, float64(u.Commission)/100, float64(req.Amount)/100)
|
// Available = commission - pendingTotal; commission is only deducted on approval.
|
||||||
|
var pendingTotal int64
|
||||||
|
if err = l.svcCtx.DB.WithContext(l.ctx).
|
||||||
|
Model(&user.Withdrawal{}).
|
||||||
|
Where("user_id = ? AND status = 0", u.Id).
|
||||||
|
Select("COALESCE(SUM(amount), 0)").
|
||||||
|
Scan(&pendingTotal).Error; err != nil {
|
||||||
|
l.Errorf("Failed to query pending withdrawals for user %d: %v", u.Id, err)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to query pending withdrawals for user %d", u.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Commission < req.Amount+pendingTotal {
|
||||||
|
logger.Errorf("User %d insufficient available commission: total=%d pending=%d requested=%d",
|
||||||
|
u.Id, u.Commission, pendingTotal, req.Amount)
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserCommissionNotEnough), "User %d has insufficient commission balance", u.Id)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserCommissionNotEnough), "User %d has insufficient commission balance", u.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
var w user.Withdrawal
|
||||||
// Atomically deduct the requested amount so concurrent commission growth is preserved.
|
err = l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if err = l.svcCtx.DB.WithContext(l.ctx).
|
w = user.Withdrawal{
|
||||||
Model(&user.User{}).
|
|
||||||
Where("id = ? AND commission >= ?", u.Id, req.Amount).
|
|
||||||
UpdateColumn("commission", gorm.Expr("commission - ?", req.Amount)).Error; err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
l.Errorf("Failed to update user %d commission balance: %v", u.Id, err)
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update user %d commission balance: %v", u.Id, err)
|
|
||||||
}
|
|
||||||
_ = l.svcCtx.UserModel.ClearUserCache(l.ctx, u)
|
|
||||||
|
|
||||||
// create withdrawal log
|
|
||||||
if err = logicCommon.WriteCommissionLog(tx, u.Id, log.CommissionTypeConvertBalance, req.Amount, ""); err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
l.Errorf("Failed to create commission log for user %d: %v", u.Id, err)
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create commission log for user %d: %v", u.Id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&user.Withdrawal{}).Create(&user.Withdrawal{
|
|
||||||
UserId: u.Id,
|
UserId: u.Id,
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
Status: 0,
|
Status: 0,
|
||||||
Reason: "",
|
Reason: "",
|
||||||
}).Error
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
l.Errorf("Failed to create withdrawal log for user %d: %v", u.Id, err)
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create withdrawal log for user %d: %v", u.Id, err)
|
|
||||||
}
|
}
|
||||||
if err = tx.Commit().Error; err != nil {
|
return tx.Create(&w).Error
|
||||||
l.Errorf("Transaction commit failed for user %d withdrawal: %v", u.Id, err)
|
})
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Transaction commit failed for user %d withdrawal: %v", u.Id, err)
|
if err != nil {
|
||||||
|
l.Errorf("Failed to create withdrawal for user %d: %v", u.Id, err)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create withdrawal for user %d: %v", u.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.WithdrawalLog{
|
return &types.WithdrawalLog{
|
||||||
|
|||||||
@@ -3226,17 +3226,17 @@ type UpdateUserBasiceInfoRequest struct {
|
|||||||
UserId int64 `json:"user_id" validate:"required"`
|
UserId int64 `json:"user_id" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Balance *int64 `json:"balance"`
|
Balance int64 `json:"balance"`
|
||||||
Commission *int64 `json:"commission"`
|
Commission int64 `json:"commission"`
|
||||||
ReferralPercentage uint8 `json:"referral_percentage"`
|
ReferralPercentage uint8 `json:"referral_percentage"`
|
||||||
OnlyFirstPurchase *bool `json:"only_first_purchase"`
|
OnlyFirstPurchase *bool `json:"only_first_purchase"`
|
||||||
GiftAmount *int64 `json:"gift_amount"`
|
GiftAmount int64 `json:"gift_amount"`
|
||||||
Telegram int64 `json:"telegram"`
|
Telegram int64 `json:"telegram"`
|
||||||
ReferCode string `json:"refer_code"`
|
ReferCode string `json:"refer_code"`
|
||||||
RefererId *int64 `json:"referer_id"`
|
RefererId int64 `json:"referer_id"`
|
||||||
Enable *bool `json:"enable"`
|
Enable *bool `json:"enable"`
|
||||||
IsAdmin *bool `json:"is_admin"`
|
IsAdmin *bool `json:"is_admin"`
|
||||||
Remark *string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserNotifyRequest struct {
|
type UpdateUserNotifyRequest struct {
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdateUserBasiceInfoRequestDistinguishesOmittedAndEmptyRemark(t *testing.T) {
|
|
||||||
var omitted UpdateUserBasiceInfoRequest
|
|
||||||
if err := json.Unmarshal([]byte(`{"user_id":1001}`), &omitted); err != nil {
|
|
||||||
t.Fatalf("unmarshal omitted remark: %v", err)
|
|
||||||
}
|
|
||||||
if omitted.Remark != nil {
|
|
||||||
t.Fatalf("omitted remark should stay nil, got %q", *omitted.Remark)
|
|
||||||
}
|
|
||||||
if omitted.RefererId != nil {
|
|
||||||
t.Fatalf("omitted referer_id should stay nil, got %d", *omitted.RefererId)
|
|
||||||
}
|
|
||||||
if omitted.Balance != nil || omitted.GiftAmount != nil || omitted.Commission != nil {
|
|
||||||
t.Fatalf("omitted money fields should stay nil, got balance=%v gift=%v commission=%v", omitted.Balance, omitted.GiftAmount, omitted.Commission)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cleared UpdateUserBasiceInfoRequest
|
|
||||||
if err := json.Unmarshal([]byte(`{"user_id":1001,"remark":""}`), &cleared); err != nil {
|
|
||||||
t.Fatalf("unmarshal empty remark: %v", err)
|
|
||||||
}
|
|
||||||
if cleared.Remark == nil {
|
|
||||||
t.Fatal("explicit empty remark should be present")
|
|
||||||
}
|
|
||||||
if *cleared.Remark != "" {
|
|
||||||
t.Fatalf("explicit empty remark should decode to empty string, got %q", *cleared.Remark)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user