新增用户确认收款功能,更新提现记录模型以支持用户确认时间,并在提现管理页面展示确认状态。同时,优化提现处理逻辑,确保在用户确认后记录相关信息,提升系统稳定性和用户体验。
This commit is contained in:
@@ -186,46 +186,68 @@ Page({
|
||||
return `${m}-${day}`
|
||||
},
|
||||
|
||||
// 确认收款(调起微信收款页)
|
||||
confirmReceive(e) {
|
||||
// 确认收款:有 package 时调起微信收款页,成功后记录;无 package 时仅调用后端记录「已确认收款」
|
||||
async confirmReceive(e) {
|
||||
const index = e.currentTarget.dataset.index
|
||||
const id = e.currentTarget.dataset.id
|
||||
const list = this.data.pendingConfirmList || []
|
||||
let item = (typeof index === 'number' || (index !== undefined && index !== '')) ? list[index] : null
|
||||
if (!item && id) item = list.find(x => x.id === id) || null
|
||||
if (!item || !item.package) {
|
||||
if (!item) {
|
||||
wx.showToast({ title: '请稍后刷新再试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const mchId = this.data.withdrawMchId
|
||||
const appId = this.data.withdrawAppId
|
||||
if (!mchId || !appId) {
|
||||
wx.showToast({ title: '参数缺失,请刷新重试', icon: 'none' })
|
||||
const hasPackage = item.package && mchId && appId && wx.canIUse('requestMerchantTransfer')
|
||||
|
||||
const recordConfirmReceived = async () => {
|
||||
const userInfo = app.globalData.userInfo
|
||||
if (userInfo && userInfo.id) {
|
||||
try {
|
||||
await app.request({
|
||||
url: '/api/miniprogram/withdraw/confirm-received',
|
||||
method: 'POST',
|
||||
data: { withdrawalId: item.id, userId: userInfo.id }
|
||||
})
|
||||
} catch (e) { /* 仅记录,不影响前端展示 */ }
|
||||
}
|
||||
const newList = list.filter(x => x.id !== item.id)
|
||||
this.setData({ pendingConfirmList: newList })
|
||||
this.loadPendingConfirm()
|
||||
}
|
||||
|
||||
if (hasPackage) {
|
||||
wx.showLoading({ title: '调起收款...', mask: true })
|
||||
wx.requestMerchantTransfer({
|
||||
mchId,
|
||||
appId,
|
||||
package: item.package,
|
||||
success: async () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '收款成功', icon: 'success' })
|
||||
await recordConfirmReceived()
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
const msg = (err.errMsg || '').includes('cancel') ? '已取消' : (err.errMsg || '收款失败')
|
||||
wx.showToast({ title: msg, icon: 'none' })
|
||||
},
|
||||
complete: () => { wx.hideLoading() }
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!wx.canIUse('requestMerchantTransfer')) {
|
||||
wx.showToast({ title: '当前微信版本不支持,请升级后重试', icon: 'none' })
|
||||
return
|
||||
|
||||
// 无 package 时仅记录「确认已收款」(当前直接打款无 package,用户点按钮即记录)
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
try {
|
||||
await recordConfirmReceived()
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '已记录确认收款', icon: 'success' })
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: (e && e.message) || '操作失败', icon: 'none' })
|
||||
}
|
||||
wx.showLoading({ title: '调起收款...', mask: true })
|
||||
wx.requestMerchantTransfer({
|
||||
mchId,
|
||||
appId,
|
||||
package: item.package,
|
||||
success: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '收款成功', icon: 'success' })
|
||||
const newList = list.filter(x => x.id !== item.id)
|
||||
this.setData({ pendingConfirmList: newList })
|
||||
this.loadPendingConfirm()
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
const msg = (err.errMsg || '').includes('cancel') ? '已取消' : (err.errMsg || '收款失败')
|
||||
wx.showToast({ title: msg, icon: 'none' })
|
||||
},
|
||||
complete: () => { wx.hideLoading() }
|
||||
})
|
||||
},
|
||||
|
||||
// 从与推广中心相同的接口拉取收益数据并更新展示(累计收益 = totalCommission,可提现 = 累计-已提现-待审核)
|
||||
|
||||
@@ -23,12 +23,19 @@
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "pages/match/match",
|
||||
"pathName": "pages/match/match",
|
||||
"query": "",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
},
|
||||
{
|
||||
"name": "看书",
|
||||
"pathName": "pages/read/read",
|
||||
"query": "id=1.4",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "分销中心",
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
soul-admin/dist/index.html
vendored
2
soul-admin/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>管理后台 - Soul创业派对</title>
|
||||
<script type="module" crossorigin src="/assets/index-Y_ZBqQPE.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BKk79j3K.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-2chBMZjx.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -22,6 +22,7 @@ interface Withdrawal {
|
||||
method?: 'wechat' | 'alipay'
|
||||
account?: string
|
||||
name?: string
|
||||
userConfirmedAt?: string | null
|
||||
userCommissionInfo?: {
|
||||
totalCommission: number
|
||||
withdrawnEarnings: number
|
||||
@@ -145,7 +146,7 @@ export function WithdrawalsPage() {
|
||||
case 'processing':
|
||||
return (
|
||||
<Badge className="bg-blue-500/20 text-blue-400 hover:bg-blue-500/20 border-0">
|
||||
处理中
|
||||
已审批等待打款
|
||||
</Badge>
|
||||
)
|
||||
case 'success':
|
||||
@@ -295,6 +296,7 @@ export function WithdrawalsPage() {
|
||||
<th className="p-4 text-left font-medium">用户佣金信息</th>
|
||||
<th className="p-4 text-left font-medium">状态</th>
|
||||
<th className="p-4 text-left font-medium">处理时间</th>
|
||||
<th className="p-4 text-left font-medium">确认收款</th>
|
||||
<th className="p-4 text-right font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -379,6 +381,15 @@ export function WithdrawalsPage() {
|
||||
<td className="p-4 text-gray-400">
|
||||
{w.processedAt ? new Date(w.processedAt).toLocaleString() : '-'}
|
||||
</td>
|
||||
<td className="p-4 text-gray-400">
|
||||
{w.userConfirmedAt ? (
|
||||
<span className="text-green-400" title={w.userConfirmedAt}>
|
||||
已确认 {new Date(w.userConfirmedAt).toLocaleString()}
|
||||
</span>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</td>
|
||||
<td className="p-4 text-right">
|
||||
{(w.status === 'pending' || w.status === 'pending_confirm') && (
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
|
||||
@@ -118,6 +118,29 @@ def run_build(root):
|
||||
|
||||
# ==================== 打包 ====================
|
||||
|
||||
DEPLOY_PORT = 8081
|
||||
|
||||
|
||||
def set_env_port(env_path, port=DEPLOY_PORT):
|
||||
"""将 .env 文件中的 PORT 设为指定值(用于部署包)"""
|
||||
if not os.path.isfile(env_path):
|
||||
return
|
||||
with open(env_path, "r", encoding="utf-8", errors="replace") as f:
|
||||
lines = f.readlines()
|
||||
found = False
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
s = line.strip()
|
||||
if "=" in s and s.split("=", 1)[0].strip() == "PORT":
|
||||
new_lines.append("PORT=%s\n" % port)
|
||||
found = True
|
||||
else:
|
||||
new_lines.append(line)
|
||||
if not found:
|
||||
new_lines.append("PORT=%s\n" % port)
|
||||
with open(env_path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
|
||||
def pack_deploy(root, binary_path, include_env=True):
|
||||
"""打包二进制和 .env 为 tar.gz"""
|
||||
@@ -126,14 +149,18 @@ def pack_deploy(root, binary_path, include_env=True):
|
||||
try:
|
||||
shutil.copy2(binary_path, os.path.join(staging, "soul-api"))
|
||||
env_src = os.path.join(root, ".env")
|
||||
staging_env = os.path.join(staging, ".env")
|
||||
if include_env and os.path.isfile(env_src):
|
||||
shutil.copy2(env_src, os.path.join(staging, ".env"))
|
||||
shutil.copy2(env_src, staging_env)
|
||||
print(" [已包含] .env")
|
||||
else:
|
||||
env_example = os.path.join(root, ".env.example")
|
||||
if os.path.isfile(env_example):
|
||||
shutil.copy2(env_example, os.path.join(staging, ".env"))
|
||||
shutil.copy2(env_example, staging_env)
|
||||
print(" [已包含] .env.example -> .env (请服务器上检查配置)")
|
||||
if os.path.isfile(staging_env):
|
||||
set_env_port(staging_env, DEPLOY_PORT)
|
||||
print(" [已设置] PORT=%s(部署用)" % DEPLOY_PORT)
|
||||
tarball = os.path.join(tempfile.gettempdir(), "soul_api_deploy.tar.gz")
|
||||
with tarfile.open(tarball, "w:gz") as tf:
|
||||
for name in os.listdir(staging):
|
||||
|
||||
@@ -118,6 +118,29 @@ def run_build(root):
|
||||
|
||||
# ==================== 打包 ====================
|
||||
|
||||
DEPLOY_PORT = 8080
|
||||
|
||||
|
||||
def set_env_port(env_path, port=DEPLOY_PORT):
|
||||
"""将 .env 文件中的 PORT 设为指定值(用于部署包)"""
|
||||
if not os.path.isfile(env_path):
|
||||
return
|
||||
with open(env_path, "r", encoding="utf-8", errors="replace") as f:
|
||||
lines = f.readlines()
|
||||
found = False
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
s = line.strip()
|
||||
if "=" in s and s.split("=", 1)[0].strip() == "PORT":
|
||||
new_lines.append("PORT=%s\n" % port)
|
||||
found = True
|
||||
else:
|
||||
new_lines.append(line)
|
||||
if not found:
|
||||
new_lines.append("PORT=%s\n" % port)
|
||||
with open(env_path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
|
||||
def pack_deploy(root, binary_path, include_env=True):
|
||||
"""打包二进制和 .env 为 tar.gz"""
|
||||
@@ -126,14 +149,18 @@ def pack_deploy(root, binary_path, include_env=True):
|
||||
try:
|
||||
shutil.copy2(binary_path, os.path.join(staging, "soul-api"))
|
||||
env_src = os.path.join(root, ".env")
|
||||
staging_env = os.path.join(staging, ".env")
|
||||
if include_env and os.path.isfile(env_src):
|
||||
shutil.copy2(env_src, os.path.join(staging, ".env"))
|
||||
shutil.copy2(env_src, staging_env)
|
||||
print(" [已包含] .env")
|
||||
else:
|
||||
env_example = os.path.join(root, ".env.example")
|
||||
if os.path.isfile(env_example):
|
||||
shutil.copy2(env_example, os.path.join(staging, ".env"))
|
||||
shutil.copy2(env_example, staging_env)
|
||||
print(" [已包含] .env.example -> .env (请服务器上检查配置)")
|
||||
if os.path.isfile(staging_env):
|
||||
set_env_port(staging_env, DEPLOY_PORT)
|
||||
print(" [已设置] PORT=%s(部署用)" % DEPLOY_PORT)
|
||||
tarball = os.path.join(tempfile.gettempdir(), "soul_api_deploy.tar.gz")
|
||||
with tarfile.open(tarball, "w:gz") as tf:
|
||||
for name in os.listdir(staging):
|
||||
|
||||
@@ -21,6 +21,9 @@ func Init(dsn string) error {
|
||||
if err := db.AutoMigrate(&model.WechatCallbackLog{}); err != nil {
|
||||
log.Printf("database: wechat_callback_logs migrate warning: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Withdrawal{}); err != nil {
|
||||
log.Printf("database: withdrawals migrate warning: %v", err)
|
||||
}
|
||||
log.Println("database: connected")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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": "已发起打款,微信处理中",
|
||||
|
||||
@@ -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": "已记录确认收款"})
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ type Withdrawal struct {
|
||||
BatchNo *string `gorm:"column:batch_no;size:100" json:"batchNo,omitempty"` // 商家批次单号
|
||||
DetailNo *string `gorm:"column:detail_no;size:100" json:"detailNo,omitempty"` // 商家明细单号
|
||||
BatchID *string `gorm:"column:batch_id;size:100" json:"batchId,omitempty"` // 微信批次单号
|
||||
PackageInfo *string `gorm:"column:package_info;size:500" json:"packageInfo,omitempty"` // 微信返回的 package_info,供小程序 wx.requestMerchantTransfer
|
||||
Remark *string `gorm:"column:remark;size:200" json:"remark,omitempty"` // 提现备注
|
||||
FailReason *string `gorm:"column:fail_reason;size:500" json:"failReason,omitempty"` // 失败原因
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
|
||||
ProcessedAt *time.Time `gorm:"column:processed_at" json:"processedAt"`
|
||||
UserConfirmedAt *time.Time `gorm:"column:user_confirmed_at" json:"userConfirmedAt,omitempty"` // 用户点击「确认收款」时间
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
|
||||
ProcessedAt *time.Time `gorm:"column:processed_at" json:"processedAt"`
|
||||
}
|
||||
|
||||
func (Withdrawal) TableName() string { return "withdrawals" }
|
||||
|
||||
@@ -244,6 +244,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
miniprogram.POST("/withdraw", handler.WithdrawPost)
|
||||
miniprogram.GET("/withdraw/records", handler.WithdrawRecords)
|
||||
miniprogram.GET("/withdraw/pending-confirm", handler.WithdrawPendingConfirm)
|
||||
miniprogram.POST("/withdraw/confirm-received", handler.WithdrawConfirmReceived)
|
||||
miniprogram.GET("/withdraw/confirm-info", handler.WithdrawConfirmInfo)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@ import (
|
||||
"soul-api/internal/config"
|
||||
|
||||
"github.com/ArtisanCloud/PowerLibs/v3/object"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
|
||||
notifyrequest "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/notify/request"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment/order/request"
|
||||
subrequest "github.com/ArtisanCloud/PowerWeChat/v3/src/basicService/subscribeMessage/request"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -394,3 +396,39 @@ func GenerateOrderSn() string {
|
||||
random := now.UnixNano() % 1000000
|
||||
return fmt.Sprintf("MP%s%06d", timestamp, random)
|
||||
}
|
||||
|
||||
// WithdrawSubscribeTemplateID 提现结果订阅消息模板 ID(与小程序 app.js withdrawSubscribeTmplId 一致)
|
||||
const WithdrawSubscribeTemplateID = "u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE"
|
||||
|
||||
// SendWithdrawSubscribeMessage 发起转账成功后发订阅消息(提现成功/待确认收款)
|
||||
// openID 为接收人 openid,amount 为提现金额(元),success 为 true 表示打款已受理
|
||||
func SendWithdrawSubscribeMessage(ctx context.Context, openID string, amount float64, success bool) error {
|
||||
if miniProgramApp == nil {
|
||||
return fmt.Errorf("小程序未初始化")
|
||||
}
|
||||
phrase := "提现成功"
|
||||
thing8 := "微信打款成功,请点击查收"
|
||||
if !success {
|
||||
phrase = "提现失败"
|
||||
thing8 = "请联系官方客服"
|
||||
}
|
||||
amountStr := fmt.Sprintf("¥%.2f", amount)
|
||||
data := &power.HashMap{
|
||||
"phrase4": object.HashMap{"value": phrase},
|
||||
"amount5": object.HashMap{"value": amountStr},
|
||||
"thing8": object.HashMap{"value": thing8},
|
||||
}
|
||||
state := "formal"
|
||||
if cfg != nil && cfg.Mode == "debug" {
|
||||
state = "developer"
|
||||
}
|
||||
_, err := miniProgramApp.SubscribeMessage.Send(ctx, &subrequest.RequestSubscribeMessageSend{
|
||||
ToUser: openID,
|
||||
TemplateID: WithdrawSubscribeTemplateID,
|
||||
Page: "/pages/my/my",
|
||||
MiniProgramState: state,
|
||||
Lang: "zh_CN",
|
||||
Data: data,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -149,11 +149,13 @@ type FundAppTransferParams struct {
|
||||
TransferSceneId string // 可选,如 "1005"
|
||||
}
|
||||
|
||||
// FundAppTransferResult 单笔转账结果
|
||||
// FundAppTransferResult 单笔转账结果(微信同步返回,无需等回调即可落库)
|
||||
type FundAppTransferResult struct {
|
||||
OutBillNo string
|
||||
TransferBillNo string
|
||||
State string
|
||||
OutBillNo string // 商户单号
|
||||
TransferBillNo string // 微信转账单号
|
||||
State string // 如 WAIT_USER_CONFIRM 表示待用户确认收款
|
||||
PackageInfo string // 供小程序 wx.requestMerchantTransfer 使用
|
||||
CreateTime string // 微信返回的 create_time
|
||||
}
|
||||
|
||||
// InitiateTransferByFundApp 发起商家转账到零钱(PowerWeChat FundApp.TransferBills 单笔接口)
|
||||
@@ -205,6 +207,8 @@ func InitiateTransferByFundApp(params FundAppTransferParams) (*FundAppTransferRe
|
||||
OutBillNo: resp.OutBillNo,
|
||||
TransferBillNo: resp.TransferBillNo,
|
||||
State: resp.State,
|
||||
PackageInfo: resp.PackageInfo,
|
||||
CreateTime: resp.CreateTime,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Binary file not shown.
23
soul-api/订阅消息.md
Normal file
23
soul-api/订阅消息.md
Normal file
@@ -0,0 +1,23 @@
|
||||
data := &power.HashMap{
|
||||
"phrase4": power.StringMap{
|
||||
"value": "提现成功",//提现结果:提现成功、提现失败
|
||||
},
|
||||
"amount5": pwer.StringMap{
|
||||
"value": "¥8.6",//提现金额
|
||||
},
|
||||
"thing8": power.StringMap{
|
||||
"value": "微信打款成功,请点击查收",//备注,如果打款失败就提示请联系官方客服
|
||||
},
|
||||
}
|
||||
MiniProgramApp.SubscribeMessage.Send(ctx, &request.RequestSubscribeMessageSend{
|
||||
ToUser: "OPENID",//需要根据订单号联表查询,提现表的user_id就是opend_id
|
||||
TemplateID: "u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE",//这串是正确的
|
||||
Page: "/pages/my/my",
|
||||
// developer为开发版;trial为体验版;formal为正式版 这块最好根据我的域名区分,
|
||||
// 开发环境是souldev.quwanzhi.com 正式环境是 soulapi.quwanzhi.com
|
||||
MiniProgramState: "formal",
|
||||
Lang: "zh_CN",
|
||||
Data: data,
|
||||
})
|
||||
|
||||
{"create_time":"2026-02-10T18:02:54+08:00","out_bill_no":"WD1770691555206100","package_info":"ABBQO+oYAAABAAAAAAAk+yPZGrq+hyjETwKLaRAAAADnGpepZahT9IkJjn90+1qg6ZgBGi0Qjs+Pff8cmSa31vfwaewAXCM6F4nJ9wEZRdwDm4QridPWurNI1lWD7iSS7oX/YzP5XOnpeAlYX3tjHLTDdDQ=","state":"WAIT_USER_CONFIRM","transfer_bill_no":"1330000114850082602100071440076263"}
|
||||
Reference in New Issue
Block a user