using Fake.Application; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.Marketing; using Oms.Application.Dtos.Orders; using Oms.Application.Shared; using Oms.Domain.OrdersAggregate.Entities; namespace Oms.Application.Services.Orders; public partial class OrderService { private const string CouponNotBetterThanLevelReason = "优惠不高于会员折扣"; /// /// 支付预览:按当前应收计算会员折/券互斥后的应付,并返回本单可选券。 /// /// 入参:订单 Id;couponInstanceId 为 null 自动推荐,0 不用券,大于 0 试算指定券。 /// 应付拆解、实际生效券与可选券列表;指定券不可用(未解锁/过期/线路不符等)时抛业务异常。 [HttpGet("/order/pay-preview")] public async Task GetPayPreviewAsync([FromQuery] OrderPayPreviewInput input) { var order = await GetOrderOrThrowAsync(input.Id); var fees = await OrderRepository.GetFeesByOrderIdsAsync([order.Id]); var receivableSubtotal = MarketingFeeHelper.SumReceivable(fees); var baseFreightAmount = MarketingFeeHelper.SumReceivableBaseFreight(fees); var marketingHelper = LazyServiceProvider.GetRequiredService(); var memberOnly = await marketingHelper.BuildBreakdownAsync( order.CustomerId, receivableSubtotal, baseFreightAmount, null, order.ConsigneeCountry, order.Id); var coupons = await marketingHelper.BuildAvailableCouponsAsync( order.CustomerId, baseFreightAmount, order.ConsigneeCountry, order.Id); foreach (var coupon in coupons.Where(x => x.CanUse && (x.EstimatedDiscount ?? 0M) <= memberOnly.LevelDiscountAmount)) { coupon.CanUse = false; coupon.UnavailableReason = CouponNotBetterThanLevelReason; } coupons = coupons .OrderByDescending(x => x.CanUse) .ThenByDescending(x => x.EstimatedDiscount ?? 0M) .ThenBy(x => x.ValidEndTime) .ToList(); var targetCouponId = input.CouponInstanceId switch { 0 => null, > 0 => input.CouponInstanceId, _ => ResolveRecommendedPayCouponId(order, coupons) }; var breakdown = targetCouponId.HasValue ? await marketingHelper.BuildBreakdownAsync( order.CustomerId, receivableSubtotal, baseFreightAmount, targetCouponId, order.ConsigneeCountry, order.Id) : memberOnly; return new OrderPayPreviewOutput { Id = order.Id, ReceivableSubtotal = breakdown.ReceivableSubtotal, BaseFreightAmount = breakdown.BaseFreightAmount, LevelDiscountAmount = breakdown.LevelDiscountAmount, CouponDiscountAmount = breakdown.CouponDiscountAmount, PayableAmount = breakdown.PayableAmount, MemberLevelName = breakdown.MemberLevelName, AppliedCouponInstanceId = breakdown.CouponInstanceId, AvailableCoupons = coupons }; } /// /// 计算支付应付:couponInstanceId 为 null 沿用订单已绑定券(不可用则不用券),0 不用券,大于 0 使用指定券。 /// private async Task BuildPayBreakdownAsync( Order order, IEnumerable fees, int? couponInstanceId) { var feeList = fees as IList ?? fees.ToList(); var receivableSubtotal = MarketingFeeHelper.SumReceivable(feeList); var baseFreightAmount = MarketingFeeHelper.SumReceivableBaseFreight(feeList); var marketingHelper = LazyServiceProvider.GetRequiredService(); Task Build(int? couponId) => marketingHelper.BuildBreakdownAsync( order.CustomerId, receivableSubtotal, baseFreightAmount, couponId, order.ConsigneeCountry, order.Id); if (couponInstanceId is > 0) { var breakdown = await Build(couponInstanceId); if (breakdown.CouponInstanceId != couponInstanceId) { throw new BusinessException("所选优惠券未达使用门槛或优惠不高于会员折扣,请重新选择"); } return breakdown; } if (couponInstanceId == 0 || order.CouponInstanceId is not > 0) { return await Build(null); } try { return await Build(order.CouponInstanceId); } catch (BusinessException) { return await Build(null); } } private static int? ResolveRecommendedPayCouponId(Order order, List coupons) { if (order.CouponInstanceId is > 0 && coupons.Any(x => x.CanUse && x.CouponInstanceId == order.CouponInstanceId.Value)) { return order.CouponInstanceId; } return coupons.FirstOrDefault(x => x.CanUse)?.CouponInstanceId; } }