1618 lines
66 KiB
C#
1618 lines
66 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.Extensions.Options;
|
||
using Microsoft.Extensions.Logging;
|
||
using YD_Event.Application.Client.Dtos;
|
||
using YD_Event.Application.Event;
|
||
using YD_Event.Application.Event.Dtos;
|
||
using YD_Event.Core.Entities;
|
||
using YD_Event.Core.Enum;
|
||
using Easy.Core;
|
||
using YD_Event.Core.Infrastructure.WeChat;
|
||
using YD_Event.Core.Infrastructure.WeChat.Dto;
|
||
using YD_Event.Core.Options;
|
||
using YD_Event.Core.Domain.Pricing;
|
||
using YD_Event.Core.Domain.Pricing.Models;
|
||
using YD_Event.Core.Domain;
|
||
using SqlSugar;
|
||
using Furion.DatabaseAccessor;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Furion.FriendlyException;
|
||
using System.Security.Claims;
|
||
using System.Text.Json;
|
||
using System.IO;
|
||
|
||
namespace YD_Event.Application.Client;
|
||
|
||
/// <summary>
|
||
/// 小程序-赛事接口
|
||
/// </summary>
|
||
[ApiDescriptionSettings("微信小程序接口")]
|
||
[Authorize]
|
||
public class EventClientController : IDynamicApiController
|
||
{
|
||
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
|
||
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
|
||
private readonly ISqlSugarRepository<ProjectGroup> _groupRepository;
|
||
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
|
||
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Banner> _bannerRepository;
|
||
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationLimit> _limitRepository;
|
||
private readonly ISqlSugarRepository<EventProjectRegistrationLimit> _projectLimitRepository;
|
||
private readonly ISqlSugarRepository<EventFeeConfig> _feeConfigRepository;
|
||
private readonly ISqlSugarRepository<WeChatPayConfig> _weChatPayConfigRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
||
private readonly ISqlSugarRepository<PointsRule> _pointsRuleRepository;
|
||
private readonly ISqlSugarRepository<PointsRuleDetail> _pointsRuleDetailRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _appUserRepository;
|
||
private readonly ISqlSugarRepository<SysProvince> _provinceRepository;
|
||
private readonly ISqlSugarRepository<SysCity> _cityRepository;
|
||
private readonly ISqlSugarRepository<SysDistrict> _districtRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
|
||
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
||
private readonly ISqlSugarRepository<RefundRecord> _refundRecordRepository;
|
||
private readonly ISqlSugarRepository<RefundConfig> _refundConfigRepository;
|
||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||
private readonly IIdGenerator _idGenerator;
|
||
private readonly WeChatPayService _weChatPayService;
|
||
private readonly IOptionsMonitor<WechatTenpayClientOptions> _wechatTenpayClientOptionsMonitor;
|
||
private readonly ILogger<EventClientController> _logger;
|
||
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
||
private readonly YD_Event.Application.Banner.BannerService _bannerService;
|
||
private readonly UnifiedRegistrationService _unifiedRegistrationService;
|
||
private readonly YD_Event.Application.Refund.RefundConfigService _refundConfigService;
|
||
|
||
public EventClientController(
|
||
ISqlSugarRepository<EventInfo> eventRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
||
ISqlSugarRepository<EventProject> eventProjectRepository,
|
||
ISqlSugarRepository<ProjectGroup> groupRepository,
|
||
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
|
||
ISqlSugarRepository<EventRegistration> registrationRepository,
|
||
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Banner> bannerRepository,
|
||
ISqlSugarRepository<EventUnit> unitRepository,
|
||
ISqlSugarRepository<EventRegistrationPerson> personRepository,
|
||
ISqlSugarRepository<EventRegistrationLimit> limitRepository,
|
||
ISqlSugarRepository<EventProjectRegistrationLimit> projectLimitRepository,
|
||
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
|
||
ISqlSugarRepository<WeChatPayConfig> weChatPayConfigRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
||
ISqlSugarRepository<PointsRule> pointsRuleRepository,
|
||
ISqlSugarRepository<PointsRuleDetail> pointsRuleDetailRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
|
||
ISqlSugarRepository<SysProvince> provinceRepository,
|
||
ISqlSugarRepository<SysCity> cityRepository,
|
||
ISqlSugarRepository<SysDistrict> districtRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
||
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
|
||
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
||
ISqlSugarRepository<RefundRecord> refundRecordRepository,
|
||
ISqlSugarRepository<RefundConfig> refundConfigRepository,
|
||
IHttpContextAccessor httpContextAccessor,
|
||
IIdGenerator idGenerator,
|
||
WeChatPayService weChatPayService,
|
||
IOptionsMonitor<WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
|
||
ILogger<EventClientController> logger,
|
||
OrderNoGeneratorService orderNoGeneratorService,
|
||
YD_Event.Application.Banner.BannerService bannerService,
|
||
UnifiedRegistrationService unifiedRegistrationService,
|
||
YD_Event.Application.Refund.RefundConfigService refundConfigService)
|
||
{
|
||
_eventRepository = eventRepository;
|
||
_projectRepository = projectRepository;
|
||
_eventProjectRepository = eventProjectRepository;
|
||
_groupRepository = groupRepository;
|
||
_eventProjectGroupRepository = eventProjectGroupRepository;
|
||
_registrationRepository = registrationRepository;
|
||
_registrationItemRepository = registrationItemRepository;
|
||
_bannerRepository = bannerRepository;
|
||
_unitRepository = unitRepository;
|
||
_personRepository = personRepository;
|
||
_limitRepository = limitRepository;
|
||
_projectLimitRepository = projectLimitRepository;
|
||
_feeConfigRepository = feeConfigRepository;
|
||
_weChatPayConfigRepository = weChatPayConfigRepository;
|
||
_merchantRepository = merchantRepository;
|
||
_pointsRuleRepository = pointsRuleRepository;
|
||
_pointsRuleDetailRepository = pointsRuleDetailRepository;
|
||
_appUserRepository = appUserRepository;
|
||
_provinceRepository = provinceRepository;
|
||
_cityRepository = cityRepository;
|
||
_districtRepository = districtRepository;
|
||
_orderRepository = orderRepository;
|
||
_merchantCertificationRepository = merchantCertificationRepository;
|
||
_paymentRepository = paymentRepository;
|
||
_refundRecordRepository = refundRecordRepository;
|
||
_refundConfigRepository = refundConfigRepository;
|
||
_httpContextAccessor = httpContextAccessor;
|
||
_idGenerator = idGenerator;
|
||
_weChatPayService = weChatPayService;
|
||
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
|
||
_logger = logger;
|
||
_orderNoGeneratorService = orderNoGeneratorService;
|
||
_bannerService = bannerService;
|
||
_unifiedRegistrationService = unifiedRegistrationService;
|
||
_refundConfigService = refundConfigService;
|
||
}
|
||
|
||
private long GetCurrentUserId()
|
||
{
|
||
var userIdStr = _httpContextAccessor.HttpContext?.User?.FindFirst(AuthClaimsConst.AuthIdKey)?.Value;
|
||
if (string.IsNullOrEmpty(userIdStr) || !long.TryParse(userIdStr, out long userId))
|
||
{
|
||
throw Oops.Bah("未登录或登录已失效");
|
||
}
|
||
return userId;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取轮播图列表
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet("banners")]
|
||
[AllowAnonymous]
|
||
[DisplayName("获取轮播图列表")]
|
||
public async Task<List<BannerDto>> GetBannerList()
|
||
{
|
||
var list = await _bannerService.GetList();
|
||
|
||
return list.Select(b => new BannerDto
|
||
{
|
||
Title = b.Title,
|
||
ImageUrl = b.ImageUrl,
|
||
LinkType = b.LinkType,
|
||
LinkUrl = b.LinkUrl,
|
||
EventId = b.EventId
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取赛事列表
|
||
/// </summary>
|
||
/// <param name="input">查询参数</param>
|
||
/// <returns></returns>
|
||
[HttpGet("list")]
|
||
[AllowAnonymous]
|
||
[DisplayName("获取赛事列表")]
|
||
public async Task<PageResult<EventListDto>> GetEventList([FromQuery] EventClientQueryInput input)
|
||
{
|
||
var query = _eventRepository.AsQueryable()
|
||
.Where(e => !e.DeleteMark && e.Status == AvailabilityStatus.Enable)
|
||
.WhereIF(!string.IsNullOrEmpty(input.Keyword), e => e.EventName.Contains(input.Keyword))
|
||
.WhereIF(input.EventType.HasValue, e => e.EventType == input.EventType)
|
||
.WhereIF(input.ChargeMode.HasValue, e => e.ChargeMode == input.ChargeMode)
|
||
.WhereIF(input.Status > 0, e => e.EventStatus == (EventStatus)input.Status);
|
||
|
||
query = query.OrderByDescending(e => e.CreatedTime);
|
||
|
||
var list = await query.ToPagedListAsync(input);
|
||
|
||
// 获取所有需要的省市区信息
|
||
var provinceIds = list.Rows.Where(e => e.ProvinceId.HasValue).Select(e => e.ProvinceId.Value).Distinct().ToList();
|
||
var cityIds = list.Rows.Where(e => e.CityId.HasValue).Select(e => e.CityId.Value).Distinct().ToList();
|
||
var districtIds = list.Rows.Where(e => e.DistrictId.HasValue).Select(e => e.DistrictId.Value).Distinct().ToList();
|
||
|
||
var provinces = provinceIds.Any()
|
||
? await _provinceRepository.AsQueryable().Where(p => provinceIds.Contains(p.Id) && !p.DeleteMark).ToListAsync()
|
||
: new List<SysProvince>();
|
||
var cities = cityIds.Any()
|
||
? await _cityRepository.AsQueryable().Where(c => cityIds.Contains(c.Id) && !c.DeleteMark).ToListAsync()
|
||
: new List<SysCity>();
|
||
var districts = districtIds.Any()
|
||
? await _districtRepository.AsQueryable().Where(d => districtIds.Contains(d.Id) && !d.DeleteMark).ToListAsync()
|
||
: new List<SysDistrict>();
|
||
|
||
var result = new PageResult<EventListDto>
|
||
{
|
||
PageNo = list.PageNo,
|
||
PageSize = list.PageSize,
|
||
Total = list.Total,
|
||
Pages = list.Pages,
|
||
Rows = list.Rows.Select(e =>
|
||
{
|
||
var provinceName = e.ProvinceId.HasValue
|
||
? provinces.FirstOrDefault(p => p.Id == e.ProvinceId.Value)?.ProvinceName ?? ""
|
||
: "";
|
||
var cityName = e.CityId.HasValue
|
||
? cities.FirstOrDefault(c => c.Id == e.CityId.Value)?.CityName ?? ""
|
||
: "";
|
||
var districtName = e.DistrictId.HasValue
|
||
? districts.FirstOrDefault(d => d.Id == e.DistrictId.Value)?.DistrictName ?? ""
|
||
: "";
|
||
|
||
return new EventListDto
|
||
{
|
||
Id = e.Id,
|
||
EventName = e.EventName,
|
||
CoverImage = e.CoverImage,
|
||
EventStatus = e.EventStatus,
|
||
RegistrationStartTime = e.RegistrationStartTime,
|
||
RegistrationEndTime = e.RegistrationEndTime,
|
||
MatchStartTime = e.MatchStartTime,
|
||
MatchEndTime = e.MatchEndTime,
|
||
Address = $"{provinceName}{cityName}{districtName}{e.DetailAddress}"
|
||
};
|
||
}).ToList()
|
||
};
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取赛事详情
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
/// <returns></returns>
|
||
[HttpGet("{id}")]
|
||
[AllowAnonymous]
|
||
[DisplayName("获取赛事详情")]
|
||
public async Task<EventDetailDto> GetEventDetail(long id)
|
||
{
|
||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == id && !e.DeleteMark);
|
||
if (evt == null) throw Oops.Bah("赛事不存在");
|
||
|
||
bool isRegistered = false;
|
||
|
||
try
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
isRegistered = await _registrationRepository.IsAnyAsync(r =>
|
||
r.EventId == id && r.CreatedUserId == userId && r.PaymentStatus != 4 && r.PaymentStatus == 1);
|
||
}
|
||
catch
|
||
{
|
||
isRegistered = false;
|
||
}
|
||
|
||
var feeConfig = await _feeConfigRepository.GetFirstAsync(f => f.EventId == id && !f.DeleteMark);
|
||
|
||
string provinceName = "";
|
||
string cityName = "";
|
||
string districtName = "";
|
||
|
||
if (evt.ProvinceId.HasValue && evt.ProvinceId > 0)
|
||
{
|
||
var province = await _provinceRepository.GetFirstAsync(p => p.Id == evt.ProvinceId.Value && !p.DeleteMark);
|
||
if (province != null) provinceName = province.ProvinceName;
|
||
}
|
||
if (evt.CityId.HasValue && evt.CityId > 0)
|
||
{
|
||
var city = await _cityRepository.GetFirstAsync(c => c.Id == evt.CityId.Value && !c.DeleteMark);
|
||
if (city != null) cityName = city.CityName;
|
||
}
|
||
if (evt.DistrictId.HasValue && evt.DistrictId > 0)
|
||
{
|
||
var district = await _districtRepository.GetFirstAsync(d => d.Id == evt.DistrictId.Value && !d.DeleteMark);
|
||
if (district != null) districtName = district.DistrictName;
|
||
}
|
||
|
||
PointsRuleDto? pointsRuleDetail = null;
|
||
if (evt.PointsRuleId.HasValue)
|
||
{
|
||
var pointsRuleEntity = await _pointsRuleRepository.GetFirstAsync(r => r.Id == evt.PointsRuleId.Value && !r.DeleteMark);
|
||
if (pointsRuleEntity != null)
|
||
{
|
||
var details = await _pointsRuleDetailRepository.AsQueryable()
|
||
.Where(d => d.PointsRuleId == pointsRuleEntity.Id && !d.DeleteMark)
|
||
.ToListAsync();
|
||
|
||
pointsRuleDetail = new PointsRuleDto
|
||
{
|
||
RuleName = pointsRuleEntity.RuleName,
|
||
ParticipationPoints = pointsRuleEntity.ParticipationPoints,
|
||
Remark = pointsRuleEntity.Remark,
|
||
Details = details.Select(d => new PointsRuleDetailDto
|
||
{
|
||
StartRank = d.StartRank,
|
||
EndRank = d.EndRank,
|
||
Points = d.Points
|
||
}).ToList()
|
||
};
|
||
}
|
||
}
|
||
|
||
var eventProjects = await _eventProjectRepository.AsQueryable()
|
||
.Where(ep => ep.EventId == id)
|
||
.ToListAsync();
|
||
|
||
var eventProjectIds = eventProjects.Select(ep => ep.Id).Distinct().ToList();
|
||
var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
|
||
|
||
var baseProjects = await _projectRepository.AsQueryable()
|
||
.Where(p => projectIds.Contains(p.Id) && !p.DeleteMark && p.Status == AvailabilityStatus.Enable)
|
||
.Select(p => new {
|
||
p.Id,
|
||
p.Name,
|
||
p.ProjectType
|
||
})
|
||
.ToListAsync();
|
||
|
||
var baseGroups = await _groupRepository.AsQueryable()
|
||
.Where(g => projectIds.Contains(g.ProjectId) && !g.DeleteMark)
|
||
.ToListAsync();
|
||
|
||
var eventProjectGroups = await _eventProjectGroupRepository.AsQueryable()
|
||
.Where(epg => eventProjectIds.Contains(epg.EventProjectId) && !epg.DeleteMark)
|
||
.ToListAsync();
|
||
|
||
var projectLimits = await _projectLimitRepository.AsQueryable()
|
||
.Where(pl => pl.EventId == id && !pl.DeleteMark)
|
||
.ToListAsync();
|
||
|
||
var units = await _unitRepository.AsQueryable()
|
||
.Where(u => u.EventId == id && !u.DeleteMark)
|
||
.OrderBy(u => u.Sort)
|
||
.ToListAsync();
|
||
|
||
var limit = await _limitRepository.GetFirstAsync(l => l.EventId == id && !l.DeleteMark);
|
||
|
||
string refundRule = "开赛前7天扣除金额5%,3-7天扣除金额10%,3天内扣除金额20%";
|
||
string pointsRule = "参与积分:5积分;名次积分:1-8名 15积分,9-20名 10积分";
|
||
|
||
// 优先从 RefundConfig 表读取配置并生成描述文本
|
||
try
|
||
{
|
||
var refundConfigs = await _refundConfigService.GetList();
|
||
var enabledConfigs = refundConfigs.Where(x => x.Status == Core.Enum.AvailabilityStatus.Enable).OrderBy(x => x.DayStart).ToList();
|
||
if (enabledConfigs.Any())
|
||
{
|
||
var ruleParts = new List<string>();
|
||
foreach (var config in enabledConfigs)
|
||
{
|
||
if (config.DayEnd >= 999)
|
||
{
|
||
ruleParts.Add($"开赛前{config.DayStart}天及以上扣除金额{Math.Round(config.DeductPercentage)}%");
|
||
}
|
||
else
|
||
{
|
||
ruleParts.Add($"开赛前{config.DayStart}-{config.DayEnd}天扣除金额{Math.Round(config.DeductPercentage)}%");
|
||
}
|
||
}
|
||
refundRule = string.Join(",", ruleParts);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogWarning(ex, "读取退款配置失败,赛事ID={EventId},使用默认规则", evt.Id);
|
||
}
|
||
|
||
// 如果有自定义配置,优先使用自定义配置(保持向后兼容)
|
||
if (!string.IsNullOrWhiteSpace(evt.CustomConfigJson))
|
||
{
|
||
try
|
||
{
|
||
var config = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(evt.CustomConfigJson);
|
||
if (config != null)
|
||
{
|
||
if (config.TryGetValue("RefundRule", out var rule) && !string.IsNullOrWhiteSpace(rule))
|
||
{
|
||
refundRule = rule;
|
||
}
|
||
if (config.TryGetValue("PointsRule", out var pRule) && !string.IsNullOrWhiteSpace(pRule))
|
||
{
|
||
pointsRule = pRule;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogWarning(ex, "解析赛事自定义配置失败,赛事ID={EventId}", evt.Id);
|
||
}
|
||
}
|
||
|
||
if (evt.PointsRuleId.HasValue)
|
||
{
|
||
var pointsRuleEntity = await _pointsRuleRepository.GetFirstAsync(r => r.Id == evt.PointsRuleId.Value && !r.DeleteMark);
|
||
if (pointsRuleEntity != null)
|
||
{
|
||
pointsRule = pointsRuleEntity.Remark ?? pointsRule;
|
||
}
|
||
}
|
||
|
||
EventFeeConfigDto? feeConfigDto = null;
|
||
if (feeConfig != null)
|
||
{
|
||
feeConfigDto = new EventFeeConfigDto
|
||
{
|
||
ChargeMode = feeConfig.ChargeMode,
|
||
Price = feeConfig.Price,
|
||
MerchantId = evt.MerchantId
|
||
};
|
||
|
||
if (evt.MerchantId.HasValue && evt.MerchantId.Value > 0)
|
||
{
|
||
var merchant = await _merchantRepository.GetByIdAsync(evt.MerchantId.Value);
|
||
if (merchant != null)
|
||
{
|
||
feeConfigDto.MerchantName = merchant.MerchantName;
|
||
feeConfigDto.CreditCode = merchant.CreditCode;
|
||
feeConfigDto.BankName = merchant.BankName;
|
||
feeConfigDto.AccountNumber = merchant.AccountNumber;
|
||
feeConfigDto.BusinessLicenseUrl = merchant.BusinessLicenseUrl;
|
||
}
|
||
}
|
||
|
||
if (eventProjects.Any())
|
||
{
|
||
feeConfigDto.ProjectPrices = new Dictionary<long, decimal>();
|
||
foreach (var ep in eventProjects)
|
||
{
|
||
feeConfigDto.ProjectPrices[ep.Id] = ep.Price / 100m;
|
||
}
|
||
}
|
||
}
|
||
|
||
var detail = new EventDetailDto
|
||
{
|
||
Id = evt.Id,
|
||
EventName = evt.EventName,
|
||
CoverImage = evt.CoverImage,
|
||
EventStatus = evt.EventStatus,
|
||
RegistrationStartTime = evt.RegistrationStartTime,
|
||
RegistrationEndTime = evt.RegistrationEndTime,
|
||
MatchStartTime = evt.MatchStartTime,
|
||
MatchEndTime = evt.MatchEndTime,
|
||
Address = $"{provinceName}{cityName}{districtName}{evt.DetailAddress}",
|
||
EventVideo = evt.EventVideo,
|
||
EventType = evt.EventType,
|
||
ChargeMode = evt.ChargeMode,
|
||
FeeConfig = feeConfigDto,
|
||
RegistrationType = evt.RegistrationType,
|
||
EventIntroduction = evt.EventIntroduction,
|
||
IsNeedMaterial = evt.IsNeedMaterial,
|
||
MaterialRemark = evt.MaterialRemark,
|
||
MaterialUploadStartTime = evt.MaterialUploadStartTime,
|
||
MaterialUploadEndTime = evt.MaterialUploadEndTime,
|
||
RefundRule = refundRule,
|
||
PointsRule = pointsRule,
|
||
PointsRuleDetail = pointsRuleDetail,
|
||
IsRegistered = isRegistered,
|
||
ProvinceName = provinceName,
|
||
CityName = cityName,
|
||
DistrictName = districtName,
|
||
|
||
IndividualProjectLimit = limit?.IndividualProjectLimit ?? 0,
|
||
TeamProjectLimit = limit?.TeamProjectLimit ?? 0,
|
||
|
||
Units = units.Select(u => new YD_Event.Application.Client.Dtos.EventUnitDto
|
||
{
|
||
UnitType = u.UnitType,
|
||
CustomUnitType = u.CustomUnitType,
|
||
UnitName = u.UnitName,
|
||
LogoUrl = u.LogoUrl,
|
||
Sort = u.Sort
|
||
}).ToList(),
|
||
|
||
Projects = eventProjects.Select(ep =>
|
||
{
|
||
var bp = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
||
var pl = projectLimits.FirstOrDefault(x => x.ProjectId == ep.Id);
|
||
var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList();
|
||
return new EventProjectDto
|
||
{
|
||
Id = ep.Id,
|
||
Name = bp?.Name ?? "",
|
||
TypeName = bp?.ProjectType.Description() ?? "",
|
||
ProjectType = (long)(bp?.ProjectType ?? 0),
|
||
Fee = ep.Price / 100m,
|
||
ProjectLimitCount = pl?.LimitCount,
|
||
Groups = epGroups.Select(eg =>
|
||
{
|
||
var baseGroup = baseGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId);
|
||
var groupName = eg.GroupName ?? baseGroup?.GroupName ?? "";
|
||
var birthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : (baseGroup?.BirthDateStart ?? DateTime.MinValue);
|
||
var birthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : (baseGroup?.BirthDateEnd ?? DateTime.MinValue);
|
||
var gender = eg.Gender;
|
||
return new EventProjectGroupDto
|
||
{
|
||
Id = eg.Id,
|
||
GroupName = groupName,
|
||
GenderRequirement = gender == Gender.Male ? "男" : (gender == Gender.Female ? "女" : "不限"),
|
||
AgeRequirement = $"{birthDateStart:yyyy-MM-dd} 至 {birthDateEnd:yyyy-MM-dd}"
|
||
};
|
||
}).ToList()
|
||
};
|
||
}).Where(x => !string.IsNullOrEmpty(x.Name)).ToList()
|
||
};
|
||
|
||
return detail;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算报名金额
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("calculate-amount")]
|
||
[DisplayName("计算报名金额")]
|
||
public async Task<CalculateAmountOutput> CalculateRegistrationAmount([FromBody] CalculateAmountInput input)
|
||
{
|
||
var inputItems = input.Items.Select(i => new UnifiedRegistrationService.CalculateAmountInputItem
|
||
{
|
||
ProjectId = i.ProjectId,
|
||
PersonCount = i.PersonCount
|
||
}).ToList();
|
||
|
||
var (totalAmount, chargeMode, itemDetails) = await _unifiedRegistrationService.CalculateRegistrationAmountAsync(input.EventId, inputItems);
|
||
|
||
var result = new CalculateAmountOutput
|
||
{
|
||
ChargeMode = chargeMode,
|
||
TotalAmount = totalAmount,
|
||
ItemDetails = itemDetails.Select(d => new CalculateAmountItemOutput
|
||
{
|
||
ProjectId = d.projectId,
|
||
ProjectName = d.projectName,
|
||
UnitPrice = d.unitPrice,
|
||
PersonCount = d.personCount,
|
||
Subtotal = d.subtotal
|
||
}).ToList()
|
||
};
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 提交赛事报名
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("apply")]
|
||
[UnitOfWork]
|
||
[DisplayName("提交赛事报名")]
|
||
public async Task<long> SubmitRegistration([FromBody] SubmitRegistrationInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
// 0. 校验报名模式
|
||
if (input.RegistrationMode == 2)
|
||
throw Oops.Bah("团队报名请前往网页端进行操作");
|
||
|
||
// 校验重复报名 - 如果已有未取消的报名记录,直接返回现有记录ID
|
||
var existingRegistration = await _registrationRepository.GetFirstAsync(
|
||
r => r.EventId == input.EventId && r.CreatedUserId == userId && r.PaymentStatus != 4); // 4=已取消
|
||
|
||
if (existingRegistration != null)
|
||
{
|
||
// 已有未取消的报名记录,直接返回现有记录ID,让前端跳转到支付页面
|
||
return existingRegistration.Id;
|
||
}
|
||
|
||
// 获取当前用户信息(用于个人报名时自动填充)
|
||
var appUser = await _appUserRepository.GetFirstAsync(u => u.Id == userId && !u.DeleteMark);
|
||
if (appUser == null && input.RegistrationMode == 1 && appUser.AuditStatus != AuditStatus.Pass)
|
||
throw Oops.Bah("请先完成身份认证");
|
||
|
||
// 个人报名模式下自动填充联系人信息
|
||
if (input.RegistrationMode == 1)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(input.ContactName))
|
||
input.ContactName = appUser?.Name;
|
||
|
||
if (string.IsNullOrWhiteSpace(input.ContactPhone))
|
||
input.ContactPhone = appUser?.Mobile;
|
||
|
||
// 验证必填联系人信息
|
||
if (string.IsNullOrWhiteSpace(input.ContactName))
|
||
throw Oops.Bah("联系人姓名不能为空,请先完善身份认证信息");
|
||
|
||
if (string.IsNullOrWhiteSpace(input.ContactPhone))
|
||
throw Oops.Bah("联系人电话不能为空,请先完善身份认证信息");
|
||
}
|
||
|
||
// 个人报名模式下自动填充参赛人员信息
|
||
if (input.RegistrationMode == 1 && input.Items != null)
|
||
{
|
||
foreach (var item in input.Items)
|
||
{
|
||
if (item.Persons == null || !item.Persons.Any())
|
||
{
|
||
if (appUser == null)
|
||
throw Oops.Bah("请先完成身份认证");
|
||
|
||
item.Persons = new List<PersonInput>
|
||
{
|
||
new PersonInput
|
||
{
|
||
UserId = appUser.Id,
|
||
Name = appUser.Name ?? "",
|
||
Gender = appUser.Gender,
|
||
IdCardNumber = appUser.IdCardNumber ?? "",
|
||
BirthDate = appUser.BirthDate ?? DateTime.Now,
|
||
Mobile = appUser.Mobile
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查所有参赛人员是否都有UserId
|
||
if (input.Items != null)
|
||
{
|
||
foreach (var item in input.Items)
|
||
{
|
||
if (item.Persons != null)
|
||
{
|
||
foreach (var p in item.Persons)
|
||
{
|
||
if (!p.UserId.HasValue || p.UserId.Value <= 0)
|
||
{
|
||
throw Oops.Bah($"参赛人员 {p.Name} 的用户ID不能为空");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 转换为统一输入模型
|
||
var unifiedInput = new UnifiedRegistrationInput
|
||
{
|
||
EventId = input.EventId,
|
||
RegistrationMode = input.RegistrationMode,
|
||
TeamName = input.TeamName,
|
||
ContactName = input.ContactName ?? "",
|
||
ContactPhone = input.ContactPhone ?? "",
|
||
CreatedUserId = userId,
|
||
IsDraft = false,
|
||
RegistrationChannel = 1, // 1-小程序渠道
|
||
Items = input.Items?.Select(item => new UnifiedRegistrationItemInput
|
||
{
|
||
ProjectId = item.ProjectId,
|
||
GroupId = item.GroupId,
|
||
GroupName = item.GroupName,
|
||
Persons = item.Persons?.Select(p => new UnifiedPersonInput
|
||
{
|
||
UserId = p.UserId,
|
||
Name = p.Name,
|
||
Gender = p.Gender,
|
||
IdCardNumber = p.IdCardNumber,
|
||
BirthDate = p.BirthDate,
|
||
Mobile = p.Mobile
|
||
}).ToList() ?? new List<UnifiedPersonInput>()
|
||
}).ToList() ?? new List<UnifiedRegistrationItemInput>()
|
||
};
|
||
|
||
// 调用统一报名服务
|
||
var result = await _unifiedRegistrationService.ProcessRegistrationAsync(unifiedInput);
|
||
|
||
return result.RegistrationId;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证优惠券
|
||
/// </summary>
|
||
[HttpPost("coupon/validate")]
|
||
[DisplayName("验证优惠券")]
|
||
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取支付信息
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 用于获取赛事报名的支付信息,包括微信JSAPI支付参数
|
||
/// OpenId 参数必须通过请求体(JSON)传入,格式验证由DTO自动处理
|
||
/// </remarks>
|
||
/// <param name="input">支付信息查询输入参数,包含报名记录ID和OpenId</param>
|
||
/// <returns>支付信息,包括微信支付参数(如果是微信支付)</returns>
|
||
[HttpPost("payment-info")]
|
||
[DisplayName("获取支付信息")]
|
||
public async Task<PaymentInfoDto> GetPaymentInfo([FromBody] GetPaymentInfoInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
// 验证报名记录归属权
|
||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == input.RegistrationId && r.CreatedUserId == userId);
|
||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||
|
||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId);
|
||
if (evt == null) throw Oops.Bah("赛事不存在");
|
||
|
||
var merchant = await _merchantRepository.GetFirstAsync(m => m.Id == evt.MerchantId && !m.DeleteMark);
|
||
|
||
// 如果支付方式已经设置为微信支付或免费,直接调用统一支付服务
|
||
int paymentMethod = reg.PaymentMethod == 0 ? 2 : reg.PaymentMethod;
|
||
|
||
// 构建统一支付输入
|
||
var unifiedInput = new UnifiedPaymentInput
|
||
{
|
||
RegistrationId = input.RegistrationId,
|
||
PaymentMethod = paymentMethod,
|
||
PaymentMethodType = reg.PaymentMethodType,
|
||
CouponId = input.CouponId,
|
||
OpenId = input.OpenId,
|
||
PaymentProofUrl = null,
|
||
OperatorUserId = userId
|
||
};
|
||
|
||
// 调用统一支付服务
|
||
var unifiedResult = await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
|
||
|
||
// 构建返回结果
|
||
var paymentInfo = new PaymentInfoDto
|
||
{
|
||
RegistrationId = unifiedResult.RegistrationId,
|
||
Amount = unifiedResult.ActualAmount,
|
||
OriginalAmount = unifiedResult.OriginalAmount,
|
||
DiscountAmount = unifiedResult.DiscountAmount,
|
||
PaymentMethod = unifiedResult.PaymentStatus == 1 ? 0 : paymentMethod, // 如果已支付,不需要显示支付方式
|
||
BankAccountName = merchant?.MerchantName,
|
||
BankAccountNo = merchant?.AccountNumber,
|
||
BankName = merchant?.BankName,
|
||
WechatPayParams = unifiedResult.WechatPayParams
|
||
};
|
||
|
||
return paymentInfo;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据赛事ID精确获取对应的微信支付配置
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 1. 通过赛事信息中的 MerchantId 查找对应的微信支付配置
|
||
/// 2. 必须确保赛事已关联商户配置,不使用兜底策略
|
||
/// 3. 只返回启用状态且未删除的配置
|
||
/// </remarks>
|
||
/// <param name="eventInfo">赛事信息,必须包含有效的 MerchantId</param>
|
||
/// <returns>微信支付配置,如果未找到则返回 null</returns>
|
||
/// <exception cref="ArgumentException">当赛事没有关联商户配置时抛出</exception>
|
||
private async Task<WeChatPayConfig?> GetWeChatPayConfigByEventAsync(EventInfo eventInfo)
|
||
{
|
||
if (!eventInfo.MerchantId.HasValue)
|
||
{
|
||
_logger.LogError("赛事未关联商户配置,赛事ID={EventId},赛事名称={EventName}",
|
||
eventInfo.Id, eventInfo.EventName);
|
||
throw new ArgumentException($"赛事[{eventInfo.EventName}]未关联商户配置,请联系管理员配置", nameof(eventInfo));
|
||
}
|
||
|
||
var payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||
x.MerchantId == eventInfo.MerchantId.Value &&
|
||
x.Status == AvailabilityStatus.Enable &&
|
||
!x.DeleteMark);
|
||
|
||
if (payConfig == null)
|
||
{
|
||
_logger.LogError("未找到赛事关联的商户支付配置,赛事ID={EventId},商户ID={MerchantId}",
|
||
eventInfo.Id, eventInfo.MerchantId);
|
||
}
|
||
|
||
return payConfig;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传支付凭证(线下支付)
|
||
/// </summary>
|
||
[HttpPost("upload-voucher/{registrationId}")]
|
||
[DisplayName("上传支付凭证")]
|
||
[UnitOfWork]
|
||
public async Task<bool> UploadPaymentVoucher(long registrationId, [FromBody] YD_Event.Application.Client.Dtos.UploadVoucherInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
// 验证报名记录归属权
|
||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
|
||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||
|
||
// 构建统一支付输入
|
||
var unifiedInput = new UnifiedPaymentInput
|
||
{
|
||
RegistrationId = registrationId,
|
||
PaymentMethod = 1, // 线下支付
|
||
CouponId = null,
|
||
OpenId = null,
|
||
PaymentProofUrl = input.VoucherUrl,
|
||
OperatorUserId = userId
|
||
};
|
||
|
||
// 调用统一支付服务
|
||
await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新参赛人信息
|
||
/// </summary>
|
||
[HttpPost("update-player")]
|
||
[DisplayName("更新参赛人信息")]
|
||
public async Task<bool> UpdateRegistrationPlayer([FromBody] UpdatePlayerInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var person = await _personRepository.GetFirstAsync(p => p.Id == input.PersonId);
|
||
if (person == null) throw Oops.Bah("参赛人不存在");
|
||
|
||
// 校验权限:确保是该用户创建的报名
|
||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == person.RegistrationId);
|
||
if (reg == null || reg.CreatedUserId != userId) throw Oops.Bah("无权操作");
|
||
|
||
person.Name = input.Name;
|
||
person.IdCard = input.IdCardNumber;
|
||
person.Gender = input.Gender;
|
||
person.Phone = input.Mobile;
|
||
person.UpdatedTime = DateTime.Now;
|
||
person.UpdatedUserId = userId;
|
||
|
||
return await _personRepository.UpdateAsync(person);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取我的已报赛事列表
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet("my-events")]
|
||
[DisplayName("获取我的已报赛事列表")]
|
||
public async Task<PageResult<MyEventListDto>> GetMyEventList([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
var query = _registrationRepository.AsQueryable()
|
||
.LeftJoin<EventInfo>((r, e) => r.EventId == e.Id)
|
||
.Where(r => r.CreatedUserId == userId && !r.DeleteMark && r.PaymentStatus == 1)
|
||
.OrderByDescending(r => r.RegistrationTime)
|
||
.Select((r, e) => new MyEventListDto
|
||
{
|
||
RegistrationId = r.Id,
|
||
EventId = r.EventId,
|
||
EventName = e.EventName,
|
||
CoverImage = e.CoverImage,
|
||
PaymentStatus = r.PaymentStatus,
|
||
RegistrationTime = r.RegistrationTime,
|
||
ProjectCount = SqlFunc.Subqueryable<EventRegistrationItem>().Where(i => i.RegistrationId == r.Id).Count(),
|
||
IsNeedMaterial = e.IsNeedMaterial,
|
||
MaterialRemark = e.MaterialRemark,
|
||
MaterialUploadStartTime = e.MaterialUploadStartTime,
|
||
MaterialUploadEndTime = e.MaterialUploadEndTime,
|
||
MaterialUrl = r.MaterialUrl,
|
||
EventStatus = (int)e.EventStatus,
|
||
RegistrationEndTime = e.RegistrationEndTime,
|
||
RegistrationStartTime = e.RegistrationStartTime
|
||
});
|
||
|
||
return await query.ToPagedListAsync(new Pagination { PageNo = page, PageSize = pageSize });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取报名详情(包含参赛人)
|
||
/// </summary>
|
||
[HttpGet("my-registration/{registrationId}")]
|
||
[DisplayName("获取报名详情")]
|
||
public async Task<RegistrationDetailDto> GetRegistrationDetail(long registrationId)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
var reg = await _registrationRepository.AsQueryable()
|
||
.LeftJoin<EventInfo>((r, e) => r.EventId == e.Id)
|
||
.Where(r => r.Id == registrationId && r.CreatedUserId == userId)
|
||
.Select((r, e) => new {
|
||
r.Id,
|
||
r.EventId,
|
||
e.EventName,
|
||
e.CoverImage,
|
||
r.PaymentStatus,
|
||
r.TotalAmount,
|
||
r.OrderNo,
|
||
r.RegistrationTime,
|
||
r.ReviewRemark,
|
||
// 为了计算 EventStatus
|
||
RegStart = e.RegistrationStartTime,
|
||
RegEnd = e.RegistrationEndTime,
|
||
MatchStart = e.MatchStartTime,
|
||
MatchEnd = e.MatchEndTime
|
||
})
|
||
.FirstAsync();
|
||
|
||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||
|
||
// 计算 EventStatus
|
||
EventStatus status = EventStatus.Enrolling;
|
||
var now = DateTime.Now;
|
||
if (now >= reg.RegStart && now <= reg.RegEnd) status = EventStatus.Enrolling;
|
||
else if (now >= reg.MatchStart && now <= reg.MatchEnd) status = EventStatus.Ongoing;
|
||
else if (now > reg.MatchEnd) status = EventStatus.Ended;
|
||
|
||
// 获取 Items 和 Persons
|
||
var items = await _registrationItemRepository.AsQueryable()
|
||
.Where(i => i.RegistrationId == registrationId)
|
||
.ToListAsync();
|
||
|
||
var persons = await _personRepository.AsQueryable()
|
||
.Where(p => p.RegistrationId == registrationId)
|
||
.ToListAsync();
|
||
|
||
return new RegistrationDetailDto
|
||
{
|
||
RegistrationId = reg.Id,
|
||
EventId = reg.EventId,
|
||
EventName = reg.EventName,
|
||
CoverImage = reg.CoverImage,
|
||
EventStatus = status,
|
||
PaymentStatus = reg.PaymentStatus,
|
||
TotalAmount = reg.TotalAmount,
|
||
OrderNo = reg.OrderNo,
|
||
RegistrationTime = reg.RegistrationTime,
|
||
ReviewRemark = reg.ReviewRemark,
|
||
Items = items.Select(i => new RegistrationItemDto
|
||
{
|
||
Id = i.Id,
|
||
ProjectName = i.ProjectName,
|
||
GroupName = i.GroupName,
|
||
Price = i.UnitPrice, // 单价
|
||
Persons = persons.Where(p => p.RegistrationItemId == i.Id).Select(p => new YD_Event.Application.Client.Dtos.RegistrationPersonDto
|
||
{
|
||
Id = p.Id,
|
||
Name = p.Name,
|
||
IdCardNumber = p.IdCard,
|
||
Gender = p.Gender,
|
||
Mobile = p.Phone
|
||
}).ToList()
|
||
}).ToList()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询报名状态
|
||
/// </summary>
|
||
/// <param name="registrationId"></param>
|
||
/// <returns></returns>
|
||
[HttpGet("status/{registrationId}")]
|
||
[DisplayName("查询报名状态")]
|
||
public async Task<RegistrationStatusDto> GetRegistrationStatus(long registrationId)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
|
||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||
|
||
return new RegistrationStatusDto
|
||
{
|
||
RegistrationId = reg.Id,
|
||
PaymentStatus = reg.PaymentStatus,
|
||
RejectReason = reg.ReviewRemark
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消报名
|
||
/// </summary>
|
||
/// <param name="registrationId"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("cancel/{registrationId}")]
|
||
[DisplayName("取消报名")]
|
||
[UnitOfWork]
|
||
public async Task<bool> CancelRegistration(long registrationId)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
return await _unifiedRegistrationService.CancelRegistrationAsync(registrationId, userId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选择支付方式
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 用于选择赛事报名的支付方式
|
||
/// - 选择微信支付时,必须提供有效的 OpenId
|
||
/// - OpenId 的格式验证由 DTO 自动处理
|
||
/// </remarks>
|
||
/// <param name="input">选择支付方式的输入参数</param>
|
||
/// <returns>操作是否成功</returns>
|
||
[HttpPost("select-payment-method")]
|
||
[UnitOfWork]
|
||
[DisplayName("选择支付方式")]
|
||
public async Task<bool> SelectPaymentMethod([FromBody] SelectPaymentMethodInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == input.RegistrationId && r.CreatedUserId == userId);
|
||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||
|
||
if (reg.PaymentStatus == 1) throw Oops.Bah("订单已支付,无法修改支付方式");
|
||
if (reg.PaymentStatus == 4) throw Oops.Bah("订单已取消");
|
||
|
||
// 如果选择微信支付,验证 OpenId 是否提供
|
||
if (input.PaymentMethod == 2)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(input.OpenId))
|
||
{
|
||
_logger.LogWarning("选择微信支付时未提供 OpenId,报名ID={RegistrationId}", input.RegistrationId);
|
||
throw Oops.Bah("选择微信支付时必须提供 OpenId");
|
||
}
|
||
}
|
||
|
||
// 构建统一支付输入
|
||
var unifiedInput = new UnifiedPaymentInput
|
||
{
|
||
RegistrationId = input.RegistrationId,
|
||
PaymentMethod = input.PaymentMethod,
|
||
CouponId = null,
|
||
OpenId = input.OpenId,
|
||
PaymentProofUrl = null,
|
||
OperatorUserId = userId
|
||
};
|
||
|
||
// 调用统一支付服务(仅设置支付方式,不处理实际支付)
|
||
// 处理支付方式转换逻辑
|
||
if (input.PaymentMethodType == PaymentMethodType.WeChatPay ||
|
||
input.PaymentMethodType == PaymentMethodType.AliPay)
|
||
{
|
||
reg.PaymentMethod = 2; // 线上支付
|
||
}
|
||
else if (input.PaymentMethodType == PaymentMethodType.BankTransfer)
|
||
{
|
||
reg.PaymentMethod = 1; // 线下支付
|
||
}
|
||
else
|
||
{
|
||
// 如果没有指定具体支付方式,使用输入的支付方式
|
||
reg.PaymentMethod = input.PaymentMethod;
|
||
}
|
||
|
||
// 设置具体支付方式
|
||
reg.PaymentMethodType = input.PaymentMethodType;
|
||
|
||
// 线下支付订单状态设置为待审核
|
||
if (reg.PaymentMethod == 1)
|
||
{
|
||
reg.PaymentStatus = 0; // 待审核
|
||
}
|
||
|
||
reg.UpdatedTime = DateTime.Now;
|
||
reg.UpdatedUserId = userId;
|
||
|
||
_logger.LogInformation("成功修改支付方式,报名ID={RegistrationId},支付方式={PaymentMethod}",
|
||
input.RegistrationId, input.PaymentMethod);
|
||
|
||
return await _registrationRepository.UpdateAsync(reg);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步支付状态(主动查询微信支付)
|
||
/// </summary>
|
||
[HttpPost("sync-payment/{registrationId}")]
|
||
[UnitOfWork]
|
||
[DisplayName("同步支付状态")]
|
||
public async Task<bool> SyncPaymentStatus(long registrationId)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
return await _unifiedRegistrationService.QueryAndSyncWeChatPaymentStatusAsync(registrationId, userId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 微信支付回调通知处理
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 处理流程:
|
||
/// 1. 接收微信支付平台回调通知
|
||
/// 2. 使用平台级别的支付配置验证签名和解密数据(微信平台证书是平台级别)
|
||
/// 3. 获取订单号后查找报名记录
|
||
/// 4. 根据报名记录获取赛事信息
|
||
/// 5. 根据赛事信息获取商户级别的支付配置(用于后续可能的退款等操作)
|
||
/// 6. 更新订单支付状态
|
||
/// </remarks>
|
||
/// <returns>微信支付平台要求的响应格式</returns>
|
||
[HttpPost("wechat-pay-notify")]
|
||
[AllowAnonymous]
|
||
[UnitOfWork]
|
||
[DisplayName("微信支付回调通知")]
|
||
public async Task<IActionResult> WeChatPayNotify()
|
||
{
|
||
try
|
||
{
|
||
using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body);
|
||
var body = await reader.ReadToEndAsync();
|
||
|
||
var headers = _httpContextAccessor.HttpContext.Request.Headers;
|
||
var timestamp = headers["Wechatpay-Timestamp"].FirstOrDefault();
|
||
var nonce = headers["Wechatpay-Nonce"].FirstOrDefault();
|
||
var signature = headers["Wechatpay-Signature"].FirstOrDefault();
|
||
var serialNumber = headers["Wechatpay-Serial"].FirstOrDefault();
|
||
|
||
if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) ||
|
||
string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(serialNumber))
|
||
{
|
||
_logger.LogWarning("微信支付回调通知参数不完整");
|
||
return new JsonResult(new { code = "FAIL", message = "参数不完整" });
|
||
}
|
||
|
||
// 第一步:使用平台级别的支付配置验证签名和解密数据
|
||
// 注意:微信支付签名验证使用的是微信平台证书(平台级别),所有商户共用
|
||
var platformPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
||
if (platformPayConfig == null)
|
||
{
|
||
_logger.LogError("未找到平台级别的微信支付配置");
|
||
return new JsonResult(new { code = "FAIL", message = "支付配置未找到" });
|
||
}
|
||
|
||
var notifyRequest = new WeChatPayNotifyRequest
|
||
{
|
||
Timestamp = timestamp,
|
||
Nonce = nonce,
|
||
Signature = signature,
|
||
SerialNumber = serialNumber,
|
||
Body = body
|
||
};
|
||
|
||
// 验证签名
|
||
var isValid = await _weChatPayService.VerifyPayNotifyAsync(platformPayConfig, notifyRequest);
|
||
if (!isValid)
|
||
{
|
||
_logger.LogWarning("微信支付回调签名验证失败");
|
||
return new JsonResult(new { code = "FAIL", message = "签名验证失败" });
|
||
}
|
||
|
||
// 解密通知数据
|
||
var notifyData = await _weChatPayService.DecryptPayNotifyDataAsync(platformPayConfig, notifyRequest);
|
||
if (notifyData == null || notifyData.TradeState != "SUCCESS")
|
||
{
|
||
_logger.LogWarning("微信支付回调数据无效或交易未成功");
|
||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||
}
|
||
|
||
// 根据商户订单号查找报名记录
|
||
var reg = await _registrationRepository.GetFirstAsync(r => r.OrderNo == notifyData.OutTradeNo);
|
||
if (reg == null)
|
||
{
|
||
_logger.LogWarning($"未找到订单:{notifyData.OutTradeNo}");
|
||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||
}
|
||
|
||
if (reg.PaymentStatus == 1)
|
||
{
|
||
_logger.LogInformation($"订单 {reg.OrderNo} 已处理过支付回调");
|
||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||
}
|
||
|
||
// 第二步:获取赛事信息并验证商户配置
|
||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId);
|
||
if (evt == null)
|
||
{
|
||
_logger.LogError($"未找到赛事信息,订单号:{reg.OrderNo},赛事ID:{reg.EventId}");
|
||
return new JsonResult(new { code = "FAIL", message = "赛事信息不存在" });
|
||
}
|
||
|
||
// 验证赛事是否关联了有效的商户支付配置
|
||
var merchantPayConfig = await GetWeChatPayConfigByEventAsync(evt);
|
||
if (merchantPayConfig == null)
|
||
{
|
||
_logger.LogError("赛事未配置有效的商户支付配置,订单号={OrderNo},赛事ID={EventId}",
|
||
reg.OrderNo, evt.Id);
|
||
return new JsonResult(new { code = "FAIL", message = "商户支付配置异常" });
|
||
}
|
||
|
||
_logger.LogInformation("订单支付成功,订单号={OrderNo},赛事ID={EventId},商户ID={MerchantId}",
|
||
reg.OrderNo, evt.Id, evt.MerchantId);
|
||
|
||
// 使用统一的支付成功处理方法
|
||
await _unifiedRegistrationService.HandlePaymentSuccessAsync(
|
||
reg.OrderNo,
|
||
notifyData.TransactionId,
|
||
notifyData.SuccessTime?.DateTime,
|
||
reg.CreatedUserId);
|
||
|
||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||
}
|
||
catch (ArgumentException ex)
|
||
{
|
||
_logger.LogError(ex, "处理微信支付回调通知时参数验证失败");
|
||
return new JsonResult(new { code = "FAIL", message = "业务验证失败" });
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理微信支付回调通知异常");
|
||
return new JsonResult(new { code = "FAIL", message = "处理失败" });
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从请求中获取 OpenId 参数
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 支持从以下来源获取 OpenId(按优先级顺序):
|
||
/// 1. 查询字符串 (QueryString):openId 或 openid 参数
|
||
/// 2. 表单数据 (FormData):openId 或 openid 字段
|
||
/// 3. 请求体 (JSON):openId 或 openid 属性
|
||
/// 4. 用户 Claims(兜底方案):openid claim
|
||
/// </remarks>
|
||
/// <returns>获取到的 OpenId 值,如果未找到则返回空字符串</returns>
|
||
private string GetOpenIdFromRequest()
|
||
{
|
||
var httpContext = _httpContextAccessor.HttpContext;
|
||
if (httpContext == null)
|
||
{
|
||
_logger.LogWarning("无法获取 HttpContext,OpenId 获取失败");
|
||
return string.Empty;
|
||
}
|
||
|
||
// 1. 尝试从查询字符串获取
|
||
var openId = httpContext.Request.Query["openId"].FirstOrDefault() ??
|
||
httpContext.Request.Query["openid"].FirstOrDefault();
|
||
if (!string.IsNullOrEmpty(openId))
|
||
{
|
||
_logger.LogDebug("从查询字符串获取到 OpenId");
|
||
return openId;
|
||
}
|
||
|
||
// 2. 尝试从表单数据获取
|
||
if (httpContext.Request.HasFormContentType)
|
||
{
|
||
openId = httpContext.Request.Form["openId"].FirstOrDefault() ??
|
||
httpContext.Request.Form["openid"].FirstOrDefault();
|
||
if (!string.IsNullOrEmpty(openId))
|
||
{
|
||
_logger.LogDebug("从表单数据获取到 OpenId");
|
||
return openId;
|
||
}
|
||
}
|
||
|
||
// 3. 尝试从请求体 JSON 中获取(需要先读取)
|
||
// 注意:为了避免多次读取请求体,这里暂时不实现
|
||
// 如果需要从 JSON 中获取,可以在具体接口的 DTO 中添加 openId 字段
|
||
|
||
// 4. 兜底方案:从用户 Claims 中获取
|
||
openId = httpContext.User?.FindFirst("openid")?.Value;
|
||
if (!string.IsNullOrEmpty(openId))
|
||
{
|
||
_logger.LogDebug("从用户 Claims 中获取到 OpenId");
|
||
return openId;
|
||
}
|
||
|
||
_logger.LogWarning("未在请求中找到 OpenId 参数");
|
||
return string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 OpenId 参数的有效性
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 验证规则:
|
||
/// 1. OpenId 不能为空或空白字符串
|
||
/// 2. OpenId 长度必须在合理范围内(微信 OpenId 通常为 28 个字符左右)
|
||
/// 3. OpenId 只能包含字母、数字和下划线
|
||
/// </remarks>
|
||
/// <param name="openId">待验证的 OpenId</param>
|
||
/// <param name="errorMessage">验证失败时的错误信息</param>
|
||
/// <returns>验证通过返回 true,否则返回 false</returns>
|
||
private bool ValidateOpenId(string openId, out string errorMessage)
|
||
{
|
||
errorMessage = string.Empty;
|
||
|
||
// 验证是否为空
|
||
if (string.IsNullOrWhiteSpace(openId))
|
||
{
|
||
errorMessage = "OpenId 参数不能为空";
|
||
_logger.LogWarning("OpenId 验证失败:参数为空");
|
||
return false;
|
||
}
|
||
|
||
// 验证长度(微信 OpenId 通常为 28 个字符,允许一定的波动范围)
|
||
if (openId.Length < 10 || openId.Length > 50)
|
||
{
|
||
errorMessage = $"OpenId 参数长度不合法,当前长度:{openId.Length}";
|
||
_logger.LogWarning("OpenId 验证失败:长度不合法,长度={Length}", openId.Length);
|
||
return false;
|
||
}
|
||
|
||
// 验证格式(只能包含字母、数字和下划线)
|
||
foreach (char c in openId)
|
||
{
|
||
if (!char.IsLetterOrDigit(c) && c != '_' && c != '-')
|
||
{
|
||
errorMessage = "OpenId 参数格式不合法,只能包含字母、数字、下划线和连字符";
|
||
_logger.LogWarning("OpenId 验证失败:格式不合法,OpenId={OpenId}", openId);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
_logger.LogDebug("OpenId 验证通过");
|
||
return true;
|
||
}
|
||
|
||
private EventStatus CalculateEventStatus(EventInfo e)
|
||
{
|
||
var now = DateTime.Now;
|
||
if (now < e.RegistrationStartTime) return EventStatus.NotStarted; // 未开始
|
||
if (now >= e.RegistrationStartTime && now <= e.RegistrationEndTime) return EventStatus.Enrolling; // 报名中
|
||
if (now > e.RegistrationEndTime && now < e.MatchStartTime) return EventStatus.RegistrationEnded; // 报名结束
|
||
if (now >= e.MatchStartTime && now <= e.MatchEndTime) return EventStatus.Ongoing; // 比赛中
|
||
if (now > e.MatchEndTime) return EventStatus.Ended; // 已结束
|
||
return EventStatus.Enrolling; // 默认返回报名中
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传赛事素材
|
||
/// </summary>
|
||
/// <param name="input">上传素材输入参数</param>
|
||
/// <returns>操作是否成功</returns>
|
||
[HttpPost("upload-material")]
|
||
[DisplayName("上传赛事素材")]
|
||
public async Task<bool> UploadMaterial([FromBody] YD_Event.Application.Client.Dtos.UploadMaterialInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
var registration = await _registrationRepository.GetFirstAsync(r => r.Id == input.RegistrationId && !r.DeleteMark);
|
||
if (registration == null) throw Oops.Bah("报名记录不存在");
|
||
|
||
if (registration.CreatedUserId != userId) throw Oops.Bah("无权操作此报名记录");
|
||
|
||
if (registration.PaymentStatus == 4) throw Oops.Bah("报名已取消,无法上传素材");
|
||
|
||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == registration.EventId && !e.DeleteMark);
|
||
if (evt == null) throw Oops.Bah("赛事不存在");
|
||
|
||
if (evt.IsNeedMaterial == false) throw Oops.Bah("此赛事不需要上传素材");
|
||
|
||
if (evt.MaterialUploadStartTime.HasValue && DateTime.Now < evt.MaterialUploadStartTime.Value)
|
||
{
|
||
throw Oops.Bah("素材上传尚未开始");
|
||
}
|
||
|
||
if (evt.MaterialUploadEndTime.HasValue && DateTime.Now > evt.MaterialUploadEndTime.Value)
|
||
{
|
||
throw Oops.Bah("素材上传已结束");
|
||
}
|
||
|
||
registration.MaterialUrl = input.MaterialUrl;
|
||
registration.UpdatedTime = DateTime.Now;
|
||
registration.UpdatedUserId = userId;
|
||
|
||
return await _registrationRepository.UpdateAsync(registration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消报名并申请退款
|
||
/// </summary>
|
||
[HttpPost("cancel-registration-refund")]
|
||
[UnitOfWork]
|
||
[DisplayName("取消报名退款")]
|
||
public async Task CancelRegistrationRefund([FromBody] CancelRegistrationRefundInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
// 验证报名记录
|
||
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
|
||
if (registration == null) throw Oops.Bah("报名记录不存在");
|
||
if (registration.CreatedUserId != userId) throw Oops.Bah("无权操作此报名记录");
|
||
if (registration.PaymentStatus != 1) throw Oops.Bah("只有已支付的报名才能申请退款");
|
||
|
||
// 获取订单信息
|
||
var order = await _orderRepository.GetFirstAsync(x => x.RegistrationId == input.RegistrationId && x.OrderType == 1);
|
||
if (order == null) throw Oops.Bah("订单不存在");
|
||
if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
|
||
|
||
// 获取赛事信息
|
||
var eventInfo = await _eventRepository.GetByIdAsync(order.EventId);
|
||
if (eventInfo == null) throw Oops.Bah("赛事信息不存在");
|
||
|
||
// 计算退款金额
|
||
var refundAmount = await CalculateRefundAmount(order.ActualAmount, eventInfo.MatchStartTime);
|
||
|
||
// 处理退款
|
||
await ProcessRefundAsync(order, registration, refundAmount, input.RefundReason ?? "用户取消报名", userId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查报名限制
|
||
/// </summary>
|
||
/// <param name="input">报名限制检查输入参数</param>
|
||
/// <returns>报名限制检查结果</returns>
|
||
[HttpPost("registration-limit/check")]
|
||
[DisplayName("检查报名限制")]
|
||
public async Task<RegistrationLimitCheckOutput> CheckRegistrationLimit([FromBody] UnifiedRegistrationInput input)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
input.CreatedUserId = userId;
|
||
return await _unifiedRegistrationService.CheckRegistrationLimitAsync(input);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算退款金额
|
||
/// </summary>
|
||
private async Task<decimal> CalculateRefundAmount(decimal totalAmount, DateTime eventStartTime)
|
||
{
|
||
var configs = await _refundConfigRepository.AsQueryable()
|
||
.Where(x => x.Status == Core.Enum.AvailabilityStatus.Enable && !x.DeleteMark)
|
||
.OrderBy(x => x.DayStart)
|
||
.ToListAsync();
|
||
|
||
if (!configs.Any()) return totalAmount;
|
||
|
||
var daysRemaining = (eventStartTime - DateTime.Now).TotalDays;
|
||
if (daysRemaining < 0) daysRemaining = 0;
|
||
|
||
var matchConfig = configs.FirstOrDefault(c =>
|
||
daysRemaining >= c.DayStart &&
|
||
(c.DayEnd == int.MaxValue ? true : daysRemaining < c.DayEnd)
|
||
);
|
||
|
||
if (matchConfig != null)
|
||
{
|
||
var deductAmount = totalAmount * (matchConfig.DeductPercentage / 100m);
|
||
return totalAmount - deductAmount;
|
||
}
|
||
|
||
return totalAmount;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理退款核心逻辑
|
||
/// </summary>
|
||
private async Task ProcessRefundAsync(
|
||
YD_Event.Core.Entities.Order order,
|
||
EventRegistration registration,
|
||
decimal refundAmount,
|
||
string refundReason,
|
||
long userId)
|
||
{
|
||
var now = DateTime.Now;
|
||
|
||
// 获取微信支付配置
|
||
var weChatPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x => !x.DeleteMark);
|
||
|
||
// 生成退款单号
|
||
var outRefundNo = "RF" + DateTime.Now.ToString("yyyyMMddHHmmss") + new Random().Next(1000, 9999);
|
||
|
||
// 创建退款记录
|
||
var refundRecord = new RefundRecord
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
OutRefundNo = outRefundNo,
|
||
RegistrationId = registration.Id,
|
||
OutTradeNo = order.OrderNo,
|
||
RefundAmount = refundAmount,
|
||
TotalAmount = order.ActualAmount,
|
||
RefundReason = refundReason,
|
||
RefundStatus = 0,
|
||
ApplyTime = now,
|
||
WeChatPayConfigId = weChatPayConfig?.Id,
|
||
CreatedTime = now,
|
||
CreatedUserId = userId,
|
||
Status = Core.Enum.AvailabilityStatus.Enable
|
||
};
|
||
|
||
// 获取支付记录中的微信交易号
|
||
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == registration.Id);
|
||
if (payment != null && !string.IsNullOrEmpty(payment.TransactionNo))
|
||
{
|
||
refundRecord.TransactionId = payment.TransactionNo;
|
||
}
|
||
|
||
await _refundRecordRepository.InsertAsync(refundRecord);
|
||
|
||
// 如果是线上支付,调用微信退款接口
|
||
if (registration.PaymentMethod == 2 && weChatPayConfig != null)
|
||
{
|
||
await CreateWeChatRefundAsync(refundRecord, order, weChatPayConfig, refundAmount, registration, userId);
|
||
}
|
||
else
|
||
{
|
||
// 线下支付直接标记退款成功
|
||
await CompleteRefundAsync(refundRecord, order, registration, userId);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调用微信退款接口
|
||
/// </summary>
|
||
private async Task CreateWeChatRefundAsync(
|
||
RefundRecord refundRecord,
|
||
YD_Event.Core.Entities.Order order,
|
||
WeChatPayConfig config,
|
||
decimal refundAmount,
|
||
EventRegistration registration,
|
||
long userId)
|
||
{
|
||
try
|
||
{
|
||
var request = new YD_Event.Core.Infrastructure.WeChat.Dto.CreateRefundRequest
|
||
{
|
||
OutTradeNo = order.OrderNo,
|
||
OutRefundNo = refundRecord.OutRefundNo,
|
||
RefundAmount = (int)(refundAmount * 100),
|
||
TotalAmount = (int)(order.ActualAmount * 100),
|
||
Reason = refundRecord.RefundReason
|
||
//NotifyUrl = config.RefundNotifyUrl
|
||
};
|
||
|
||
var result = await _weChatPayService.CreateRefundAsync(config, request);
|
||
|
||
if (result.Success)
|
||
{
|
||
refundRecord.RefundId = result.RefundId;
|
||
refundRecord.WeChatResponse = result.WeChatResponse;
|
||
|
||
if (result.RefundStatus == "SUCCESS")
|
||
{
|
||
await CompleteRefundAsync(refundRecord, order, registration, userId);
|
||
}
|
||
else
|
||
{
|
||
refundRecord.RefundStatus = 0;
|
||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
refundRecord.RefundStatus = 2;
|
||
refundRecord.ErrorMessage = result.Message;
|
||
refundRecord.WeChatResponse = result.WeChatResponse;
|
||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||
throw Oops.Bah($"微信退款申请失败: {result.Message}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
refundRecord.RefundStatus = 2;
|
||
refundRecord.ErrorMessage = ex.Message;
|
||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 完成退款流程
|
||
/// </summary>
|
||
private async Task CompleteRefundAsync(
|
||
RefundRecord refundRecord,
|
||
YD_Event.Core.Entities.Order originalOrder,
|
||
EventRegistration registration,
|
||
long userId)
|
||
{
|
||
var now = DateTime.Now;
|
||
|
||
// 更新退款记录状态
|
||
refundRecord.RefundStatus = 1;
|
||
refundRecord.SuccessTime = now;
|
||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||
|
||
// 创建退款订单
|
||
var refundOrder = new YD_Event.Core.Entities.Order
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
OrderNo = refundRecord.OutRefundNo,
|
||
OrderType = 2,
|
||
MerchantId = originalOrder.MerchantId,
|
||
MerchantCertificationId = originalOrder.MerchantCertificationId,
|
||
EventId = originalOrder.EventId,
|
||
RegistrationId = originalOrder.RegistrationId,
|
||
RelatedOrderId = originalOrder.Id,
|
||
OriginalAmount = originalOrder.ActualAmount,
|
||
ActualAmount = refundRecord.RefundAmount,
|
||
OrderStatus = OrderStatus.Refunded,
|
||
PaymentTime = now,
|
||
Remark = refundRecord.RefundReason,
|
||
CreatedTime = now,
|
||
CreatedUserId = userId,
|
||
Status = Core.Enum.AvailabilityStatus.Enable
|
||
};
|
||
await _orderRepository.InsertAsync(refundOrder);
|
||
|
||
// 更新原订单状态
|
||
originalOrder.OrderStatus = OrderStatus.Refunded;
|
||
originalOrder.UpdatedTime = now;
|
||
originalOrder.UpdatedUserId = userId;
|
||
await _orderRepository.UpdateAsync(originalOrder);
|
||
|
||
// 更新报名状态
|
||
registration.PaymentStatus = 3;
|
||
registration.UpdatedTime = now;
|
||
registration.UpdatedUserId = userId;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
// 更新支付记录
|
||
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == originalOrder.RegistrationId);
|
||
if (payment != null)
|
||
{
|
||
payment.RefundStatus = 1;
|
||
payment.UpdatedTime = now;
|
||
payment.UpdatedUserId = userId;
|
||
await _paymentRepository.UpdateAsync(payment);
|
||
}
|
||
}
|
||
}
|