diff --git a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go index e9cbad1..baf71c7 100644 --- a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go +++ b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go @@ -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,62 +38,158 @@ 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 { - // 创建退款记录 - 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 - }) + // 支付宝退款成功,创建成功记录 + err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess) if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 退款失败 err: %v", err) + return nil, err } + return &types.AdminRefundOrderResp{ Status: model.OrderStatusRefunded, - RefundNo: fmt.Sprintf("refund-%s", order.OrderNo), + RefundNo: refundNo, Amount: req.RefundAmount, }, nil } 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 != "", + } } diff --git a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go index 838e63c..16cc447 100644 --- a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go +++ b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go @@ -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) } - if status == refunddomestic.STATUS_SUCCESS { - order.Status = "refunded" - order.RefundTime = sql.NullTime{ - Time: time.Now(), - Valid: true, - } - } else if status == refunddomestic.STATUS_ABNORMAL { - return nil // 异常状态直接返回 - } else { - 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 } - 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 } diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index f3e3381..80abc4d 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -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 } - // 退款 - 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 - } - } 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 - } - // 直接成功 + // 发起退款并创建退款记录 + 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 { + // 微信退款调用失败,创建失败记录 + 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 != "", + } +} diff --git a/app/main/model/vars.go b/app/main/model/vars.go index 60b3e0a..5bef461 100644 --- a/app/main/model/vars.go +++ b/app/main/model/vars.go @@ -41,11 +41,12 @@ var AgentLeveNameSVIP string = "SVIP" // 订单状态 const ( - OrderStatusPending = "pending" - OrderStatusPaid = "paid" - OrderStatusFailed = "failed" - OrderStatusRefunded = "refunded" - OrderStatusClosed = "closed" + OrderStatusPending = "pending" + OrderStatusPaid = "paid" + OrderStatusFailed = "failed" + OrderStatusRefunded = "refunded" + OrderStatusRefunding = "refunding" + OrderStatusClosed = "closed" ) // 订单退款状态