新增后台微信退款,以及完善退款记录
This commit is contained in:
parent
184d61a5c7
commit
3a324e6656
@ -16,6 +16,13 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
const (
|
||||
PaymentPlatformAlipay = "alipay"
|
||||
PaymentPlatformWechat = "wechat"
|
||||
OrderStatusPaid = "paid"
|
||||
RefundNoPrefix = "refund-"
|
||||
)
|
||||
|
||||
type AdminRefundOrderLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
@ -31,37 +38,110 @@ func NewAdminRefundOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminRefundOrder, 查询订单失败 err: %v", err)
|
||||
}
|
||||
|
||||
// 检查订单状态
|
||||
if order.Status != "paid" {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态不正确,无法退款 err: %v", err)
|
||||
if order.Status != OrderStatusPaid {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态: %s", order.Status)
|
||||
}
|
||||
|
||||
// 检查退款金额
|
||||
if req.RefundAmount > order.Amount {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额不能大于订单金额 err: %v", err)
|
||||
if refundAmount > order.Amount {
|
||||
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)
|
||||
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() {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.AdminRefundOrderResp{
|
||||
Status: model.OrderStatusRefunded,
|
||||
RefundNo: refundNo,
|
||||
Amount: req.RefundAmount,
|
||||
}, nil
|
||||
} else {
|
||||
// 支付宝退款失败,创建失败记录但不更新订单状态
|
||||
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: fmt.Sprintf("refund-%s", order.OrderNo),
|
||||
PlatformRefundId: sql.NullString{String: refundResp.TradeNo, Valid: true},
|
||||
RefundNo: refundNo,
|
||||
PlatformRefundId: l.createNullString(platformRefundId),
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: order.ProductId,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundReason: sql.NullString{String: req.RefundReason, Valid: true},
|
||||
Status: model.OrderRefundStatusPending,
|
||||
RefundReason: l.createNullString(req.RefundReason),
|
||||
Status: refundStatus, // 使用传入的状态,不再硬编码
|
||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
|
||||
@ -70,23 +150,46 @@ func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq)
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.Status = model.OrderStatusRefunded
|
||||
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
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 退款失败 err: %v", err)
|
||||
}
|
||||
return &types.AdminRefundOrderResp{
|
||||
Status: model.OrderStatusRefunded,
|
||||
RefundNo: fmt.Sprintf("refund-%s", order.OrderNo),
|
||||
Amount: req.RefundAmount,
|
||||
}, nil
|
||||
} else {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败, : %v", refundResp.Msg)), "AdminRefundOrder, 退款失败 err: %v", err)
|
||||
}
|
||||
|
||||
// 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 != "",
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,14 @@ import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/model"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
@ -34,21 +36,71 @@ func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, st
|
||||
return errors.Wrapf(err, "查找查询订单信息失败: %s", orderNo)
|
||||
}
|
||||
|
||||
// 只处理成功和失败状态
|
||||
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 = "refunded"
|
||||
order.Status = orderStatus
|
||||
order.RefundTime = sql.NullTime{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
}
|
||||
} else if status == refunddomestic.STATUS_ABNORMAL {
|
||||
return nil // 异常状态直接返回
|
||||
} else {
|
||||
return nil // 其他状态直接返回
|
||||
}
|
||||
|
||||
if err := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type PaySuccessNotifyUserHandler struct {
|
||||
@ -135,41 +136,132 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
|
||||
// 退款
|
||||
// 发起退款并创建退款记录
|
||||
refundErr := l.processRefund(ctx, order, "业务处理失败,自动退款")
|
||||
if refundErr != nil {
|
||||
logx.Errorf("退款处理失败,订单ID: %d, 错误: %v", order.Id, refundErr)
|
||||
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 {
|
||||
logx.Error(refundErr)
|
||||
return asynq.SkipRetry
|
||||
// 微信退款调用失败,创建失败记录
|
||||
createRefundErr := l.createRefundRecord(ctx, order, refundNo, "", model.OrderRefundStatusFailed, refundReason)
|
||||
if createRefundErr != nil {
|
||||
logx.Errorf("创建微信退款失败记录时出错: %v", createRefundErr)
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
logx.Error(refundErr)
|
||||
return asynq.SkipRetry
|
||||
// 支付宝退款调用失败,创建失败记录
|
||||
createRefundErr := l.createRefundRecord(ctx, order, refundNo, "", model.OrderRefundStatusFailed, refundReason)
|
||||
if createRefundErr != nil {
|
||||
logx.Errorf("创建支付宝退款失败记录时出错: %v", createRefundErr)
|
||||
}
|
||||
return refundErr
|
||||
}
|
||||
|
||||
if refund.IsSuccess() {
|
||||
logx.Errorf("支付宝退款成功, orderID: %d", order.Id)
|
||||
// 更新订单状态为退款
|
||||
order.Status = "refunded"
|
||||
order.RefundTime = sql.NullTime{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
// 支付宝退款成功,创建成功记录并更新订单状态
|
||||
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)
|
||||
}
|
||||
updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order)
|
||||
if updateOrderErr != nil {
|
||||
logx.Errorf("更新订单状态失败,订单ID: %d, 错误: %v", order.Id, updateOrderErr)
|
||||
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
|
||||
|
||||
// 更新订单状态为已退款
|
||||
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 asynq.SkipRetry
|
||||
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
logx.Errorf("支付宝退款失败:%v", refundErr)
|
||||
return asynq.SkipRetry
|
||||
// 支付宝退款失败,创建失败记录
|
||||
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},
|
||||
}
|
||||
|
||||
return asynq.SkipRetry
|
||||
_, 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 != "",
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ const (
|
||||
OrderStatusPaid = "paid"
|
||||
OrderStatusFailed = "failed"
|
||||
OrderStatusRefunded = "refunded"
|
||||
OrderStatusRefunding = "refunding"
|
||||
OrderStatusClosed = "closed"
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user