feat: 完成支付宝接入
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建订单输入DTO
|
||||||
|
/// </summary>
|
||||||
|
public class CreateOrderInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 商品类型
|
||||||
|
/// </summary>
|
||||||
|
public GoodsTypeEnum GoodsType { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建订单输出DTO
|
||||||
|
/// </summary>
|
||||||
|
public class CreateOrderOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 订单ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid OrderId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商家订单号
|
||||||
|
/// </summary>
|
||||||
|
public string OutTradeNo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付页面HTML内容
|
||||||
|
/// </summary>
|
||||||
|
public string PaymentPageHtml { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询订单状态输入DTO
|
||||||
|
/// </summary>
|
||||||
|
public class QueryOrderStatusInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 商家订单号
|
||||||
|
/// </summary>
|
||||||
|
public string OutTradeNo { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询订单状态输出DTO
|
||||||
|
/// </summary>
|
||||||
|
public class QueryOrderStatusOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 订单ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid OrderId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商家订单号
|
||||||
|
/// </summary>
|
||||||
|
public string OutTradeNo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付宝交易号
|
||||||
|
/// </summary>
|
||||||
|
public string? TradeNo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交易状态
|
||||||
|
/// </summary>
|
||||||
|
public TradeStatusEnum TradeStatus { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交易状态描述
|
||||||
|
/// </summary>
|
||||||
|
public string TradeStatusDescription { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订单金额
|
||||||
|
/// </summary>
|
||||||
|
public decimal TotalAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品名称
|
||||||
|
/// </summary>
|
||||||
|
public string GoodsName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品类型
|
||||||
|
/// </summary>
|
||||||
|
public GoodsTypeEnum GoodsType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreationTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最后修改时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LastModificationTime { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IPayService : IApplicationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建订单并发起支付
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">创建订单输入</param>
|
||||||
|
/// <returns>订单创建结果</returns>
|
||||||
|
Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付宝异步通知处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="form">表单数据</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<string> AlipayNotifyAsync([FromForm] IFormCollection form);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询订单状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">查询订单状态输入</param>
|
||||||
|
/// <returns>订单状态信息</returns>
|
||||||
|
Task<QueryOrderStatusOutput> QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input);
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public PayService(
|
||||||
|
AlipayManager alipayManager,
|
||||||
|
PayManager payManager,
|
||||||
|
ILogger<PayService> logger, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository)
|
||||||
|
{
|
||||||
|
_alipayManager = alipayManager;
|
||||||
|
_payManager = payManager;
|
||||||
|
_logger = logger;
|
||||||
|
_payOrderRepository = payOrderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建订单并发起支付
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">创建订单输入</param>
|
||||||
|
/// <returns>订单创建结果</returns>
|
||||||
|
[HttpPost("pay/Order")]
|
||||||
|
public async Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input)
|
||||||
|
{
|
||||||
|
// 1. 通过PayManager创建订单
|
||||||
|
var order = await _payManager.CreateOrderAsync(input.GoodsType);
|
||||||
|
|
||||||
|
// 2. 通过AlipayManager发起页面支付
|
||||||
|
var paymentPageHtml = await _alipayManager.PaymentPageAsync(
|
||||||
|
order.OutTradeNo,
|
||||||
|
order.GoodsName,
|
||||||
|
order.TotalAmount);
|
||||||
|
|
||||||
|
// 3. 返回结果
|
||||||
|
return new CreateOrderOutput
|
||||||
|
{
|
||||||
|
OrderId = order.Id,
|
||||||
|
OutTradeNo = order.OutTradeNo,
|
||||||
|
PaymentPageHtml = JsonConvert.SerializeObject(paymentPageHtml)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 支付宝异步通知处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="form">表单数据</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("pay/AlipayNotify")]
|
||||||
|
public async Task<string> AlipayNotifyAsync([FromForm] IFormCollection form)
|
||||||
|
{
|
||||||
|
// 1. 将表单数据转换为字典
|
||||||
|
var notifyData = new Dictionary<string, string>();
|
||||||
|
foreach (var item in form)
|
||||||
|
{
|
||||||
|
notifyData[item.Key] = item.Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("收到支付宝回调通知:{NotifyData}", System.Text.Json.JsonSerializer.Serialize(notifyData));
|
||||||
|
|
||||||
|
// 2. 验证签名
|
||||||
|
await _alipayManager.VerifyNotifyAsync(notifyData);
|
||||||
|
|
||||||
|
|
||||||
|
// 3. 记录支付通知
|
||||||
|
await _payManager.RecordPayNoticeAsync(notifyData);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
await _payManager.UpdateOrderStatusAsync(outTradeNo, status, tradeNo);
|
||||||
|
|
||||||
|
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询订单状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">查询订单状态输入</param>
|
||||||
|
/// <returns>订单状态信息</returns>
|
||||||
|
[HttpGet("pay/OrderStatus")]
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取交易状态描述
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tradeStatus">交易状态</param>
|
||||||
|
/// <returns>状态描述</returns>
|
||||||
|
private string GetTradeStatusDescription(TradeStatusEnum tradeStatus)
|
||||||
|
{
|
||||||
|
return tradeStatus switch
|
||||||
|
{
|
||||||
|
TradeStatusEnum.WAIT_TRADE => "准备发起",
|
||||||
|
TradeStatusEnum.WAIT_BUYER_PAY => "等待买家付款",
|
||||||
|
TradeStatusEnum.TRADE_SUCCESS => "交易成功",
|
||||||
|
TradeStatusEnum.TRADE_FINISHED => "交易结束",
|
||||||
|
TradeStatusEnum.TRADE_CLOSED => "交易关闭",
|
||||||
|
_ => "未知状态"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 价格特性
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class PriceAttribute : Attribute
|
||||||
|
{
|
||||||
|
public decimal Price { get; }
|
||||||
|
|
||||||
|
public PriceAttribute(double price)
|
||||||
|
{
|
||||||
|
Price = (decimal)price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示名称特性
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class DisplayNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
public DisplayNameAttribute(string displayName)
|
||||||
|
{
|
||||||
|
DisplayName = displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品枚举
|
||||||
|
/// </summary>
|
||||||
|
public enum GoodsTypeEnum
|
||||||
|
{
|
||||||
|
[Price(29.9)]
|
||||||
|
[DisplayName("意心Vip会员1个月")]
|
||||||
|
YiXinVip1 = 1,
|
||||||
|
|
||||||
|
[Price(80.7)]
|
||||||
|
[DisplayName("意心Vip会员3个月")]
|
||||||
|
YiXinVip3 = 3,
|
||||||
|
|
||||||
|
[Price(143.9)]
|
||||||
|
[DisplayName("意心Vip会员6个月")]
|
||||||
|
YiXinVip6 = 6,
|
||||||
|
|
||||||
|
[Price(199.9)]
|
||||||
|
[DisplayName("意心Vip会员10个月")]
|
||||||
|
YiXinVip10 = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GoodsTypeEnumExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品总金额
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>总金额</returns>
|
||||||
|
public static decimal GetTotalAmount(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||||
|
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
||||||
|
return priceAttribute?.Price ?? 0m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品价格描述
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>价格描述</returns>
|
||||||
|
public static string GetPriceDescription(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var price = goodsType.GetTotalAmount();
|
||||||
|
return $"¥{price:F1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品名称
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>商品名称</returns>
|
||||||
|
public static string GetDisplayName(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||||
|
var displayNameAttribute = fieldInfo?.GetCustomAttribute<DisplayNameAttribute>();
|
||||||
|
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
public enum TradeStatusEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 准备发起
|
||||||
|
/// </summary>
|
||||||
|
WAIT_TRADE = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交易创建
|
||||||
|
/// </summary>
|
||||||
|
WAIT_BUYER_PAY = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交易关闭
|
||||||
|
/// </summary>
|
||||||
|
TRADE_CLOSED = 20,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交易成功
|
||||||
|
/// </summary>
|
||||||
|
TRADE_SUCCESS = 100,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 交易结束
|
||||||
|
/// </summary>
|
||||||
|
TRADE_FINISHED = -10
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,27 +85,6 @@ namespace Yi.Framework.AiHub.Domain
|
|||||||
|
|
||||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user