Files
soul-yongping/soul-api/internal/handler/withdraw.go

224 lines
6.3 KiB
Go
Raw Normal View History

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,
},
})
}