All checks were successful
continuous-integration/drone/push Build is passing
950 lines
35 KiB
C#
950 lines
35 KiB
C#
using MiAssessment.Core.Interfaces;
|
||
using MiAssessment.Model.Data;
|
||
using MiAssessment.Model.Models.Common;
|
||
using MiAssessment.Model.Models.Order;
|
||
using MiAssessment.Model.Models.Payment;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace MiAssessment.Core.Services;
|
||
|
||
/// <summary>
|
||
/// 订单服务实现
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 提供订单模块的核心业务功能,包括:
|
||
/// - 订单列表查询
|
||
/// - 订单详情查询
|
||
/// - 订单创建
|
||
/// - 发起支付
|
||
/// - 支付结果查询
|
||
/// </remarks>
|
||
public class OrderService : IOrderService
|
||
{
|
||
private readonly MiAssessmentDbContext _dbContext;
|
||
private readonly ILogger<OrderService> _logger;
|
||
private readonly IWechatPayService _wechatPayService;
|
||
private readonly IConfigService _configService;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
public OrderService(
|
||
MiAssessmentDbContext dbContext,
|
||
ILogger<OrderService> logger,
|
||
IWechatPayService wechatPayService,
|
||
IConfigService configService)
|
||
{
|
||
_dbContext = dbContext;
|
||
_logger = logger;
|
||
_wechatPayService = wechatPayService;
|
||
_configService = configService;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<OrderItemDto>> GetListAsync(long userId, int page, int pageSize, int? orderType)
|
||
{
|
||
_logger.LogDebug("获取订单列表,userId: {UserId}, page: {Page}, pageSize: {PageSize}, orderType: {OrderType}",
|
||
userId, page, pageSize, orderType);
|
||
|
||
// 确保分页参数有效
|
||
if (page < 1) page = 1;
|
||
if (pageSize < 1) pageSize = 20;
|
||
if (pageSize > 100) pageSize = 100;
|
||
|
||
// 构建查询 - 用户数据隔离(Requirements 7.1)
|
||
// 只展示已支付或退款的订单,排除待支付(1)和已取消(6)(Requirements 9)
|
||
var query = _dbContext.Orders
|
||
.AsNoTracking()
|
||
.Where(o => o.UserId == userId && !o.IsDeleted && o.Status != 1 && o.Status != 6);
|
||
|
||
// 支持按订单类型筛选(Requirements 7.1)
|
||
if (orderType.HasValue)
|
||
{
|
||
query = query.Where(o => o.OrderType == orderType.Value);
|
||
}
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 分页查询,按创建时间降序排列
|
||
var orders = await query
|
||
.OrderByDescending(o => o.CreateTime)
|
||
.Skip((page - 1) * pageSize)
|
||
.Take(pageSize)
|
||
.Select(o => new OrderItemDto
|
||
{
|
||
Id = o.Id,
|
||
OrderNo = o.OrderNo,
|
||
OrderType = o.OrderType,
|
||
ProductName = o.ProductName,
|
||
Amount = o.Amount,
|
||
Status = o.Status,
|
||
StatusText = GetOrderStatusText(o.Status),
|
||
CreateTime = o.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||
AssessmentRecordId = null // 稍后填充
|
||
})
|
||
.ToListAsync();
|
||
|
||
// 获取测评订单关联的测评记录信息(ID + 状态)
|
||
if (orders.Any(o => o.OrderType == 1))
|
||
{
|
||
var orderIds = orders.Where(o => o.OrderType == 1).Select(o => o.Id).ToList();
|
||
var assessmentRecords = await _dbContext.AssessmentRecords
|
||
.AsNoTracking()
|
||
.Where(r => orderIds.Contains(r.OrderId) && !r.IsDeleted)
|
||
.Select(r => new { r.OrderId, r.Id, r.Status })
|
||
.ToListAsync();
|
||
|
||
foreach (var order in orders.Where(o => o.OrderType == 1))
|
||
{
|
||
var record = assessmentRecords.FirstOrDefault(r => r.OrderId == order.Id);
|
||
if (record != null)
|
||
{
|
||
order.AssessmentRecordId = record.Id;
|
||
order.AssessmentStatus = record.Status;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取规划订单关联的预约记录状态
|
||
if (orders.Any(o => o.OrderType == 2))
|
||
{
|
||
var bookingOrderIds = orders.Where(o => o.OrderType == 2).Select(o => o.Id).ToList();
|
||
var bookings = await _dbContext.PlannerBookings
|
||
.AsNoTracking()
|
||
.Where(b => bookingOrderIds.Contains(b.OrderId) && !b.IsDeleted)
|
||
.Select(b => new { b.OrderId, b.Status })
|
||
.ToListAsync();
|
||
|
||
foreach (var order in orders.Where(o => o.OrderType == 2))
|
||
{
|
||
var booking = bookings.FirstOrDefault(b => b.OrderId == order.Id);
|
||
if (booking != null)
|
||
{
|
||
order.BookingStatus = booking.Status;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 计算综合显示状态文本
|
||
foreach (var order in orders)
|
||
{
|
||
order.DisplayStatusText = GetDisplayStatusText(order.Status, order.AssessmentStatus, order.OrderType, order.BookingStatus);
|
||
}
|
||
|
||
_logger.LogDebug("获取到 {Count} 条订单记录,总数: {Total}", orders.Count, total);
|
||
|
||
return PagedResult<OrderItemDto>.Create(orders, total, page, pageSize);
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
public async Task<OrderDetailDto?> GetDetailAsync(long userId, long orderId)
|
||
{
|
||
_logger.LogDebug("获取订单详情,userId: {UserId}, orderId: {OrderId}", userId, orderId);
|
||
|
||
// 查询订单,验证归属当前用户(Requirements 7.2, 7.3)
|
||
var order = await _dbContext.Orders
|
||
.AsNoTracking()
|
||
.Where(o => o.Id == orderId && !o.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
// 订单不存在
|
||
if (order == null)
|
||
{
|
||
_logger.LogWarning("订单不存在,orderId: {OrderId}", orderId);
|
||
return null;
|
||
}
|
||
|
||
// 验证订单归属当前用户(Requirements 7.3)
|
||
if (order.UserId != userId)
|
||
{
|
||
_logger.LogWarning("订单不属于当前用户,userId: {UserId}, orderUserId: {OrderUserId}, orderId: {OrderId}",
|
||
userId, order.UserId, orderId);
|
||
return null;
|
||
}
|
||
|
||
// 构建订单详情
|
||
var orderDetail = new OrderDetailDto
|
||
{
|
||
Id = order.Id,
|
||
OrderNo = order.OrderNo,
|
||
OrderType = order.OrderType,
|
||
ProductName = order.ProductName,
|
||
Amount = order.Amount,
|
||
PayAmount = order.PayAmount,
|
||
Status = order.Status,
|
||
StatusText = GetOrderStatusText(order.Status),
|
||
CreateTime = order.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||
PayTime = order.PayTime?.ToString("yyyy-MM-dd HH:mm:ss")
|
||
};
|
||
|
||
// 根据订单类型获取关联信息(Requirements 7.2)
|
||
if (order.OrderType == 1)
|
||
{
|
||
// 测评订单 - 获取关联的测评记录
|
||
var assessmentRecord = await _dbContext.AssessmentRecords
|
||
.AsNoTracking()
|
||
.Where(r => r.OrderId == orderId && !r.IsDeleted)
|
||
.Join(
|
||
_dbContext.AssessmentTypes.AsNoTracking(),
|
||
r => r.AssessmentTypeId,
|
||
at => at.Id,
|
||
(r, at) => new { Record = r, AssessmentType = at }
|
||
)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (assessmentRecord != null)
|
||
{
|
||
orderDetail.AssessmentRecordId = assessmentRecord.Record.Id;
|
||
orderDetail.AssessmentInfo = new OrderAssessmentInfoDto
|
||
{
|
||
RecordId = assessmentRecord.Record.Id,
|
||
AssessmentName = assessmentRecord.AssessmentType.Name,
|
||
Status = assessmentRecord.Record.Status,
|
||
StatusText = GetAssessmentStatusText(assessmentRecord.Record.Status)
|
||
};
|
||
}
|
||
}
|
||
else if (order.OrderType == 2)
|
||
{
|
||
// 规划订单 - 获取关联的规划预约
|
||
var plannerBooking = await _dbContext.PlannerBookings
|
||
.AsNoTracking()
|
||
.Where(b => b.OrderId == orderId && !b.IsDeleted)
|
||
.Join(
|
||
_dbContext.Planners.AsNoTracking(),
|
||
b => b.PlannerId,
|
||
p => p.Id,
|
||
(b, p) => new { Booking = b, Planner = p }
|
||
)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (plannerBooking != null)
|
||
{
|
||
orderDetail.PlannerBookingId = plannerBooking.Booking.Id;
|
||
orderDetail.PlannerInfo = new OrderPlannerInfoDto
|
||
{
|
||
BookingId = plannerBooking.Booking.Id,
|
||
PlannerName = plannerBooking.Planner.Name,
|
||
Status = plannerBooking.Booking.Status,
|
||
StatusText = GetBookingStatusText(plannerBooking.Booking.Status)
|
||
};
|
||
}
|
||
}
|
||
|
||
_logger.LogDebug("获取订单详情成功,orderId: {OrderId}, orderType: {OrderType}", orderId, order.OrderType);
|
||
|
||
return orderDetail;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PayResultDto?> GetPayResultAsync(long userId, long orderId)
|
||
{
|
||
_logger.LogDebug("查询支付结果,userId: {UserId}, orderId: {OrderId}", userId, orderId);
|
||
|
||
// 查询订单,验证归属当前用户(Requirements 9.2)
|
||
var order = await _dbContext.Orders
|
||
.AsNoTracking()
|
||
.Where(o => o.Id == orderId && !o.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
// 订单不存在
|
||
if (order == null)
|
||
{
|
||
_logger.LogWarning("订单不存在,orderId: {OrderId}", orderId);
|
||
return null;
|
||
}
|
||
|
||
// 验证订单归属当前用户
|
||
if (order.UserId != userId)
|
||
{
|
||
_logger.LogWarning("订单不属于当前用户,userId: {UserId}, orderUserId: {OrderUserId}, orderId: {OrderId}",
|
||
userId, order.UserId, orderId);
|
||
return null;
|
||
}
|
||
|
||
// 构建支付结果
|
||
var payResult = new PayResultDto
|
||
{
|
||
IsPaid = order.Status >= 2 && order.Status != 6, // 已支付、已完成、退款中、已退款都算已支付,已取消不算
|
||
Status = order.Status,
|
||
AssessmentRecordId = null
|
||
};
|
||
|
||
// 如果是测评订单,获取关联的测评记录ID
|
||
if (order.OrderType == 1)
|
||
{
|
||
var assessmentRecord = await _dbContext.AssessmentRecords
|
||
.AsNoTracking()
|
||
.Where(r => r.OrderId == orderId && !r.IsDeleted)
|
||
.Select(r => r.Id)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (assessmentRecord > 0)
|
||
{
|
||
payResult.AssessmentRecordId = assessmentRecord;
|
||
}
|
||
}
|
||
|
||
_logger.LogDebug("查询支付结果成功,orderId: {OrderId}, isPaid: {IsPaid}, status: {Status}",
|
||
orderId, payResult.IsPaid, payResult.Status);
|
||
|
||
return payResult;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> HandlePaymentSuccessAsync(string orderNo, string transactionId)
|
||
{
|
||
_logger.LogInformation("处理支付成功回调,orderNo: {OrderNo}, transactionId: {TransactionId}", orderNo, transactionId);
|
||
|
||
var order = await _dbContext.Orders
|
||
.FirstOrDefaultAsync(o => o.OrderNo == orderNo && !o.IsDeleted);
|
||
|
||
if (order == null)
|
||
{
|
||
_logger.LogWarning("支付回调订单不存在,orderNo: {OrderNo}", orderNo);
|
||
return false;
|
||
}
|
||
|
||
// 幂等:已支付的订单不重复处理
|
||
if (order.Status >= 2)
|
||
{
|
||
_logger.LogInformation("订单已处理,跳过,orderNo: {OrderNo}, status: {Status}", orderNo, order.Status);
|
||
return true;
|
||
}
|
||
|
||
// 更新订单状态为已支付
|
||
order.Status = 2;
|
||
order.PayTime = DateTime.Now;
|
||
order.TransactionId = transactionId;
|
||
order.PayType = 1; // 微信支付
|
||
order.UpdateTime = DateTime.Now;
|
||
|
||
// 测评订单:更新关联的测评记录状态为待测评
|
||
if (order.OrderType == 1)
|
||
{
|
||
var record = await _dbContext.AssessmentRecords
|
||
.FirstOrDefaultAsync(r => r.OrderId == order.Id && !r.IsDeleted);
|
||
if (record != null)
|
||
{
|
||
record.Status = 1; // 待测评
|
||
record.UpdateTime = DateTime.Now;
|
||
}
|
||
}
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
// 佣金分配(Requirements 3.8.3)
|
||
try
|
||
{
|
||
await CreateCommissionsAsync(order);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 佣金分配失败不影响订单状态,记录日志后续补偿
|
||
_logger.LogError(ex, "佣金分配异常,orderId: {OrderId},需人工处理", order.Id);
|
||
}
|
||
|
||
_logger.LogInformation("订单支付状态更新成功,orderNo: {OrderNo}, orderId: {OrderId}", orderNo, order.Id);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 支付成功后创建佣金记录
|
||
/// 规则:
|
||
/// - 有直接上级和间接上级:直接上级30%,间接上级10%
|
||
/// - 只有直接上级无间接上级:直接上级40%
|
||
/// - 无上级:不产生佣金
|
||
/// </summary>
|
||
private async Task CreateCommissionsAsync(MiAssessment.Model.Entities.Order order)
|
||
{
|
||
if (order.PayAmount <= 0)
|
||
{
|
||
_logger.LogDebug("订单实付金额为0,跳过佣金分配,orderId: {OrderId}", order.Id);
|
||
return;
|
||
}
|
||
|
||
// 获取下单用户
|
||
var user = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(u => u.Id == order.UserId && !u.IsDeleted);
|
||
|
||
if (user == null || !user.ParentUserId.HasValue)
|
||
{
|
||
_logger.LogDebug("用户无上级,跳过佣金分配,userId: {UserId}", order.UserId);
|
||
return;
|
||
}
|
||
|
||
// 读取佣金比例配置
|
||
var directRateStr = await _configService.GetConfigValueAsync("commission_rate_direct");
|
||
var indirectRateStr = await _configService.GetConfigValueAsync("commission_rate_indirect");
|
||
var directRate = decimal.TryParse(directRateStr, out var dr) ? dr : 0.30m;
|
||
var indirectRate = decimal.TryParse(indirectRateStr, out var ir) ? ir : 0.10m;
|
||
|
||
// 查找直接上级
|
||
var parentUser = await _dbContext.Users
|
||
.FirstOrDefaultAsync(u => u.Id == user.ParentUserId.Value && !u.IsDeleted);
|
||
|
||
if (parentUser == null)
|
||
{
|
||
_logger.LogDebug("直接上级用户不存在,跳过佣金分配,parentUserId: {ParentUserId}", user.ParentUserId);
|
||
return;
|
||
}
|
||
|
||
// 查找间接上级(上上级)
|
||
MiAssessment.Model.Entities.User? grandParentUser = null;
|
||
if (parentUser.ParentUserId.HasValue)
|
||
{
|
||
grandParentUser = await _dbContext.Users
|
||
.FirstOrDefaultAsync(u => u.Id == parentUser.ParentUserId.Value && !u.IsDeleted);
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
|
||
if (grandParentUser != null)
|
||
{
|
||
// 有间接上级:直接上级30%,间接上级10%
|
||
var directCommission = Math.Round(order.PayAmount * directRate, 2);
|
||
var indirectCommission = Math.Round(order.PayAmount * indirectRate, 2);
|
||
|
||
// 直接上级佣金
|
||
_dbContext.Commissions.Add(new MiAssessment.Model.Entities.Commission
|
||
{
|
||
UserId = parentUser.Id,
|
||
FromUserId = user.Id,
|
||
OrderId = order.Id,
|
||
OrderAmount = order.PayAmount,
|
||
CommissionRate = directRate * 100,
|
||
CommissionAmount = directCommission,
|
||
Level = 1,
|
||
Status = 1,
|
||
CreateTime = now,
|
||
UpdateTime = now
|
||
});
|
||
parentUser.Balance += directCommission;
|
||
parentUser.TotalIncome += directCommission;
|
||
parentUser.UpdateTime = now;
|
||
|
||
// 间接上级佣金
|
||
_dbContext.Commissions.Add(new MiAssessment.Model.Entities.Commission
|
||
{
|
||
UserId = grandParentUser.Id,
|
||
FromUserId = user.Id,
|
||
OrderId = order.Id,
|
||
OrderAmount = order.PayAmount,
|
||
CommissionRate = indirectRate * 100,
|
||
CommissionAmount = indirectCommission,
|
||
Level = 2,
|
||
Status = 1,
|
||
CreateTime = now,
|
||
UpdateTime = now
|
||
});
|
||
grandParentUser.Balance += indirectCommission;
|
||
grandParentUser.TotalIncome += indirectCommission;
|
||
grandParentUser.UpdateTime = now;
|
||
|
||
_logger.LogInformation("佣金分配完成:直接上级{ParentId}获得{DirectAmount},间接上级{GrandParentId}获得{IndirectAmount},orderId: {OrderId}",
|
||
parentUser.Id, directCommission, grandParentUser.Id, indirectCommission, order.Id);
|
||
}
|
||
else
|
||
{
|
||
// 无间接上级:直接上级获得 directRate + indirectRate
|
||
var totalRate = directRate + indirectRate;
|
||
var commission = Math.Round(order.PayAmount * totalRate, 2);
|
||
|
||
_dbContext.Commissions.Add(new MiAssessment.Model.Entities.Commission
|
||
{
|
||
UserId = parentUser.Id,
|
||
FromUserId = user.Id,
|
||
OrderId = order.Id,
|
||
OrderAmount = order.PayAmount,
|
||
CommissionRate = totalRate * 100,
|
||
CommissionAmount = commission,
|
||
Level = 1,
|
||
Status = 1,
|
||
CreateTime = now,
|
||
UpdateTime = now
|
||
});
|
||
parentUser.Balance += commission;
|
||
parentUser.TotalIncome += commission;
|
||
parentUser.UpdateTime = now;
|
||
|
||
_logger.LogInformation("佣金分配完成:直接上级{ParentId}获得{Amount}(无间接上级),orderId: {OrderId}",
|
||
parentUser.Id, commission, order.Id);
|
||
}
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// 创建订单
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 使用数据库事务确保多表操作的数据一致性(Requirements 8.4, 8.5)
|
||
/// - 测评订单:创建订单记录和测评记录(Requirements 8.1)
|
||
/// - 规划订单:创建订单记录和规划预约记录(Requirements 8.2)
|
||
/// - 邀请码抵扣:金额设为0,标记邀请码已使用(Requirements 8.3)
|
||
/// </remarks>
|
||
public async Task<CreateOrderResponse> CreateAsync(long userId, CreateOrderRequest request)
|
||
{
|
||
_logger.LogDebug("创建订单,userId: {UserId}, orderType: {OrderType}, productId: {ProductId}, inviteCodeId: {InviteCodeId}",
|
||
userId, request.OrderType, request.ProductId, request.InviteCodeId);
|
||
|
||
// 验证订单类型
|
||
if (request.OrderType != 1 && request.OrderType != 2)
|
||
{
|
||
throw new ArgumentException("无效的订单类型");
|
||
}
|
||
|
||
// 获取产品信息和价格
|
||
string productName;
|
||
decimal amount;
|
||
|
||
if (request.OrderType == 1)
|
||
{
|
||
// 测评订单 - 获取测评类型信息
|
||
var assessmentType = await _dbContext.AssessmentTypes
|
||
.AsNoTracking()
|
||
.Where(at => at.Id == request.ProductId && !at.IsDeleted && at.Status == 1)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (assessmentType == null)
|
||
{
|
||
throw new ArgumentException("测评类型不存在或已下线");
|
||
}
|
||
|
||
productName = assessmentType.Name;
|
||
amount = assessmentType.Price;
|
||
|
||
// 验证测评信息
|
||
if (request.AssessmentInfo == null)
|
||
{
|
||
throw new ArgumentException("测评订单必须提供测评信息");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 规划订单 - 获取规划师信息
|
||
var planner = await _dbContext.Planners
|
||
.AsNoTracking()
|
||
.Where(p => p.Id == request.ProductId && !p.IsDeleted && p.Status == 1)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (planner == null)
|
||
{
|
||
throw new ArgumentException("规划师不存在或已下线");
|
||
}
|
||
|
||
productName = planner.Name + " - 学业规划";
|
||
amount = planner.Price;
|
||
|
||
// 验证规划预约信息
|
||
if (request.PlannerInfo == null)
|
||
{
|
||
throw new ArgumentException("规划订单必须提供预约信息");
|
||
}
|
||
}
|
||
|
||
// 计算实付金额
|
||
decimal payAmount = amount;
|
||
bool needPay = true;
|
||
int? payType = null;
|
||
|
||
// 如果使用邀请码,验证并设置金额为0(Requirements 8.3)
|
||
if (request.InviteCodeId.HasValue)
|
||
{
|
||
var inviteCode = await _dbContext.InviteCodes
|
||
.Where(ic => ic.Id == request.InviteCodeId.Value && !ic.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (inviteCode == null)
|
||
{
|
||
throw new ArgumentException("邀请码不存在");
|
||
}
|
||
|
||
if (inviteCode.Status == 3)
|
||
{
|
||
throw new ArgumentException("邀请码已被使用");
|
||
}
|
||
|
||
if (inviteCode.Status != 2)
|
||
{
|
||
throw new ArgumentException("邀请码状态无效");
|
||
}
|
||
|
||
// 使用邀请码,金额设为0
|
||
payAmount = 0;
|
||
needPay = false;
|
||
payType = 2; // 邀请码支付
|
||
}
|
||
|
||
// 生成订单编号
|
||
var orderNo = GenerateOrderNo();
|
||
|
||
// 使用事务确保数据一致性(Requirements 8.4, 8.5)
|
||
using var transaction = await _dbContext.Database.BeginTransactionAsync();
|
||
try
|
||
{
|
||
var now = DateTime.Now;
|
||
|
||
// 创建订单记录
|
||
var order = new MiAssessment.Model.Entities.Order
|
||
{
|
||
OrderNo = orderNo,
|
||
UserId = userId,
|
||
OrderType = request.OrderType,
|
||
ProductId = request.ProductId,
|
||
ProductName = productName,
|
||
Amount = amount,
|
||
PayAmount = payAmount,
|
||
PayType = payType,
|
||
InviteCodeId = request.InviteCodeId,
|
||
Status = needPay ? 1 : 2, // 需要支付则待支付,否则已支付
|
||
PayTime = needPay ? null : now,
|
||
CreateTime = now,
|
||
UpdateTime = now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
_dbContext.Orders.Add(order);
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
long? assessmentRecordId = null;
|
||
|
||
if (request.OrderType == 1)
|
||
{
|
||
// 测评订单 - 创建测评记录(Requirements 8.1)
|
||
// 需要支付的订单,测评记录初始状态为0(待支付),支付成功后由回调设为1(待测评)
|
||
// 免支付订单(邀请码)直接设为1(待测评)
|
||
var assessmentRecord = new MiAssessment.Model.Entities.AssessmentRecord
|
||
{
|
||
UserId = userId,
|
||
OrderId = order.Id,
|
||
AssessmentTypeId = request.ProductId,
|
||
Name = request.AssessmentInfo!.Name,
|
||
Phone = request.AssessmentInfo.Phone,
|
||
Gender = request.AssessmentInfo.Gender,
|
||
Age = request.AssessmentInfo.Age,
|
||
EducationStage = request.AssessmentInfo.EducationStage,
|
||
Province = request.AssessmentInfo.Province,
|
||
City = request.AssessmentInfo.City,
|
||
District = request.AssessmentInfo.District,
|
||
Status = needPay ? 0 : 1, // 需要支付则待支付(0),免支付则待测评(1)
|
||
CreateTime = now,
|
||
UpdateTime = now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
_dbContext.AssessmentRecords.Add(assessmentRecord);
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
assessmentRecordId = assessmentRecord.Id;
|
||
}
|
||
else
|
||
{
|
||
// 规划订单 - 创建规划预约记录(Requirements 8.2)
|
||
// 解析预约日期时间
|
||
DateTime bookingDate = DateTime.Today;
|
||
string bookingTime = "待确认";
|
||
if (!string.IsNullOrEmpty(request.PlannerInfo!.BookDateTime))
|
||
{
|
||
if (DateTime.TryParse(request.PlannerInfo.BookDateTime, out var parsedDateTime))
|
||
{
|
||
bookingDate = parsedDateTime.Date;
|
||
bookingTime = parsedDateTime.ToString("HH:mm");
|
||
}
|
||
}
|
||
|
||
var plannerBooking = new MiAssessment.Model.Entities.PlannerBooking
|
||
{
|
||
UserId = userId,
|
||
OrderId = order.Id,
|
||
PlannerId = request.ProductId,
|
||
BookingDate = bookingDate,
|
||
BookingTime = bookingTime,
|
||
Name = request.PlannerInfo.Name,
|
||
Phone = request.PlannerInfo.Phone,
|
||
Gender = 0,
|
||
Grade = request.PlannerInfo.Grade,
|
||
MajorName = request.PlannerInfo.MajorName,
|
||
ScoreChinese = request.PlannerInfo.ScoreChinese,
|
||
ScoreMath = request.PlannerInfo.ScoreMath,
|
||
ScoreEnglish = request.PlannerInfo.ScoreEnglish,
|
||
ScorePhysics = request.PlannerInfo.ScorePhysics,
|
||
ScoreChemistry = request.PlannerInfo.ScoreChemistry,
|
||
ScoreBiology = request.PlannerInfo.ScoreBiology,
|
||
ScoreGeography = request.PlannerInfo.ScoreGeography,
|
||
ScorePolitics = request.PlannerInfo.ScorePolitics,
|
||
FamilyAtmosphere = request.PlannerInfo.FamilyAtmosphere,
|
||
Expectation = request.PlannerInfo.Expectation,
|
||
Status = 1, // 待确认
|
||
CreateTime = now,
|
||
UpdateTime = now,
|
||
IsDeleted = false
|
||
};
|
||
|
||
_dbContext.PlannerBookings.Add(plannerBooking);
|
||
await _dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
// 如果使用邀请码,标记邀请码已使用(Requirements 8.3)
|
||
if (request.InviteCodeId.HasValue)
|
||
{
|
||
var inviteCode = await _dbContext.InviteCodes
|
||
.Where(ic => ic.Id == request.InviteCodeId.Value)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (inviteCode != null)
|
||
{
|
||
inviteCode.Status = 3; // 已使用
|
||
inviteCode.UseUserId = userId;
|
||
inviteCode.UseOrderId = order.Id;
|
||
inviteCode.UseTime = now;
|
||
inviteCode.UpdateTime = now;
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
}
|
||
}
|
||
|
||
// 提交事务
|
||
await transaction.CommitAsync();
|
||
|
||
_logger.LogInformation("订单创建成功,orderId: {OrderId}, orderNo: {OrderNo}, orderType: {OrderType}, payAmount: {PayAmount}, needPay: {NeedPay}",
|
||
order.Id, orderNo, request.OrderType, payAmount, needPay);
|
||
|
||
return new CreateOrderResponse
|
||
{
|
||
OrderId = order.Id,
|
||
OrderNo = orderNo,
|
||
PayAmount = payAmount,
|
||
NeedPay = needPay,
|
||
AssessmentRecordId = assessmentRecordId
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 回滚事务(Requirements 8.4)
|
||
await transaction.RollbackAsync();
|
||
_logger.LogError(ex, "创建订单失败,userId: {UserId}, orderType: {OrderType}, productId: {ProductId}",
|
||
userId, request.OrderType, request.ProductId);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成唯一订单编号
|
||
/// </summary>
|
||
/// <returns>订单编号,格式:yyyyMMddHHmmss + 6位随机数</returns>
|
||
private static string GenerateOrderNo()
|
||
{
|
||
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
|
||
var random = new Random().Next(100000, 999999);
|
||
return $"{timestamp}{random}";
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// 发起支付
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 调用微信支付统一下单接口,返回小程序调起支付所需的参数。
|
||
/// - 验证订单存在且归属当前用户
|
||
/// - 验证订单状态为待支付(Status=1)
|
||
/// - 如果订单已支付(Status>=2)或已取消(Status=6),拒绝支付请求
|
||
/// - 调用IWechatPayService发起微信支付
|
||
/// Requirements: 9.1, 9.3
|
||
/// </remarks>
|
||
public async Task<PayResponse> PayAsync(long userId, long orderId)
|
||
{
|
||
_logger.LogDebug("发起支付,userId: {UserId}, orderId: {OrderId}", userId, orderId);
|
||
|
||
// 1. 查询订单
|
||
var order = await _dbContext.Orders
|
||
.AsNoTracking()
|
||
.Where(o => o.Id == orderId && !o.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
// 2. 验证订单存在
|
||
if (order == null)
|
||
{
|
||
_logger.LogWarning("订单不存在,orderId: {OrderId}", orderId);
|
||
throw new ArgumentException("订单不存在");
|
||
}
|
||
|
||
// 3. 验证订单归属当前用户
|
||
if (order.UserId != userId)
|
||
{
|
||
_logger.LogWarning("订单不属于当前用户,userId: {UserId}, orderUserId: {OrderUserId}, orderId: {OrderId}",
|
||
userId, order.UserId, orderId);
|
||
throw new ArgumentException("无权限操作此订单");
|
||
}
|
||
|
||
// 4. 验证订单状态(Requirements 9.3)
|
||
// 订单已支付(Status>=2 且不是已取消)
|
||
if (order.Status >= 2 && order.Status != 6)
|
||
{
|
||
_logger.LogWarning("订单已支付,不能重复支付,orderId: {OrderId}, status: {Status}", orderId, order.Status);
|
||
throw new ArgumentException("订单已支付,不能重复支付");
|
||
}
|
||
|
||
// 订单已取消
|
||
if (order.Status == 6)
|
||
{
|
||
_logger.LogWarning("订单已取消,不能支付,orderId: {OrderId}", orderId);
|
||
throw new ArgumentException("订单已取消,不能支付");
|
||
}
|
||
|
||
// 订单状态不是待支付
|
||
if (order.Status != 1)
|
||
{
|
||
_logger.LogWarning("订单状态异常,不能支付,orderId: {OrderId}, status: {Status}", orderId, order.Status);
|
||
throw new ArgumentException("订单状态异常,不能支付");
|
||
}
|
||
|
||
// 5. 获取用户信息
|
||
var user = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Id == userId)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (user == null)
|
||
{
|
||
_logger.LogWarning("用户不存在,userId: {UserId}", userId);
|
||
throw new ArgumentException("用户不存在");
|
||
}
|
||
|
||
// 6. 构建微信支付请求(Requirements 9.1)
|
||
var payRequest = new WechatPayRequest
|
||
{
|
||
OrderNo = order.OrderNo,
|
||
Amount = order.PayAmount,
|
||
Body = order.ProductName,
|
||
Attach = order.OrderType == 1 ? "assessment" : "planner", // 根据订单类型设置附加数据
|
||
OpenId = user.OpenId ?? string.Empty,
|
||
UserId = (int)userId
|
||
};
|
||
|
||
_logger.LogDebug("调用微信支付,orderNo: {OrderNo}, amount: {Amount}, body: {Body}",
|
||
payRequest.OrderNo, payRequest.Amount, payRequest.Body);
|
||
|
||
// 7. 调用微信支付服务
|
||
var payResult = await _wechatPayService.CreatePaymentAsync(payRequest);
|
||
|
||
// 8. 检查支付结果
|
||
if (payResult.Status != 1 || payResult.Data == null)
|
||
{
|
||
_logger.LogWarning("微信支付下单失败,orderId: {OrderId}, msg: {Msg}", orderId, payResult.Msg);
|
||
throw new InvalidOperationException(payResult.Msg ?? "支付下单失败,请稍后重试");
|
||
}
|
||
|
||
_logger.LogInformation("微信支付下单成功,orderId: {OrderId}, orderNo: {OrderNo}", orderId, order.OrderNo);
|
||
|
||
// 9. 返回支付参数
|
||
return new PayResponse
|
||
{
|
||
TimeStamp = payResult.Data.TimeStamp,
|
||
NonceStr = payResult.Data.NonceStr,
|
||
Package = payResult.Data.Package,
|
||
SignType = payResult.Data.SignType,
|
||
PaySign = payResult.Data.PaySign
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取订单状态文本
|
||
/// </summary>
|
||
/// <param name="status">订单状态值</param>
|
||
/// <returns>状态文本</returns>
|
||
private static string GetOrderStatusText(int status)
|
||
{
|
||
return status switch
|
||
{
|
||
1 => "待支付",
|
||
2 => "已支付",
|
||
3 => "已完成",
|
||
4 => "退款中",
|
||
5 => "已退款",
|
||
6 => "已取消",
|
||
_ => "未知"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取综合显示状态文本(结合订单状态和测评记录状态)
|
||
/// </summary>
|
||
/// <param name="orderStatus">订单状态</param>
|
||
/// <param name="assessmentStatus">测评记录状态(可为null)</param>
|
||
/// <returns>用于前端展示的状态文本</returns>
|
||
private static string GetDisplayStatusText(int orderStatus, int? assessmentStatus, int orderType = 1, int? bookingStatus = null)
|
||
{
|
||
// 退款相关状态优先
|
||
if (orderStatus == 4) return "退款中";
|
||
if (orderStatus == 5) return "已退款";
|
||
if (orderStatus == 6) return "已取消";
|
||
if (orderStatus == 1) return "待支付";
|
||
|
||
// 规划订单,根据预约状态显示
|
||
if (orderType == 2 && bookingStatus.HasValue)
|
||
{
|
||
return GetBookingStatusText(bookingStatus.Value);
|
||
}
|
||
|
||
// 测评订单,根据测评记录状态显示
|
||
if (assessmentStatus.HasValue)
|
||
{
|
||
return assessmentStatus.Value switch
|
||
{
|
||
1 => "待测评",
|
||
2 => "测评中",
|
||
3 => "测评生成中",
|
||
4 => "已测评",
|
||
5 => "生成失败",
|
||
6 => "报告生成中",
|
||
_ => GetOrderStatusText(orderStatus)
|
||
};
|
||
}
|
||
|
||
return GetOrderStatusText(orderStatus);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取测评状态文本
|
||
/// </summary>
|
||
/// <param name="status">测评状态值</param>
|
||
/// <returns>状态文本</returns>
|
||
private static string GetAssessmentStatusText(int status)
|
||
{
|
||
return status switch
|
||
{
|
||
0 => "待支付",
|
||
1 => "待测评",
|
||
2 => "测评中",
|
||
3 => "生成中",
|
||
4 => "已完成",
|
||
5 => "生成失败",
|
||
6 => "报告生成中",
|
||
_ => "未知"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取预约状态文本
|
||
/// </summary>
|
||
/// <param name="status">预约状态值</param>
|
||
/// <returns>状态文本</returns>
|
||
private static string GetBookingStatusText(int status)
|
||
{
|
||
return status switch
|
||
{
|
||
1 => "待联系",
|
||
2 => "联系中",
|
||
3 => "已完成",
|
||
4 => "已取消",
|
||
_ => "未知"
|
||
};
|
||
}
|
||
}
|