feat: 激活码与VIP充值支持按天计费

- 新增 VIP 天数概念,支持月数与天数组合计算过期时间
- 激活码商品新增 VipDays 配置,并新增 1 天会员试用组合包
- VIP 充值统一按天数计算(1 个月 = 31 天),兼容原有逻辑
- 激活码兑换时支持仅天数或天月组合的 VIP 充值
This commit is contained in:
ccnetcore
2025-12-28 17:44:33 +08:00
parent bdaa53bac8
commit 4b9f845fae
4 changed files with 37 additions and 11 deletions

View File

@@ -25,11 +25,17 @@ public class RechargeCreateInput
public string Content { get; set; } = string.Empty; public string Content { get; set; } = string.Empty;
/// <summary> /// <summary>
/// VIP月数为空或0表示永久VIP /// VIP月数为空或0表示永久VIP1个月按31天计算
/// </summary> /// </summary>
[Range(0, int.MaxValue, ErrorMessage = "月数必须大于等于0")] [Range(0, int.MaxValue, ErrorMessage = "月数必须大于等于0")]
public int? Months { get; set; } public int? Months { get; set; }
/// <summary>
/// VIP天数为空或0表示不使用天数充值
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "天数必须大于等于0")]
public int? Days { get; set; }
/// <summary> /// <summary>
/// 备注 /// 备注
/// </summary> /// </summary>

View File

@@ -92,7 +92,7 @@ public class ActivationCodeService : ApplicationService, IActivationCodeService
isCreateRechargeRecord: !goods.IsCombo); isCreateRechargeRecord: !goods.IsCombo);
} }
if (goods.VipMonths > 0) if (goods.VipMonths > 0 || goods.VipDays > 0)
{ {
await _rechargeService.RechargeVipAsync(new RechargeCreateInput await _rechargeService.RechargeVipAsync(new RechargeCreateInput
{ {
@@ -100,6 +100,7 @@ public class ActivationCodeService : ApplicationService, IActivationCodeService
RechargeAmount = totalAmount, RechargeAmount = totalAmount,
Content = packageName, Content = packageName,
Months = goods.VipMonths, Months = goods.VipMonths,
Days = goods.VipDays,
Remark = "激活码兑换", Remark = "激活码兑换",
ContactInfo = null ContactInfo = null
}); });

View File

@@ -55,13 +55,24 @@ namespace Yi.Framework.AiHub.Application.Services
/// </summary> /// </summary>
/// <param name="input">充值输入参数</param> /// <param name="input">充值输入参数</param>
/// <returns></returns> /// <returns></returns>
[HttpPost("recharge/vip")] [RemoteService(isEnabled:false)]
public async Task RechargeVipAsync(RechargeCreateInput input) public async Task RechargeVipAsync(RechargeCreateInput input)
{ {
DateTime? expireDateTime = null; DateTime? expireDateTime = null;
// 如果传入了月数,计算过期时间 // 计算总天数1个月 = 31天
int totalDays = 0;
if (input.Months.HasValue && input.Months.Value > 0) if (input.Months.HasValue && input.Months.Value > 0)
{
totalDays += input.Months.Value * 31;
}
if (input.Days.HasValue && input.Days.Value > 0)
{
totalDays += input.Days.Value;
}
// 如果有天数,计算过期时间
if (totalDays > 0)
{ {
// 直接查询该用户最大的过期时间 // 直接查询该用户最大的过期时间
var maxExpireTime = await _repository._DbQueryable var maxExpireTime = await _repository._DbQueryable
@@ -75,9 +86,9 @@ namespace Yi.Framework.AiHub.Application.Services
? maxExpireTime.Value ? maxExpireTime.Value
: DateTime.Now; : DateTime.Now;
// 计算新的过期时间 // 计算新的过期时间
expireDateTime = baseDateTime.AddMonths(input.Months.Value); expireDateTime = baseDateTime.AddDays(totalDays);
} }
// 如果月数为空或0表示永久VIPExpireDateTime保持为null // 如果总天数为0表示永久VIPExpireDateTime保持为null
// 创建充值记录 // 创建充值记录
var rechargeRecord = new AiRechargeAggregateRoot var rechargeRecord = new AiRechargeAggregateRoot

View File

@@ -12,6 +12,7 @@ public class ActivationCodeGoodsAttribute : Attribute
public decimal Price { get; } public decimal Price { get; }
public long TokenAmount { get; } public long TokenAmount { get; }
public int VipMonths { get; } public int VipMonths { get; }
public int VipDays { get; }
public bool IsCombo { get; } public bool IsCombo { get; }
public bool IsReusable { get; } public bool IsReusable { get; }
public bool IsSameTypeOnce { get; } public bool IsSameTypeOnce { get; }
@@ -26,11 +27,13 @@ public class ActivationCodeGoodsAttribute : Attribute
bool isReusable, bool isReusable,
bool isSameTypeOnce, bool isSameTypeOnce,
string displayName, string displayName,
string content) string content,
int vipDays = 0)
{ {
Price = (decimal)price; Price = (decimal)price;
TokenAmount = tokenAmount; TokenAmount = tokenAmount;
VipMonths = vipMonths; VipMonths = vipMonths;
VipDays = vipDays;
IsCombo = isCombo; IsCombo = isCombo;
IsReusable = isReusable; IsReusable = isReusable;
IsSameTypeOnce = isSameTypeOnce; IsSameTypeOnce = isSameTypeOnce;
@@ -86,7 +89,12 @@ public enum ActivationCodeGoodsTypeEnum
isSameTypeOnce: false, displayName: "1亿 尊享Token", content: "特价包")] isSameTypeOnce: false, displayName: "1亿 尊享Token", content: "特价包")]
Premium1Yi = 6, Premium1Yi = 6,
/// <summary>
/// 1【意心Ai会员1天+50w 尊享Token】新人试用首单组合包
/// </summary>
[ActivationCodeGoods(price: 1, tokenAmount: 500000, vipMonths: 0, vipDays: 1, isCombo: true, isReusable: false,
isSameTypeOnce: true, displayName: "意心Ai会员1天+50w 尊享Token", content: "新人首单组合包")]
Vip1DayTest = 7,
/// <summary> /// <summary>
/// 0【10w 尊享Token】免费包 /// 0【10w 尊享Token】免费包