feat: 完成支付宝接入

This commit is contained in:
chenchun
2025-08-13 12:07:35 +08:00
parent 9332b17fc1
commit 0ba4e3240b
14 changed files with 751 additions and 21 deletions

View File

@@ -0,0 +1,10 @@
using Microsoft.Extensions.Logging;
namespace Yi.Framework.AiHub.Domain.Alipay;
public class AlipayException:UserFriendlyException
{
public AlipayException(string message, string? code = null, string? details = null, Exception? innerException = null, LogLevel logLevel = LogLevel.Warning) : base(message, code, details, innerException, logLevel)
{
}
}

View File

@@ -0,0 +1,65 @@
using Alipay.EasySDK.Factory;
using Alipay.EasySDK.Kernel.Util;
using Alipay.EasySDK.Payment.Page.Models;
using Microsoft.Extensions.Logging;
using Volo.Abp.Domain.Services;
namespace Yi.Framework.AiHub.Domain.Alipay;
public class AlipayManager : DomainService
{
private readonly ILogger<AlipayManager> _logger;
public AlipayManager(ILogger<AlipayManager> logger)
{
_logger = logger;
}
/// <summary>
/// 统一Page支付
/// </summary>
/// <returns></returns>
/// <exception cref="AlipayException"></exception>
public Task<AlipayTradePagePayResponse> PaymentPageAsync(string productName, string orderNumber, decimal totalAmount)
{
try
{
// 2. 发起API调用以创建当面付收款二维码为例
var response = Factory.Payment.Page()
.Pay(productName, orderNumber, totalAmount.ToString(), "https://ccnetcore.com/pay/sucess");
// 3. 处理响应或异常
if (ResponseChecker.Success(response))
{
_logger.LogInformation($"支付宝PaymentPage发起调用成功返回内容{response.Body}");
//插入数据库
return Task.FromResult(response);
}
else
{
throw new AlipayException($"支付宝PaymentPage发起调用失败原因{response.Body}");
}
}
catch (Exception ex)
{
throw new AlipayException($"支付宝PaymentPage发起调用错误原因{ex.Message}", innerException: ex);
}
}
/// <summary>
/// 通知验签
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
/// <exception cref="AlipayException"></exception>
public Task VerifyNotifyAsync(Dictionary<string, string> form)
{
var result = Factory.Payment.Common().VerifyNotify(form);
if (result == false)
{
throw new AlipayException($"支付宝支付,验签失败");
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,58 @@
using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing;
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Domain.Entities.Pay;
/// <summary>
/// 支付通知记录
/// </summary>
[SugarTable("Ai_PayNoticeRecord")]
public class PayNoticeRecordAggregateRoot: FullAuditedAggregateRoot<Guid>
{
/// <summary>
/// 通知时间
/// </summary>
public DateTime NotifyTime { get; set; }
/// <summary>
/// 支付宝交易号
/// </summary>
public string TradeNo { get; set; }
/// <summary>
/// 商家订单号
/// </summary>
public string OutTradeNo { get; set; }
/// <summary>
/// 买家openId
/// </summary>
public string BuyerId { get; set; }
/// <summary>
/// 订单状态
/// </summary>
public TradeStatusEnum TradeStatus { get; set; }
/// <summary>
/// 订单金额
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// 实收金额
/// </summary>
public decimal ReceiptAmount { get; set; }
/// <summary>
/// 用户支付金额
/// </summary>
public decimal BuyerPayAmount { get; set; }
/// <summary>
/// 通知原始数据
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string NotifyData { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,52 @@
using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing;
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Domain.Entities.Pay;
/// <summary>
/// 支付订单
/// </summary>
[SugarTable("Ai_PayOrder")]
public class PayOrderAggregateRoot : FullAuditedAggregateRoot<Guid>
{
/// <summary>
/// 商家订单号
/// </summary>
public string OutTradeNo { get; set; }
/// <summary>
/// 下单用户
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 下单用户名称
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 订单状态
/// </summary>
public TradeStatusEnum TradeStatus { get; set; } = TradeStatusEnum.WAIT_TRADE;
/// <summary>
/// 订单金额
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// 商品类型
/// </summary>
public GoodsTypeEnum GoodsType { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 支付宝交易号
/// </summary>
public string? TradeNo { get; set; }
}

View File

@@ -0,0 +1,137 @@
using System.Text.Json;
using Volo.Abp.Domain.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Domain.Entities.Pay;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Domain.Managers;
/// <summary>
/// 支付管理器
/// </summary>
public class PayManager : DomainService
{
private readonly ISqlSugarRepository<PayNoticeRecordAggregateRoot, Guid> _payNoticeRepository;
private readonly ICurrentUser _currentUser;
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
public PayManager(
ISqlSugarRepository<PayNoticeRecordAggregateRoot, Guid> payNoticeRepository,
ICurrentUser currentUser, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository)
{
_payNoticeRepository = payNoticeRepository;
_currentUser = currentUser;
_payOrderRepository = payOrderRepository;
}
/// <summary>
/// 创建订单
/// </summary>
/// <param name="goodsType">商品类型</param>
/// <returns>订单信息</returns>
public async Task<PayOrderAggregateRoot> CreateOrderAsync(GoodsTypeEnum goodsType)
{
// 验证用户是否登录
if (!_currentUser.IsAuthenticated)
{
throw new UserFriendlyException("用户未登录");
}
// 生成订单号
var outTradeNo = GenerateOutTradeNo();
// 获取商品信息
var goodsName = goodsType.GetDisplayName();
var totalAmount = goodsType.GetTotalAmount();
// 创建订单实体
var payOrder = new PayOrderAggregateRoot
{
OutTradeNo = outTradeNo,
UserId = _currentUser.GetId(),
UserName = _currentUser.UserName ?? string.Empty,
TotalAmount = totalAmount,
GoodsName = goodsName,
GoodsType = goodsType
};
// 保存订单
await _payOrderRepository.InsertAsync(payOrder);
return payOrder;
}
/// <summary>
/// 更新订单状态
/// </summary>
/// <param name="outTradeNo">商户订单号</param>
/// <param name="tradeStatus">交易状态</param>
/// <param name="tradeNo">支付宝交易号</param>
/// <returns></returns>
public async Task UpdateOrderStatusAsync(string outTradeNo, TradeStatusEnum tradeStatus, string? tradeNo = null)
{
var order = await _payOrderRepository.GetFirstAsync(x => x.OutTradeNo == outTradeNo);
if (order == null)
{
throw new UserFriendlyException($"订单不存在:{outTradeNo}");
}
order.TradeStatus = tradeStatus;
if (!string.IsNullOrEmpty(tradeNo))
{
order.TradeNo = tradeNo;
}
order.LastModificationTime = DateTime.Now;
await _payOrderRepository.UpdateAsync(order);
}
/// <summary>
/// 记录支付通知
/// </summary>
/// <param name="notifyData">通知数据</param>
/// <returns></returns>
public async Task RecordPayNoticeAsync(Dictionary<string, string> notifyData)
{
var payNotice = new PayNoticeRecordAggregateRoot
{
NotifyTime =DateTime.Parse(notifyData.GetValueOrDefault("notify_time", string.Empty)) ,
TradeNo = notifyData.GetValueOrDefault("trade_no", string.Empty),
OutTradeNo = notifyData.GetValueOrDefault("out_trade_no", string.Empty),
BuyerId = notifyData.GetValueOrDefault("buyer_id", string.Empty),
TradeStatus = ParseTradeStatus(notifyData.GetValueOrDefault("trade_status", string.Empty)),
TotalAmount = decimal.TryParse(notifyData.GetValueOrDefault("total_amount", "-1"), out var amount) ? amount : 0,
NotifyData = JsonSerializer.Serialize(notifyData),
};
await _payNoticeRepository.InsertAsync(payNotice);
}
/// <summary>
/// 生成商户订单号
/// </summary>
/// <returns></returns>
private string GenerateOutTradeNo()
{
return $"YI_{DateTime.Now:yyyyMMddHHmmss}_{Random.Shared.Next(1000, 9999)}";
}
/// <summary>
/// 解析交易状态
/// </summary>
/// <param name="tradeStatus">状态字符串</param>
/// <returns></returns>
private TradeStatusEnum ParseTradeStatus(string tradeStatus)
{
return tradeStatus switch
{
"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
};
}
}

View File

@@ -85,27 +85,6 @@ namespace Yi.Framework.AiHub.Domain
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
try
{
// 2. 发起API调用以创建当面付收款二维码为例
var response = Factory.Payment.Page()
.Pay("YiXin Ai Vip", "2234567234891", "0.01","https://ccnetcore.com/pay/Alipay/test");
// 3. 处理响应或异常
if (ResponseChecker.Success(response))
{
Console.WriteLine("调用成功");
Console.WriteLine(response.Body);
}
else
{
Console.WriteLine("调用失败,原因:" + response.Body);
}
}
catch (Exception ex)
{
Console.WriteLine("调用遭遇异常,原因:" + ex.Message);
throw ex;
}
}
}
}