✨ 新增几大中心功能
This commit is contained in:
528
server/api/v1/admin/app_withdraw_orders.go
Normal file
528
server/api/v1/admin/app_withdraw_orders.go
Normal file
@@ -0,0 +1,528 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/Go-Web-Template/server/global"
|
||||
appModel "git.echol.cn/loser/Go-Web-Template/server/model/app"
|
||||
"git.echol.cn/loser/Go-Web-Template/server/model/common/response"
|
||||
"git.echol.cn/loser/Go-Web-Template/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type AppWithdrawOrdersApi struct{}
|
||||
|
||||
type appWithdrawOrderListItem struct {
|
||||
ID uint `json:"id"`
|
||||
AppUserID uint `json:"appUserId"`
|
||||
Username string `json:"username"`
|
||||
AmountLi int64 `json:"amountLi"`
|
||||
FeeLi int64 `json:"feeLi"`
|
||||
NetLi int64 `json:"netLi"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
OperatorUserID uint `json:"operatorUserId"`
|
||||
ApprovedBy uint `json:"approvedBy"`
|
||||
ApprovedAt *time.Time `json:"approvedAt"`
|
||||
PaidBy uint `json:"paidBy"`
|
||||
PaidAt *time.Time `json:"paidAt"`
|
||||
}
|
||||
|
||||
// GetAppWithdrawOrdersList
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 后台获取提现订单列表(单位:厘)
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param pageSize query int false "每页大小" default(10)
|
||||
// @Param userId query int false "玩家ID"
|
||||
// @Param username query string false "用户名(模糊)"
|
||||
// @Param status query string false "状态:pending/approved/rejected/paid/failed"
|
||||
// @Param startAt query string false "开始时间(ISO8601/RFC3339)"
|
||||
// @Param endAt query string false "结束时间(ISO8601/RFC3339)"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /admin/app-withdraw-orders [get]
|
||||
func (a *AppWithdrawOrdersApi) GetAppWithdrawOrdersList(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 200 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
var userID uint
|
||||
if raw := strings.TrimSpace(c.Query("userId")); raw != "" {
|
||||
if v, err := strconv.Atoi(raw); err == nil && v > 0 {
|
||||
userID = uint(v)
|
||||
}
|
||||
}
|
||||
username := strings.TrimSpace(c.Query("username"))
|
||||
status := strings.TrimSpace(c.Query("status"))
|
||||
|
||||
var startAt *time.Time
|
||||
if raw := strings.TrimSpace(c.Query("startAt")); raw != "" {
|
||||
if t, err := time.Parse(time.RFC3339, raw); err == nil {
|
||||
startAt = &t
|
||||
}
|
||||
}
|
||||
var endAt *time.Time
|
||||
if raw := strings.TrimSpace(c.Query("endAt")); raw != "" {
|
||||
if t, err := time.Parse(time.RFC3339, raw); err == nil {
|
||||
endAt = &t
|
||||
}
|
||||
}
|
||||
|
||||
db := global.GVA_DB.Model(&appModel.AppWithdrawOrder{})
|
||||
if userID > 0 {
|
||||
db = db.Where("app_user_id = ?", userID)
|
||||
}
|
||||
if username != "" {
|
||||
db = db.Where("username LIKE ?", "%"+username+"%")
|
||||
}
|
||||
if status != "" {
|
||||
db = db.Where("status = ?", status)
|
||||
}
|
||||
if startAt != nil {
|
||||
db = db.Where("created_at >= ?", *startAt)
|
||||
}
|
||||
if endAt != nil {
|
||||
db = db.Where("created_at <= ?", *endAt)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
var list []appWithdrawOrderListItem
|
||||
if err := db.
|
||||
Order("id desc").
|
||||
Limit(pageSize).
|
||||
Offset((page - 1) * pageSize).
|
||||
Select("id, app_user_id, username, amount_li, fee_li, net_li, status, created_at, operator_user_id, approved_by, approved_at, paid_by, paid_at").
|
||||
Scan(&list).Error; err != nil {
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithData(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, c)
|
||||
}
|
||||
|
||||
// GetAppWithdrawOrderById
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 后台获取提现订单详情(单位:厘)
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param id path int true "订单ID"
|
||||
// @Success 200 {object} response.Response{data=appModel.AppWithdrawOrder,msg=string} "获取成功"
|
||||
// @Router /admin/app-withdraw-orders/{id} [get]
|
||||
func (a *AppWithdrawOrdersApi) GetAppWithdrawOrderById(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
var rec appModel.AppWithdrawOrder
|
||||
if err := global.GVA_DB.First(&rec, uint(id)).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.FailWithMessage("订单不存在", c)
|
||||
return
|
||||
}
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(rec, c)
|
||||
}
|
||||
|
||||
type createWithdrawOrderRequest struct {
|
||||
AppUserID uint `json:"appUserId" binding:"required"`
|
||||
AmountLi int64 `json:"amountLi" binding:"required"`
|
||||
FeeLi int64 `json:"feeLi"`
|
||||
Channel string `json:"channel"`
|
||||
PayeeAccount string `json:"payeeAccount"`
|
||||
PayeeRealName string `json:"payeeRealName"`
|
||||
ApplyRemark string `json:"applyRemark"`
|
||||
}
|
||||
|
||||
// CreateAppWithdrawOrder
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 后台创建提现订单(会冻结账户余额)
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param body body createWithdrawOrderRequest true "参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /admin/app-withdraw-orders [post]
|
||||
func (a *AppWithdrawOrdersApi) CreateAppWithdrawOrder(c *gin.Context) {
|
||||
var req createWithdrawOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
if req.AmountLi <= 0 {
|
||||
response.FailWithMessage("amountLi 必须 > 0", c)
|
||||
return
|
||||
}
|
||||
if req.FeeLi < 0 {
|
||||
response.FailWithMessage("feeLi 不合法", c)
|
||||
return
|
||||
}
|
||||
net := req.AmountLi - req.FeeLi
|
||||
if net <= 0 {
|
||||
response.FailWithMessage("手续费不能大于等于提现金额", c)
|
||||
return
|
||||
}
|
||||
|
||||
operatorID := utils.GetUserID(c)
|
||||
if operatorID == 0 {
|
||||
response.FailWithMessage("未登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
var user appModel.AppUser
|
||||
if err := tx.First(&user, req.AppUserID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("玩家不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var asset appModel.AppUserAsset
|
||||
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", user.ID).First(&asset).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
asset = appModel.AppUserAsset{AppUserID: user.ID}
|
||||
if err := tx.Create(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", user.ID).First(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
available := asset.AccountBalanceLi - asset.AccountFrozenLi
|
||||
if available < req.AmountLi {
|
||||
return errors.New("可提现余额不足")
|
||||
}
|
||||
asset.AccountFrozenLi += req.AmountLi
|
||||
if err := tx.Model(&asset).Select("account_frozen_li").Updates(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
order := appModel.AppWithdrawOrder{
|
||||
AppUserID: user.ID,
|
||||
Username: user.Username,
|
||||
AmountLi: req.AmountLi,
|
||||
FeeLi: req.FeeLi,
|
||||
NetLi: net,
|
||||
Status: appModel.AppWithdrawOrderStatusPending,
|
||||
Channel: strings.TrimSpace(req.Channel),
|
||||
PayeeAccount: strings.TrimSpace(req.PayeeAccount),
|
||||
PayeeRealName: strings.TrimSpace(req.PayeeRealName),
|
||||
ApplyRemark: strings.TrimSpace(req.ApplyRemark),
|
||||
OperatorUserID: operatorID,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return tx.Create(&order).Error
|
||||
}); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
type auditWithdrawOrderRequest struct {
|
||||
AuditRemark string `json:"auditRemark"`
|
||||
RejectReason string `json:"rejectReason"`
|
||||
}
|
||||
|
||||
// ApproveAppWithdrawOrder
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 审核通过提现订单
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "订单ID"
|
||||
// @Param body body auditWithdrawOrderRequest false "参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "操作成功"
|
||||
// @Router /admin/app-withdraw-orders/{id}/approve [post]
|
||||
func (a *AppWithdrawOrdersApi) ApproveAppWithdrawOrder(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
operatorID := utils.GetUserID(c)
|
||||
if operatorID == 0 {
|
||||
response.FailWithMessage("未登录", c)
|
||||
return
|
||||
}
|
||||
var req auditWithdrawOrderRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
|
||||
now := time.Now()
|
||||
if err := global.GVA_DB.Model(&appModel.AppWithdrawOrder{}).
|
||||
Where("id = ? AND status = ?", uint(id), appModel.AppWithdrawOrderStatusPending).
|
||||
Updates(map[string]any{
|
||||
"status": appModel.AppWithdrawOrderStatusApproved,
|
||||
"audit_remark": strings.TrimSpace(req.AuditRemark),
|
||||
"approved_by": operatorID,
|
||||
"approved_at": &now,
|
||||
"operator_user_id": operatorID,
|
||||
}).Error; err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("已审核通过", c)
|
||||
}
|
||||
|
||||
// RejectAppWithdrawOrder
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 审核拒绝提现订单(会解冻)
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "订单ID"
|
||||
// @Param body body auditWithdrawOrderRequest true "参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "操作成功"
|
||||
// @Router /admin/app-withdraw-orders/{id}/reject [post]
|
||||
func (a *AppWithdrawOrdersApi) RejectAppWithdrawOrder(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
operatorID := utils.GetUserID(c)
|
||||
if operatorID == 0 {
|
||||
response.FailWithMessage("未登录", c)
|
||||
return
|
||||
}
|
||||
var req auditWithdrawOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
reason := strings.TrimSpace(req.RejectReason)
|
||||
if reason == "" {
|
||||
response.FailWithMessage("rejectReason 不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
var order appModel.AppWithdrawOrder
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, uint(id)).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("订单不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if order.Status != appModel.AppWithdrawOrderStatusPending {
|
||||
return errors.New("当前状态不可拒绝")
|
||||
}
|
||||
|
||||
var asset appModel.AppUserAsset
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", order.AppUserID).First(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if asset.AccountFrozenLi < order.AmountLi {
|
||||
return errors.New("冻结余额异常")
|
||||
}
|
||||
asset.AccountFrozenLi -= order.AmountLi
|
||||
if err := tx.Model(&asset).Select("account_frozen_li").Updates(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
return tx.Model(&order).Updates(map[string]any{
|
||||
"status": appModel.AppWithdrawOrderStatusRejected,
|
||||
"reject_reason": reason,
|
||||
"audit_remark": strings.TrimSpace(req.AuditRemark),
|
||||
"approved_by": operatorID,
|
||||
"approved_at": &now,
|
||||
"operator_user_id": operatorID,
|
||||
}).Error
|
||||
}); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("已拒绝并解冻", c)
|
||||
}
|
||||
|
||||
type payWithdrawOrderRequest struct {
|
||||
ExternalTxID string `json:"externalTxId"`
|
||||
FailureReason string `json:"failureReason"`
|
||||
}
|
||||
|
||||
// MarkPaidAppWithdrawOrder
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 确认已打款(会扣减余额并解冻)
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "订单ID"
|
||||
// @Param body body payWithdrawOrderRequest false "参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "操作成功"
|
||||
// @Router /admin/app-withdraw-orders/{id}/mark-paid [post]
|
||||
func (a *AppWithdrawOrdersApi) MarkPaidAppWithdrawOrder(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
operatorID := utils.GetUserID(c)
|
||||
if operatorID == 0 {
|
||||
response.FailWithMessage("未登录", c)
|
||||
return
|
||||
}
|
||||
var req payWithdrawOrderRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
|
||||
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
var order appModel.AppWithdrawOrder
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, uint(id)).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("订单不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if order.Status != appModel.AppWithdrawOrderStatusApproved {
|
||||
return errors.New("当前状态不可打款确认")
|
||||
}
|
||||
|
||||
var asset appModel.AppUserAsset
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", order.AppUserID).First(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if asset.AccountFrozenLi < order.AmountLi {
|
||||
return errors.New("冻结余额异常")
|
||||
}
|
||||
before := asset.AccountBalanceLi
|
||||
if before < order.AmountLi {
|
||||
return errors.New("账户余额不足")
|
||||
}
|
||||
asset.AccountFrozenLi -= order.AmountLi
|
||||
asset.AccountBalanceLi -= order.AmountLi
|
||||
if err := tx.Model(&asset).Select("account_frozen_li", "account_balance_li").Updates(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
after := asset.AccountBalanceLi
|
||||
txRec := appModel.AppAssetTransaction{
|
||||
AppUserID: order.AppUserID,
|
||||
AssetType: appModel.AppAssetTypeAccount,
|
||||
RechargeType: appModel.AppRechargeType("withdraw_paid"),
|
||||
DeltaLi: -order.AmountLi,
|
||||
BeforeLi: before,
|
||||
AfterLi: after,
|
||||
Reason: "提现打款(订单#" + strconv.Itoa(int(order.ID)) + ")",
|
||||
OperatorUserID: operatorID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := tx.Create(&txRec).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
return tx.Model(&order).Updates(map[string]any{
|
||||
"status": appModel.AppWithdrawOrderStatusPaid,
|
||||
"paid_by": operatorID,
|
||||
"paid_at": &now,
|
||||
"external_tx_id": strings.TrimSpace(req.ExternalTxID),
|
||||
"operator_user_id": operatorID,
|
||||
}).Error
|
||||
}); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("已确认打款", c)
|
||||
}
|
||||
|
||||
// MarkFailedAppWithdrawOrder
|
||||
// @Tags AppWithdrawOrdersAdmin
|
||||
// @Summary 标记打款失败(会解冻)
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "订单ID"
|
||||
// @Param body body payWithdrawOrderRequest true "参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "操作成功"
|
||||
// @Router /admin/app-withdraw-orders/{id}/mark-failed [post]
|
||||
func (a *AppWithdrawOrdersApi) MarkFailedAppWithdrawOrder(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
operatorID := utils.GetUserID(c)
|
||||
if operatorID == 0 {
|
||||
response.FailWithMessage("未登录", c)
|
||||
return
|
||||
}
|
||||
var req payWithdrawOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
reason := strings.TrimSpace(req.FailureReason)
|
||||
if reason == "" {
|
||||
response.FailWithMessage("failureReason 不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
var order appModel.AppWithdrawOrder
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, uint(id)).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("订单不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if order.Status != appModel.AppWithdrawOrderStatusApproved {
|
||||
return errors.New("当前状态不可标记失败")
|
||||
}
|
||||
|
||||
var asset appModel.AppUserAsset
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_user_id = ?", order.AppUserID).First(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if asset.AccountFrozenLi < order.AmountLi {
|
||||
return errors.New("冻结余额异常")
|
||||
}
|
||||
asset.AccountFrozenLi -= order.AmountLi
|
||||
if err := tx.Model(&asset).Select("account_frozen_li").Updates(&asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Model(&order).Updates(map[string]any{
|
||||
"status": appModel.AppWithdrawOrderStatusFailed,
|
||||
"failure_reason": reason,
|
||||
"external_tx_id": strings.TrimSpace(req.ExternalTxID),
|
||||
"operator_user_id": operatorID,
|
||||
}).Error
|
||||
}); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("已标记失败并解冻", c)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user