feat: 支付完成后自动为用户充值VIP并支持按商品类型计算有效期
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
@@ -10,5 +11,6 @@ public class CreateOrderInput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品类型
|
/// 商品类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required]
|
||||||
public GoodsTypeEnum GoodsType { get; set; }
|
public GoodsTypeEnum GoodsType { get; set; }
|
||||||
}
|
}
|
||||||
@@ -25,9 +25,10 @@ public class RechargeCreateInput
|
|||||||
public string Content { get; set; } = string.Empty;
|
public string Content { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 到期时间(为空表示永久VIP)
|
/// VIP月数(为空或0表示永久VIP)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? ExpireDateTime { get; set; }
|
[Range(0, int.MaxValue, ErrorMessage = "月数必须大于等于0")]
|
||||||
|
public int? Months { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 备注
|
/// 备注
|
||||||
|
|||||||
@@ -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
|
public interface IRechargeService
|
||||||
{
|
{
|
||||||
@@ -6,4 +8,11 @@ public interface IRechargeService
|
|||||||
/// 移除用户vip及角色
|
/// 移除用户vip及角色
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task RemoveVipRoleByExpireAsync();
|
Task RemoveVipRoleByExpireAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 给用户充值VIP
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">充值输入参数</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task RechargeVipAsync(RechargeCreateInput input);
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ using Yi.Framework.AiHub.Domain.Entities.Pay;
|
|||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services;
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
@@ -26,16 +27,19 @@ public class PayService : ApplicationService, IPayService
|
|||||||
private readonly PayManager _payManager;
|
private readonly PayManager _payManager;
|
||||||
private readonly ILogger<PayService> _logger;
|
private readonly ILogger<PayService> _logger;
|
||||||
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
||||||
|
private readonly IRechargeService _rechargeService;
|
||||||
|
|
||||||
public PayService(
|
public PayService(
|
||||||
AlipayManager alipayManager,
|
AlipayManager alipayManager,
|
||||||
PayManager payManager,
|
PayManager payManager,
|
||||||
ILogger<PayService> logger, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository)
|
ILogger<PayService> logger, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository,
|
||||||
|
IRechargeService rechargeService)
|
||||||
{
|
{
|
||||||
_alipayManager = alipayManager;
|
_alipayManager = alipayManager;
|
||||||
_payManager = payManager;
|
_payManager = payManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_payOrderRepository = payOrderRepository;
|
_payOrderRepository = payOrderRepository;
|
||||||
|
_rechargeService = rechargeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -89,7 +93,7 @@ public class PayService : ApplicationService, IPayService
|
|||||||
|
|
||||||
|
|
||||||
// 3. 记录支付通知
|
// 3. 记录支付通知
|
||||||
await _payManager.RecordPayNoticeAsync(notifyData,signStr);
|
await _payManager.RecordPayNoticeAsync(notifyData, signStr);
|
||||||
|
|
||||||
// 4. 更新订单状态
|
// 4. 更新订单状态
|
||||||
var outTradeNo = notifyData.GetValueOrDefault("out_trade_no", string.Empty);
|
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))
|
if (!string.IsNullOrEmpty(outTradeNo) && !string.IsNullOrEmpty(tradeStatus))
|
||||||
{
|
{
|
||||||
var status = ParseTradeStatus(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);
|
_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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
|||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services
|
namespace Yi.Framework.AiHub.Application.Services
|
||||||
{
|
{
|
||||||
public class RechargeService : ApplicationService,IRechargeService
|
public class RechargeService : ApplicationService, IRechargeService
|
||||||
{
|
{
|
||||||
private readonly ISqlSugarRepository<AiRechargeAggregateRoot> _repository;
|
private readonly ISqlSugarRepository<AiRechargeAggregateRoot> _repository;
|
||||||
private readonly ICurrentUser _currentUser;
|
private readonly ICurrentUser _currentUser;
|
||||||
@@ -57,13 +57,33 @@ namespace Yi.Framework.AiHub.Application.Services
|
|||||||
[HttpPost("recharge/vip")]
|
[HttpPost("recharge/vip")]
|
||||||
public async Task RechargeVipAsync(RechargeCreateInput input)
|
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
|
var rechargeRecord = new AiRechargeAggregateRoot
|
||||||
{
|
{
|
||||||
UserId = input.UserId,
|
UserId = input.UserId,
|
||||||
RechargeAmount = input.RechargeAmount,
|
RechargeAmount = input.RechargeAmount,
|
||||||
Content = input.Content,
|
Content = input.Content,
|
||||||
ExpireDateTime = input.ExpireDateTime,
|
ExpireDateTime = expireDateTime,
|
||||||
Remark = input.Remark,
|
Remark = input.Remark,
|
||||||
ContactInfo = input.ContactInfo
|
ContactInfo = input.ContactInfo
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
|||||||
public class PriceAttribute : Attribute
|
public class PriceAttribute : Attribute
|
||||||
{
|
{
|
||||||
public decimal Price { get; }
|
public decimal Price { get; }
|
||||||
|
public int ValidMonths { get; }
|
||||||
|
|
||||||
public PriceAttribute(double price)
|
public PriceAttribute(double price, int validMonths)
|
||||||
{
|
{
|
||||||
Price = (decimal)price;
|
Price = (decimal)price;
|
||||||
|
ValidMonths = validMonths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,23 +38,23 @@ public class DisplayNameAttribute : Attribute
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum GoodsTypeEnum
|
public enum GoodsTypeEnum
|
||||||
{
|
{
|
||||||
[Price(0.01)]
|
[Price(0.01, 1)]
|
||||||
[DisplayName("YiXinVip Test")]
|
[DisplayName("YiXinVip Test")]
|
||||||
YiXinVipTest = 0,
|
YiXinVipTest = 0,
|
||||||
|
|
||||||
[Price(29.9)]
|
[Price(29.9, 1)]
|
||||||
[DisplayName("YiXinVip 1 month")]
|
[DisplayName("YiXinVip 1 month")]
|
||||||
YiXinVip1 = 1,
|
YiXinVip1 = 1,
|
||||||
|
|
||||||
[Price(80.7)]
|
[Price(80.7, 3)]
|
||||||
[DisplayName("YiXinVip 3 month")]
|
[DisplayName("YiXinVip 3 month")]
|
||||||
YiXinVip3 = 3,
|
YiXinVip3 = 3,
|
||||||
|
|
||||||
[Price(143.9)]
|
[Price(143.9, 6)]
|
||||||
[DisplayName("YiXinVip 6 month")]
|
[DisplayName("YiXinVip 6 month")]
|
||||||
YiXinVip6 = 6,
|
YiXinVip6 = 6,
|
||||||
|
|
||||||
[Price(199.9)]
|
[Price(199.9, 10)]
|
||||||
[DisplayName("YiXinVip 10 month")]
|
[DisplayName("YiXinVip 10 month")]
|
||||||
YiXinVip10 = 10
|
YiXinVip10 = 10
|
||||||
}
|
}
|
||||||
@@ -93,4 +95,28 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var displayNameAttribute = fieldInfo?.GetCustomAttribute<DisplayNameAttribute>();
|
var displayNameAttribute = fieldInfo?.GetCustomAttribute<DisplayNameAttribute>();
|
||||||
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
|
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品有效月份
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>有效月份</returns>
|
||||||
|
public static int GetValidMonths(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||||
|
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
||||||
|
return priceAttribute?.ValidMonths ?? 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品月均价格
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>月均价格</returns>
|
||||||
|
public static decimal GetMonthlyPrice(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var totalPrice = goodsType.GetTotalAmount();
|
||||||
|
var validMonths = goodsType.GetValidMonths();
|
||||||
|
return validMonths > 0 ? totalPrice / validMonths : 0m;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Alipay.EasySDK.Factory;
|
using Alipay.EasySDK.Factory;
|
||||||
using Alipay.EasySDK.Kernel.Util;
|
using Alipay.EasySDK.Kernel.Util;
|
||||||
using Alipay.EasySDK.Payment.Page.Models;
|
using Alipay.EasySDK.Payment.Page.Models;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
@@ -9,10 +10,12 @@ namespace Yi.Framework.AiHub.Domain.Alipay;
|
|||||||
public class AlipayManager : DomainService
|
public class AlipayManager : DomainService
|
||||||
{
|
{
|
||||||
private readonly ILogger<AlipayManager> _logger;
|
private readonly ILogger<AlipayManager> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
public AlipayManager(ILogger<AlipayManager> logger)
|
public AlipayManager(ILogger<AlipayManager> logger, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -27,7 +30,7 @@ public class AlipayManager : DomainService
|
|||||||
{
|
{
|
||||||
// 2. 发起API调用(以创建当面付收款二维码为例)
|
// 2. 发起API调用(以创建当面付收款二维码为例)
|
||||||
var response = Factory.Payment.Page()
|
var response = Factory.Payment.Page()
|
||||||
.Pay(productName, orderNumber, totalAmount.ToString(), "https://ccnetcore.com/pay/sucess");
|
.Pay(productName, orderNumber, totalAmount.ToString(), _configuration["Alipay:ReturnUrl"]);
|
||||||
// 3. 处理响应或异常
|
// 3. 处理响应或异常
|
||||||
if (ResponseChecker.Success(response))
|
if (ResponseChecker.Success(response))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class PayManager : DomainService
|
|||||||
/// <param name="tradeStatus">交易状态</param>
|
/// <param name="tradeStatus">交易状态</param>
|
||||||
/// <param name="tradeNo">支付宝交易号</param>
|
/// <param name="tradeNo">支付宝交易号</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task UpdateOrderStatusAsync(string outTradeNo, TradeStatusEnum tradeStatus, string? tradeNo = null)
|
public async Task<PayOrderAggregateRoot> UpdateOrderStatusAsync(string outTradeNo, TradeStatusEnum tradeStatus, string? tradeNo = null)
|
||||||
{
|
{
|
||||||
var order = await _payOrderRepository.GetFirstAsync(x => x.OutTradeNo == outTradeNo);
|
var order = await _payOrderRepository.GetFirstAsync(x => x.OutTradeNo == outTradeNo);
|
||||||
if (order == null)
|
if (order == null)
|
||||||
@@ -84,6 +84,7 @@ public class PayManager : DomainService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _payOrderRepository.UpdateAsync(order);
|
await _payOrderRepository.UpdateAsync(order);
|
||||||
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -128,13 +129,10 @@ public class PayManager : DomainService
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private TradeStatusEnum ParseTradeStatus(string tradeStatus)
|
private TradeStatusEnum ParseTradeStatus(string tradeStatus)
|
||||||
{
|
{
|
||||||
return tradeStatus switch
|
if (Enum.TryParse<TradeStatusEnum>(tradeStatus, out var result))
|
||||||
{
|
{
|
||||||
"WAIT_BUYER_PAY" => TradeStatusEnum.WAIT_BUYER_PAY,
|
return result;
|
||||||
"TRADE_SUCCESS" => TradeStatusEnum.TRADE_SUCCESS,
|
}
|
||||||
"TRADE_FINISHED" => TradeStatusEnum.TRADE_FINISHED,
|
return TradeStatusEnum.WAIT_TRADE;
|
||||||
"TRADE_CLOSED" => TradeStatusEnum.TRADE_CLOSED,
|
|
||||||
_ => TradeStatusEnum.WAIT_TRADE
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user