829 lines
28 KiB
Go
829 lines
28 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"dianshang/internal/model"
|
||
"dianshang/internal/repository"
|
||
"dianshang/pkg/logger"
|
||
"dianshang/pkg/utils"
|
||
"fmt"
|
||
"time"
|
||
)
|
||
|
||
type RefundService struct {
|
||
refundRepo *repository.RefundRepository
|
||
orderRepo *repository.OrderRepository
|
||
wechatPaySvc *WeChatPayService
|
||
}
|
||
|
||
func NewRefundService(refundRepo *repository.RefundRepository, orderRepo *repository.OrderRepository, wechatPaySvc *WeChatPayService) *RefundService {
|
||
return &RefundService{
|
||
refundRepo: refundRepo,
|
||
orderRepo: orderRepo,
|
||
wechatPaySvc: wechatPaySvc,
|
||
}
|
||
}
|
||
|
||
// CreateRefund 创建退款申请
|
||
func (s *RefundService) CreateRefund(ctx context.Context, req *CreateRefundRequest) (*model.Refund, error) {
|
||
logger.Info("开始创建退款申请",
|
||
"orderID", req.OrderID,
|
||
"refundAmount", req.RefundAmount,
|
||
"refundReason", req.RefundReason,
|
||
"userID", req.UserID)
|
||
|
||
// 1. 验证订单
|
||
order, err := s.orderRepo.GetByID(req.OrderID)
|
||
if err != nil {
|
||
logger.Error("查询订单失败", "error", err, "orderID", req.OrderID)
|
||
return nil, fmt.Errorf("订单不存在")
|
||
}
|
||
|
||
// 2. 验证订单状态
|
||
if order.Status != model.OrderStatusPaid {
|
||
return nil, fmt.Errorf("订单状态不允许退款,当前状态: %s", order.GetStatusText())
|
||
}
|
||
|
||
// 3. 验证用户权限
|
||
if order.UserID != req.UserID {
|
||
return nil, fmt.Errorf("无权限操作此订单")
|
||
}
|
||
|
||
// 4. 验证退款金额
|
||
if req.RefundAmount <= 0 {
|
||
return nil, fmt.Errorf("退款金额必须大于0")
|
||
}
|
||
|
||
// 将前端传递的元金额转换为分(数据库统一使用分存储)
|
||
refundAmountInCents := req.RefundAmount * 100
|
||
|
||
// 计算已退款金额
|
||
totalRefunded, err := s.refundRepo.GetTotalRefundedByOrderID(req.OrderID)
|
||
if err != nil {
|
||
logger.Error("查询已退款金额失败", "error", err, "orderID", req.OrderID)
|
||
return nil, fmt.Errorf("查询退款信息失败")
|
||
}
|
||
|
||
// 检查退款金额是否超过可退款金额(订单金额也需要转换为分进行比较)
|
||
orderAmountInCents := order.TotalAmount * 100
|
||
availableRefund := orderAmountInCents - totalRefunded
|
||
if refundAmountInCents > availableRefund {
|
||
return nil, fmt.Errorf("退款金额超过可退款金额,可退款: %.2f", availableRefund/100.0)
|
||
}
|
||
|
||
// 5. 生成退款记录
|
||
refund := &model.Refund{
|
||
RefundNo: utils.GenerateRefundNo(),
|
||
WechatOutRefundNo: utils.GenerateWechatOutRefundNo(),
|
||
OrderID: req.OrderID,
|
||
OrderNo: order.OrderNo, // 设置订单号
|
||
UserID: req.UserID,
|
||
RefundAmount: refundAmountInCents, // 存储为分
|
||
ActualRefundAmount: refundAmountInCents, // 设置实际退款金额,初始等于申请退款金额(分)
|
||
RefundReason: req.RefundReason,
|
||
RefundType: req.RefundType,
|
||
Status: model.RefundStatusPending,
|
||
WechatRefundStatus: "",
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
// 6. 保存退款记录
|
||
err = s.refundRepo.Create(refund)
|
||
if err != nil {
|
||
logger.Error("创建退款记录失败", "error", err)
|
||
return nil, fmt.Errorf("创建退款申请失败")
|
||
}
|
||
|
||
// 7. 更新订单状态为退款中
|
||
if order.Status != model.OrderStatusReturning {
|
||
orderUpdates := map[string]interface{}{
|
||
"status": model.OrderStatusReturning,
|
||
"refund_time": time.Now(),
|
||
"updated_at": time.Now(),
|
||
}
|
||
err = s.orderRepo.UpdateByID(order.ID, orderUpdates)
|
||
if err != nil {
|
||
logger.Error("更新订单状态为退款中失败", "error", err, "orderID", order.ID)
|
||
// 不返回错误,因为退款记录已经创建成功
|
||
} else {
|
||
logger.Info("订单状态已更新为退款中", "orderID", order.ID, "orderNo", order.OrderNo)
|
||
}
|
||
}
|
||
|
||
// 8. 创建退款日志
|
||
statusTo := model.RefundStatusPending
|
||
userID := req.UserID
|
||
err = s.createRefundLog(refund.ID, "create", nil, &statusTo, "用户申请退款", &userID)
|
||
if err != nil {
|
||
logger.Warn("创建退款日志失败", "error", err, "refundID", refund.ID)
|
||
}
|
||
|
||
logger.Info("退款申请创建成功",
|
||
"refundID", refund.ID,
|
||
"refundNo", refund.RefundNo,
|
||
"orderID", req.OrderID,
|
||
"refundAmountYuan", req.RefundAmount,
|
||
"refundAmountCents", refundAmountInCents)
|
||
|
||
return refund, nil
|
||
}
|
||
|
||
// ProcessRefund 处理退款(管理员审核通过后调用)
|
||
func (s *RefundService) ProcessRefund(ctx context.Context, refundID uint, adminID uint, adminRemark string) error {
|
||
logger.Info("开始处理退款", "refundID", refundID, "adminID", adminID)
|
||
|
||
// 1. 查询退款记录
|
||
refund, err := s.refundRepo.GetByID(refundID)
|
||
if err != nil {
|
||
logger.Error("查询退款记录失败", "error", err, "refundID", refundID)
|
||
return fmt.Errorf("退款记录不存在")
|
||
}
|
||
|
||
// 2. 验证退款状态
|
||
if refund.Status != model.RefundStatusPending {
|
||
return fmt.Errorf("退款状态不允许处理,当前状态: %s", refund.GetStatusText())
|
||
}
|
||
|
||
// 3. 查询订单信息
|
||
order, err := s.orderRepo.GetByID(refund.OrderID)
|
||
if err != nil {
|
||
logger.Error("查询订单失败", "error", err, "orderID", refund.OrderID)
|
||
return fmt.Errorf("订单不存在")
|
||
}
|
||
|
||
// 4. 更新退款状态为处理中
|
||
err = s.refundRepo.UpdateByID(refundID, map[string]interface{}{
|
||
"status": model.RefundStatusProcessing,
|
||
"admin_remark": adminRemark,
|
||
"audit_time": time.Now(),
|
||
})
|
||
if err != nil {
|
||
logger.Error("更新退款状态失败", "error", err, "refundID", refundID)
|
||
return fmt.Errorf("更新退款状态失败")
|
||
}
|
||
|
||
// 5. 创建退款日志
|
||
statusFrom := model.RefundStatusPending
|
||
statusTo := model.RefundStatusProcessing
|
||
err = s.createRefundLog(refundID, "approve", &statusFrom, &statusTo, fmt.Sprintf("管理员审核通过: %s", adminRemark), &adminID)
|
||
if err != nil {
|
||
logger.Warn("创建退款日志失败", "error", err, "refundID", refundID)
|
||
}
|
||
|
||
// 6. 调用微信退款API
|
||
wechatResp, err := s.wechatPaySvc.CreateRefund(ctx, refund, order)
|
||
if err != nil {
|
||
logger.Error("调用微信退款API失败", "error", err, "refundID", refundID)
|
||
|
||
// 更新退款状态为失败
|
||
s.refundRepo.UpdateByID(refundID, map[string]interface{}{
|
||
"status": model.RefundStatusFailed,
|
||
"admin_remark": fmt.Sprintf("微信退款失败: %v", err),
|
||
})
|
||
statusFrom := model.RefundStatusProcessing
|
||
statusTo := model.RefundStatusFailed
|
||
s.createRefundLog(refundID, "fail", &statusFrom, &statusTo, fmt.Sprintf("微信退款失败: %v", err), &adminID)
|
||
|
||
return fmt.Errorf("微信退款失败: %v", err)
|
||
}
|
||
|
||
// 7. 更新退款记录的微信信息
|
||
updates := map[string]interface{}{
|
||
"wechat_refund_id": wechatResp.Data["refund_id"],
|
||
"wechat_refund_status": wechatResp.Data["status"],
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
// 如果微信返回了用户收款账户信息
|
||
if userAccount, ok := wechatResp.Data["user_received_account"].(string); ok && userAccount != "" {
|
||
updates["wechat_user_received_account"] = userAccount
|
||
}
|
||
|
||
// 如果微信返回了退款账户信息
|
||
if refundAccount, ok := wechatResp.Data["funds_account"].(string); ok && refundAccount != "" {
|
||
updates["wechat_refund_account"] = refundAccount
|
||
}
|
||
|
||
// 如果微信退款立即成功
|
||
if status, ok := wechatResp.Data["status"].(string); ok && status == "SUCCESS" {
|
||
updates["status"] = model.RefundStatusSuccess
|
||
if successTime, ok := wechatResp.Data["success_time"].(string); ok && successTime != "" {
|
||
if parsedTime, err := time.Parse("2006-01-02T15:04:05+08:00", successTime); err == nil {
|
||
updates["wechat_success_time"] = parsedTime
|
||
}
|
||
}
|
||
}
|
||
|
||
err = s.refundRepo.UpdateByID(refundID, updates)
|
||
if err != nil {
|
||
logger.Error("更新退款微信信息失败", "error", err, "refundID", refundID)
|
||
}
|
||
|
||
// 8. 如果退款成功,更新订单退款信息
|
||
if status, ok := wechatResp.Data["status"].(string); ok && status == "SUCCESS" {
|
||
err = s.updateOrderRefundInfo(order, refund)
|
||
if err != nil {
|
||
logger.Error("更新订单退款信息失败", "error", err, "orderID", order.ID)
|
||
}
|
||
|
||
// 创建成功日志
|
||
statusFrom := model.RefundStatusProcessing
|
||
statusTo := model.RefundStatusSuccess
|
||
s.createRefundLog(refundID, "success", &statusFrom, &statusTo, "微信退款成功", &adminID)
|
||
} else {
|
||
// 创建处理中日志
|
||
statusFrom := model.RefundStatusProcessing
|
||
statusTo := model.RefundStatusProcessing
|
||
s.createRefundLog(refundID, "processing", &statusFrom, &statusTo, "微信退款处理中", &adminID)
|
||
}
|
||
|
||
logger.Info("退款处理完成",
|
||
"refundID", refundID,
|
||
"wechatRefundID", wechatResp.Data["refund_id"],
|
||
"status", wechatResp.Data["status"])
|
||
|
||
return nil
|
||
}
|
||
|
||
// RejectRefund 拒绝退款申请
|
||
func (s *RefundService) RejectRefund(ctx context.Context, refundID uint, adminID uint, rejectReason string) error {
|
||
logger.Info("拒绝退款申请", "refundID", refundID, "adminID", adminID, "reason", rejectReason)
|
||
|
||
// 1. 查询退款记录
|
||
refund, err := s.refundRepo.GetByID(refundID)
|
||
if err != nil {
|
||
logger.Error("查询退款记录失败", "error", err, "refundID", refundID)
|
||
return fmt.Errorf("退款记录不存在")
|
||
}
|
||
|
||
// 2. 验证退款状态
|
||
if refund.Status != model.RefundStatusPending {
|
||
return fmt.Errorf("退款状态不允许拒绝,当前状态: %s", refund.GetStatusText())
|
||
}
|
||
|
||
// 3. 更新退款状态为已拒绝
|
||
err = s.refundRepo.UpdateByID(refundID, map[string]interface{}{
|
||
"status": model.RefundStatusRejected,
|
||
"reject_reason": rejectReason,
|
||
"reject_time": time.Now(),
|
||
})
|
||
if err != nil {
|
||
logger.Error("更新退款状态失败", "error", err, "refundID", refundID)
|
||
return fmt.Errorf("更新退款状态失败")
|
||
}
|
||
|
||
// 4. 创建退款日志
|
||
statusFrom := model.RefundStatusPending
|
||
statusTo := model.RefundStatusRejected
|
||
err = s.createRefundLog(refundID, "reject", &statusFrom, &statusTo, fmt.Sprintf("管理员拒绝: %s", rejectReason), &adminID)
|
||
if err != nil {
|
||
logger.Warn("创建退款日志失败", "error", err, "refundID", refundID)
|
||
}
|
||
|
||
logger.Info("退款申请已拒绝", "refundID", refundID)
|
||
return nil
|
||
}
|
||
|
||
// HandleWeChatRefundNotify 处理微信退款回调通知(解析和解密)
|
||
func (s *RefundService) HandleWeChatRefundNotify(ctx context.Context, body []byte, headers map[string]string) (*WeChatRefundNotify, error) {
|
||
logger.Info("开始处理微信退款回调通知")
|
||
|
||
if s.wechatPaySvc == nil {
|
||
return nil, fmt.Errorf("微信支付服务未初始化")
|
||
}
|
||
|
||
// 调用微信支付服务解析和解密回调数据
|
||
notify, err := s.wechatPaySvc.HandleRefundNotify(ctx, body, headers)
|
||
if err != nil {
|
||
logger.Error("解析退款回调数据失败", "error", err)
|
||
return nil, err
|
||
}
|
||
|
||
logger.Info("成功解析退款回调数据", "eventType", notify.EventType)
|
||
return notify, nil
|
||
}
|
||
|
||
// HandleRefundCallback 处理微信退款回调
|
||
func (s *RefundService) HandleRefundCallback(ctx context.Context, notify *WeChatRefundNotify) error {
|
||
logger.Info("处理微信退款回调", "eventType", notify.EventType)
|
||
|
||
if notify.DecryptedData == nil {
|
||
return fmt.Errorf("回调数据中缺少解密数据")
|
||
}
|
||
|
||
outRefundNo := notify.DecryptedData.OutRefundNo
|
||
if outRefundNo == "" {
|
||
return fmt.Errorf("回调数据中缺少退款单号")
|
||
}
|
||
|
||
// 1. 查询退款记录
|
||
refund, err := s.refundRepo.GetByWechatOutRefundNo(outRefundNo)
|
||
if err != nil {
|
||
logger.Error("根据微信退款单号查询退款记录失败", "error", err, "outRefundNo", outRefundNo)
|
||
return fmt.Errorf("退款记录不存在")
|
||
}
|
||
|
||
// 2. 根据事件类型处理不同的退款状态
|
||
var newStatus int
|
||
var logRemark string
|
||
|
||
switch notify.EventType {
|
||
case "REFUND.SUCCESS":
|
||
// 退款成功
|
||
if refund.Status == model.RefundStatusSuccess {
|
||
logger.Info("退款已经是成功状态,跳过处理", "refundID", refund.ID)
|
||
return nil
|
||
}
|
||
newStatus = model.RefundStatusSuccess
|
||
logRemark = "微信退款回调:退款成功"
|
||
|
||
case "REFUND.ABNORMAL":
|
||
// 退款异常
|
||
newStatus = model.RefundStatusFailed
|
||
logRemark = "微信退款回调:退款异常"
|
||
|
||
case "REFUND.CLOSED":
|
||
// 退款关闭
|
||
newStatus = model.RefundStatusFailed
|
||
logRemark = "微信退款回调:退款关闭"
|
||
|
||
default:
|
||
logger.Warn("未知的退款回调事件类型", "eventType", notify.EventType)
|
||
return nil
|
||
}
|
||
|
||
// 3. 更新退款状态和微信信息
|
||
updates := map[string]interface{}{
|
||
"status": newStatus,
|
||
"wechat_refund_id": notify.DecryptedData.RefundId,
|
||
"wechat_refund_status": notify.DecryptedData.RefundStatus,
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
// 只有成功时才更新收款账户和成功时间
|
||
if notify.EventType == "REFUND.SUCCESS" {
|
||
updates["wechat_user_received_account"] = notify.DecryptedData.UserReceivedAccount
|
||
|
||
// 解析成功时间
|
||
if notify.DecryptedData.SuccessTime != "" {
|
||
if successTime, err := time.Parse("2006-01-02T15:04:05+08:00", notify.DecryptedData.SuccessTime); err == nil {
|
||
updates["wechat_success_time"] = successTime
|
||
}
|
||
}
|
||
}
|
||
|
||
err = s.refundRepo.UpdateByID(refund.ID, updates)
|
||
if err != nil {
|
||
logger.Error("更新退款状态失败", "error", err, "refundID", refund.ID)
|
||
return fmt.Errorf("更新退款状态失败")
|
||
}
|
||
|
||
// 4. 只有退款成功时才更新订单退款信息
|
||
if newStatus == model.RefundStatusSuccess {
|
||
order, err := s.orderRepo.GetByID(refund.OrderID)
|
||
if err != nil {
|
||
logger.Error("查询订单失败", "error", err, "orderID", refund.OrderID)
|
||
} else {
|
||
err = s.updateOrderRefundInfo(order, refund)
|
||
if err != nil {
|
||
logger.Error("更新订单退款信息失败", "error", err, "orderID", order.ID)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 5. 创建退款日志
|
||
statusFrom := refund.Status
|
||
statusTo := newStatus
|
||
var operatorID *uint = nil
|
||
err = s.createRefundLog(refund.ID, "callback", &statusFrom, &statusTo, logRemark, operatorID)
|
||
if err != nil {
|
||
logger.Warn("创建退款日志失败", "error", err, "refundID", refund.ID)
|
||
}
|
||
|
||
logger.Info("微信退款回调处理完成", "refundID", refund.ID, "outRefundNo", outRefundNo, "newStatus", newStatus)
|
||
return nil
|
||
}
|
||
|
||
// GetRefundsByOrderID 获取订单的退款记录
|
||
func (s *RefundService) GetRefundsByOrderID(ctx context.Context, orderID uint, userID uint) ([]*model.Refund, error) {
|
||
// 验证用户权限
|
||
order, err := s.orderRepo.GetByID(orderID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("订单不存在")
|
||
}
|
||
|
||
if order.UserID != userID {
|
||
return nil, fmt.Errorf("无权限查看此订单的退款信息")
|
||
}
|
||
|
||
refunds, err := s.refundRepo.GetByOrderID(orderID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 转换为指针切片
|
||
result := make([]*model.Refund, len(refunds))
|
||
for i := range refunds {
|
||
result[i] = &refunds[i]
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// SyncRefundAndOrderStatus 同步退款状态和订单状态
|
||
// 这个方法用于修复退款状态已成功但订单状态未更新的问题
|
||
func (s *RefundService) SyncRefundAndOrderStatus(ctx context.Context) error {
|
||
logger.Info("开始同步退款状态和订单状态")
|
||
|
||
// 1. 查询所有状态为成功的退款记录
|
||
refunds, err := s.refundRepo.GetRefundsByStatus(model.RefundStatusSuccess)
|
||
if err != nil {
|
||
logger.Error("查询成功退款记录失败", "error", err)
|
||
return fmt.Errorf("查询成功退款记录失败: %v", err)
|
||
}
|
||
|
||
// 2. 遍历每个退款记录,检查对应的订单状态
|
||
for _, refund := range refunds {
|
||
// 获取订单信息
|
||
order, err := s.orderRepo.GetByID(refund.OrderID)
|
||
if err != nil {
|
||
logger.Error("获取订单信息失败", "error", err, "orderID", refund.OrderID)
|
||
continue
|
||
}
|
||
|
||
// 如果订单状态不是已退款,则需要更新
|
||
if order.Status != model.OrderStatusRefunded {
|
||
// 计算订单总退款金额
|
||
totalRefunded, err := s.refundRepo.GetTotalRefundedByOrderID(order.ID)
|
||
if err != nil {
|
||
logger.Error("计算订单总退款金额失败", "error", err, "orderID", order.ID)
|
||
continue
|
||
}
|
||
|
||
// 如果总退款金额大于等于订单金额,则更新订单状态为已退款
|
||
if totalRefunded >= order.TotalAmount {
|
||
updates := map[string]interface{}{
|
||
"status": model.OrderStatusRefunded,
|
||
"refunded_at": time.Now(),
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
err = s.orderRepo.UpdateByID(order.ID, updates)
|
||
if err != nil {
|
||
logger.Error("更新订单状态为已退款失败", "error", err, "orderID", order.ID)
|
||
continue
|
||
}
|
||
|
||
logger.Info("订单状态已更新为已退款",
|
||
"orderID", order.ID,
|
||
"orderNo", order.OrderNo,
|
||
"totalAmount", order.TotalAmount,
|
||
"totalRefunded", totalRefunded)
|
||
} else if order.Status != model.OrderStatusReturning {
|
||
// 如果是部分退款且订单状态不是退款中,则更新为退款中
|
||
updates := map[string]interface{}{
|
||
"status": model.OrderStatusReturning,
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
err = s.orderRepo.UpdateByID(order.ID, updates)
|
||
if err != nil {
|
||
logger.Error("更新订单状态为退款中失败", "error", err, "orderID", order.ID)
|
||
continue
|
||
}
|
||
|
||
logger.Info("订单状态已更新为退款中",
|
||
"orderID", order.ID,
|
||
"orderNo", order.OrderNo,
|
||
"totalAmount", order.TotalAmount,
|
||
"totalRefunded", totalRefunded)
|
||
}
|
||
}
|
||
}
|
||
|
||
logger.Info("同步退款状态和订单状态完成")
|
||
return nil
|
||
}
|
||
|
||
// GetRefundsByUserID 获取用户的退款记录
|
||
func (s *RefundService) GetRefundsByUserID(ctx context.Context, userID uint, page, pageSize int) ([]*model.Refund, int64, error) {
|
||
refunds, total, err := s.refundRepo.GetByUserID(userID, page, pageSize)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
// 转换为指针切片
|
||
result := make([]*model.Refund, len(refunds))
|
||
for i := range refunds {
|
||
result[i] = &refunds[i]
|
||
|
||
// 检查退款状态是否为成功,但订单状态不是已退款
|
||
if refunds[i].Status == model.RefundStatusSuccess && refunds[i].Order.Status != model.OrderStatusRefunded {
|
||
// 计算订单总退款金额
|
||
totalRefunded, err := s.refundRepo.GetTotalRefundedByOrderID(refunds[i].OrderID)
|
||
if err != nil {
|
||
logger.Error("计算订单总退款金额失败", "error", err, "orderID", refunds[i].OrderID)
|
||
continue
|
||
}
|
||
|
||
// 如果总退款金额大于等于订单金额,则更新订单状态为已退款
|
||
if totalRefunded >= refunds[i].Order.TotalAmount {
|
||
updates := map[string]interface{}{
|
||
"status": model.OrderStatusRefunded,
|
||
"refunded_at": time.Now(),
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
err = s.orderRepo.UpdateByID(refunds[i].OrderID, updates)
|
||
if err != nil {
|
||
logger.Error("更新订单状态为已退款失败", "error", err, "orderID", refunds[i].OrderID)
|
||
continue
|
||
}
|
||
|
||
// 更新当前退款记录中的订单状态
|
||
result[i].Order.Status = model.OrderStatusRefunded
|
||
|
||
logger.Info("订单状态已更新为已退款",
|
||
"orderID", refunds[i].OrderID,
|
||
"orderNo", refunds[i].OrderNo,
|
||
"totalAmount", refunds[i].Order.TotalAmount,
|
||
"totalRefunded", totalRefunded)
|
||
} else if refunds[i].Order.Status != model.OrderStatusReturning {
|
||
// 如果是部分退款且订单状态不是退款中,则更新为退款中
|
||
updates := map[string]interface{}{
|
||
"status": model.OrderStatusReturning,
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
err = s.orderRepo.UpdateByID(refunds[i].OrderID, updates)
|
||
if err != nil {
|
||
logger.Error("更新订单状态为退款中失败", "error", err, "orderID", refunds[i].OrderID)
|
||
continue
|
||
}
|
||
|
||
// 更新当前退款记录中的订单状态
|
||
result[i].Order.Status = model.OrderStatusReturning
|
||
|
||
logger.Info("订单状态已更新为退款中",
|
||
"orderID", refunds[i].OrderID,
|
||
"orderNo", refunds[i].OrderNo,
|
||
"totalAmount", refunds[i].Order.TotalAmount,
|
||
"totalRefunded", totalRefunded)
|
||
}
|
||
}
|
||
}
|
||
|
||
return result, total, nil
|
||
}
|
||
|
||
// GetRefundByID 获取退款详情
|
||
func (s *RefundService) GetRefundByID(ctx context.Context, refundID uint, userID uint) (*model.Refund, error) {
|
||
refund, err := s.refundRepo.GetByID(refundID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("退款记录不存在")
|
||
}
|
||
|
||
// 验证用户权限
|
||
if refund.UserID != userID {
|
||
return nil, fmt.Errorf("无权限查看此退款记录")
|
||
}
|
||
|
||
return refund, nil
|
||
}
|
||
|
||
// QueryRefundStatus 查询微信退款状态
|
||
func (s *RefundService) QueryRefundStatus(ctx context.Context, refundID uint) error {
|
||
logger.Info("查询微信退款状态", "refundID", refundID)
|
||
|
||
// 1. 查询退款记录
|
||
refund, err := s.refundRepo.GetByID(refundID)
|
||
if err != nil {
|
||
logger.Error("查询退款记录失败", "error", err, "refundID", refundID)
|
||
return fmt.Errorf("退款记录不存在")
|
||
}
|
||
|
||
if refund.WechatOutRefundNo == "" {
|
||
return fmt.Errorf("退款记录没有微信退款单号")
|
||
}
|
||
|
||
// 2. 调用微信查询退款API
|
||
wechatRefund, err := s.wechatPaySvc.QueryRefund(ctx, refund.WechatOutRefundNo)
|
||
if err != nil {
|
||
logger.Error("查询微信退款状态失败", "error", err, "outRefundNo", refund.WechatOutRefundNo)
|
||
return fmt.Errorf("查询微信退款状态失败: %v", err)
|
||
}
|
||
|
||
// 3. 更新退款记录
|
||
updates := map[string]interface{}{
|
||
"wechat_refund_status": wechatRefund.WechatRefundStatus,
|
||
"wechat_user_received_account": wechatRefund.WechatUserReceivedAccount,
|
||
"wechat_refund_account": wechatRefund.WechatRefundAccount,
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
// 如果微信退款成功,更新本地状态
|
||
if wechatRefund.WechatRefundStatus == "SUCCESS" {
|
||
// 无论当前退款状态如何,只要微信退款成功,就更新为成功状态
|
||
updates["status"] = model.RefundStatusSuccess
|
||
if wechatRefund.WechatSuccessTime != nil {
|
||
updates["wechat_success_time"] = *wechatRefund.WechatSuccessTime
|
||
}
|
||
|
||
// 更新订单退款信息
|
||
order, err := s.orderRepo.GetByID(refund.OrderID)
|
||
if err == nil {
|
||
s.updateOrderRefundInfo(order, refund)
|
||
}
|
||
|
||
// 只有当状态发生变化时才创建日志
|
||
if refund.Status != model.RefundStatusSuccess {
|
||
statusFrom := refund.Status
|
||
statusTo := model.RefundStatusSuccess
|
||
var operatorID *uint = nil
|
||
s.createRefundLog(refund.ID, "query_success", &statusFrom, &statusTo, "查询确认微信退款成功", operatorID)
|
||
}
|
||
}
|
||
|
||
err = s.refundRepo.UpdateByID(refund.ID, updates)
|
||
if err != nil {
|
||
logger.Error("更新退款状态失败", "error", err, "refundID", refundID)
|
||
return fmt.Errorf("更新退款状态失败")
|
||
}
|
||
|
||
logger.Info("退款状态查询完成", "refundID", refundID, "status", wechatRefund.WechatRefundStatus)
|
||
return nil
|
||
}
|
||
|
||
// updateOrderRefundInfo 更新订单退款信息
|
||
func (s *RefundService) updateOrderRefundInfo(order *model.Order, refund *model.Refund) error {
|
||
// 计算订单总退款金额
|
||
totalRefunded, err := s.refundRepo.GetTotalRefundedByOrderID(order.ID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 计算退款次数
|
||
refundCount, err := s.refundRepo.GetRefundCountByOrderID(order.ID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
updates := map[string]interface{}{
|
||
"total_refund_amount": totalRefunded,
|
||
"refund_count": refundCount,
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
// 如果全额退款,更新订单状态为已退款
|
||
if totalRefunded >= order.TotalAmount {
|
||
updates["status"] = model.OrderStatusRefunded
|
||
updates["refunded_at"] = time.Now()
|
||
logger.Info("更新订单状态为已退款",
|
||
"orderID", order.ID,
|
||
"totalAmount", order.TotalAmount,
|
||
"totalRefunded", totalRefunded,
|
||
"refundID", refund.ID)
|
||
} else if order.Status == model.OrderStatusReturning {
|
||
// 如果是部分退款且当前状态是退款中,保持退款中状态
|
||
// 这样可以区分部分退款和全额退款的订单
|
||
updates["status"] = model.OrderStatusReturning
|
||
logger.Info("保持订单状态为退款中",
|
||
"orderID", order.ID,
|
||
"totalAmount", order.TotalAmount,
|
||
"totalRefunded", totalRefunded,
|
||
"refundID", refund.ID)
|
||
}
|
||
|
||
err = s.orderRepo.UpdateByID(order.ID, updates)
|
||
if err != nil {
|
||
logger.Error("更新订单退款信息失败", "error", err, "orderID", order.ID)
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// createRefundLog 创建退款日志
|
||
func (s *RefundService) createRefundLog(refundID uint, action string, statusFrom, statusTo *int, remark string, operatorID *uint) error {
|
||
log := &model.RefundLog{
|
||
RefundID: refundID,
|
||
Action: action,
|
||
StatusFrom: statusFrom,
|
||
StatusTo: statusTo,
|
||
OperatorType: "admin",
|
||
OperatorID: operatorID,
|
||
Remark: remark,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
return s.refundRepo.CreateLog(log)
|
||
}
|
||
|
||
// GetPendingRefunds 获取待处理的退款申请(管理员)
|
||
func (s *RefundService) GetPendingRefunds(ctx context.Context, page, pageSize int) ([]*model.Refund, int64, error) {
|
||
logger.Info("获取待处理退款申请", "page", page, "pageSize", pageSize)
|
||
|
||
refunds, total, err := s.refundRepo.GetPendingRefunds(page, pageSize)
|
||
if err != nil {
|
||
logger.Error("获取待处理退款申请失败", "error", err)
|
||
return nil, 0, err
|
||
}
|
||
|
||
// 转换为指针切片
|
||
result := make([]*model.Refund, len(refunds))
|
||
for i := range refunds {
|
||
result[i] = &refunds[i]
|
||
}
|
||
|
||
return result, total, nil
|
||
}
|
||
|
||
// GetAllRefunds 获取所有退款记录(管理员)
|
||
func (s *RefundService) GetAllRefunds(ctx context.Context, page, pageSize int, status, userID string) ([]*model.Refund, int64, error) {
|
||
logger.Info("获取所有退款记录", "page", page, "pageSize", pageSize, "status", status, "userID", userID)
|
||
|
||
// 构建查询条件
|
||
conditions := make(map[string]interface{})
|
||
if status != "" {
|
||
conditions["status"] = status
|
||
}
|
||
if userID != "" {
|
||
conditions["user_id"] = userID
|
||
}
|
||
|
||
refunds, total, err := s.refundRepo.GetAllRefunds(page, pageSize, conditions)
|
||
if err != nil {
|
||
logger.Error("获取所有退款记录失败", "error", err)
|
||
return nil, 0, err
|
||
}
|
||
|
||
// 转换为指针切片
|
||
result := make([]*model.Refund, len(refunds))
|
||
for i := range refunds {
|
||
result[i] = &refunds[i]
|
||
}
|
||
|
||
return result, total, nil
|
||
}
|
||
|
||
// GetRefundLogs 获取退款日志(管理员)
|
||
func (s *RefundService) GetRefundLogs(ctx context.Context, refundID uint) ([]model.RefundLog, error) {
|
||
logger.Info("获取退款日志", "refundID", refundID)
|
||
|
||
logs, err := s.refundRepo.GetRefundLogsByRefundID(refundID)
|
||
if err != nil {
|
||
logger.Error("获取退款日志失败", "error", err, "refundID", refundID)
|
||
return nil, err
|
||
}
|
||
|
||
return logs, nil
|
||
}
|
||
|
||
// GetRefundDetailForAdmin 获取退款详情(管理员专用)
|
||
func (s *RefundService) GetRefundDetailForAdmin(ctx context.Context, refundID uint) (*model.Refund, error) {
|
||
logger.Info("管理员获取退款详情", "refundID", refundID)
|
||
|
||
refund, err := s.refundRepo.GetByID(refundID)
|
||
if err != nil {
|
||
logger.Error("获取退款详情失败", "error", err, "refundID", refundID)
|
||
return nil, fmt.Errorf("退款记录不存在")
|
||
}
|
||
|
||
return refund, nil
|
||
}
|
||
|
||
// GetRefundStatistics 获取退款统计数据
|
||
func (s *RefundService) GetRefundStatistics(ctx context.Context, startTime, endTime time.Time) (map[string]interface{}, error) {
|
||
logger.Info("获取退款统计数据", "startTime", startTime, "endTime", endTime)
|
||
|
||
stats, err := s.refundRepo.GetRefundStatistics(startTime, endTime)
|
||
if err != nil {
|
||
logger.Error("获取退款统计数据失败", "error", err)
|
||
return nil, err
|
||
}
|
||
|
||
// 转换数据格式以匹配前端期望的格式
|
||
result := map[string]interface{}{
|
||
"total_refunds": stats["total_count"],
|
||
"pending_refunds": stats["pending_count"],
|
||
"processing_refunds": stats["processing_count"],
|
||
"total_amount": stats["total_amount"],
|
||
"success_count": stats["success_count"],
|
||
"success_amount": stats["success_amount"],
|
||
"approved_count": stats["approved_count"],
|
||
"rejected_count": stats["rejected_count"],
|
||
"failed_count": stats["failed_count"],
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// 请求结构体
|
||
type CreateRefundRequest struct {
|
||
OrderID uint `json:"order_id" binding:"required"`
|
||
UserID uint `json:"user_id"` // 由后端从JWT token中获取,不需要前端提供
|
||
RefundAmount float64 `json:"refund_amount" binding:"required,gt=0"`
|
||
RefundReason string `json:"refund_reason" binding:"required,max=500"`
|
||
RefundType int `json:"refund_type" binding:"required,oneof=1 2"` // 1:仅退款 2:退货退款
|
||
} |