修改bug
This commit is contained in:
parent
5dbcf3ce5e
commit
d3c3a847b0
@ -177,6 +177,11 @@ public class CreateOrUpdateMemberInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? IdCardImageUrl { get; set; }
|
public string? IdCardImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份证背面图片URL
|
||||||
|
/// </summary>
|
||||||
|
public string? IdCardBackImageUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 护照图片URL
|
/// 护照图片URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -273,6 +278,11 @@ public class AppUserOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? IdCardImageUrl { get; set; }
|
public string? IdCardImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份证背面图片URL
|
||||||
|
/// </summary>
|
||||||
|
public string? IdCardBackImageUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 护照图片URL
|
/// 护照图片URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -102,6 +102,11 @@ public class MyAuthInfoDto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? IdCardImageUrl { get; set; }
|
public string? IdCardImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份证背面图片(认证类型1时使用)
|
||||||
|
/// </summary>
|
||||||
|
public string? IdCardBackImageUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 护照图片(认证类型2时使用)
|
/// 护照图片(认证类型2时使用)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -171,6 +176,12 @@ public class SubmitAuthInput
|
|||||||
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
||||||
public string? IdCardImageUrl { get; set; }
|
public string? IdCardImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份证背面图片(认证类型1时必填)
|
||||||
|
/// </summary>
|
||||||
|
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
||||||
|
public string? IdCardBackImageUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 护照图片(认证类型2时必填)
|
/// 护照图片(认证类型2时必填)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -87,25 +87,7 @@ public class MyController : IDynamicApiController
|
|||||||
var pointsBalance = (int)(pointsIn - pointsOut);
|
var pointsBalance = (int)(pointsIn - pointsOut);
|
||||||
|
|
||||||
// 认证状态: 0-待提交, 1-已认证, 2-审核中, 3-失败
|
// 认证状态: 0-待提交, 1-已认证, 2-审核中, 3-失败
|
||||||
int authStatus = 0;
|
int authStatus = (int)user.AuditStatus;
|
||||||
bool isAuthInfoSubmitted = !string.IsNullOrEmpty(user.Name) && !string.IsNullOrEmpty(user.IdCardNumber);
|
|
||||||
|
|
||||||
if (!isAuthInfoSubmitted)
|
|
||||||
{
|
|
||||||
authStatus = 0; // 未认证
|
|
||||||
}
|
|
||||||
else if (user.AuditStatus == AuditStatus.Pass)
|
|
||||||
{
|
|
||||||
authStatus = 1; // 已认证
|
|
||||||
}
|
|
||||||
else if (user.AuditStatus == AuditStatus.PendingAudit)
|
|
||||||
{
|
|
||||||
authStatus = 2; // 审核中
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
authStatus = 3; // 失败/其他
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取单位名称
|
// 获取单位名称
|
||||||
string? merchantName = null;
|
string? merchantName = null;
|
||||||
@ -155,6 +137,7 @@ public class MyController : IDynamicApiController
|
|||||||
BirthDate = user.BirthDate,
|
BirthDate = user.BirthDate,
|
||||||
IdCardNumber = user.IdCardNumber,
|
IdCardNumber = user.IdCardNumber,
|
||||||
IdCardImageUrl = user.IdCardImageUrl,
|
IdCardImageUrl = user.IdCardImageUrl,
|
||||||
|
IdCardBackImageUrl = user.IdCardBackImageUrl,
|
||||||
PassportPhotoUrl = user.PassportPhotoUrl,
|
PassportPhotoUrl = user.PassportPhotoUrl,
|
||||||
AuditStatus = user.AuditStatus,
|
AuditStatus = user.AuditStatus,
|
||||||
AuditRemark = user.AuditRemark
|
AuditRemark = user.AuditRemark
|
||||||
@ -180,6 +163,7 @@ public class MyController : IDynamicApiController
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空");
|
if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空");
|
||||||
if (string.IsNullOrEmpty(input.IdCardImageUrl)) throw Oops.Bah("身份证正面图片不能为空");
|
if (string.IsNullOrEmpty(input.IdCardImageUrl)) throw Oops.Bah("身份证正面图片不能为空");
|
||||||
|
if (string.IsNullOrEmpty(input.IdCardBackImageUrl)) throw Oops.Bah("身份证背面图片不能为空");
|
||||||
}
|
}
|
||||||
else if (input.AuthType == 2)
|
else if (input.AuthType == 2)
|
||||||
{
|
{
|
||||||
@ -201,12 +185,14 @@ public class MyController : IDynamicApiController
|
|||||||
{
|
{
|
||||||
user.IdCardNumber = input.IdCardNumber;
|
user.IdCardNumber = input.IdCardNumber;
|
||||||
user.IdCardImageUrl = input.IdCardImageUrl;
|
user.IdCardImageUrl = input.IdCardImageUrl;
|
||||||
|
user.IdCardBackImageUrl = input.IdCardBackImageUrl;
|
||||||
user.PassportPhotoUrl = null; // 清空护照图片
|
user.PassportPhotoUrl = null; // 清空护照图片
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
user.PassportPhotoUrl = input.PassportPhotoUrl;
|
user.PassportPhotoUrl = input.PassportPhotoUrl;
|
||||||
user.IdCardImageUrl = null; // 清空身份证图片
|
user.IdCardImageUrl = null; // 清空身份证图片
|
||||||
|
user.IdCardBackImageUrl = null; // 清空身份证背面图片
|
||||||
user.Gender = (Gender)input.Gender.Value;
|
user.Gender = (Gender)input.Gender.Value;
|
||||||
user.BirthDate = input.BirthDate.Value;
|
user.BirthDate = input.BirthDate.Value;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,98 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace YD_Event.Application.Event;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付超时处理后台服务
|
||||||
|
/// </summary>
|
||||||
|
public class PaymentExpirationBackgroundService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ILogger<PaymentExpirationBackgroundService> _logger;
|
||||||
|
private readonly PeriodicTimer _timer;
|
||||||
|
|
||||||
|
public PaymentExpirationBackgroundService(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
ILogger<PaymentExpirationBackgroundService> logger)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_logger = logger;
|
||||||
|
_timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("支付超时处理后台服务已启动");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (await _timer.WaitForNextTickAsync(stoppingToken))
|
||||||
|
{
|
||||||
|
await ProcessExpiredPaymentsAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("支付超时处理后台服务已停止");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessExpiredPaymentsAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("开始处理超时未支付订单");
|
||||||
|
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var registrationRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventRegistration>>();
|
||||||
|
var orderRepository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<YD_Event.Core.Entities.Order>>();
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var expirationTime = now.AddMinutes(-30);
|
||||||
|
|
||||||
|
var expiredRegistrations = await registrationRepository.AsQueryable()
|
||||||
|
.Where(x => x.PaymentMethod == 2
|
||||||
|
&& x.PaymentStatus == 0
|
||||||
|
&& x.CreatedTime <= expirationTime
|
||||||
|
&& !x.DeleteMark)
|
||||||
|
.ToListAsync(stoppingToken);
|
||||||
|
|
||||||
|
if (expiredRegistrations.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("发现 {Count} 个超时未支付的线上订单", expiredRegistrations.Count);
|
||||||
|
|
||||||
|
var idsToCancel = expiredRegistrations.Select(x => x.Id).ToList();
|
||||||
|
var orderNos = expiredRegistrations.Where(x => !string.IsNullOrEmpty(x.OrderNo)).Select(x => x.OrderNo).ToList();
|
||||||
|
|
||||||
|
await registrationRepository.UpdateAsync(x => new EventRegistration
|
||||||
|
{
|
||||||
|
PaymentStatus = 4,
|
||||||
|
UpdatedTime = now
|
||||||
|
}, x => idsToCancel.Contains(x.Id));
|
||||||
|
|
||||||
|
if (orderNos.Any())
|
||||||
|
{
|
||||||
|
await orderRepository.UpdateAsync(x => new YD_Event.Core.Entities.Order
|
||||||
|
{
|
||||||
|
OrderStatus = OrderStatus.Cancelled,
|
||||||
|
UpdatedTime = now
|
||||||
|
}, x => orderNos.Contains(x.OrderNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("已成功取消 {Count} 个超时未支付订单", expiredRegistrations.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "处理超时未支付订单时发生错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
_timer.Dispose();
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -37,6 +37,7 @@ public class UnifiedRegistrationService : ITransient
|
|||||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
||||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _couponRepository;
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _couponRepository;
|
||||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> _userCouponRepository;
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> _userCouponRepository;
|
||||||
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> _redemptionCodeRepository;
|
||||||
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
||||||
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
|
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
|
||||||
private readonly ISqlSugarClient _db;
|
private readonly ISqlSugarClient _db;
|
||||||
@ -66,6 +67,7 @@ public class UnifiedRegistrationService : ITransient
|
|||||||
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
||||||
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> couponRepository,
|
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> couponRepository,
|
||||||
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
|
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
|
||||||
|
ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> redemptionCodeRepository,
|
||||||
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
||||||
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
|
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
|
||||||
ISqlSugarClient db,
|
ISqlSugarClient db,
|
||||||
@ -94,6 +96,7 @@ public class UnifiedRegistrationService : ITransient
|
|||||||
_merchantRepository = merchantRepository;
|
_merchantRepository = merchantRepository;
|
||||||
_couponRepository = couponRepository;
|
_couponRepository = couponRepository;
|
||||||
_userCouponRepository = userCouponRepository;
|
_userCouponRepository = userCouponRepository;
|
||||||
|
_redemptionCodeRepository = redemptionCodeRepository;
|
||||||
_paymentRepository = paymentRepository;
|
_paymentRepository = paymentRepository;
|
||||||
_merchantCertificationRepository = merchantCertificationRepository;
|
_merchantCertificationRepository = merchantCertificationRepository;
|
||||||
_db = db;
|
_db = db;
|
||||||
@ -1512,4 +1515,137 @@ public class UnifiedRegistrationService : ITransient
|
|||||||
|
|
||||||
return payConfig;
|
return payConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一优惠券验证和使用方法
|
||||||
|
/// </summary>
|
||||||
|
[UnitOfWork]
|
||||||
|
public async Task<ValidateCouponOutput> ValidateAndUseCouponAsync(ValidateCouponInput input, long userId)
|
||||||
|
{
|
||||||
|
var result = new ValidateCouponOutput
|
||||||
|
{
|
||||||
|
IsValid = false,
|
||||||
|
InvalidReason = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查找兑换码
|
||||||
|
var redemptionCode = await _redemptionCodeRepository.GetFirstAsync(rc =>
|
||||||
|
rc.Code == input.CouponCode &&
|
||||||
|
rc.Status == 0 &&
|
||||||
|
!rc.DeleteMark);
|
||||||
|
|
||||||
|
if (redemptionCode == null)
|
||||||
|
{
|
||||||
|
result.InvalidReason = "兑换码不存在或已使用";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找优惠券
|
||||||
|
var coupon = await _couponRepository.GetFirstAsync(c =>
|
||||||
|
c.Id == redemptionCode.CouponId &&
|
||||||
|
c.Status == AvailabilityStatus.Enable &&
|
||||||
|
!c.DeleteMark);
|
||||||
|
|
||||||
|
if (coupon == null)
|
||||||
|
{
|
||||||
|
result.InvalidReason = "优惠券不存在或已失效";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查优惠券是否已过期
|
||||||
|
if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now)
|
||||||
|
{
|
||||||
|
result.InvalidReason = "优惠券已过期";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查优惠券是否已开始
|
||||||
|
if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now)
|
||||||
|
{
|
||||||
|
result.InvalidReason = "优惠券尚未生效";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查库存
|
||||||
|
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
|
||||||
|
{
|
||||||
|
result.InvalidReason = "优惠券已被领完";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查优惠券是否适用于该赛事
|
||||||
|
if (!string.IsNullOrEmpty(coupon.EventIds))
|
||||||
|
{
|
||||||
|
var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList();
|
||||||
|
if (!eventIds.Contains(input.EventId))
|
||||||
|
{
|
||||||
|
result.InvalidReason = "该优惠券不适用于当前赛事";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取
|
||||||
|
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
|
||||||
|
uc.CouponId == coupon.Id &&
|
||||||
|
uc.UserId == userId &&
|
||||||
|
uc.Status == 0 &&
|
||||||
|
!uc.DeleteMark);
|
||||||
|
|
||||||
|
// 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取)
|
||||||
|
if (userCoupon == null)
|
||||||
|
{
|
||||||
|
// 创建用户优惠券记录
|
||||||
|
userCoupon = new YD_Event.Core.Entities.UserCoupon
|
||||||
|
{
|
||||||
|
Id = _idGenerator.NewLong(),
|
||||||
|
UserId = userId,
|
||||||
|
CouponId = coupon.Id,
|
||||||
|
ClaimTime = DateTime.Now,
|
||||||
|
Status = 0,
|
||||||
|
RedemptionCodeId = redemptionCode.Id,
|
||||||
|
RedemptionCode = redemptionCode.Code,
|
||||||
|
CreatedUserId = userId,
|
||||||
|
CreatedTime = DateTime.Now,
|
||||||
|
DeleteMark = false
|
||||||
|
};
|
||||||
|
await _userCouponRepository.InsertAsync(userCoupon);
|
||||||
|
|
||||||
|
// 更新兑换码状态和关联
|
||||||
|
redemptionCode.Status = 1;
|
||||||
|
redemptionCode.UsedTime = DateTime.Now;
|
||||||
|
redemptionCode.UserCouponId = userCoupon.Id;
|
||||||
|
await _redemptionCodeRepository.UpdateAsync(redemptionCode);
|
||||||
|
|
||||||
|
// 更新优惠券已领取数量
|
||||||
|
coupon.ClaimedQuantity += 1;
|
||||||
|
await _couponRepository.UpdateAsync(coupon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算优惠金额
|
||||||
|
decimal discountAmount = 0;
|
||||||
|
decimal finalAmount = input.OriginalAmount;
|
||||||
|
|
||||||
|
// Type: 0-折扣, 1-定额
|
||||||
|
if (coupon.Type == 0) // 折扣
|
||||||
|
{
|
||||||
|
discountAmount = input.OriginalAmount * (1 - coupon.Value / 100);
|
||||||
|
}
|
||||||
|
else if (coupon.Type == 1) // 定额
|
||||||
|
{
|
||||||
|
discountAmount = Math.Min(coupon.Value, input.OriginalAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalAmount = input.OriginalAmount - discountAmount;
|
||||||
|
finalAmount = Math.Max(0, finalAmount);
|
||||||
|
|
||||||
|
result.IsValid = true;
|
||||||
|
result.CouponId = coupon.Id;
|
||||||
|
result.CouponName = coupon.Name ?? "优惠券";
|
||||||
|
result.DiscountType = coupon.Type;
|
||||||
|
result.DiscountValue = coupon.Value;
|
||||||
|
result.DiscountAmount = discountAmount;
|
||||||
|
result.FinalAmount = finalAmount;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -801,132 +801,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
|||||||
[HttpPost("coupon/validate")]
|
[HttpPost("coupon/validate")]
|
||||||
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
|
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
|
||||||
{
|
{
|
||||||
var result = new ValidateCouponOutput
|
|
||||||
{
|
|
||||||
IsValid = false,
|
|
||||||
InvalidReason = ""
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查找兑换码
|
|
||||||
var redemptionCode = await _redemptionCodeRepository.GetFirstAsync(rc =>
|
|
||||||
rc.Code == input.CouponCode &&
|
|
||||||
rc.Status == 0 && // 0-未使用
|
|
||||||
!rc.DeleteMark);
|
|
||||||
|
|
||||||
if (redemptionCode == null)
|
|
||||||
{
|
|
||||||
result.InvalidReason = "兑换码不存在或已使用";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找优惠券
|
|
||||||
var coupon = await _couponRepository.GetFirstAsync(c =>
|
|
||||||
c.Id == redemptionCode.CouponId &&
|
|
||||||
c.Status == AvailabilityStatus.Enable &&
|
|
||||||
!c.DeleteMark);
|
|
||||||
|
|
||||||
if (coupon == null)
|
|
||||||
{
|
|
||||||
result.InvalidReason = "优惠券不存在或已失效";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查优惠券是否已过期
|
|
||||||
if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now)
|
|
||||||
{
|
|
||||||
result.InvalidReason = "优惠券已过期";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查优惠券是否已开始
|
|
||||||
if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now)
|
|
||||||
{
|
|
||||||
result.InvalidReason = "优惠券尚未生效";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查库存
|
|
||||||
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
|
|
||||||
{
|
|
||||||
result.InvalidReason = "优惠券已被领完";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查优惠券是否适用于该赛事
|
|
||||||
if (!string.IsNullOrEmpty(coupon.EventIds))
|
|
||||||
{
|
|
||||||
var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList();
|
|
||||||
if (!eventIds.Contains(input.EventId))
|
|
||||||
{
|
|
||||||
result.InvalidReason = "该优惠券不适用于当前赛事";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取
|
|
||||||
var userId = _authManager.UserId;
|
var userId = _authManager.UserId;
|
||||||
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
|
|
||||||
uc.CouponId == coupon.Id &&
|
|
||||||
uc.UserId == userId &&
|
|
||||||
uc.Status == 0 && // 0-未使用
|
|
||||||
!uc.DeleteMark);
|
|
||||||
|
|
||||||
// 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取)
|
return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
|
||||||
if (userCoupon == null)
|
|
||||||
{
|
|
||||||
// 创建用户优惠券记录
|
|
||||||
userCoupon = new YD_Event.Core.Entities.UserCoupon
|
|
||||||
{
|
|
||||||
Id = _idGenerator.NewLong(),
|
|
||||||
UserId = userId,
|
|
||||||
CouponId = coupon.Id,
|
|
||||||
ClaimTime = DateTime.Now,
|
|
||||||
Status = 0, // 未使用
|
|
||||||
RedemptionCodeId = redemptionCode.Id,
|
|
||||||
RedemptionCode = redemptionCode.Code,
|
|
||||||
CreatedUserId = userId,
|
|
||||||
CreatedTime = DateTime.Now,
|
|
||||||
DeleteMark = false
|
|
||||||
};
|
|
||||||
await _userCouponRepository.InsertAsync(userCoupon);
|
|
||||||
|
|
||||||
// 更新兑换码状态和关联
|
|
||||||
redemptionCode.Status = 1; // 已使用
|
|
||||||
redemptionCode.UsedTime = DateTime.Now;
|
|
||||||
redemptionCode.UserCouponId = userCoupon.Id;
|
|
||||||
await _redemptionCodeRepository.UpdateAsync(redemptionCode);
|
|
||||||
|
|
||||||
// 更新优惠券已领取数量
|
|
||||||
coupon.ClaimedQuantity += 1;
|
|
||||||
await _couponRepository.UpdateAsync(coupon);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算优惠金额
|
|
||||||
decimal discountAmount = 0;
|
|
||||||
decimal finalAmount = input.OriginalAmount;
|
|
||||||
|
|
||||||
// Type: 0-折扣, 1-定额
|
|
||||||
if (coupon.Type == 0) // 折扣
|
|
||||||
{
|
|
||||||
discountAmount = input.OriginalAmount * (1 - coupon.Value / 100);
|
|
||||||
}
|
|
||||||
else if (coupon.Type == 1) // 定额
|
|
||||||
{
|
|
||||||
discountAmount = Math.Min(coupon.Value, input.OriginalAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalAmount = input.OriginalAmount - discountAmount;
|
|
||||||
finalAmount = Math.Max(0, finalAmount); // 确保不小于0
|
|
||||||
|
|
||||||
result.IsValid = true;
|
|
||||||
result.CouponId = coupon.Id;
|
|
||||||
result.CouponName = coupon.Name ?? "优惠券";
|
|
||||||
result.DiscountType = coupon.Type;
|
|
||||||
result.DiscountValue = coupon.Value;
|
|
||||||
result.DiscountAmount = discountAmount;
|
|
||||||
result.FinalAmount = finalAmount;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPaymentStatusDesc(int status)
|
private string GetPaymentStatusDesc(int status)
|
||||||
|
|||||||
@ -263,6 +263,11 @@
|
|||||||
身份证图片URL
|
身份证图片URL
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:YD_Event.Application.AppUser.Dtos.CreateOrUpdateMemberInput.IdCardBackImageUrl">
|
||||||
|
<summary>
|
||||||
|
身份证背面图片URL
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:YD_Event.Application.AppUser.Dtos.CreateOrUpdateMemberInput.PassportPhotoUrl">
|
<member name="P:YD_Event.Application.AppUser.Dtos.CreateOrUpdateMemberInput.PassportPhotoUrl">
|
||||||
<summary>
|
<summary>
|
||||||
护照图片URL
|
护照图片URL
|
||||||
@ -353,6 +358,11 @@
|
|||||||
身份证图片URL
|
身份证图片URL
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:YD_Event.Application.AppUser.Dtos.AppUserOutput.IdCardBackImageUrl">
|
||||||
|
<summary>
|
||||||
|
身份证背面图片URL
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:YD_Event.Application.AppUser.Dtos.AppUserOutput.PassportPhotoUrl">
|
<member name="P:YD_Event.Application.AppUser.Dtos.AppUserOutput.PassportPhotoUrl">
|
||||||
<summary>
|
<summary>
|
||||||
护照图片URL
|
护照图片URL
|
||||||
@ -2780,6 +2790,11 @@
|
|||||||
身份证正面图片(认证类型1时使用)
|
身份证正面图片(认证类型1时使用)
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:YD_Event.Application.Client.Dtos.MyAuthInfoDto.IdCardBackImageUrl">
|
||||||
|
<summary>
|
||||||
|
身份证背面图片(认证类型1时使用)
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:YD_Event.Application.Client.Dtos.MyAuthInfoDto.PassportPhotoUrl">
|
<member name="P:YD_Event.Application.Client.Dtos.MyAuthInfoDto.PassportPhotoUrl">
|
||||||
<summary>
|
<summary>
|
||||||
护照图片(认证类型2时使用)
|
护照图片(认证类型2时使用)
|
||||||
@ -2840,6 +2855,11 @@
|
|||||||
身份证正面图片(认证类型1时必填)
|
身份证正面图片(认证类型1时必填)
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:YD_Event.Application.Client.Dtos.SubmitAuthInput.IdCardBackImageUrl">
|
||||||
|
<summary>
|
||||||
|
身份证背面图片(认证类型1时必填)
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:YD_Event.Application.Client.Dtos.SubmitAuthInput.PassportPhotoUrl">
|
<member name="P:YD_Event.Application.Client.Dtos.SubmitAuthInput.PassportPhotoUrl">
|
||||||
<summary>
|
<summary>
|
||||||
护照图片(认证类型2时必填)
|
护照图片(认证类型2时必填)
|
||||||
@ -7344,6 +7364,11 @@
|
|||||||
<param name="now">当前时间</param>
|
<param name="now">当前时间</param>
|
||||||
<returns>计算后的赛事状态</returns>
|
<returns>计算后的赛事状态</returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="T:YD_Event.Application.Event.PaymentExpirationBackgroundService">
|
||||||
|
<summary>
|
||||||
|
支付超时处理后台服务
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="T:YD_Event.Application.Event.UnifiedRegistrationService">
|
<member name="T:YD_Event.Application.Event.UnifiedRegistrationService">
|
||||||
<summary>
|
<summary>
|
||||||
统一报名处理服务
|
统一报名处理服务
|
||||||
@ -7459,6 +7484,11 @@
|
|||||||
根据赛事获取对应的微信支付配置
|
根据赛事获取对应的微信支付配置
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateAndUseCouponAsync(YD_Event.Application.Event.Dtos.ValidateCouponInput,System.Int64)">
|
||||||
|
<summary>
|
||||||
|
统一优惠券验证和使用方法
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="T:YD_Event.Application.Event.UserEventRecordService">
|
<member name="T:YD_Event.Application.Event.UserEventRecordService">
|
||||||
<summary>
|
<summary>
|
||||||
用户端参赛记录管理
|
用户端参赛记录管理
|
||||||
|
|||||||
@ -85,11 +85,17 @@ public class AppUser : Entity<long>, ISoftDelete, ICreatedUserId, ICreatedTime,
|
|||||||
public string? IdCardNumber { get; set; }
|
public string? IdCardNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 身份证图片URL
|
/// 身份证正面图片URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(Length = 512, IsNullable = true)]
|
[SugarColumn(Length = 512, IsNullable = true)]
|
||||||
public string? IdCardImageUrl { get; set; }
|
public string? IdCardImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份证背面图片URL
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 512, IsNullable = true)]
|
||||||
|
public string? IdCardBackImageUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 护照图片URL
|
/// 护照图片URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
17
YD_Event.Core/Enum/CouponType.cs
Normal file
17
YD_Event.Core/Enum/CouponType.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace YD_Event.Core.Enum;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 优惠券类型
|
||||||
|
/// </summary>
|
||||||
|
public enum CouponType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 折扣
|
||||||
|
/// </summary>
|
||||||
|
Discount = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 定额
|
||||||
|
/// </summary>
|
||||||
|
FixedAmount = 1
|
||||||
|
}
|
||||||
@ -601,7 +601,12 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="P:YD_Event.Core.Entities.AppUser.IdCardImageUrl">
|
<member name="P:YD_Event.Core.Entities.AppUser.IdCardImageUrl">
|
||||||
<summary>
|
<summary>
|
||||||
身份证图片URL
|
身份证正面图片URL
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:YD_Event.Core.Entities.AppUser.IdCardBackImageUrl">
|
||||||
|
<summary>
|
||||||
|
身份证背面图片URL
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="P:YD_Event.Core.Entities.AppUser.PassportPhotoUrl">
|
<member name="P:YD_Event.Core.Entities.AppUser.PassportPhotoUrl">
|
||||||
@ -4272,6 +4277,21 @@
|
|||||||
单价(项目)
|
单价(项目)
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="T:YD_Event.Core.Enum.CouponType">
|
||||||
|
<summary>
|
||||||
|
优惠券类型
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:YD_Event.Core.Enum.CouponType.Discount">
|
||||||
|
<summary>
|
||||||
|
折扣
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="F:YD_Event.Core.Enum.CouponType.FixedAmount">
|
||||||
|
<summary>
|
||||||
|
定额
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="T:YD_Event.Core.Enum.CoverType">
|
<member name="T:YD_Event.Core.Enum.CoverType">
|
||||||
<summary>
|
<summary>
|
||||||
博客封面类型
|
博客封面类型
|
||||||
|
|||||||
@ -35,6 +35,9 @@ public class Startup : AppStartup
|
|||||||
// 注册赛事状态自动更新后台服务
|
// 注册赛事状态自动更新后台服务
|
||||||
services.AddHostedService<YD_Event.Application.Event.EventStatusUpdateService>();
|
services.AddHostedService<YD_Event.Application.Event.EventStatusUpdateService>();
|
||||||
|
|
||||||
|
// 注册支付超时处理后台服务
|
||||||
|
services.AddHostedService<YD_Event.Application.Event.PaymentExpirationBackgroundService>();
|
||||||
|
|
||||||
//解决Encoding无法使用gb2312编码问题
|
//解决Encoding无法使用gb2312编码问题
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user