2025-08-13 17:42:13 +08:00
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2025-08-13 12:07:35 +08:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using Volo.Abp.Application.Services;
|
|
|
|
|
|
using Yi.Framework.AiHub.Domain.Alipay;
|
|
|
|
|
|
using Yi.Framework.AiHub.Domain.Managers;
|
|
|
|
|
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
|
|
|
|
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Yi.Framework.AiHub.Application.Contracts.IServices;
|
|
|
|
|
|
using Volo.Abp;
|
|
|
|
|
|
using Yi.Framework.AiHub.Domain.Entities.Pay;
|
|
|
|
|
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
2025-08-13 18:30:56 +08:00
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
|
using System.Reflection;
|
2025-10-12 20:07:58 +08:00
|
|
|
|
using Volo.Abp.Users;
|
2025-08-13 22:19:31 +08:00
|
|
|
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Yi.Framework.AiHub.Application.Services;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 支付服务
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class PayService : ApplicationService, IPayService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly AlipayManager _alipayManager;
|
|
|
|
|
|
private readonly PayManager _payManager;
|
|
|
|
|
|
private readonly ILogger<PayService> _logger;
|
|
|
|
|
|
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
2025-08-13 22:19:31 +08:00
|
|
|
|
private readonly IRechargeService _rechargeService;
|
2025-10-12 16:42:26 +08:00
|
|
|
|
private readonly PremiumPackageManager _premiumPackageManager;
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
public PayService(
|
|
|
|
|
|
AlipayManager alipayManager,
|
|
|
|
|
|
PayManager payManager,
|
2025-10-12 16:42:26 +08:00
|
|
|
|
ILogger<PayService> logger,
|
|
|
|
|
|
ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository,
|
|
|
|
|
|
IRechargeService rechargeService,
|
|
|
|
|
|
PremiumPackageManager premiumPackageManager)
|
2025-08-13 12:07:35 +08:00
|
|
|
|
{
|
|
|
|
|
|
_alipayManager = alipayManager;
|
|
|
|
|
|
_payManager = payManager;
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_payOrderRepository = payOrderRepository;
|
2025-08-13 22:19:31 +08:00
|
|
|
|
_rechargeService = rechargeService;
|
2025-10-12 16:42:26 +08:00
|
|
|
|
_premiumPackageManager = premiumPackageManager;
|
2025-08-13 12:07:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建订单并发起支付
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input">创建订单输入</param>
|
|
|
|
|
|
/// <returns>订单创建结果</returns>
|
2025-08-13 17:42:13 +08:00
|
|
|
|
[Authorize]
|
2025-08-13 12:07:35 +08:00
|
|
|
|
[HttpPost("pay/Order")]
|
|
|
|
|
|
public async Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input)
|
|
|
|
|
|
{
|
2025-10-12 16:42:26 +08:00
|
|
|
|
// 1. 通过PayManager创建订单(内部会验证VIP资格)
|
2025-08-13 12:07:35 +08:00
|
|
|
|
var order = await _payManager.CreateOrderAsync(input.GoodsType);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 通过AlipayManager发起页面支付
|
|
|
|
|
|
var paymentPageHtml = await _alipayManager.PaymentPageAsync(
|
|
|
|
|
|
order.GoodsName,
|
2025-08-13 17:42:13 +08:00
|
|
|
|
order.OutTradeNo,
|
2025-08-15 23:41:01 +08:00
|
|
|
|
order.TotalAmount,
|
|
|
|
|
|
input.ReturnUrl);
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 3. 返回结果
|
|
|
|
|
|
return new CreateOrderOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
OrderId = order.Id,
|
|
|
|
|
|
OutTradeNo = order.OutTradeNo,
|
2025-08-13 17:42:13 +08:00
|
|
|
|
PaymentPageHtml = paymentPageHtml.Body
|
2025-08-13 12:07:35 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 支付宝异步通知处理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="form">表单数据</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpPost("pay/AlipayNotify")]
|
2025-08-13 17:42:13 +08:00
|
|
|
|
[AllowAnonymous]
|
2025-08-13 12:07:35 +08:00
|
|
|
|
public async Task<string> AlipayNotifyAsync([FromForm] IFormCollection form)
|
|
|
|
|
|
{
|
2025-08-13 17:42:13 +08:00
|
|
|
|
// 1. 将表单数据转换为字典,保持原始顺序
|
2025-08-13 12:07:35 +08:00
|
|
|
|
var notifyData = new Dictionary<string, string>();
|
|
|
|
|
|
foreach (var item in form)
|
|
|
|
|
|
{
|
|
|
|
|
|
notifyData[item.Key] = item.Value.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 18:21:05 +08:00
|
|
|
|
var signStr = string.Join("&", notifyData.Select(kv => $"{kv.Key}={kv.Value}"));
|
|
|
|
|
|
_logger.LogInformation($"收到支付宝回调通知:{signStr}");
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 验证签名
|
|
|
|
|
|
await _alipayManager.VerifyNotifyAsync(notifyData);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 记录支付通知
|
2025-08-13 22:19:31 +08:00
|
|
|
|
await _payManager.RecordPayNoticeAsync(notifyData, signStr);
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 4. 更新订单状态
|
|
|
|
|
|
var outTradeNo = notifyData.GetValueOrDefault("out_trade_no", string.Empty);
|
|
|
|
|
|
var tradeStatus = notifyData.GetValueOrDefault("trade_status", string.Empty);
|
|
|
|
|
|
var tradeNo = notifyData.GetValueOrDefault("trade_no", string.Empty);
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(outTradeNo) && !string.IsNullOrEmpty(tradeStatus))
|
|
|
|
|
|
{
|
|
|
|
|
|
var status = ParseTradeStatus(tradeStatus);
|
2025-08-13 22:19:31 +08:00
|
|
|
|
var order = await _payManager.UpdateOrderStatusAsync(outTradeNo, status, tradeNo);
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
|
2025-08-13 22:19:31 +08:00
|
|
|
|
|
2025-10-15 11:53:54 +08:00
|
|
|
|
// 验证交易状态,只有交易成功才执行充值逻辑
|
|
|
|
|
|
if (status != TradeStatusEnum.TRADE_SUCCESS)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError($"订单 {outTradeNo} 状态为 {tradeStatus},不执行充值逻辑");
|
|
|
|
|
|
return "success";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 16:42:26 +08:00
|
|
|
|
// 5. 根据商品类型进行不同的处理
|
|
|
|
|
|
if (order.GoodsType.IsPremiumPackage())
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理尊享包商品:创建尊享包记录
|
|
|
|
|
|
await _premiumPackageManager.CreatePremiumPackageAsync(
|
|
|
|
|
|
order.UserId,
|
|
|
|
|
|
order.GoodsType,
|
|
|
|
|
|
order.TotalAmount,
|
|
|
|
|
|
expireMonths: null // 尊享包不设置过期时间,或者可以根据需求设置
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation(
|
|
|
|
|
|
$"用户 {order.UserId} 购买尊享包成功,订单号:{outTradeNo},商品:{order.GoodsName}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (order.GoodsType.IsVipService())
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理VIP服务商品:充值VIP
|
|
|
|
|
|
await _rechargeService.RechargeVipAsync(new RechargeCreateInput
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = order.UserId,
|
|
|
|
|
|
RechargeAmount = order.TotalAmount,
|
|
|
|
|
|
Content = order.GoodsName,
|
|
|
|
|
|
Months = order.GoodsType.GetValidMonths(),
|
|
|
|
|
|
Remark = "自助充值",
|
|
|
|
|
|
ContactInfo = null
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation(
|
|
|
|
|
|
$"用户 {order.UserId} 充值VIP成功,订单号:{outTradeNo},月数:{order.GoodsType.GetValidMonths()}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-08-13 22:19:31 +08:00
|
|
|
|
{
|
2025-10-12 16:42:26 +08:00
|
|
|
|
_logger.LogWarning($"未知的商品类型:{order.GoodsType},订单号:{outTradeNo}");
|
|
|
|
|
|
}
|
2025-08-13 12:07:35 +08:00
|
|
|
|
}
|
2025-08-13 17:42:13 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new AlipayException($"回调格式错误");
|
|
|
|
|
|
}
|
2025-08-13 12:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
return "success";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询订单状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input">查询订单状态输入</param>
|
|
|
|
|
|
/// <returns>订单状态信息</returns>
|
|
|
|
|
|
[HttpGet("pay/OrderStatus")]
|
2025-08-13 17:42:13 +08:00
|
|
|
|
[Authorize]
|
2025-08-13 12:07:35 +08:00
|
|
|
|
public async Task<QueryOrderStatusOutput> QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 通过PayManager查询订单
|
|
|
|
|
|
var order = await _payOrderRepository.GetFirstAsync(x => x.OutTradeNo == input.OutTradeNo);
|
|
|
|
|
|
if (order == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new UserFriendlyException($"订单不存在:{input.OutTradeNo}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new QueryOrderStatusOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
OrderId = order.Id,
|
|
|
|
|
|
OutTradeNo = order.OutTradeNo,
|
|
|
|
|
|
TradeNo = order.TradeNo,
|
|
|
|
|
|
TradeStatus = order.TradeStatus,
|
|
|
|
|
|
TradeStatusDescription = GetTradeStatusDescription(order.TradeStatus),
|
|
|
|
|
|
TotalAmount = order.TotalAmount,
|
|
|
|
|
|
GoodsName = order.GoodsName,
|
|
|
|
|
|
GoodsType = order.GoodsType,
|
|
|
|
|
|
CreationTime = order.CreationTime,
|
|
|
|
|
|
LastModificationTime = order.LastModificationTime
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 20:07:58 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取商品列表
|
|
|
|
|
|
/// </summary>
|
2025-10-12 21:04:08 +08:00
|
|
|
|
/// <param name="input">获取商品列表输入</param>
|
2025-10-12 20:07:58 +08:00
|
|
|
|
/// <returns>商品列表</returns>
|
|
|
|
|
|
[HttpGet("pay/GoodsList")]
|
2025-10-12 21:04:08 +08:00
|
|
|
|
public async Task<List<GoodsListOutput>> GetGoodsListAsync([FromQuery] GetGoodsListInput input)
|
2025-10-12 20:07:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
var goodsList = new List<GoodsListOutput>();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前用户的累加充值金额(仅已登录用户)
|
|
|
|
|
|
decimal totalRechargeAmount = 0m;
|
|
|
|
|
|
if (CurrentUser.IsAuthenticated)
|
|
|
|
|
|
{
|
|
|
|
|
|
totalRechargeAmount = await _payManager.GetUserTotalRechargeAmountAsync(CurrentUser.GetId());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有商品枚举
|
|
|
|
|
|
foreach (GoodsTypeEnum goodsType in Enum.GetValues(typeof(GoodsTypeEnum)))
|
|
|
|
|
|
{
|
2025-10-12 21:04:08 +08:00
|
|
|
|
// 如果指定了商品类别,则过滤
|
|
|
|
|
|
if (input.GoodsCategoryType.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var goodsCategory = goodsType.GetGoodsCategory();
|
|
|
|
|
|
if (goodsCategory != input.GoodsCategoryType.Value)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue; // 跳过不匹配的商品
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 20:07:58 +08:00
|
|
|
|
var originalPrice = goodsType.GetTotalAmount();
|
|
|
|
|
|
decimal actualPrice = originalPrice;
|
|
|
|
|
|
decimal? discountAmount = null;
|
|
|
|
|
|
string? discountDescription = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是尊享包商品,计算折扣
|
2025-10-12 21:04:08 +08:00
|
|
|
|
if (goodsType.IsPremiumPackage())
|
2025-10-12 20:07:58 +08:00
|
|
|
|
{
|
2025-10-14 23:02:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentUser.IsAuthenticated)
|
2025-10-12 20:07:58 +08:00
|
|
|
|
{
|
2025-10-12 21:04:08 +08:00
|
|
|
|
discountAmount = goodsType.CalculateDiscount(totalRechargeAmount);
|
|
|
|
|
|
actualPrice = goodsType.GetDiscountedPrice(totalRechargeAmount);
|
|
|
|
|
|
if (discountAmount > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
discountDescription = $"根据累积充值已优惠 ¥{discountAmount:F2}";
|
|
|
|
|
|
}
|
2025-10-14 23:02:44 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
discountDescription = $"累积充值过低,暂无优惠";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
discountDescription = $"登录后查看优惠";
|
2025-10-12 20:07:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var goodsItem = new GoodsListOutput
|
|
|
|
|
|
{
|
2025-10-12 21:04:08 +08:00
|
|
|
|
GoodsName = goodsType.GetChineseName(),
|
2025-10-12 20:07:58 +08:00
|
|
|
|
OriginalPrice = originalPrice,
|
2025-10-12 21:04:08 +08:00
|
|
|
|
ReferencePrice = goodsType.GetReferencePrice(),
|
2025-10-12 20:07:58 +08:00
|
|
|
|
GoodsPrice = actualPrice,
|
2025-10-12 21:04:08 +08:00
|
|
|
|
GoodsCategory = goodsType.GetGoodsCategory().ToString(),
|
|
|
|
|
|
Remark = goodsType.GetRemark(),
|
2025-10-12 20:07:58 +08:00
|
|
|
|
DiscountAmount = discountAmount,
|
2025-10-15 23:00:42 +08:00
|
|
|
|
DiscountDescription = discountDescription,
|
|
|
|
|
|
GoodsType = goodsType
|
2025-10-12 20:07:58 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
goodsList.Add(goodsItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return goodsList;
|
|
|
|
|
|
}
|
2025-10-14 23:02:44 +08:00
|
|
|
|
|
2025-10-12 20:07:58 +08:00
|
|
|
|
|
2025-08-13 12:07:35 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取交易状态描述
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="tradeStatus">交易状态</param>
|
|
|
|
|
|
/// <returns>状态描述</returns>
|
|
|
|
|
|
private string GetTradeStatusDescription(TradeStatusEnum tradeStatus)
|
|
|
|
|
|
{
|
2025-08-13 18:30:56 +08:00
|
|
|
|
var fieldInfo = tradeStatus.GetType().GetField(tradeStatus.ToString());
|
|
|
|
|
|
var descriptionAttribute = fieldInfo?.GetCustomAttribute<DescriptionAttribute>();
|
|
|
|
|
|
return descriptionAttribute?.Description ?? "未知状态";
|
2025-08-13 12:07:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 解析交易状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="tradeStatus">状态字符串</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private TradeStatusEnum ParseTradeStatus(string tradeStatus)
|
|
|
|
|
|
{
|
2025-08-13 18:30:56 +08:00
|
|
|
|
if (Enum.TryParse<TradeStatusEnum>(tradeStatus, out var result))
|
2025-08-13 12:07:35 +08:00
|
|
|
|
{
|
2025-08-13 18:30:56 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2025-10-14 23:02:44 +08:00
|
|
|
|
|
2025-08-13 18:30:56 +08:00
|
|
|
|
return TradeStatusEnum.WAIT_TRADE;
|
2025-08-13 12:07:35 +08:00
|
|
|
|
}
|
2025-10-14 23:02:44 +08:00
|
|
|
|
}
|