This commit is contained in:
2025-12-13 17:44:18 +08:00
commit c7a30fb094
599 changed files with 65988 additions and 0 deletions

View File

@@ -0,0 +1,338 @@
package pay
import (
"context"
"net/http"
"strings"
"time"
"qnc-server/pkg/lzkit/lzUtils"
"github.com/pkg/errors"
"github.com/smartwalle/alipay/v3"
"qnc-server/app/main/api/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AlipayCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAlipayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AlipayCallbackLogic {
return &AlipayCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Request) error {
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(r)
if err != nil {
logx.Errorf("支付宝支付回调,%v", err)
return nil
}
// 根据订单号前缀判断订单类型
orderNo := notification.OutTradeNo
if strings.HasPrefix(orderNo, "Q_") {
// 查询订单处理
return l.handleQueryOrderPayment(w, notification)
} else if strings.HasPrefix(orderNo, "U_") {
// 代理升级订单处理
return l.handleAgentUpgradeOrderPayment(w, notification)
} else if strings.HasPrefix(orderNo, "A_") {
// 旧系统会员充值订单(已废弃,新系统使用升级功能)
// return l.handleAgentVipOrderPayment(w, notification)
// 直接返回成功,避免旧订单影响
alipay.ACKNotification(w)
return nil
} else {
// 兼容旧订单,假设没有前缀的是查询订单
return l.handleQueryOrderPayment(w, notification)
}
}
// 处理查询订单支付
func (l *AlipayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error {
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, notification.OutTradeNo)
if findOrderErr != nil {
logx.Errorf("支付宝支付回调,查找订单失败: %+v", findOrderErr)
return nil
}
if order.Status != "pending" {
alipay.ACKNotification(w)
return nil
}
user, err := l.svcCtx.UserModel.FindOne(l.ctx, order.UserId)
if err != nil {
logx.Errorf("支付宝支付回调,查找用户失败: %+v", err)
return nil
}
amount := lzUtils.ToAlipayAmount(order.Amount)
if user.Inside != 1 {
// 确保订单金额和状态正确,防止重复更新
if amount != notification.TotalAmount {
logx.Errorf("支付宝支付回调,金额不一致")
return nil
}
}
switch notification.TradeStatus {
case alipay.TradeStatusSuccess:
order.Status = "paid"
order.PayTime = lzUtils.TimeToNullTime(time.Now())
default:
return nil
}
order.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo)
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
logx.Errorf("支付宝支付回调,修改订单信息失败: %+v", updateErr)
return nil
}
if order.Status == "paid" {
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
logx.Errorf("异步任务调度失败: %v", asyncErr)
return asyncErr
}
}
alipay.ACKNotification(w)
return nil
}
// 处理代理升级订单支付
func (l *AlipayCallbackLogic) handleAgentUpgradeOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error {
orderNo := notification.OutTradeNo
// 1. 查找订单
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if findOrderErr != nil {
logx.Errorf("支付宝支付回调,查找升级订单失败: %+v", findOrderErr)
alipay.ACKNotification(w)
return nil
}
// 2. 验证金额
amount := lzUtils.ToAlipayAmount(order.Amount)
user, err := l.svcCtx.UserModel.FindOne(l.ctx, order.UserId)
if err == nil && user.Inside != 1 {
if amount != notification.TotalAmount {
logx.Errorf("支付宝支付回调,升级订单金额不一致,订单号: %s", orderNo)
alipay.ACKNotification(w)
return nil
}
}
// 3. 检查订单状态
if order.Status != "pending" {
alipay.ACKNotification(w)
return nil
}
// 4. 查找升级记录
upgradeRecords, findUpgradeErr := l.svcCtx.AgentUpgradeModel.FindAll(l.ctx, l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("order_no = ?", orderNo).
Limit(1), "")
if findUpgradeErr != nil || len(upgradeRecords) == 0 {
logx.Errorf("支付宝支付回调,查找升级记录失败,订单号: %s, 错误: %+v", orderNo, findUpgradeErr)
alipay.ACKNotification(w)
return nil
}
upgradeRecord := upgradeRecords[0]
// 5. 检查升级记录状态
if upgradeRecord.Status != 1 {
// 升级记录状态不是待支付,直接返回成功
alipay.ACKNotification(w)
return nil
}
// 6. 处理支付状态
switch notification.TradeStatus {
case alipay.TradeStatusSuccess:
order.Status = "paid"
order.PayTime = lzUtils.TimeToNullTime(time.Now())
order.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo)
case alipay.TradeStatusClosed:
order.Status = "closed"
order.CloseTime = lzUtils.TimeToNullTime(time.Now())
default:
alipay.ACKNotification(w)
return nil
}
// 7. 更新订单状态
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
logx.Errorf("支付宝支付回调,更新升级订单状态失败: %+v", updateErr)
alipay.ACKNotification(w)
return nil
}
// 8. 如果支付成功,执行升级操作
if order.Status == "paid" {
err := l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 8.1 执行升级操作
if err := l.svcCtx.AgentService.ProcessUpgrade(transCtx, upgradeRecord.AgentId, upgradeRecord.ToLevel, upgradeRecord.UpgradeType, upgradeRecord.UpgradeFee, upgradeRecord.RebateAmount, orderNo, ""); err != nil {
return errors.Wrapf(err, "执行升级操作失败")
}
// 8.2 更新升级记录状态为已完成
upgradeRecord.Status = 2 // 已完成status: 1=待处理2=已完成3=已失败)
upgradeRecord.Remark = lzUtils.StringToNullString("支付成功,升级完成")
if updateErr := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(transCtx, session, upgradeRecord); updateErr != nil {
return errors.Wrapf(updateErr, "更新升级记录状态失败")
}
return nil
})
if err != nil {
logx.Errorf("支付宝支付回调,处理升级订单失败,订单号: %s, 错误: %+v", orderNo, err)
// 即使升级失败,也返回成功给支付宝,避免重复回调
} else {
logx.Infof("支付宝支付回调,代理升级成功,订单号: %s, 代理ID: %d, 从等级 %d 升级到等级 %d", orderNo, upgradeRecord.AgentId, upgradeRecord.FromLevel, upgradeRecord.ToLevel)
}
}
alipay.ACKNotification(w)
return nil
}
// 处理代理会员订单支付(已废弃,新系统使用升级功能)
/*
func (l *AlipayCallbackLogic) handleAgentVipOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error {
agentOrder, findAgentOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, notification.OutTradeNo)
if findAgentOrderErr != nil {
logx.Errorf("支付宝支付回调,查找代理会员订单失败: %+v", findAgentOrderErr)
return nil
}
if agentOrder.Status != "pending" {
alipay.ACKNotification(w)
return nil
}
user, err := l.svcCtx.UserModel.FindOne(l.ctx, agentOrder.UserId)
if err != nil {
logx.Errorf("支付宝支付回调,查找用户失败: %+v", err)
return nil
}
amount := lzUtils.ToAlipayAmount(agentOrder.Amount)
if user.Inside != 1 {
// 确保订单金额和状态正确,防止重复更新
if amount != notification.TotalAmount {
logx.Errorf("支付宝支付回调,金额不一致")
return nil
}
}
switch notification.TradeStatus {
case alipay.TradeStatusSuccess:
agentOrder.Status = "paid"
default:
return nil
}
if agentOrder.Status == "paid" {
err = l.svcCtx.AgentModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
agentModel, err := l.svcCtx.AgentModel.FindOne(transCtx, agentOrder.AgentId)
if err != nil {
return fmt.Errorf("查找代理信息失败: %+v", err)
}
agentOrder.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo)
if updateErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, agentOrder); updateErr != nil {
return fmt.Errorf("修改代理会员订单信息失败: %+v", updateErr)
}
// 记录旧等级,用于判断是否为升级
oldLevel := agentModel.LevelName
// 设置会员等级
agentModel.LevelName = agentOrder.LevelName
// 延长会员时间
// 检查是否是有效期内续费(不发放奖励)还是重新激活(发放奖励)
isValidRenewal := oldLevel == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid && agentModel.MembershipExpiryTime.Time.After(time.Now())
if isValidRenewal {
logx.Infof("代理会员有效期内续费成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
} else {
logx.Infof("代理会员新购、升级或重新激活成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
}
agentModel.MembershipExpiryTime = lzUtils.RenewMembership(agentModel.MembershipExpiryTime)
if updateErr := l.svcCtx.AgentModel.UpdateWithVersion(l.ctx, nil, agentModel); updateErr != nil {
return fmt.Errorf("修改代理信息失败: %+v", updateErr)
}
// 如果不是有效期内续费,给上级代理发放升级奖励
if !isValidRenewal && (agentOrder.LevelName == model.AgentLeveNameVIP || agentOrder.LevelName == model.AgentLeveNameSVIP) {
// 验证升级路径的有效性
if oldLevel != agentOrder.LevelName {
upgradeRewardErr := l.svcCtx.AgentService.GiveUpgradeReward(transCtx, agentModel.Id, oldLevel, agentOrder.LevelName, session)
if upgradeRewardErr != nil {
logx.Errorf("发放升级奖励失败代理ID%d旧等级%s新等级%s错误%+v", agentModel.Id, oldLevel, agentOrder.LevelName, upgradeRewardErr)
// 升级奖励失败不影响主流程,只记录日志
} else {
logx.Infof("发放升级奖励成功代理ID%d旧等级%s新等级%s", agentModel.Id, oldLevel, agentOrder.LevelName)
}
}
}
return nil
})
if err != nil {
logx.Errorf("支付宝支付回调,处理代理会员订单失败: %+v", err)
refundErr := l.handleRefund(agentOrder)
if refundErr != nil {
logx.Errorf("支付宝支付回调,退款失败: %+v", refundErr)
}
return nil
}
}
alipay.ACKNotification(w)
return nil
} */
/* func (l *AlipayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeOrder) error {
ctx := context.Background()
// 退款
if order.PaymentMethod == "wechat" {
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
if refundErr != nil {
return refundErr
}
} else {
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
if refundErr != nil {
return refundErr
}
if refund.IsSuccess() {
logx.Errorf("支付宝退款成功, orderID: %d", order.Id)
// 更新订单状态为退款
order.Status = "refunded"
updateOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(ctx, nil, order)
if updateOrderErr != nil {
logx.Errorf("更新订单状态失败订单ID: %d, 错误: %v", order.Id, updateOrderErr)
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
}
return nil
} else {
logx.Errorf("支付宝退款失败:%v", refundErr)
return refundErr
}
// 直接成功
}
return nil
} */

View File

@@ -0,0 +1,83 @@
package pay
import (
"context"
"fmt"
"time"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/common/xerr"
"qnc-server/pkg/lzkit/lzUtils"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type IapCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewIapCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IapCallbackLogic {
return &IapCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *IapCallbackLogic) IapCallback(req *types.IapCallbackReq) error {
// Step 1: 查找订单
order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, fmt.Sprintf("%d", req.OrderID))
if findOrderErr != nil {
logx.Errorf("苹果内购支付回调,查找订单失败: %+v", findOrderErr)
return nil
}
// Step 2: 验证订单状态
if order.Status != "pending" {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 订单状态异常: %+v", order)
}
// Step 3: 调用 VerifyReceipt 验证苹果支付凭证
//receipt := req.TransactionReceipt // 从请求中获取支付凭证
//verifyResponse, verifyErr := l.svcCtx.ApplePayService.VerifyReceipt(l.ctx, receipt)
//if verifyErr != nil {
// return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 验证订单异常: %+v", verifyErr)
//}
// Step 4: 验证订单
//product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, order.Id)
//if findProductErr != nil {
// return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "苹果内购支付回调, 获取订单相关商品失败: %+v", findProductErr)
//}
//isProductMatched := false
//appleProductID := l.svcCtx.ApplePayService.GetIappayAppID(product.ProductEn)
//for _, item := range verifyResponse.Receipt.InApp {
// if item.ProductID == appleProductID {
// isProductMatched = true
// order.PlatformOrderId = lzUtils.StringToNullString(item.TransactionID) // 记录交易 ID
// break
// }
//}
//if !isProductMatched {
// return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 商品 ID 不匹配,订单 ID: %d, 回调苹果商品 ID: %s", order.Id, verifyResponse.Receipt.InApp[0].ProductID)
//}
// Step 5: 更新订单状态 mm
order.Status = "paid"
order.PayTime = lzUtils.TimeToNullTime(time.Now())
// 更新订单到数据库
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 修改订单信息失败: %+v", updateErr)
}
// Step 6: 处理订单完成后的逻辑
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调,异步任务调度失败: %v", asyncErr)
}
return nil
}

View File

@@ -0,0 +1,51 @@
package pay
import (
"context"
"strings"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type PaymentCheckLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewPaymentCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentCheckLogic {
return &PaymentCheckLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *types.PaymentCheckResp, err error) {
// 根据订单号前缀判断订单类型
if strings.HasPrefix(req.OrderNo, "U_") {
// 升级订单
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级订单失败: %v", err)
}
return &types.PaymentCheckResp{
Type: "agent_upgrade",
Status: order.Status,
}, nil
}
// 查询订单(包括代理订单)
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", err)
}
return &types.PaymentCheckResp{
Type: "query",
Status: order.Status,
}, nil
}

View File

@@ -0,0 +1,475 @@
package pay
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"time"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/app/main/model"
"qnc-server/common/ctxdata"
"qnc-server/common/xerr"
"qnc-server/pkg/lzkit/lzUtils"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type PaymentLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
type PaymentTypeResp struct {
amount float64
outTradeNo string
description string
orderID string // 订单ID用于开发环境测试支付模式
}
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
return &PaymentLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) {
var paymentTypeResp *PaymentTypeResp
var prepayData interface{}
var orderID string
// 检查是否为开发环境的测试支付模式
env := os.Getenv("ENV")
isDevTestPayment := env == "development" && (req.PayMethod == "test" || req.PayMethod == "test_empty")
isEmptyReportMode := env == "development" && req.PayMethod == "test_empty"
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
switch req.PayType {
case "agent_vip":
paymentTypeResp, err = l.AgentVipOrderPayment(req, session)
if err != nil {
return err
}
case "query":
paymentTypeResp, err = l.QueryOrderPayment(req, session)
if err != nil {
return err
}
case "agent_upgrade":
paymentTypeResp, err = l.AgentUpgradeOrderPayment(req, session)
if err != nil {
return err
}
}
// 开发环境测试支付模式:跳过实际支付流程
// 注意:订单状态更新在事务外进行,避免在事务中查询不到订单的问题
if isDevTestPayment {
// 获取订单ID从 QueryOrderPayment 返回的 orderID
if paymentTypeResp.orderID == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "开发测试模式订单ID无效")
}
orderID = paymentTypeResp.orderID
// 在事务中只记录订单ID不更新订单状态
// 订单状态的更新和后续流程在事务提交后处理
logx.Infof("开发环境测试支付模式:订单 %s (ID: %s) 将在事务提交后更新状态", paymentTypeResp.outTradeNo, orderID)
// 返回测试支付标识
prepayData = "test_payment_success"
return nil
}
// 正常支付流程
var createOrderErr error
if req.PayMethod == "wechat" {
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "alipay" {
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "appleiap" {
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
}
if createOrderErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr)
}
return nil
})
if err != nil {
return nil, err
}
// 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程
if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != "" {
// 使用 goroutine 异步处理,确保事务已完全提交
go func() {
// 短暂延迟,确保事务已完全提交到数据库
time.Sleep(200 * time.Millisecond)
finalOrderID := paymentTypeResp.orderID
// 查找订单并更新状态为已支付
order, findOrderErr := l.svcCtx.OrderModel.FindOne(context.Background(), finalOrderID)
if findOrderErr != nil {
logx.Errorf("开发测试模式查找订单失败订单ID: %s, 错误: %v", finalOrderID, findOrderErr)
return
}
// 更新订单状态为已支付
order.Status = "paid"
now := time.Now()
order.PayTime = sql.NullTime{Time: now, Valid: true}
// 空报告模式:在 PaymentPlatform 字段中标记,用于后续生成空报告
if isEmptyReportMode {
order.PaymentPlatform = "test_empty"
logx.Infof("开发环境空报告模式:订单 %s (ID: %s) 已标记为空报告模式", paymentTypeResp.outTradeNo, finalOrderID)
}
// 更新订单状态(在事务外执行)
updateErr := l.svcCtx.OrderModel.UpdateWithVersion(context.Background(), nil, order)
if updateErr != nil {
logx.Errorf("开发测试模式更新订单状态失败订单ID: %s, 错误: %+v", finalOrderID, updateErr)
return
}
logx.Infof("开发环境测试支付模式:订单 %s (ID: %s) 已自动标记为已支付", paymentTypeResp.outTradeNo, finalOrderID)
// 再次短暂延迟,确保订单状态更新已提交
time.Sleep(100 * time.Millisecond)
// 根据订单类型处理后续流程
if strings.HasPrefix(paymentTypeResp.outTradeNo, "U_") {
// 升级订单:直接执行升级操作
upgradeRecords, findUpgradeErr := l.svcCtx.AgentUpgradeModel.FindAll(context.Background(), l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("order_no = ?", paymentTypeResp.outTradeNo).
Limit(1), "")
if findUpgradeErr != nil || len(upgradeRecords) == 0 {
logx.Errorf("开发测试模式,查找升级记录失败,订单号: %s, 错误: %+v", paymentTypeResp.outTradeNo, findUpgradeErr)
return
}
upgradeRecord := upgradeRecords[0]
// 执行升级操作
err := l.svcCtx.AgentWalletModel.Trans(context.Background(), func(transCtx context.Context, session sqlx.Session) error {
if err := l.svcCtx.AgentService.ProcessUpgrade(transCtx, upgradeRecord.AgentId, upgradeRecord.ToLevel, upgradeRecord.UpgradeType, upgradeRecord.UpgradeFee, upgradeRecord.RebateAmount, paymentTypeResp.outTradeNo, ""); err != nil {
return errors.Wrapf(err, "执行升级操作失败")
}
// 更新升级记录状态为已完成
upgradeRecord.Status = 2 // 已完成status: 1=待处理2=已完成3=已失败)
upgradeRecord.Remark = lzUtils.StringToNullString("测试支付成功,升级完成")
if updateErr := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(transCtx, session, upgradeRecord); updateErr != nil {
return errors.Wrapf(updateErr, "更新升级记录状态失败")
}
return nil
})
if err != nil {
logx.Errorf("开发测试模式,处理升级订单失败,订单号: %s, 错误: %+v", paymentTypeResp.outTradeNo, err)
} else {
logx.Infof("开发测试模式,代理升级成功,订单号: %s, 代理ID: %s", paymentTypeResp.outTradeNo, upgradeRecord.AgentId)
}
} else {
// 查询订单:发送支付成功通知任务,触发后续流程(生成报告和代理处理)
if sendErr := l.svcCtx.AsynqService.SendQueryTask(finalOrderID); sendErr != nil {
logx.Errorf("开发测试模式发送支付成功通知任务失败订单ID: %s, 错误: %+v", finalOrderID, sendErr)
} else {
logx.Infof("开发测试模式已发送支付成功通知任务订单ID: %s", finalOrderID)
}
}
}()
}
switch v := prepayData.(type) {
case string:
// 如果 prepayData 是字符串类型,直接返回
return &types.PaymentResp{PrepayId: v, OrderNo: paymentTypeResp.outTradeNo}, nil
default:
return &types.PaymentResp{PrepayData: prepayData, OrderNo: paymentTypeResp.outTradeNo}, nil
}
}
func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
}
outTradeNo := req.Id
redisKey := fmt.Sprintf(types.QueryCacheKey, userID, outTradeNo)
cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey)
if cacheErr != nil {
if cacheErr == redis.Nil {
return nil, errors.Wrapf(xerr.NewErrMsg("订单已过期"), "生成订单, 缓存不存在, %+v", cacheErr)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取缓存失败, %+v", cacheErr)
}
var data types.QueryCacheLoad
err = json.Unmarshal([]byte(cache), &data)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %v", err)
}
product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 查找产品错误: %v", err)
}
var amount float64
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取用户信息失败: %v", err)
}
var agentLinkModel *model.AgentLink
if data.AgentIdentifier != "" {
var findAgentLinkErr error
agentLinkModel, findAgentLinkErr = l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, data.AgentIdentifier)
if findAgentLinkErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取代理链接失败: %+v", findAgentLinkErr)
}
amount = agentLinkModel.SetPrice
} else {
amount = product.SellPrice
}
if user.Inside == 1 {
amount = 0.01
}
order := model.Order{
Id: uuid.NewString(),
OrderNo: outTradeNo,
UserId: userID,
ProductId: product.Id,
PaymentPlatform: req.PayMethod,
PaymentScene: "app",
Amount: amount,
Status: "pending",
}
_, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order)
if insertOrderErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存订单失败: %+v", insertOrderErr)
}
orderID := order.Id
// 如果是代理推广订单,创建完整的代理订单记录
if data.AgentIdentifier != "" && agentLinkModel != nil {
// 获取代理信息
agent, err := l.svcCtx.AgentModel.FindOne(l.ctx, agentLinkModel.AgentId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 查询代理信息失败: %+v", err)
}
// 获取产品配置(必须存在)
productConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, product.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单失败,产品配置不存在, productId: %s请先在后台配置产品价格参数", product.Id)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 查询产品配置失败: %+v", err)
}
// 获取等级加成(需要从系统配置读取)
levelBonus, err := l.getLevelBonus(agent.Level)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取等级加成配置失败: %+v", err)
}
// 使用产品配置的底价计算实际底价
basePrice := productConfig.BasePrice
actualBasePrice := basePrice + float64(levelBonus)
// 计算提价成本(使用产品配置)
priceThreshold := 0.0
priceFeeRate := 0.0
if productConfig.PriceThreshold.Valid {
priceThreshold = productConfig.PriceThreshold.Float64
}
if productConfig.PriceFeeRate.Valid {
priceFeeRate = productConfig.PriceFeeRate.Float64
}
priceCost := 0.0
if agentLinkModel.SetPrice > priceThreshold {
priceCost = (agentLinkModel.SetPrice - priceThreshold) * priceFeeRate
}
// 计算代理收益
agentProfit := agentLinkModel.SetPrice - actualBasePrice - priceCost
// 创建代理订单记录
agentOrder := model.AgentOrder{
Id: uuid.NewString(),
AgentId: agentLinkModel.AgentId,
OrderId: orderID,
ProductId: product.Id,
OrderAmount: amount,
SetPrice: agentLinkModel.SetPrice,
ActualBasePrice: actualBasePrice,
PriceCost: priceCost,
AgentProfit: agentProfit,
ProcessStatus: 0, // 待处理
}
_, agentOrderInsert := l.svcCtx.AgentOrderModel.Insert(l.ctx, session, &agentOrder)
if agentOrderInsert != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
}
}
return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil
}
// AgentVipOrderPayment 代理会员充值订单(已废弃,新系统使用升级功能替代)
func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
// 新代理系统已废弃会员充值功能,请使用升级功能
return nil, errors.Wrapf(xerr.NewErrMsg("该功能已废弃,请使用代理升级功能"), "")
}
// AgentUpgradeOrderPayment 代理升级订单支付
func (l *PaymentLogic) AgentUpgradeOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 解析升级记录ID
upgradeId := req.Id
// 2. 查找升级记录
upgradeRecord, err := l.svcCtx.AgentUpgradeModel.FindOne(l.ctx, upgradeId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("升级记录不存在"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级记录失败, %v", err)
}
// 3. 验证升级记录状态(必须是待支付状态)
if upgradeRecord.Status != 1 {
return nil, errors.Wrapf(xerr.NewErrMsg("升级记录状态不正确,无法支付"), "")
}
// 4. 验证代理ID是否匹配
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
if agent.Id != upgradeRecord.AgentId {
return nil, errors.Wrapf(xerr.NewErrMsg("无权支付此升级订单"), "")
}
// 5. 生成订单号(升级订单前缀 U_限制长度不超过32
base := l.svcCtx.AlipayService.GenerateOutTradeNo()
outTradeNo := "U_" + base
if len(outTradeNo) > 32 {
outTradeNo = outTradeNo[:32]
}
// 6. 获取用户信息(用于内部用户判断)
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户信息失败: %v", err)
}
// 7. 计算支付金额
amount := upgradeRecord.UpgradeFee
if user.Inside == 1 {
amount = 0.01 // 内部用户测试金额
}
// 8. 创建订单记录
order := model.Order{
Id: uuid.NewString(),
OrderNo: outTradeNo,
UserId: userID,
ProductId: "", // 升级订单没有产品ID
PaymentPlatform: req.PayMethod,
PaymentScene: "app",
Amount: amount,
Status: "pending",
}
orderInsertResult, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order)
if insertOrderErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建订单失败: %+v", insertOrderErr)
}
_ = orderInsertResult
orderID := order.Id
// 9. 更新升级记录的订单号
upgradeRecord.OrderNo = lzUtils.StringToNullString(outTradeNo)
if updateErr := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(l.ctx, session, upgradeRecord); updateErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新升级记录订单号失败: %+v", updateErr)
}
// 10. 生成描述信息
levelNames := map[int64]string{
1: "普通代理",
2: "黄金代理",
3: "钻石代理",
}
fromLevelName := levelNames[upgradeRecord.FromLevel]
toLevelName := levelNames[upgradeRecord.ToLevel]
description := fmt.Sprintf("代理升级:%s → %s", fromLevelName, toLevelName)
return &PaymentTypeResp{
amount: amount,
outTradeNo: outTradeNo,
description: description,
orderID: orderID,
}, nil
}
// getLevelBonus 获取等级加成(从配置表读取)
func (l *PaymentLogic) getLevelBonus(level int64) (int64, error) {
var configKey string
switch level {
case 1: // 普通
configKey = "level_1_bonus"
case 2: // 黄金
configKey = "level_2_bonus"
case 3: // 钻石
configKey = "level_3_bonus"
default:
return 0, nil
}
bonus, err := l.getConfigFloat(configKey)
if err != nil {
// 配置不存在时返回默认值
l.Errorf("获取等级加成配置失败, level: %d, key: %s, err: %v使用默认值", level, configKey, err)
switch level {
case 1:
return 6, nil
case 2:
return 3, nil
case 3:
return 0, nil
}
return 0, nil
}
return int64(bonus), nil
}
// getConfigFloat 获取配置值(浮点数)
func (l *PaymentLogic) getConfigFloat(configKey string) (float64, error) {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey)
if err != nil {
return 0, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取配置失败, key: %s, %v", configKey, err)
}
value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil {
return 0, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析配置值失败, key: %s, value: %s, %v", configKey, config.ConfigValue, err)
}
return value, nil
}

View File

@@ -0,0 +1,338 @@
package pay
import (
"context"
"net/http"
"strings"
"time"
"qnc-server/app/main/api/internal/service"
"qnc-server/pkg/lzkit/lzUtils"
"qnc-server/app/main/api/internal/svc"
"github.com/pkg/errors"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type WechatPayCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewWechatPayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatPayCallbackLogic {
return &WechatPayCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *WechatPayCallbackLogic) WechatPayCallback(w http.ResponseWriter, r *http.Request) error {
notification, err := l.svcCtx.WechatPayService.HandleWechatPayNotification(l.ctx, r)
if err != nil {
logx.Errorf("微信支付回调,%v", err)
return nil
}
// 根据订单号前缀判断订单类型
orderNo := *notification.OutTradeNo
if strings.HasPrefix(orderNo, "Q_") {
// 查询订单处理
return l.handleQueryOrderPayment(w, notification)
} else if strings.HasPrefix(orderNo, "U_") {
// 代理升级订单处理
return l.handleAgentUpgradeOrderPayment(w, notification)
} else if strings.HasPrefix(orderNo, "A_") {
// 旧系统会员充值订单(已废弃,新系统使用升级功能)
// return l.handleAgentVipOrderPayment(w, notification)
// 直接返回成功,避免旧订单影响
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
} else {
// 兼容旧订单,假设没有前缀的是查询订单
return l.handleQueryOrderPayment(w, notification)
}
}
// 处理查询订单支付
func (l *WechatPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error {
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo)
if findOrderErr != nil {
logx.Errorf("微信支付回调,查找订单信息失败: %+v", findOrderErr)
return nil
}
amount := lzUtils.ToWechatAmount(order.Amount)
if amount != *notification.Amount.Total {
logx.Errorf("微信支付回调,金额不一致")
return nil
}
if order.Status != "pending" {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
switch *notification.TradeState {
case service.TradeStateSuccess:
order.Status = "paid"
order.PayTime = lzUtils.TimeToNullTime(time.Now())
case service.TradeStateClosed:
order.Status = "closed"
order.CloseTime = lzUtils.TimeToNullTime(time.Now())
case service.TradeStateRevoked:
order.Status = "failed"
default:
return nil
}
order.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId)
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
logx.Errorf("微信支付回调,更新订单失败%+v", updateErr)
return nil
}
if order.Status == "paid" {
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
logx.Errorf("异步任务调度失败: %v", asyncErr)
return asyncErr
}
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
// 处理代理升级订单支付
func (l *WechatPayCallbackLogic) handleAgentUpgradeOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error {
orderNo := *notification.OutTradeNo
// 1. 查找订单
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if findOrderErr != nil {
logx.Errorf("微信支付回调,查找升级订单失败: %+v", findOrderErr)
return nil
}
// 2. 验证金额
amount := lzUtils.ToWechatAmount(order.Amount)
if amount != *notification.Amount.Total {
logx.Errorf("微信支付回调,升级订单金额不一致,订单号: %s", orderNo)
return nil
}
// 3. 检查订单状态
if order.Status != "pending" {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
// 4. 查找升级记录
upgradeRecords, findUpgradeErr := l.svcCtx.AgentUpgradeModel.FindAll(l.ctx, l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("order_no = ?", orderNo).
Limit(1), "")
if findUpgradeErr != nil || len(upgradeRecords) == 0 {
logx.Errorf("微信支付回调,查找升级记录失败,订单号: %s, 错误: %+v", orderNo, findUpgradeErr)
return nil
}
upgradeRecord := upgradeRecords[0]
// 5. 检查升级记录状态
if upgradeRecord.Status != 1 {
// 升级记录状态不是待支付,直接返回成功
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
// 6. 处理支付状态
switch *notification.TradeState {
case service.TradeStateSuccess:
order.Status = "paid"
order.PayTime = lzUtils.TimeToNullTime(time.Now())
order.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId)
case service.TradeStateClosed:
order.Status = "closed"
order.CloseTime = lzUtils.TimeToNullTime(time.Now())
case service.TradeStateRevoked:
order.Status = "failed"
default:
return nil
}
// 7. 更新订单状态
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
logx.Errorf("微信支付回调,更新升级订单状态失败: %+v", updateErr)
return nil
}
// 8. 如果支付成功,执行升级操作
if order.Status == "paid" {
err := l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 8.1 执行升级操作
if err := l.svcCtx.AgentService.ProcessUpgrade(transCtx, upgradeRecord.AgentId, upgradeRecord.ToLevel, upgradeRecord.UpgradeType, upgradeRecord.UpgradeFee, upgradeRecord.RebateAmount, orderNo, ""); err != nil {
return errors.Wrapf(err, "执行升级操作失败")
}
// 8.2 更新升级记录状态为已完成
upgradeRecord.Status = 2 // 已完成status: 1=待处理2=已完成3=已失败)
upgradeRecord.Remark = lzUtils.StringToNullString("支付成功,升级完成")
if updateErr := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(transCtx, session, upgradeRecord); updateErr != nil {
return errors.Wrapf(updateErr, "更新升级记录状态失败")
}
return nil
})
if err != nil {
logx.Errorf("微信支付回调,处理升级订单失败,订单号: %s, 错误: %+v", orderNo, err)
// 即使升级失败,也返回成功给微信,避免重复回调
} else {
logx.Infof("微信支付回调,代理升级成功,订单号: %s, 代理ID: %d, 从等级 %d 升级到等级 %d", orderNo, upgradeRecord.AgentId, upgradeRecord.FromLevel, upgradeRecord.ToLevel)
}
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
// 处理代理会员订单支付(已废弃,新系统使用升级功能)
/*
func (l *WechatPayCallbackLogic) handleAgentVipOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error {
agentOrder, findAgentOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo)
if findAgentOrderErr != nil {
logx.Errorf("微信支付回调,查找代理会员订单失败: %+v", findAgentOrderErr)
return nil
}
if agentOrder.Status != "pending" {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
user, err := l.svcCtx.UserModel.FindOne(l.ctx, agentOrder.UserId)
if err != nil {
logx.Errorf("微信支付回调,查找用户失败: %+v", err)
return nil
}
amount := lzUtils.ToWechatAmount(agentOrder.Amount)
if user.Inside != 1 {
if amount != *notification.Amount.Total {
logx.Errorf("微信支付回调,金额不一致")
return nil
}
}
switch *notification.TradeState {
case service.TradeStateSuccess:
agentOrder.Status = "paid"
default:
return nil
}
if agentOrder.Status == "paid" {
err = l.svcCtx.AgentModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
agentModel, err := l.svcCtx.AgentModel.FindOne(transCtx, agentOrder.AgentId)
if err != nil {
return fmt.Errorf("查找代理信息失败: %+v", err)
}
agentOrder.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId)
if updateErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, agentOrder); updateErr != nil {
return fmt.Errorf("修改代理会员订单信息失败: %+v", updateErr)
}
// 记录旧等级,用于判断是否为升级
oldLevel := agentModel.LevelName
// 设置会员等级
agentModel.LevelName = agentOrder.LevelName
// 延长会员时间
// 检查是否是有效期内续费(不发放奖励)还是重新激活(发放奖励)
isValidRenewal := oldLevel == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid && agentModel.MembershipExpiryTime.Time.After(time.Now())
if isValidRenewal {
logx.Infof("代理会员有效期内续费成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
} else {
logx.Infof("代理会员新购、升级或重新激活成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
}
agentModel.MembershipExpiryTime = lzUtils.RenewMembership(agentModel.MembershipExpiryTime)
if updateErr := l.svcCtx.AgentModel.UpdateWithVersion(l.ctx, nil, agentModel); updateErr != nil {
return fmt.Errorf("修改代理信息失败: %+v", updateErr)
}
// 如果不是有效期内续费,给上级代理发放升级奖励
if !isValidRenewal && (agentOrder.LevelName == model.AgentLeveNameVIP || agentOrder.LevelName == model.AgentLeveNameSVIP) {
// 验证升级路径的有效性
if oldLevel != agentOrder.LevelName {
upgradeRewardErr := l.svcCtx.AgentService.GiveUpgradeReward(transCtx, agentModel.Id, oldLevel, agentOrder.LevelName, session)
if upgradeRewardErr != nil {
logx.Errorf("发放升级奖励失败代理ID%d旧等级%s新等级%s错误%+v", agentModel.Id, oldLevel, agentOrder.LevelName, upgradeRewardErr)
// 升级奖励失败不影响主流程,只记录日志
} else {
logx.Infof("发放升级奖励成功代理ID%d旧等级%s新等级%s", agentModel.Id, oldLevel, agentOrder.LevelName)
}
}
}
return nil
})
if err != nil {
logx.Errorf("微信支付回调,处理代理会员订单失败: %+v", err)
refundErr := l.handleRefund(agentOrder)
if refundErr != nil {
logx.Errorf("微信支付回调,退款失败: %+v", refundErr)
}
return nil
}
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
return nil
}
*/
/*
func (l *WechatPayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeOrder) error {
ctx := context.Background()
// 退款
if order.PaymentMethod == "wechat" {
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
if refundErr != nil {
return refundErr
}
} else {
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
if refundErr != nil {
return refundErr
}
if refund.IsSuccess() {
logx.Errorf("支付宝退款成功, orderID: %d", order.Id)
// 更新订单状态为退款
order.Status = "refunded"
updateOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(ctx, nil, order)
if updateOrderErr != nil {
logx.Errorf("更新订单状态失败订单ID: %d, 错误: %v", order.Id, updateOrderErr)
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
}
return nil
} else {
logx.Errorf("支付宝退款失败:%v", refundErr)
return refundErr
}
}
return nil
} */

View File

@@ -0,0 +1,239 @@
package pay
import (
"context"
"database/sql"
"net/http"
"strings"
"time"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/model"
"qnc-server/common/globalkey"
"github.com/pkg/errors"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type WechatPayRefundCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewWechatPayRefundCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatPayRefundCallbackLogic {
return &WechatPayRefundCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// handleQueryOrderRefund 处理查询订单退款
func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, status refunddomestic.Status) error {
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
return errors.Wrapf(err, "查找查询订单信息失败: %s", orderNo)
}
// 检查订单是否已经处理过退款
if order.Status == model.OrderStatusRefunded {
logx.Infof("订单已经是退款状态,无需重复处理: orderNo=%s", orderNo)
return nil
}
// 只处理成功和失败状态
var orderStatus, refundStatus string
switch status {
case refunddomestic.STATUS_SUCCESS:
orderStatus = model.OrderStatusRefunded
refundStatus = model.OrderRefundStatusSuccess
case refunddomestic.STATUS_CLOSED:
// 退款关闭,保持订单原状态,更新退款记录为失败
refundStatus = model.OrderRefundStatusFailed
case refunddomestic.STATUS_ABNORMAL:
// 退款异常,保持订单原状态,更新退款记录为失败
refundStatus = model.OrderRefundStatusFailed
default:
// 其他状态暂不处理
return nil
}
// 使用事务同时更新订单和退款记录
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 更新订单状态(仅在退款成功时更新)
if status == refunddomestic.STATUS_SUCCESS {
order.Status = orderStatus
order.RefundTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
if err := l.svcCtx.OrderModel.UpdateWithVersion(ctx, session, order); err != nil {
return errors.Wrapf(err, "更新查询订单状态失败: %s", orderNo)
}
}
// 查找最新的pending状态的退款记录
refund, err := l.findLatestPendingRefund(ctx, order.Id)
if err != nil {
if err == model.ErrNotFound {
logx.Errorf("未找到订单对应的待处理退款记录: orderNo=%s, orderId=%d", orderNo, order.Id)
return nil // 没有退款记录时不报错,只记录警告
}
return errors.Wrapf(err, "查找退款记录失败: orderNo=%s", orderNo)
}
// 检查退款记录是否已经处理过
if refund.Status == model.OrderRefundStatusSuccess {
logx.Infof("退款记录已经是成功状态,无需重复处理: orderNo=%s, refundId=%d", orderNo, refund.Id)
return nil
}
refund.Status = refundStatus
if status == refunddomestic.STATUS_SUCCESS {
refund.RefundTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
} else if status == refunddomestic.STATUS_CLOSED {
refund.CloseTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
}
if _, err := l.svcCtx.OrderRefundModel.Update(ctx, session, refund); err != nil {
return errors.Wrapf(err, "更新退款记录状态失败: orderNo=%s", orderNo)
}
return nil
})
if err != nil {
return errors.Wrapf(err, "更新订单和退款记录失败: %s", orderNo)
}
return nil
}
// handleAgentOrderRefund 处理代理会员订单退款(已废弃,新系统使用升级功能)
/* func (l *WechatPayRefundCallbackLogic) handleAgentOrderRefund(orderNo string, status refunddomestic.Status) error {
order, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
return errors.Wrapf(err, "查找代理会员订单信息失败: %s", orderNo)
}
// 检查订单是否已经处理过退款
if order.Status == "refunded" {
logx.Infof("代理会员订单已经是退款状态,无需重复处理: orderNo=%s", orderNo)
return nil
}
if status == refunddomestic.STATUS_SUCCESS {
order.Status = "refunded"
} else if status == refunddomestic.STATUS_ABNORMAL {
return nil // 异常状态直接返回
} else {
return nil // 其他状态直接返回
}
if err := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, order); err != nil {
return errors.Wrapf(err, "更新代理会员订单状态失败: %s", orderNo)
}
return nil
} */
// sendSuccessResponse 发送成功响应
func (l *WechatPayRefundCallbackLogic) sendSuccessResponse(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
}
func (l *WechatPayRefundCallbackLogic) WechatPayRefundCallback(w http.ResponseWriter, r *http.Request) error {
// 1. 处理微信退款通知
notification, err := l.svcCtx.WechatPayService.HandleRefundNotification(l.ctx, r)
if err != nil {
logx.Errorf("微信退款回调处理失败: %v", err)
l.sendSuccessResponse(w)
return nil
}
// 2. 检查关键字段是否为空
if notification.OutTradeNo == nil {
logx.Errorf("微信退款回调OutTradeNo字段为空")
l.sendSuccessResponse(w)
return nil
}
orderNo := *notification.OutTradeNo
// 3. 判断退款状态优先使用Status如果Status为nil则使用SuccessTime判断
var status refunddomestic.Status
var statusDetermined bool = false
if notification.Status != nil {
status = *notification.Status
statusDetermined = true
} else if notification.SuccessTime != nil && !notification.SuccessTime.IsZero() {
// 如果Status为空但SuccessTime有值说明退款成功
status = refunddomestic.STATUS_SUCCESS
statusDetermined = true
} else {
logx.Errorf("微信退款回调Status和SuccessTime都为空无法确定退款状态: orderNo=%s", orderNo)
l.sendSuccessResponse(w)
return nil
}
if !statusDetermined {
logx.Errorf("微信退款回调无法确定退款状态: orderNo=%s", orderNo)
l.sendSuccessResponse(w)
return nil
}
var processErr error
// 4. 根据订单号前缀处理不同类型的订单
switch {
case strings.HasPrefix(orderNo, "Q_"):
processErr = l.handleQueryOrderRefund(orderNo, status)
case strings.HasPrefix(orderNo, "A_"):
// 旧系统会员充值订单退款(已废弃,新系统使用升级功能)
// processErr = l.handleAgentOrderRefund(orderNo, status)
// 直接返回,避免旧订单影响
processErr = nil
default:
// 兼容旧订单,假设没有前缀的是查询订单
processErr = l.handleQueryOrderRefund(orderNo, status)
}
// 5. 处理错误并响应
if processErr != nil {
logx.Errorf("处理退款订单失败: orderNo=%s, err=%v", orderNo, processErr)
}
// 无论处理是否成功,都返回成功响应给微信
l.sendSuccessResponse(w)
return nil
}
// findLatestPendingRefund 查找订单最新的pending状态退款记录
func (l *WechatPayRefundCallbackLogic) findLatestPendingRefund(ctx context.Context, orderId string) (*model.OrderRefund, error) {
// 使用SelectBuilder查询最新的pending状态退款记录
builder := l.svcCtx.OrderRefundModel.SelectBuilder().
Where("order_id = ? AND status = ? AND del_state = ?", orderId, model.OrderRefundStatusPending, globalkey.DelStateNo).
OrderBy("id DESC").
Limit(1)
refunds, err := l.svcCtx.OrderRefundModel.FindAll(ctx, builder, "")
if err != nil {
return nil, err
}
if len(refunds) == 0 {
return nil, model.ErrNotFound
}
return refunds[0], nil
}