f9fa4756e9
Build docker and publish / build (20.15.1) (push) Failing after 8m37s
Co-authored-by: multica-agent <github@multica.ai>
105 lines
3.5 KiB
Go
105 lines
3.5 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/perfect-panel/server/internal/model/user"
|
|
"github.com/perfect-panel/server/internal/svc"
|
|
"github.com/perfect-panel/server/internal/types"
|
|
"github.com/perfect-panel/server/pkg/constant"
|
|
"github.com/perfect-panel/server/pkg/logger"
|
|
"github.com/perfect-panel/server/pkg/xerr"
|
|
"github.com/pkg/errors"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CommissionWithdrawLogic struct {
|
|
logger.Logger
|
|
ctx context.Context
|
|
svcCtx *svc.ServiceContext
|
|
}
|
|
|
|
// Commission Withdraw
|
|
func NewCommissionWithdrawLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommissionWithdrawLogic {
|
|
return &CommissionWithdrawLogic{
|
|
Logger: logger.WithContext(ctx),
|
|
ctx: ctx,
|
|
svcCtx: svcCtx,
|
|
}
|
|
}
|
|
|
|
func (l *CommissionWithdrawLogic) CommissionWithdraw(req *types.CommissionWithdrawRequest) (resp *types.WithdrawalLog, err error) {
|
|
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
|
if !ok {
|
|
logger.Error("current user is not found in context")
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
|
}
|
|
|
|
// Validate payment method fields
|
|
switch req.Method {
|
|
case user.WithdrawalMethodAlipay, user.WithdrawalMethodWechat:
|
|
if req.QrCodeUrl == "" {
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "qr_code_url is required for method %d", req.Method)
|
|
}
|
|
case user.WithdrawalMethodBank:
|
|
if req.Account == "" {
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "account is required for bank transfer")
|
|
}
|
|
default: // WithdrawalMethodOther
|
|
if req.Account == "" && req.Content == "" {
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "account or content is required for other methods")
|
|
}
|
|
}
|
|
|
|
// Sum all pending (status=0) withdrawals to compute available balance.
|
|
// 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 = ?", u.Id, user.WithdrawalStatusPending).
|
|
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)
|
|
}
|
|
|
|
var w user.Withdrawal
|
|
err = l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error {
|
|
w = user.Withdrawal{
|
|
UserId: u.Id,
|
|
Amount: req.Amount,
|
|
Content: req.Content,
|
|
Status: user.WithdrawalStatusPending,
|
|
Reason: "",
|
|
Method: req.Method,
|
|
Account: req.Account,
|
|
QrCodeUrl: req.QrCodeUrl,
|
|
}
|
|
return tx.Create(&w).Error
|
|
})
|
|
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{
|
|
Id: w.Id,
|
|
UserId: u.Id,
|
|
Amount: req.Amount,
|
|
Content: req.Content,
|
|
Status: user.WithdrawalStatusPending,
|
|
Reason: "",
|
|
Method: req.Method,
|
|
Account: req.Account,
|
|
QrCodeUrl: req.QrCodeUrl,
|
|
CreatedAt: w.CreatedAt.UnixMilli(),
|
|
UpdatedAt: w.UpdatedAt.UnixMilli(),
|
|
}, nil
|
|
}
|