新增用户确认收款功能,更新提现记录模型以支持用户确认时间,并在提现管理页面展示确认状态。同时,优化提现处理逻辑,确保在用户确认后记录相关信息,提升系统稳定性和用户体验。

This commit is contained in:
乘风
2026-02-11 09:56:57 +08:00
parent ff92ec0e3e
commit a174d8e16d
16 changed files with 289 additions and 54 deletions

View File

@@ -1,6 +1,7 @@
package handler
import (
"context"
"fmt"
"net/http"
"time"
@@ -69,10 +70,15 @@ func AdminWithdrawalsList(c *gin.Context) {
st = "pending_confirm"
}
}
userConfirmedAt := interface{}(nil)
if w.UserConfirmedAt != nil && !w.UserConfirmedAt.IsZero() {
userConfirmedAt = w.UserConfirmedAt.Format("2006-01-02 15:04:05")
}
withdrawals = append(withdrawals, gin.H{
"id": w.ID, "userId": w.UserID, "userName": userName, "userAvatar": userAvatar,
"amount": w.Amount, "status": st, "createdAt": w.CreatedAt,
"method": "wechat", "account": account,
"userConfirmedAt": userConfirmedAt,
})
}
c.JSON(http.StatusOK, gin.H{"success": true, "withdrawals": withdrawals, "stats": gin.H{"total": len(withdrawals)}})
@@ -218,20 +224,36 @@ func AdminWithdrawalsAction(c *gin.Context) {
return
}
// 打款已受理(FundApp 单笔),更新为处理中并保存商户单号、微信转账单号
fmt.Printf("[AdminWithdrawals] 微信已受理 id=%s out_bill_no=%s transfer_bill_no=%s\n", body.ID, result.OutBillNo, result.TransferBillNo)
processingStatus := "processing"
if err := db.Model(&w).Updates(map[string]interface{}{
"status": processingStatus,
"detail_no": result.OutBillNo, // 回调用 out_bill_no 匹配此字段
"batch_no": result.OutBillNo, // 单笔无批次,存同一单号便于查询
// 打款已受理(微信同步返回立即落库商户单号、微信单号、package_info、按 state 设 status不依赖回调
fmt.Printf("[AdminWithdrawals] 微信已受理 id=%s out_bill_no=%s transfer_bill_no=%s state=%s\n", body.ID, result.OutBillNo, result.TransferBillNo, result.State)
rowStatus := "processing"
if result.State == "WAIT_USER_CONFIRM" {
rowStatus = "pending_confirm" // 待用户在小程序点击确认收款,回调在用户确认后才触发
}
upd := map[string]interface{}{
"status": rowStatus,
"detail_no": result.OutBillNo,
"batch_no": result.OutBillNo,
"batch_id": result.TransferBillNo,
"processed_at": now,
}).Error; err != nil {
}
if result.PackageInfo != "" {
upd["package_info"] = result.PackageInfo
}
if err := db.Model(&w).Updates(upd).Error; err != nil {
fmt.Printf("[AdminWithdrawals] 更新提现状态失败 id=%s: %v\n", body.ID, err)
c.JSON(http.StatusOK, gin.H{"success": false, "error": "更新状态失败: " + err.Error()})
return
}
// 发起转账成功后发订阅消息(异步,失败不影响接口返回)
if openID != "" {
go func() {
ctx := context.Background()
if err := wechat.SendWithdrawSubscribeMessage(ctx, openID, w.Amount, true); err != nil {
fmt.Printf("[AdminWithdrawals] 订阅消息发送失败 id=%s: %v\n", body.ID, err)
}
}()
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "已发起打款,微信处理中",

View File

@@ -258,8 +258,10 @@ func WithdrawConfirmInfo(c *gin.Context) {
if appId == "" {
appId = "wxb8bbb2b10dec74aa"
}
// package 需由「用户确认模式」转账接口返回并落库,当前批量转账无 package返回空有值时可调 wx.requestMerchantTransfer
packageInfo := ""
if w.PackageInfo != nil && *w.PackageInfo != "" {
packageInfo = *w.PackageInfo
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
@@ -293,8 +295,16 @@ func WithdrawPendingConfirm(c *gin.Context) {
"amount": w.Amount,
"createdAt": w.CreatedAt,
}
// 若有 package 信息requestMerchantTransfer 用),一并返回;当前直接打款无 package给空字符串
item["package"] = ""
if w.PackageInfo != nil && *w.PackageInfo != "" {
item["package"] = *w.PackageInfo
} else {
item["package"] = ""
}
if w.UserConfirmedAt != nil && !w.UserConfirmedAt.IsZero() {
item["userConfirmedAt"] = w.UserConfirmedAt.Format("2006-01-02 15:04:05")
} else {
item["userConfirmedAt"] = nil
}
out = append(out, item)
}
mchId := os.Getenv("WECHAT_MCH_ID")
@@ -314,3 +324,41 @@ func WithdrawPendingConfirm(c *gin.Context) {
},
})
}
// WithdrawConfirmReceived POST /api/miniprogram/withdraw/confirm-received 用户确认收款(记录已点击确认)
// body: { "withdrawalId": "xxx", "userId": "xxx" },仅本人可操作,更新 user_confirmed_at
func WithdrawConfirmReceived(c *gin.Context) {
var req struct {
WithdrawalID string `json:"withdrawalId" binding:"required"`
UserID string `json:"userId" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "缺少 withdrawalId 或 userId"})
return
}
db := database.DB()
var w model.Withdrawal
if err := db.Where("id = ? AND user_id = ?", req.WithdrawalID, req.UserID).First(&w).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "提现记录不存在或无权操作"})
return
}
st := ""
if w.Status != nil {
st = *w.Status
}
// 仅处理中或已成功的可标记「用户已确认收款」
if st != "processing" && st != "pending_confirm" && st != "success" {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "当前状态不可确认收款"})
return
}
if w.UserConfirmedAt != nil && !w.UserConfirmedAt.IsZero() {
c.JSON(http.StatusOK, gin.H{"success": true, "message": "已确认过"})
return
}
now := time.Now()
if err := db.Model(&w).Update("user_confirmed_at", now).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "更新失败"})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "message": "已记录确认收款"})
}