From 40c0a5ac641d3448d089f949beb6042adc13188b Mon Sep 17 00:00:00 2001 From: ccnetcore Date: Wed, 13 Aug 2025 22:19:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E4=BB=98=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=90=8E=E8=87=AA=E5=8A=A8=E4=B8=BA=E7=94=A8=E6=88=B7=E5=85=85?= =?UTF-8?q?=E5=80=BCVIP=E5=B9=B6=E6=94=AF=E6=8C=81=E6=8C=89=E5=95=86?= =?UTF-8?q?=E5=93=81=E7=B1=BB=E5=9E=8B=E8=AE=A1=E7=AE=97=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/Pay/CreateOrderInput.cs | 2 + .../Dtos/Recharge/RechargeCreateInput.cs | 5 ++- .../IServices/IRechargeService.cs | 11 +++++- .../Services/PayService.cs | 21 ++++++++-- .../Services/RechargeService.cs | 24 +++++++++++- .../Enums/GoodsTypeEnum.cs | 38 ++++++++++++++++--- .../Alipay/AlipayManager.cs | 7 +++- .../Managers/PayManager.cs | 14 +++---- 8 files changed, 98 insertions(+), 24 deletions(-) diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/CreateOrderInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/CreateOrderInput.cs index 58887ba5..d2e85949 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/CreateOrderInput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/CreateOrderInput.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Yi.Framework.AiHub.Domain.Shared.Enums; namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay; @@ -10,5 +11,6 @@ public class CreateOrderInput /// /// 商品类型 /// + [Required] public GoodsTypeEnum GoodsType { get; set; } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Recharge/RechargeCreateInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Recharge/RechargeCreateInput.cs index a8354fb7..96aa06ce 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Recharge/RechargeCreateInput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Recharge/RechargeCreateInput.cs @@ -25,9 +25,10 @@ public class RechargeCreateInput public string Content { get; set; } = string.Empty; /// - /// 到期时间(为空表示永久VIP) + /// VIP月数(为空或0表示永久VIP) /// - public DateTime? ExpireDateTime { get; set; } + [Range(0, int.MaxValue, ErrorMessage = "月数必须大于等于0")] + public int? Months { get; set; } /// /// 备注 diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IRechargeService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IRechargeService.cs index 6d821715..838d392f 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IRechargeService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IRechargeService.cs @@ -1,4 +1,6 @@ -namespace Yi.Framework.AiHub.Application.Contracts.IServices; +using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge; + +namespace Yi.Framework.AiHub.Application.Contracts.IServices; public interface IRechargeService { @@ -6,4 +8,11 @@ public interface IRechargeService /// 移除用户vip及角色 /// Task RemoveVipRoleByExpireAsync(); + + /// + /// 给用户充值VIP + /// + /// 充值输入参数 + /// + Task RechargeVipAsync(RechargeCreateInput input); } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs index 89711314..77e00f93 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs @@ -14,6 +14,7 @@ using Yi.Framework.AiHub.Domain.Entities.Pay; using Yi.Framework.SqlSugarCore.Abstractions; using System.ComponentModel; using System.Reflection; +using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge; namespace Yi.Framework.AiHub.Application.Services; @@ -26,16 +27,19 @@ public class PayService : ApplicationService, IPayService private readonly PayManager _payManager; private readonly ILogger _logger; private readonly ISqlSugarRepository _payOrderRepository; + private readonly IRechargeService _rechargeService; public PayService( AlipayManager alipayManager, PayManager payManager, - ILogger logger, ISqlSugarRepository payOrderRepository) + ILogger logger, ISqlSugarRepository payOrderRepository, + IRechargeService rechargeService) { _alipayManager = alipayManager; _payManager = payManager; _logger = logger; _payOrderRepository = payOrderRepository; + _rechargeService = rechargeService; } /// @@ -89,7 +93,7 @@ public class PayService : ApplicationService, IPayService // 3. 记录支付通知 - await _payManager.RecordPayNoticeAsync(notifyData,signStr); + await _payManager.RecordPayNoticeAsync(notifyData, signStr); // 4. 更新订单状态 var outTradeNo = notifyData.GetValueOrDefault("out_trade_no", string.Empty); @@ -99,9 +103,20 @@ public class PayService : ApplicationService, IPayService if (!string.IsNullOrEmpty(outTradeNo) && !string.IsNullOrEmpty(tradeStatus)) { var status = ParseTradeStatus(tradeStatus); - await _payManager.UpdateOrderStatusAsync(outTradeNo, status, tradeNo); + var order = await _payManager.UpdateOrderStatusAsync(outTradeNo, status, tradeNo); _logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus); + + //5.充值Vip + await _rechargeService.RechargeVipAsync(new RechargeCreateInput + { + UserId = order.UserId, + RechargeAmount = order.TotalAmount, + Content = order.GoodsName, + Months = order.GoodsType.GetValidMonths(), + Remark = "自助充值", + ContactInfo = null + }); } else { diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/RechargeService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/RechargeService.cs index 0bc30196..082a9093 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/RechargeService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/RechargeService.cs @@ -13,7 +13,7 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services { - public class RechargeService : ApplicationService,IRechargeService + public class RechargeService : ApplicationService, IRechargeService { private readonly ISqlSugarRepository _repository; private readonly ICurrentUser _currentUser; @@ -57,13 +57,33 @@ namespace Yi.Framework.AiHub.Application.Services [HttpPost("recharge/vip")] public async Task RechargeVipAsync(RechargeCreateInput input) { + DateTime? expireDateTime = null; + + // 如果传入了月数,计算过期时间 + if (input.Months.HasValue && input.Months.Value > 0) + { + // 直接查询该用户最大的过期时间 + var maxExpireTime = await _repository._DbQueryable + .Where(x => x.UserId == input.UserId && x.ExpireDateTime.HasValue) + .MaxAsync(x => x.ExpireDateTime); + + // 如果最大过期时间大于现在时间,从最大过期时间开始计算 + // 否则从当天开始计算 + DateTime baseDateTime = maxExpireTime.HasValue && maxExpireTime.Value > DateTime.Now + ? maxExpireTime.Value + : DateTime.Now; + // 计算新的过期时间 + expireDateTime = baseDateTime.AddMonths(input.Months.Value); + } + // 如果月数为空或0,表示永久VIP,ExpireDateTime保持为null + // 创建充值记录 var rechargeRecord = new AiRechargeAggregateRoot { UserId = input.UserId, RechargeAmount = input.RechargeAmount, Content = input.Content, - ExpireDateTime = input.ExpireDateTime, + ExpireDateTime = expireDateTime, Remark = input.Remark, ContactInfo = input.ContactInfo }; diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs index 6d674456..83b62e0d 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs @@ -10,10 +10,12 @@ namespace Yi.Framework.AiHub.Domain.Shared.Enums; public class PriceAttribute : Attribute { public decimal Price { get; } + public int ValidMonths { get; } - public PriceAttribute(double price) + public PriceAttribute(double price, int validMonths) { Price = (decimal)price; + ValidMonths = validMonths; } } @@ -36,23 +38,23 @@ public class DisplayNameAttribute : Attribute /// public enum GoodsTypeEnum { - [Price(0.01)] + [Price(0.01, 1)] [DisplayName("YiXinVip Test")] YiXinVipTest = 0, - [Price(29.9)] + [Price(29.9, 1)] [DisplayName("YiXinVip 1 month")] YiXinVip1 = 1, - [Price(80.7)] + [Price(80.7, 3)] [DisplayName("YiXinVip 3 month")] YiXinVip3 = 3, - [Price(143.9)] + [Price(143.9, 6)] [DisplayName("YiXinVip 6 month")] YiXinVip6 = 6, - [Price(199.9)] + [Price(199.9, 10)] [DisplayName("YiXinVip 10 month")] YiXinVip10 = 10 } @@ -93,4 +95,28 @@ public static class GoodsTypeEnumExtensions var displayNameAttribute = fieldInfo?.GetCustomAttribute(); return displayNameAttribute?.DisplayName ?? goodsType.ToString(); } + + /// + /// 获取商品有效月份 + /// + /// 商品类型 + /// 有效月份 + public static int GetValidMonths(this GoodsTypeEnum goodsType) + { + var fieldInfo = goodsType.GetType().GetField(goodsType.ToString()); + var priceAttribute = fieldInfo?.GetCustomAttribute(); + return priceAttribute?.ValidMonths ?? 1; + } + + /// + /// 获取商品月均价格 + /// + /// 商品类型 + /// 月均价格 + public static decimal GetMonthlyPrice(this GoodsTypeEnum goodsType) + { + var totalPrice = goodsType.GetTotalAmount(); + var validMonths = goodsType.GetValidMonths(); + return validMonths > 0 ? totalPrice / validMonths : 0m; + } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Alipay/AlipayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Alipay/AlipayManager.cs index f967fa15..5a36122c 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Alipay/AlipayManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Alipay/AlipayManager.cs @@ -1,6 +1,7 @@ using Alipay.EasySDK.Factory; using Alipay.EasySDK.Kernel.Util; using Alipay.EasySDK.Payment.Page.Models; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Services; @@ -9,10 +10,12 @@ namespace Yi.Framework.AiHub.Domain.Alipay; public class AlipayManager : DomainService { private readonly ILogger _logger; + private readonly IConfiguration _configuration; - public AlipayManager(ILogger logger) + public AlipayManager(ILogger logger, IConfiguration configuration) { _logger = logger; + _configuration = configuration; } /// @@ -27,7 +30,7 @@ public class AlipayManager : DomainService { // 2. 发起API调用(以创建当面付收款二维码为例) var response = Factory.Payment.Page() - .Pay(productName, orderNumber, totalAmount.ToString(), "https://ccnetcore.com/pay/sucess"); + .Pay(productName, orderNumber, totalAmount.ToString(), _configuration["Alipay:ReturnUrl"]); // 3. 处理响应或异常 if (ResponseChecker.Success(response)) { diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs index 316b4e15..4939005b 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs @@ -69,7 +69,7 @@ public class PayManager : DomainService /// 交易状态 /// 支付宝交易号 /// - public async Task UpdateOrderStatusAsync(string outTradeNo, TradeStatusEnum tradeStatus, string? tradeNo = null) + public async Task UpdateOrderStatusAsync(string outTradeNo, TradeStatusEnum tradeStatus, string? tradeNo = null) { var order = await _payOrderRepository.GetFirstAsync(x => x.OutTradeNo == outTradeNo); if (order == null) @@ -84,6 +84,7 @@ public class PayManager : DomainService } await _payOrderRepository.UpdateAsync(order); + return order; } @@ -128,13 +129,10 @@ public class PayManager : DomainService /// private TradeStatusEnum ParseTradeStatus(string tradeStatus) { - return tradeStatus switch + if (Enum.TryParse(tradeStatus, out var result)) { - "WAIT_BUYER_PAY" => TradeStatusEnum.WAIT_BUYER_PAY, - "TRADE_SUCCESS" => TradeStatusEnum.TRADE_SUCCESS, - "TRADE_FINISHED" => TradeStatusEnum.TRADE_FINISHED, - "TRADE_CLOSED" => TradeStatusEnum.TRADE_CLOSED, - _ => TradeStatusEnum.WAIT_TRADE - }; + return result; + } + return TradeStatusEnum.WAIT_TRADE; } } \ No newline at end of file