package handler import ( "fmt" "net/http" "os" "soul-api/internal/database" "soul-api/internal/model" "soul-api/internal/wechat" "github.com/gin-gonic/gin" ) // WithdrawPost POST /api/withdraw 创建提现申请并发起微信转账 func WithdrawPost(c *gin.Context) { var req struct { UserID string `json:"userId" binding:"required"` Amount float64 `json:"amount" binding:"required"` UserName string `json:"userName"` // 可选,实名校验用 Remark string `json:"remark"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "参数错误"}) return } // 金额验证 if req.Amount <= 0 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "提现金额必须大于0"}) return } if req.Amount < 1 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "最低提现金额为1元"}) return } db := database.DB() // 查询用户信息,获取 openid 和待提现金额 var user model.User if err := db.Where("id = ?", req.UserID).First(&user).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "用户不存在"}) return } if user.OpenID == nil || *user.OpenID == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "用户未绑定微信"}) return } // 检查待结算收益是否足够 pendingEarnings := 0.0 if user.PendingEarnings != nil { pendingEarnings = *user.PendingEarnings } if pendingEarnings < req.Amount { c.JSON(http.StatusBadRequest, gin.H{ "success": false, "message": fmt.Sprintf("可提现金额不足(当前:%.2f元)", pendingEarnings), }) return } // 生成转账单号 outBatchNo := wechat.GenerateTransferBatchNo() outDetailNo := wechat.GenerateTransferDetailNo() // 创建提现记录 status := "pending" remark := req.Remark if remark == "" { remark = "提现" } withdrawal := model.Withdrawal{ ID: outDetailNo, UserID: req.UserID, Amount: req.Amount, Status: &status, BatchNo: &outBatchNo, DetailNo: &outDetailNo, Remark: &remark, } if err := db.Create(&withdrawal).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "创建提现记录失败"}) return } // 发起微信转账 transferAmount := int(req.Amount * 100) // 转为分 transferParams := wechat.TransferParams{ OutBatchNo: outBatchNo, OutDetailNo: outDetailNo, OpenID: *user.OpenID, Amount: transferAmount, UserName: req.UserName, Remark: remark, BatchName: "用户提现", BatchRemark: fmt.Sprintf("用户 %s 提现 %.2f 元", req.UserID, req.Amount), } result, err := wechat.InitiateTransfer(transferParams) if err != nil { // 转账失败,更新提现状态为失败 failedStatus := "failed" failReason := fmt.Sprintf("发起转账失败: %v", err) db.Model(&withdrawal).Updates(map[string]interface{}{ "status": failedStatus, "fail_reason": failReason, }) c.JSON(http.StatusInternalServerError, gin.H{ "success": false, "message": "发起转账失败,请稍后重试", "error": err.Error(), }) return } // 更新提现记录状态 processingStatus := "processing" batchID := result.BatchID db.Model(&withdrawal).Updates(map[string]interface{}{ "status": processingStatus, "batch_id": batchID, }) // 扣减用户的待结算收益,增加已提现金额 db.Model(&user).Updates(map[string]interface{}{ "pending_earnings": db.Raw("pending_earnings - ?", req.Amount), "withdrawn_earnings": db.Raw("COALESCE(withdrawn_earnings, 0) + ?", req.Amount), }) fmt.Printf("[Withdraw] 用户 %s 提现 %.2f 元,转账批次号: %s\n", req.UserID, req.Amount, outBatchNo) c.JSON(http.StatusOK, gin.H{ "success": true, "message": "提现申请已提交,预计2小时内到账", "data": map[string]interface{}{ "id": withdrawal.ID, "amount": req.Amount, "status": processingStatus, "out_batch_no": outBatchNo, "batch_id": result.BatchID, "created_at": withdrawal.CreatedAt, }, }) } // WithdrawRecords GET /api/withdraw/records?userId= 当前用户提现记录(GORM) func WithdrawRecords(c *gin.Context) { userId := c.Query("userId") if userId == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "缺少 userId"}) return } var list []model.Withdrawal if err := database.DB().Where("user_id = ?", userId).Order("created_at DESC").Limit(100).Find(&list).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"list": []interface{}{}}}) return } out := make([]gin.H, 0, len(list)) for _, w := range list { st := "" if w.Status != nil { st = *w.Status } out = append(out, gin.H{ "id": w.ID, "amount": w.Amount, "status": st, "createdAt": w.CreatedAt, "processedAt": w.ProcessedAt, }) } c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"list": out}}) } // WithdrawPendingConfirm GET /api/withdraw/pending-confirm?userId= 待确认/处理中收款列表 // 返回 pending、processing、pending_confirm 的提现,供小程序展示;并返回 mchId、appId 供确认收款用 func WithdrawPendingConfirm(c *gin.Context) { userId := c.Query("userId") if userId == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "缺少 userId"}) return } db := database.DB() var list []model.Withdrawal // 进行中的提现:待处理、处理中、待确认收款(与 next 的 pending_confirm 兼容) if err := db.Where("user_id = ? AND status IN ?", userId, []string{"pending", "processing", "pending_confirm"}). Order("created_at DESC"). Find(&list).Error; err != nil { list = nil } out := make([]gin.H, 0, len(list)) for _, w := range list { item := gin.H{ "id": w.ID, "amount": w.Amount, "createdAt": w.CreatedAt, } // 若有 package 信息(requestMerchantTransfer 用),一并返回;当前直接打款无 package,给空字符串 item["package"] = "" out = append(out, item) } mchId := os.Getenv("WECHAT_MCH_ID") if mchId == "" { mchId = "1318592501" } appId := os.Getenv("WECHAT_APPID") if appId == "" { appId = "wxb8bbb2b10dec74aa" } c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "list": out, "mchId": mchId, "appId": appId, }, }) }