新增后台微信退款,以及完善退款记录

This commit is contained in:
liangzai 2025-06-19 01:49:03 +08:00
parent 184d61a5c7
commit 3a324e6656
4 changed files with 334 additions and 86 deletions

View File

@ -16,6 +16,13 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
) )
const (
PaymentPlatformAlipay = "alipay"
PaymentPlatformWechat = "wechat"
OrderStatusPaid = "paid"
RefundNoPrefix = "refund-"
)
type AdminRefundOrderLogic struct { type AdminRefundOrderLogic struct {
logx.Logger logx.Logger
ctx context.Context ctx context.Context
@ -31,62 +38,158 @@ func NewAdminRefundOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
} }
func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq) (resp *types.AdminRefundOrderResp, err error) { func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq) (resp *types.AdminRefundOrderResp, err error) {
// 获取订单信息 // 获取并验证订单
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id) order, err := l.getAndValidateOrder(req.Id, req.RefundAmount)
if err != nil {
return nil, err
}
// 根据支付平台处理退款
switch order.PaymentPlatform {
case PaymentPlatformAlipay:
return l.handleAlipayRefund(order, req)
case PaymentPlatformWechat:
return l.handleWechatRefund(order, req)
default:
return nil, errors.Wrapf(xerr.NewErrMsg("不支持的支付平台"), "AdminRefundOrder, 不支持的支付平台: %s", order.PaymentPlatform)
}
}
// getAndValidateOrder 获取并验证订单信息
func (l *AdminRefundOrderLogic) getAndValidateOrder(orderId int64, refundAmount float64) (*model.Order, error) {
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, orderId)
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminRefundOrder, 查询订单失败 err: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminRefundOrder, 查询订单失败 err: %v", err)
} }
// 检查订单状态 // 检查订单状态
if order.Status != "paid" { if order.Status != OrderStatusPaid {
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态不正确,无法退款 err: %v", err) return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态: %s", order.Status)
} }
// 检查退款金额 // 检查退款金额
if req.RefundAmount > order.Amount { if refundAmount > order.Amount {
return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额不能大于订单金额 err: %v", err) return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额: %f, 订单金额: %f", refundAmount, order.Amount)
} }
return order, nil
}
// handleAlipayRefund 处理支付宝退款
func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用支付宝退款接口
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount) refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount)
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 退款失败 err: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err)
} }
refundNo := l.generateRefundNo(order.OrderNo)
if refundResp.IsSuccess() { if refundResp.IsSuccess() {
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { // 支付宝退款成功,创建成功记录
// 创建退款记录 err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess)
refund := &model.OrderRefund{
RefundNo: fmt.Sprintf("refund-%s", order.OrderNo),
PlatformRefundId: sql.NullString{String: refundResp.TradeNo, Valid: true},
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: sql.NullString{String: req.RefundReason, Valid: true},
Status: model.OrderRefundStatusPending,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
// 更新订单状态
order.Status = model.OrderStatusRefunded
order.RefundTime = sql.NullTime{Time: time.Now(), Valid: true}
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 退款失败 err: %v", err) return nil, err
} }
return &types.AdminRefundOrderResp{ return &types.AdminRefundOrderResp{
Status: model.OrderStatusRefunded, Status: model.OrderStatusRefunded,
RefundNo: fmt.Sprintf("refund-%s", order.OrderNo), RefundNo: refundNo,
Amount: req.RefundAmount, Amount: req.RefundAmount,
}, nil }, nil
} else { } else {
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败, : %v", refundResp.Msg)), "AdminRefundOrder, 退款失败 err: %v", err) // 支付宝退款失败,创建失败记录但不更新订单状态
err = l.createRefundRecordOnly(order, req, refundNo, refundResp.TradeNo, model.OrderRefundStatusFailed)
if err != nil {
logx.Errorf("创建退款失败记录时出错: %v", err)
}
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败: %v", refundResp.Msg)), "AdminRefundOrder, 支付宝退款失败")
}
}
// handleWechatRefund 处理微信退款
func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用微信退款接口
err := l.svcCtx.WechatPayService.WeChatRefund(l.ctx, order.OrderNo, req.RefundAmount, order.Amount)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 微信退款失败 err: %v", err)
} }
// 微信退款是异步的创建pending状态的退款记录
refundNo := l.generateRefundNo(order.OrderNo)
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, "", model.OrderStatusRefunding, model.OrderRefundStatusPending)
if err != nil {
return nil, err
}
return &types.AdminRefundOrderResp{
Status: model.OrderRefundStatusPending,
RefundNo: refundNo,
Amount: req.RefundAmount,
}, nil
}
// createRefundRecordAndUpdateOrder 创建退款记录并更新订单状态
func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, orderStatus, refundStatus string) error {
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建退款记录
refund := &model.OrderRefund{
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, // 使用传入的状态,不再硬编码
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
// 更新订单状态
order.Status = orderStatus
order.RefundTime = sql.NullTime{Time: time.Now(), Valid: true}
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
}
// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况)
func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error {
refund := &model.OrderRefund{
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
_, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund)
if err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
return nil
}
// generateRefundNo 生成退款单号
func (l *AdminRefundOrderLogic) generateRefundNo(orderNo string) string {
return fmt.Sprintf("%s%s", RefundNoPrefix, orderNo)
}
// createNullString 创建 sql.NullString
func (l *AdminRefundOrderLogic) createNullString(value string) sql.NullString {
return sql.NullString{
String: value,
Valid: value != "",
}
} }

View File

@ -5,12 +5,14 @@ import (
"database/sql" "database/sql"
"net/http" "net/http"
"qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/model"
"strings" "strings"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
) )
type WechatPayRefundCallbackLogic struct { type WechatPayRefundCallbackLogic struct {
@ -34,21 +36,71 @@ func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, st
return errors.Wrapf(err, "查找查询订单信息失败: %s", orderNo) return errors.Wrapf(err, "查找查询订单信息失败: %s", orderNo)
} }
if status == refunddomestic.STATUS_SUCCESS { // 只处理成功和失败状态
order.Status = "refunded" var orderStatus, refundStatus string
order.RefundTime = sql.NullTime{ switch status {
Time: time.Now(), case refunddomestic.STATUS_SUCCESS:
Valid: true, orderStatus = model.OrderStatusRefunded
} refundStatus = model.OrderRefundStatusSuccess
} else if status == refunddomestic.STATUS_ABNORMAL { case refunddomestic.STATUS_CLOSED:
return nil // 异常状态直接返回 // 退款关闭,保持订单原状态,更新退款记录为失败
} else { refundStatus = model.OrderRefundStatusFailed
return nil // 其他状态直接返回 case refunddomestic.STATUS_ABNORMAL:
// 退款异常,保持订单原状态,更新退款记录为失败
refundStatus = model.OrderRefundStatusFailed
default:
// 其他状态暂不处理
return nil
} }
if err := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); err != nil { // 使用事务同时更新订单和退款记录
return errors.Wrapf(err, "更新查询订单状态失败: %s", orderNo) 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)
}
}
// 更新退款记录状态
refund, err := l.svcCtx.OrderRefundModel.FindOneByOrderId(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)
}
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 return nil
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/bytedance/sonic" "github.com/bytedance/sonic"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
) )
type PaySuccessNotifyUserHandler struct { type PaySuccessNotifyUserHandler struct {
@ -135,41 +136,132 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
return asynq.SkipRetry return asynq.SkipRetry
} }
// 退款 // 发起退款并创建退款记录
if order.PaymentPlatform == "wechat" { refundErr := l.processRefund(ctx, order, "业务处理失败,自动退款")
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount) if refundErr != nil {
if refundErr != nil { logx.Errorf("退款处理失败订单ID: %d, 错误: %v", order.Id, refundErr)
logx.Error(refundErr) return asynq.SkipRetry
return asynq.SkipRetry
}
} else {
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
if refundErr != nil {
logx.Error(refundErr)
return asynq.SkipRetry
}
if refund.IsSuccess() {
logx.Errorf("支付宝退款成功, orderID: %d", order.Id)
// 更新订单状态为退款
order.Status = "refunded"
order.RefundTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order)
if updateOrderErr != nil {
logx.Errorf("更新订单状态失败订单ID: %d, 错误: %v", order.Id, updateOrderErr)
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
}
return asynq.SkipRetry
} else {
logx.Errorf("支付宝退款失败:%v", refundErr)
return asynq.SkipRetry
}
// 直接成功
} }
} }
return asynq.SkipRetry return asynq.SkipRetry
} }
// processRefund 处理退款逻辑
func (l *PaySuccessNotifyUserHandler) processRefund(ctx context.Context, order *model.Order, refundReason string) error {
refundNo := fmt.Sprintf("refund-%s", order.OrderNo)
if order.PaymentPlatform == "wechat" {
// 微信退款(异步)
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
if refundErr != nil {
// 微信退款调用失败,创建失败记录
createRefundErr := l.createRefundRecord(ctx, order, refundNo, "", model.OrderRefundStatusFailed, refundReason)
if createRefundErr != nil {
logx.Errorf("创建微信退款失败记录时出错: %v", createRefundErr)
}
return refundErr
}
// 微信退款调用成功创建pending记录并更新订单状态
return l.svcCtx.OrderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建pending状态的退款记录
if err := l.createRefundRecordWithSession(ctx, session, order, refundNo, "", model.OrderRefundStatusPending, refundReason); err != nil {
return fmt.Errorf("创建微信退款记录失败: %v", err)
}
// 更新订单状态为退款中
order.Status = model.OrderStatusRefunding
order.RefundTime = sql.NullTime{Time: time.Now(), Valid: true}
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
} else if order.PaymentPlatform == "alipay" {
// 支付宝退款(同步)
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
if refundErr != nil {
// 支付宝退款调用失败,创建失败记录
createRefundErr := l.createRefundRecord(ctx, order, refundNo, "", model.OrderRefundStatusFailed, refundReason)
if createRefundErr != nil {
logx.Errorf("创建支付宝退款失败记录时出错: %v", createRefundErr)
}
return refundErr
}
if refund.IsSuccess() {
// 支付宝退款成功,创建成功记录并更新订单状态
return l.svcCtx.OrderModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建成功状态的退款记录
if err := l.createRefundRecordWithSession(ctx, session, order, refundNo, refund.TradeNo, model.OrderRefundStatusSuccess, refundReason); err != nil {
return fmt.Errorf("创建支付宝退款成功记录失败: %v", err)
}
// 更新订单状态为已退款
order.Status = model.OrderStatusRefunded
order.RefundTime = sql.NullTime{Time: time.Now(), Valid: true}
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
} else {
// 支付宝退款失败,创建失败记录
createRefundErr := l.createRefundRecord(ctx, order, refundNo, refund.TradeNo, model.OrderRefundStatusFailed, refundReason)
if createRefundErr != nil {
logx.Errorf("创建支付宝退款失败记录时出错: %v", createRefundErr)
}
return fmt.Errorf("支付宝退款失败: %s", refund.Msg)
}
} else {
return fmt.Errorf("不支持的支付平台: %s", order.PaymentPlatform)
}
}
// createRefundRecord 创建退款记录(无事务)
func (l *PaySuccessNotifyUserHandler) createRefundRecord(ctx context.Context, order *model.Order, refundNo, platformRefundId, status, reason string) error {
refund := &model.OrderRefund{
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: order.Amount,
RefundReason: l.createNullString(reason),
Status: status,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
_, err := l.svcCtx.OrderRefundModel.Insert(ctx, nil, refund)
return err
}
// createRefundRecordWithSession 创建退款记录(带事务)
func (l *PaySuccessNotifyUserHandler) createRefundRecordWithSession(ctx context.Context, session sqlx.Session, order *model.Order, refundNo, platformRefundId, status, reason string) error {
refund := &model.OrderRefund{
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: order.Amount,
RefundReason: l.createNullString(reason),
Status: status,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
_, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund)
return err
}
// createNullString 创建 sql.NullString
func (l *PaySuccessNotifyUserHandler) createNullString(value string) sql.NullString {
return sql.NullString{
String: value,
Valid: value != "",
}
}

View File

@ -41,11 +41,12 @@ var AgentLeveNameSVIP string = "SVIP"
// 订单状态 // 订单状态
const ( const (
OrderStatusPending = "pending" OrderStatusPending = "pending"
OrderStatusPaid = "paid" OrderStatusPaid = "paid"
OrderStatusFailed = "failed" OrderStatusFailed = "failed"
OrderStatusRefunded = "refunded" OrderStatusRefunded = "refunded"
OrderStatusClosed = "closed" OrderStatusRefunding = "refunding"
OrderStatusClosed = "closed"
) )
// 订单退款状态 // 订单退款状态