diff --git a/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs b/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs
index 93bd129..c5ba7a0 100644
--- a/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs
+++ b/YD_Event.Application/AppUser/Dtos/AppUserDtos.cs
@@ -177,6 +177,11 @@ public class CreateOrUpdateMemberInput
///
public string? IdCardImageUrl { get; set; }
+ ///
+ /// 身份证背面图片URL
+ ///
+ public string? IdCardBackImageUrl { get; set; }
+
///
/// 护照图片URL
///
@@ -273,6 +278,11 @@ public class AppUserOutput
///
public string? IdCardImageUrl { get; set; }
+ ///
+ /// 身份证背面图片URL
+ ///
+ public string? IdCardBackImageUrl { get; set; }
+
///
/// 护照图片URL
///
diff --git a/YD_Event.Application/Client/Dtos/MyDtos.cs b/YD_Event.Application/Client/Dtos/MyDtos.cs
index 0601940..5d4c480 100644
--- a/YD_Event.Application/Client/Dtos/MyDtos.cs
+++ b/YD_Event.Application/Client/Dtos/MyDtos.cs
@@ -102,6 +102,11 @@ public class MyAuthInfoDto
///
public string? IdCardImageUrl { get; set; }
+ ///
+ /// 身份证背面图片(认证类型1时使用)
+ ///
+ public string? IdCardBackImageUrl { get; set; }
+
///
/// 护照图片(认证类型2时使用)
///
@@ -171,6 +176,12 @@ public class SubmitAuthInput
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
public string? IdCardImageUrl { get; set; }
+ ///
+ /// 身份证背面图片(认证类型1时必填)
+ ///
+ [MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
+ public string? IdCardBackImageUrl { get; set; }
+
///
/// 护照图片(认证类型2时必填)
///
diff --git a/YD_Event.Application/Client/MyController.cs b/YD_Event.Application/Client/MyController.cs
index 172aaf7..57aaf7f 100644
--- a/YD_Event.Application/Client/MyController.cs
+++ b/YD_Event.Application/Client/MyController.cs
@@ -87,25 +87,7 @@ public class MyController : IDynamicApiController
var pointsBalance = (int)(pointsIn - pointsOut);
// 认证状态: 0-待提交, 1-已认证, 2-审核中, 3-失败
- int authStatus = 0;
- 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; // 失败/其他
- }
+ int authStatus = (int)user.AuditStatus;
// 获取单位名称
string? merchantName = null;
@@ -155,6 +137,7 @@ public class MyController : IDynamicApiController
BirthDate = user.BirthDate,
IdCardNumber = user.IdCardNumber,
IdCardImageUrl = user.IdCardImageUrl,
+ IdCardBackImageUrl = user.IdCardBackImageUrl,
PassportPhotoUrl = user.PassportPhotoUrl,
AuditStatus = user.AuditStatus,
AuditRemark = user.AuditRemark
@@ -180,6 +163,7 @@ public class MyController : IDynamicApiController
{
if (string.IsNullOrEmpty(input.IdCardNumber)) throw Oops.Bah("身份证号不能为空");
if (string.IsNullOrEmpty(input.IdCardImageUrl)) throw Oops.Bah("身份证正面图片不能为空");
+ if (string.IsNullOrEmpty(input.IdCardBackImageUrl)) throw Oops.Bah("身份证背面图片不能为空");
}
else if (input.AuthType == 2)
{
@@ -201,12 +185,14 @@ public class MyController : IDynamicApiController
{
user.IdCardNumber = input.IdCardNumber;
user.IdCardImageUrl = input.IdCardImageUrl;
+ user.IdCardBackImageUrl = input.IdCardBackImageUrl;
user.PassportPhotoUrl = null; // 清空护照图片
}
else
{
user.PassportPhotoUrl = input.PassportPhotoUrl;
user.IdCardImageUrl = null; // 清空身份证图片
+ user.IdCardBackImageUrl = null; // 清空身份证背面图片
user.Gender = (Gender)input.Gender.Value;
user.BirthDate = input.BirthDate.Value;
}
diff --git a/YD_Event.Application/Event/PaymentExpirationBackgroundService.cs b/YD_Event.Application/Event/PaymentExpirationBackgroundService.cs
new file mode 100644
index 0000000..0bf3517
--- /dev/null
+++ b/YD_Event.Application/Event/PaymentExpirationBackgroundService.cs
@@ -0,0 +1,98 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace YD_Event.Application.Event;
+
+///
+/// 支付超时处理后台服务
+///
+public class PaymentExpirationBackgroundService : BackgroundService
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ILogger _logger;
+ private readonly PeriodicTimer _timer;
+
+ public PaymentExpirationBackgroundService(
+ IServiceProvider serviceProvider,
+ ILogger 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>();
+ var orderRepository = scope.ServiceProvider.GetRequiredService>();
+
+ 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();
+ }
+}
diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs
index 8177f7e..4a4a295 100644
--- a/YD_Event.Application/Event/UnifiedRegistrationService.cs
+++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs
@@ -37,6 +37,7 @@ public class UnifiedRegistrationService : ITransient
private readonly ISqlSugarRepository _merchantRepository;
private readonly ISqlSugarRepository _couponRepository;
private readonly ISqlSugarRepository _userCouponRepository;
+ private readonly ISqlSugarRepository _redemptionCodeRepository;
private readonly ISqlSugarRepository _paymentRepository;
private readonly ISqlSugarRepository _merchantCertificationRepository;
private readonly ISqlSugarClient _db;
@@ -66,6 +67,7 @@ public class UnifiedRegistrationService : ITransient
ISqlSugarRepository merchantRepository,
ISqlSugarRepository couponRepository,
ISqlSugarRepository userCouponRepository,
+ ISqlSugarRepository redemptionCodeRepository,
ISqlSugarRepository paymentRepository,
ISqlSugarRepository merchantCertificationRepository,
ISqlSugarClient db,
@@ -94,6 +96,7 @@ public class UnifiedRegistrationService : ITransient
_merchantRepository = merchantRepository;
_couponRepository = couponRepository;
_userCouponRepository = userCouponRepository;
+ _redemptionCodeRepository = redemptionCodeRepository;
_paymentRepository = paymentRepository;
_merchantCertificationRepository = merchantCertificationRepository;
_db = db;
@@ -1512,4 +1515,137 @@ public class UnifiedRegistrationService : ITransient
return payConfig;
}
+
+ ///
+ /// 统一优惠券验证和使用方法
+ ///
+ [UnitOfWork]
+ public async Task 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;
+ }
}
diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs
index d4fea25..79a1975 100644
--- a/YD_Event.Application/Event/UserEventRegistrationService.cs
+++ b/YD_Event.Application/Event/UserEventRegistrationService.cs
@@ -801,132 +801,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
[HttpPost("coupon/validate")]
public async Task 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 userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
- uc.CouponId == coupon.Id &&
- uc.UserId == userId &&
- uc.Status == 0 && // 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); // 确保不小于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;
+ return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
}
private string GetPaymentStatusDesc(int status)
diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml
index 3cf04d8..1b4bfbd 100644
--- a/YD_Event.Application/YD_Event.Application.xml
+++ b/YD_Event.Application/YD_Event.Application.xml
@@ -263,6 +263,11 @@
身份证图片URL
+
+
+ 身份证背面图片URL
+
+
护照图片URL
@@ -353,6 +358,11 @@
身份证图片URL
+
+
+ 身份证背面图片URL
+
+
护照图片URL
@@ -2780,6 +2790,11 @@
身份证正面图片(认证类型1时使用)
+
+
+ 身份证背面图片(认证类型1时使用)
+
+
护照图片(认证类型2时使用)
@@ -2840,6 +2855,11 @@
身份证正面图片(认证类型1时必填)
+
+
+ 身份证背面图片(认证类型1时必填)
+
+
护照图片(认证类型2时必填)
@@ -7344,6 +7364,11 @@
当前时间
计算后的赛事状态
+
+
+ 支付超时处理后台服务
+
+
统一报名处理服务
@@ -7459,6 +7484,11 @@
根据赛事获取对应的微信支付配置
+
+
+ 统一优惠券验证和使用方法
+
+
用户端参赛记录管理
diff --git a/YD_Event.Core/Entities/AppUser.cs b/YD_Event.Core/Entities/AppUser.cs
index f654135..b25de03 100644
--- a/YD_Event.Core/Entities/AppUser.cs
+++ b/YD_Event.Core/Entities/AppUser.cs
@@ -85,11 +85,17 @@ public class AppUser : Entity, ISoftDelete, ICreatedUserId, ICreatedTime,
public string? IdCardNumber { get; set; }
///
- /// 身份证图片URL
+ /// 身份证正面图片URL
///
[SugarColumn(Length = 512, IsNullable = true)]
public string? IdCardImageUrl { get; set; }
+ ///
+ /// 身份证背面图片URL
+ ///
+ [SugarColumn(Length = 512, IsNullable = true)]
+ public string? IdCardBackImageUrl { get; set; }
+
///
/// 护照图片URL
///
diff --git a/YD_Event.Core/Enum/CouponType.cs b/YD_Event.Core/Enum/CouponType.cs
new file mode 100644
index 0000000..c297fa2
--- /dev/null
+++ b/YD_Event.Core/Enum/CouponType.cs
@@ -0,0 +1,17 @@
+namespace YD_Event.Core.Enum;
+
+///
+/// 优惠券类型
+///
+public enum CouponType
+{
+ ///
+ /// 折扣
+ ///
+ Discount = 0,
+
+ ///
+ /// 定额
+ ///
+ FixedAmount = 1
+}
diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml
index 6f06941..16334d5 100644
--- a/YD_Event.Core/YD_Event.Core.xml
+++ b/YD_Event.Core/YD_Event.Core.xml
@@ -601,7 +601,12 @@
- 身份证图片URL
+ 身份证正面图片URL
+
+
+
+
+ 身份证背面图片URL
@@ -4272,6 +4277,21 @@
单价(项目)
+
+
+ 优惠券类型
+
+
+
+
+ 折扣
+
+
+
+
+ 定额
+
+
博客封面类型
diff --git a/YD_Event.Web.Core/Startup.cs b/YD_Event.Web.Core/Startup.cs
index 6a1e068..6b59773 100644
--- a/YD_Event.Web.Core/Startup.cs
+++ b/YD_Event.Web.Core/Startup.cs
@@ -35,6 +35,9 @@ public class Startup : AppStartup
// 注册赛事状态自动更新后台服务
services.AddHostedService();
+ // 注册支付超时处理后台服务
+ services.AddHostedService();
+
//解决Encoding无法使用gb2312编码问题
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);