refactor: 😀重构支付宝支付模块

This commit is contained in:
喵你个旺呀 2025-01-22 00:14:01 +08:00
parent e24e3befb5
commit 2df7100717
10 changed files with 796 additions and 322 deletions

View File

@ -3,16 +3,33 @@
// https://openhome.alipay.com/develop/sandbox/app
"Alipay": {
"AppId": "9000000000000000", // APPID
"AlipayWebsocketUrl": "openchannel-sandbox.dl.alipaydev.com", // websocket
"ServerUrl": "https://openapi-sandbox.dl.alipaydev.com/gateway.do", //
"AuthUrl": "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm", //
"NotifyUrl": "http://xxxx.xxx/api/Alipay/Notify", //
"PrivateKey": "xxxxxxxxx", //
"SignType": "RSA2", //
"EncryptKey": "xxxxxxxx", //
"AlipayPublicCertPath": "/AlipayCrt/alipayPublicCert.crt", //
"RootCertPath": "/AlipayCrt/alipayRootCert.crt", //
"AppCertPath": "/AlipayCrt/appPublicCert.crt" //
"WebsocketUrl": "openchannel-sandbox.dl.alipaydev.com", // websocket
//"AuthUrl": "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm", //
"AuthUrl": "https://openauth-sandbox.dl.alipaydev.com/oauth2/publicAppAuthorize.htm", //
"AppAuthUrl": "http://xxxxxxxxxx", //
"NotifyUrl": "http://xxxxxxxxx/api/sysAlipay/Notify", //
"RootCertPath": "Alipaycrt/alipayRootCert.crt", //
"AccountList": [
{
"Name": "sandbox 默认应用",
"AppId": "xxxxxxxxxxxxxx",
"SignType": "RSA2",
"PrivateKey": "xxxxxxxxxxxxxxxxx",
"EncryptKey": "xxxxxxxxxxxxxxxxxxxx",
"AppCertPath": "Alipaycrt/appPublicCert.crt", //
"AlipayPublicCertPath": "Alipaycrt/alipayPublicCert.crt" //
},
{
"Name": "sandbox 默认应用2",
"AppId": "xxxxxxxxxxxxxx",
"SignType": "RSA2",
"PrivateKey": "xxxxxxxxxxxxxxxxx",
"EncryptKey": "xxxxxxxxxxxxxxxxxxxx",
"AppCertPath": "Alipaycrt/appPublicCert.crt", //
"AlipayPublicCertPath": "Alipaycrt/alipayPublicCert.crt" //
}
]
}
}

View File

@ -0,0 +1,149 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 支付宝授权记录表
/// </summary>
[SysTable]
[SugarTable(null, "支付宝授权记录表")]
[SugarIndex("index_{table}_U", nameof(UserId), OrderByType.Asc)]
[SugarIndex("index_{table}_T", nameof(OpenId), OrderByType.Asc)]
public class SysAlipayAuthInfo : EntityBase
{
/// <summary>
/// 商户AppId
/// </summary>
[SugarColumn(ColumnDescription = "商户AppId", Length = 64)]
public string? AppId { get; set; }
/// <summary>
/// 开放ID
/// </summary>
[SugarColumn(ColumnDescription = "开放ID", Length = 64)]
public string? OpenId { get; set; }
/// <summary>
/// 用户ID
/// </summary>
[SugarColumn(ColumnDescription = "用户ID", Length = 64)]
public string? UserId { get; set; }
/// <summary>
/// 性别
/// </summary>
[SugarColumn(ColumnDescription = "性别", Length = 8)]
public GenderEnum Gender { get; set; }
/// <summary>
/// 年龄
/// </summary>
[SugarColumn(ColumnDescription = "年龄", Length = 16)]
public int Age { get; set; }
/// <summary>
/// 手机号
/// </summary>
[SugarColumn(ColumnDescription = "手机号", Length = 32)]
public string Mobile { get; set; }
/// <summary>
/// 显示名称
/// </summary>
[SugarColumn(ColumnDescription = "显示名称", Length = 128)]
public string DisplayName { get; set; }
/// <summary>
/// 昵称
/// </summary>
[SugarColumn(ColumnDescription = "昵称", Length = 64)]
public string NickName { get; set; }
/// <summary>
/// 用户名
/// </summary>
[SugarColumn(ColumnDescription = "用户名", Length = 64)]
public string UserName { get; set; }
/// <summary>
/// 头像
/// </summary>
[SugarColumn(ColumnDescription = "头像", Length = 512)]
public string? Avatar { get; set; }
/// <summary>
/// 邮箱
/// </summary>
[SugarColumn(ColumnDescription = "邮箱", Length = 128)]
public string? Email { get; set; }
/// <summary>
/// 用户民族
/// </summary>
[SugarColumn(ColumnDescription = "用户民族", Length = 32)]
public string? UserNation { get; set; }
/// <summary>
/// 淘宝ID
/// </summary>
[SugarColumn(ColumnDescription = "淘宝ID", Length = 64)]
public string? TaobaoId { get; set; }
/// <summary>
/// 电话
/// </summary>
[SugarColumn(ColumnDescription = "电话", Length = 32)]
public string? Phone { get; set; }
/// <summary>
/// 生日
/// </summary>
[SugarColumn(ColumnDescription = "生日", Length = 32)]
public string? PersonBirthday { get; set; }
/// <summary>
/// 职业
/// </summary>
[SugarColumn(ColumnDescription = "职业", Length = 64)]
public string? Profession { get; set; }
/// <summary>
/// 省份
/// </summary>
[SugarColumn(ColumnDescription = "省份", Length = 64)]
public string? Province { get; set; }
/// <summary>
/// 用户状态
/// </summary>
[SugarColumn(ColumnDescription = "用户状态", Length = 32)]
public string? UserStatus { get; set; }
/// <summary>
/// 学历
/// </summary>
[SugarColumn(ColumnDescription = "学历", Length = 32)]
public string? Degree { get; set; }
/// <summary>
/// 用户类型
/// </summary>
[SugarColumn(ColumnDescription = "用户类型", Length = 32)]
public string? UserType { get; set; }
/// <summary>
/// 邮编
/// </summary>
[SugarColumn(ColumnDescription = "邮编", Length = 16)]
public string? Zip { get; set; }
/// <summary>
/// 地址
/// </summary>
[SugarColumn(ColumnDescription = "地址", Length = 256)]
public string? Address { get; set; }
}

View File

@ -0,0 +1,108 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 支付宝交易记录表
/// </summary>
[SysTable]
[SugarTable(null, "支付宝交易记录表")]
[SugarIndex("index_{table}_U", nameof(UserId), OrderByType.Asc)]
[SugarIndex("index_{table}_T", nameof(TradeNo), OrderByType.Asc)]
[SugarIndex("index_{table}_O", nameof(OutTradeNo), OrderByType.Asc)]
public class SysAlipayTransaction : EntityBase
{
/// <summary>
/// 用户Id
/// </summary>
[SugarColumn(ColumnDescription = "用户Id", Length = 64)]
public long UserId { get; set; }
/// <summary>
/// 交易号
/// </summary>
[SugarColumn(ColumnDescription = "交易号", Length = 64)]
public string? TradeNo { get; set; }
/// <summary>
/// 商户订单号
/// </summary>
[SugarColumn(ColumnDescription = "商户订单号", Length = 64)]
public string OutTradeNo { get; set; }
/// <summary>
/// 交易金额
/// </summary>
[SugarColumn(ColumnDescription = "交易金额", Length = 20)]
public decimal TotalAmount { get; set; }
/// <summary>
/// 交易状态
/// </summary>
[SugarColumn(ColumnDescription = "交易状态", Length = 32)]
public string TradeStatus { get; set; }
/// <summary>
/// 交易完成时间
/// </summary>
[SugarColumn(ColumnDescription = "交易完成时间")]
public DateTime? FinishTime { get; set; }
/// <summary>
/// 交易标题
/// </summary>
[SugarColumn(ColumnDescription = "交易标题", Length = 256)]
public string Subject { get; set; }
/// <summary>
/// 交易描述
/// </summary>
[SugarColumn(ColumnDescription = "交易描述", Length = 512)]
public string? Body { get; set; }
/// <summary>
/// 买家支付宝账号
/// </summary>
[SugarColumn(ColumnDescription = "买家支付宝账号", Length = 128)]
public string? BuyerLogonId { get; set; }
/// <summary>
/// 买家支付宝用户ID
/// </summary>
[SugarColumn(ColumnDescription = "买家支付宝用户ID", Length = 32)]
public string? BuyerUserId { get; set; }
/// <summary>
/// 卖家支付宝用户ID
/// </summary>
[SugarColumn(ColumnDescription = "卖家支付宝用户ID", Length = 32)]
public string? SellerUserId { get; set; }
/// <summary>
/// 商户AppId
/// </summary>
[SugarColumn(ColumnDescription = "商户AppId", Length = 64)]
public string? AppId { get; set; }
/// <summary>
/// 交易扩展信息
/// </summary>
[SugarColumn(ColumnDescription = "交易扩展信息", Length = 1024)]
public string? ExtendInfo { get; set; }
/// <summary>
/// 交易异常信息
/// </summary>
[SugarColumn(ColumnDescription = "交易扩展信息", Length = 1024)]
public string? ErrorInfo { get; set; }
/// <summary>
/// 交易备注
/// </summary>
[SugarColumn(ColumnDescription = "交易备注", Length = 512)]
public string? Remark { get; set; }
}

View File

@ -4,6 +4,8 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Aop.Api;
namespace Admin.NET.Core;
/// <summary>
@ -12,57 +14,106 @@ namespace Admin.NET.Core;
public sealed class AlipayOptions : IConfigurableOptions
{
/// <summary>
/// 支付宝 APPID必填
/// 支付宝网关地址
/// </summary>
public string AppId { get; set; }
public string ServerUrl { get; init; }
/// <summary>
/// 支付宝 websocket 服务地址 (必填)
/// 支付宝授权回调地址
/// </summary>
public string AlipayWebsocketUrl { get; set; }
public string AuthUrl { get; init; }
/// <summary>
/// 支付宝网关地址 (必填)
/// 应用授权回调地址
/// </summary>
public string ServerUrl { get; set; }
public string AppAuthUrl { get; init; }
/// <summary>
/// 支付宝授权回调地址 (必填)
/// 支付宝 websocket 服务地址
/// </summary>
public string AuthUrl { get; set; }
public string WebsocketUrl { get; init; }
/// <summary>
/// 应用回调地址
/// </summary>
public string NotifyUrl { get; set; }
public string NotifyUrl { get; init; }
/// <summary>
/// 加密算法(必填)
/// 支付宝根证书存放路径
/// </summary>
public string SignType { get; set; }
public string RootCertPath { get; init; }
/// <summary>
/// 支付宝商户账号列表
/// </summary>
public List<AlipayMerchantAccount> AccountList { get; init; }
/// <summary>
/// 获取支付宝客户端
/// </summary>
/// <param name="account"></param>
public DefaultAopClient GetClient(AlipayMerchantAccount account)
{
account = account ?? throw new Exception("未找到支付宝商户账号");
string path = App.WebHostEnvironment.ContentRootPath;
return new DefaultAopClient(new AlipayConfig
{
Format = "json",
Charset = "UTF-8",
ServerUrl = ServerUrl,
AppId = account.AppId,
SignType = account.SignType,
PrivateKey = account.PrivateKey,
EncryptKey = account.EncryptKey,
RootCertPath = Path.Combine(path, RootCertPath),
AppCertPath = Path.Combine(path, account.AppCertPath),
AlipayPublicCertPath = Path.Combine(path, account.AlipayPublicCertPath)
});
}
}
/// <summary>
/// 支付宝商户账号信息
/// </summary>
public class AlipayMerchantAccount
{
/// <summary>
/// 配置Id
/// </summary>
public long Id { get; init; }
/// <summary>
/// 商户名称
/// </summary>
public string Name { get; init; }
/// <summary>
/// 商户AppId
/// </summary>
public string AppId { get; init; }
/// <summary>
/// 应用私钥
/// </summary>
public string PrivateKey { get; init; }
/// <summary>
/// 从支付宝获取敏感信息时的加密密钥(可选)
/// </summary>
public string EncryptKey { get; set; }
public string EncryptKey { get; init; }
/// <summary>
/// 应用私钥 (必填)
/// 加密算法
/// </summary>
public string PrivateKey { get; set; }
public string SignType { get; init; }
/// <summary>
/// 支付宝公钥证书存放路径(证书加签方式必填)
/// 应用公钥证书路径
/// </summary>
public string AlipayPublicCertPath { get; set; }
public string AppCertPath { get; init; }
/// <summary>
/// 支付宝根证书存放路径(证书加签方式必填)
/// 支付宝公钥证书路径
/// </summary>
public string RootCertPath { get; set; }
/// <summary>
/// 应用公钥证书存放路径(证书加签方式必填)
/// </summary>
public string AppCertPath { get; set; }
public string AlipayPublicCertPath { get; init; }
}

View File

@ -1,279 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Aop.Api;
using Aop.Api.Domain;
using Aop.Api.Request;
using Aop.Api.Response;
using Aop.Api.Util;
using Microsoft.AspNetCore.Hosting;
namespace Admin.NET.Core.Service;
/// <summary>
/// 支付宝支付服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 240)]
public class AlipayService : IDynamicApiController, ITransient
{
private readonly IEnumerable<IAlipayNotify> _alipayNotifyList;
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly SysConfigService _sysConfigService;
private readonly IHttpContextAccessor _httpContext;
private readonly AlipayOptions _alipayOptions;
private readonly IAopClient _alipayClient;
private readonly UserManager _userManager;
public AlipayService(
UserManager userManager,
IHttpContextAccessor httpContext,
SysConfigService sysConfigService,
IWebHostEnvironment webHostEnvironment,
IOptions<AlipayOptions> alipayOptions)
{
_userManager = userManager;
_httpContext = httpContext;
_sysConfigService = sysConfigService;
_alipayOptions = alipayOptions.Value;
_webHostEnvironment = webHostEnvironment;
_alipayNotifyList = App.GetServices<IAlipayNotify>();
// 初始化支付宝客户端
string path = App.WebHostEnvironment.ContentRootPath;
_alipayClient = new DefaultAopClient(new AlipayConfig
{
Format = "json",
Charset = "UTF-8",
AppId = _alipayOptions.AppId,
SignType = _alipayOptions.SignType,
ServerUrl = _alipayOptions.ServerUrl,
PrivateKey = _alipayOptions.PrivateKey,
EncryptKey = _alipayOptions.EncryptKey,
AppCertPath = Path.Combine(path, _alipayOptions.AppCertPath),
RootCertPath = Path.Combine(path, _alipayOptions.RootCertPath),
AlipayPublicCertPath = Path.Combine(path, _alipayOptions.AlipayPublicCertPath)
});
}
/// <summary>
/// 获取授权信息 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[NonUnify]
[AllowAnonymous]
[DisplayName("获取授权信息")]
[ApiDescriptionSettings(Name = "GetAuthInfo"), HttpGet]
public ActionResult GetAuthInfo([FromQuery] AlipayAuthInfoInput input)
{
var type = input.UserId.Split('-').FirstOrDefault().ToInt();
var userId = input.UserId.Split('-').LastOrDefault().ToLong();
// 当前网页接口地址
var currentUrl = $"{_httpContext.HttpContext!.Request.GetOrigin()}{_httpContext.HttpContext!.Request.Path}?userId={input.UserId}";
if (string.IsNullOrEmpty(input.AuthCode))
{
// 重新授权
var url = $"{_alipayOptions.AuthUrl}?app_id={_alipayOptions.AppId}&scope=auth_user&redirect_uri={currentUrl}";
return new RedirectResult(url);
}
// 组装授权请求参数
AlipaySystemOauthTokenRequest request = new()
{
GrantType = AlipayConst.GrantType,
Code = input.AuthCode
};
AlipaySystemOauthTokenResponse response = _alipayClient.CertificateExecute(request);
// token换取用户信息
AlipayUserInfoShareRequest infoShareRequest = new();
AlipayUserInfoShareResponse info = _alipayClient.CertificateExecute(infoShareRequest, response.AccessToken);
// 循环执行扫码后需要执行的业务逻辑需要至少一个继承方法返回true否则抛出异常
var pass = false;
foreach (var notify in _alipayNotifyList)
if (notify.ScanCallback(type, userId, info)) pass = true;
if (!pass) throw Oops.Oh("未处理的授权逻辑");
// 执行完,重定向到指定界面
var authPageUrl = _sysConfigService.GetConfigValueByCode<string>(ConfigConst.AlipayAuthPageUrl + type).Result;
return new RedirectResult(authPageUrl);
}
/// <summary>
/// 支付回调 🔖
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[DisplayName("支付回调")]
[ApiDescriptionSettings(Name = "Notify"), HttpPost]
public string Notify()
{
SortedDictionary<string, string> sorted = [];
foreach (string key in _httpContext.HttpContext!.Request.Form.Keys)
sorted.Add(key, _httpContext.HttpContext.Request.Form[key]);
string alipayPublicKey = Path.Combine(_webHostEnvironment.ContentRootPath, _alipayOptions.AlipayPublicCertPath!.Replace('/', '\\').TrimStart('\\'));
bool signVerified = AlipaySignature.RSACertCheckV1(sorted, alipayPublicKey, "UTF-8", _alipayOptions.SignType); // 调用SDK验证签名
if (!signVerified) throw Oops.Oh("交易失败");
var outTradeNo = sorted.GetValueOrDefault("out_trade_no");
try
{
// 记录回调日志
File.AppendAllText($"{_webHostEnvironment.ContentRootPath}\\AlipayLog\\Notify-{DateTime.Today:yyyy-MM-dd}.txt",
$"支付宝支付到平台({DateTime.Now:yyyy-MM-dd HH:mm:ss}){Environment.NewLine} " +
$"登录人:{_userManager.UserId}-{_userManager.RealName}{Environment.NewLine} " +
$"IP{App.HttpContext?.GetRemoteIpAddressToIPv4(true)} {Environment.NewLine} " +
$"交易号:{outTradeNo}{Environment.NewLine} " +
$"参数:{JSON.Serialize(sorted)}{Environment.NewLine} " +
$"-----------------------------------------------------------------------------------------------------------------------" +
$"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}");
}
catch (Exception ex)
{
Log.Error("支付宝支付回调日志写入失败:", ex);
}
if (sorted.GetValueOrDefault(AlipayConst.TradeStatus) == AlipayConst.TradeSuccess)
{
// 约定交易码前四位为类型码,后面为订单号
var tradeNo = long.Parse(outTradeNo);
var type = long.Parse(outTradeNo[..4]);
// 循环执行业务逻辑,若都未处理(回调全部返回false)则交易失败
var isError = true;
foreach (var notify in _alipayNotifyList)
if (notify.TopUpCallback(type, tradeNo)) isError = false;
if (isError) throw Oops.Oh("交易失败");
}
return "success";
}
/// <summary>
/// 统一收单下单并支付页面接口 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("统一收单下单并支付页面接口")]
[ApiDescriptionSettings(Name = "AlipayTradePagePay"), HttpPost]
public string AlipayTradePagePay(AlipayTradePagePayInput input)
{
AlipayTradeWapPayRequest request = new();
// 组装业务参数model
AlipayTradeWapPayModel model = new()
{
Subject = input.Subject,
OutTradeNo = input.OutTradeNo,
TotalAmount = input.TotalAmount,
Body = input.Body,
ProductCode = "QUICK_WAP_WAY",
TimeExpire = input.TimeoutExpress
};
request.SetBizModel(model);
// 设置异步通知接收地址
request.SetNotifyUrl(_alipayOptions.NotifyUrl);
var response = _alipayClient.SdkExecute(request);
if (response.IsError) throw Oops.Oh(response.SubMsg);
return $"{_alipayOptions.ServerUrl}?{response.Body}";
}
/// <summary>
/// 交易预创建 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("交易预创建")]
[ApiDescriptionSettings(Name = "AlipayPreCreate"), HttpPost]
public string AlipayPreCreate(AlipayPreCreateInput input)
{
AlipayTradePrecreateRequest request = new();
// 设置异步通知接收地址
request.SetNotifyUrl(_alipayOptions.NotifyUrl);
// 组装业务参数model
AlipayTradePrecreateModel model = new()
{
Subject = input.Subject,
OutTradeNo = input.OutTradeNo,
TotalAmount = input.TotalAmount,
TimeoutExpress = input.TimeoutExpress
};
request.SetBizModel(model);
var response = _alipayClient.CertificateExecute(request);
if (response.IsError) throw Oops.Oh(response.SubMsg);
return response.QrCode;
}
/// <summary>
/// 单笔转账到支付宝账户
/// https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer
/// </summary>
[NonAction]
public Task<AlipayFundTransUniTransferResponse> Transfer(AlipayFundTransUniTransferInput input)
{
// 构造请求参数以调用接口
AlipayFundTransUniTransferRequest request = new();
AlipayFundTransUniTransferModel model = new()
{
BizScene = AlipayConst.BizScene,
ProductCode = AlipayConst.ProductCode,
// 设置商家侧唯一订单号
OutBizNo = input.OutBizNo,
// 设置订单总金额
TransAmount = input.TransAmount.ToString(),
// 设置转账业务的标题
OrderTitle = input.OrderTitle
};
// 设置收款方信息
Participant payeeInfo = new()
{
CertType = input.CertType.ToString(),
CertNo = input.CertNo,
Identity = input.Identity,
Name = input.Name,
IdentityType = input.IdentityType.ToString()
};
model.PayeeInfo = payeeInfo;
// 设置业务备注
model.Remark = input.Remark;
// 设置转账业务请求的扩展参数
string payerShowNameUseAlias = input.PayerShowNameUseAlias.ToString().ToLower();
model.BusinessParams = $"{{\"payer_show_name_use_alias\":\"{payerShowNameUseAlias}\"}}";
request.SetBizModel(model);
var response = _alipayClient.CertificateExecute(request);
try
{
File.AppendAllText($"{_webHostEnvironment.ContentRootPath}\\AlipayLog\\{DateTime.Today:yyyy-MM-dd}.txt",
$"支付宝付款到账户({DateTime.Now:yyyy-MM-dd HH:mm:ss}){Environment.NewLine} " +
$"登录人:{_userManager.UserId}-{_userManager.RealName}{Environment.NewLine} " +
$"IP{App.HttpContext?.GetRemoteIpAddressToIPv4(true)} {Environment.NewLine} " +
$"参数:{JSON.Serialize(model)}{Environment.NewLine} " +
$"返回:{JSON.Serialize(response)}{Environment.NewLine}" +
$"-----------------------------------------------------------------------------------------------------------------------" +
$"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}");
}
catch (Exception ex)
{
Log.Error("单笔转账到支付宝账户日志写入失败:", ex);
}
return Task.FromResult(response);
}
}

View File

@ -4,28 +4,37 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json.Serialization;
using Aop.Api.Domain;
using Newtonsoft.Json;
namespace Admin.NET.Core.Service;
public class AlipayFundTransUniTransferInput
{
/// <summary>
/// 商家侧唯一订单号
/// 用户ID
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 商户AppId
/// </summary>
public string AppId { get; set; }
/// <summary>
/// 商家订单号
/// </summary>
[Required(ErrorMessage = "订单号不能为空")]
public string OutBizNo { get; set; }
/// <summary>
/// 转账金额
/// </summary>
[Required(ErrorMessage = "转账金额不能为空")]
public decimal? TransAmount { get; set; }
public decimal TransAmount { get; set; }
/// <summary>
/// 转账业务标题
/// 业务标题
/// </summary>
[Required(ErrorMessage = "业务标题不能为空")]
public string OrderTitle { get; set; }
/// <summary>
@ -46,25 +55,21 @@ public class AlipayFundTransUniTransferInput
/// <summary>
/// 收款方证件号码,条件必填
/// </summary>
[CommonValidation($"{nameof(CertType)} != null && string.IsNullOrWhiteSpace({nameof(CertNo)})", "", ErrorMessage = "证件号码不能为空")]
public string CertNo { get; set; }
/// <summary>
/// 收款方身份标识
/// </summary>
[Required(ErrorMessage = "身份标识不能为空")]
public string Identity { get; set; }
/// <summary>
/// 收款方真实姓名
/// </summary>
[Required(ErrorMessage = "真实姓名不能为空")]
public string Name { get; set; }
/// <summary>
/// 收款方身份标识类型
/// </summary>
[Required(ErrorMessage = "身份标识类型不能为空")]
public AlipayIdentityTypeEnum? IdentityType { get; set; }
}
@ -159,10 +164,17 @@ public class AlipayAuthInfoInput
/// <summary>
/// 用户Id
/// </summary>
[JsonProperty("user_id")]
[JsonPropertyName("user_id")]
[FromQuery(Name = "user_id")]
public string UserId { get; set; }
/// <summary>
/// 授权码
/// </summary>
[JsonProperty("auth_code")]
[JsonPropertyName("auth_code")]
[FromQuery(Name = "auth_code")]
public string AuthCode { get; set; }
}

View File

@ -0,0 +1,271 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Aop.Api;
using Aop.Api.Domain;
using Aop.Api.Request;
using Aop.Api.Response;
using Aop.Api.Util;
using Microsoft.AspNetCore.Hosting;
using NewLife.Reflection;
namespace Admin.NET.Core.Service;
/// <summary>
/// 支付宝支付服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 240)]
public class SysAlipayService : IDynamicApiController, ITransient
{
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly SysConfigService _sysConfigService;
private readonly List<IAopClient> _alipayClientList;
private readonly IHttpContextAccessor _httpContext;
private readonly AlipayOptions _option;
private readonly ISqlSugarClient _db;
public SysAlipayService(
ISqlSugarClient db,
IHttpContextAccessor httpContext,
SysConfigService sysConfigService,
IWebHostEnvironment webHostEnvironment,
IOptions<AlipayOptions> alipayOptions)
{
_db = db;
_httpContext = httpContext;
_sysConfigService = sysConfigService;
_option = alipayOptions.Value;
_webHostEnvironment = webHostEnvironment;
// 初始化支付宝客户端列表
_alipayClientList = [];
foreach (var account in _option.AccountList) _alipayClientList.Add(_option.GetClient(account));
}
/// <summary>
/// 获取授权信息 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[NonUnify]
[AllowAnonymous]
[DisplayName("获取授权信息")]
[ApiDescriptionSettings(Name = "AuthInfo"), HttpGet]
public ActionResult GetAuthInfo([FromQuery] AlipayAuthInfoInput input)
{
var type = input.UserId?.Split('-').FirstOrDefault().ToInt();
var userId = input.UserId?.Split('-').LastOrDefault().ToLong();
var account = _option.AccountList.FirstOrDefault();
var alipayClient = _alipayClientList.First();
// 当前网页接口地址
var currentUrl = $"{_option.AppAuthUrl}{_httpContext.HttpContext!.Request.Path}?userId={input.UserId}";
if (string.IsNullOrEmpty(input.AuthCode))
{
// 重新授权
var url = $"{_option.AuthUrl}?app_id={account!.AppId}&scope=auth_user&redirect_uri={currentUrl}";
return new RedirectResult(url);
}
// 组装授权请求参数
AlipaySystemOauthTokenRequest request = new()
{
GrantType = AlipayConst.GrantType,
Code = input.AuthCode
};
AlipaySystemOauthTokenResponse response = alipayClient.CertificateExecute(request);
// token换取用户信息
AlipayUserInfoShareRequest infoShareRequest = new();
AlipayUserInfoShareResponse info = alipayClient.CertificateExecute(infoShareRequest, response.AccessToken);
// 记录授权信息
var entity = _db.Queryable<SysAlipayAuthInfo>().First(u =>
(!string.IsNullOrWhiteSpace(u.UserId) && u.UserId == info.UserId) ||
(!string.IsNullOrWhiteSpace(u.OpenId) && u.OpenId == info.OpenId)) ?? new ();
entity.Copy(info, excludes: [nameof(SysAlipayAuthInfo.Gender), nameof(SysAlipayAuthInfo.Age)]);
entity.Age = int.Parse(info.Age);
entity.Gender = info.Gender switch
{
"m" => GenderEnum.Male,
"f" => GenderEnum.Female,
_ => GenderEnum.Unknown
};
entity.AppId = account!.AppId;
if (entity.Id <= 0) _db.Insertable(entity).ExecuteCommand();
else _db.Updateable(entity).ExecuteCommand();
// 执行完,重定向到指定界面
//var authPageUrl = _sysConfigService.GetConfigValueByCode<string>(ConfigConst.AlipayAuthPageUrl + type).Result;
//return new RedirectResult(authPageUrl);
return new RedirectResult(_option.AppAuthUrl + "/index.html");
}
/// <summary>
/// 支付回调 🔖
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[DisplayName("支付回调")]
[ApiDescriptionSettings(Name = "Notify"), HttpPost]
public string Notify()
{
SortedDictionary<string, string> sorted = [];
foreach (string key in _httpContext.HttpContext!.Request.Form.Keys)
sorted.Add(key, _httpContext.HttpContext.Request.Form[key]);
var account = _option.AccountList.FirstOrDefault();
string alipayPublicKey = Path.Combine(_webHostEnvironment.ContentRootPath, account!.AlipayPublicCertPath!.Replace('/', '\\').TrimStart('\\'));
bool signVerified = AlipaySignature.RSACertCheckV1(sorted, alipayPublicKey, "UTF-8", account.SignType); // 调用SDK验证签名
if (!signVerified) throw Oops.Oh("交易失败");
// 更新交易记录
var outTradeNo = sorted.GetValueOrDefault("out_trade_no");
var transaction = _db.Queryable<SysAlipayTransaction>().First(x => x.OutTradeNo == outTradeNo) ?? throw Oops.Oh("交易记录不存在");
transaction.TradeNo = sorted.GetValueOrDefault("trade_no");
transaction.TradeStatus = sorted.GetValueOrDefault("trade_status");
transaction.FinishTime = sorted.ContainsKey("gmt_payment") ? DateTime.Parse(sorted.GetValueOrDefault("gmt_payment")) : null;
transaction.BuyerLogonId = sorted.GetValueOrDefault("buyer_logon_id");
transaction.BuyerUserId = sorted.GetValueOrDefault("buyer_user_id");
transaction.SellerUserId = sorted.GetValueOrDefault("seller_id");
transaction.Remark = sorted.GetValueOrDefault("remark");
_db.Updateable(transaction).ExecuteCommand();
return "success";
}
/// <summary>
/// 统一收单下单并支付页面接口 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("统一收单下单并支付页面接口")]
[ApiDescriptionSettings(Name = "AlipayTradePagePay"), HttpPost]
public string AlipayTradePagePay(AlipayTradePagePayInput input)
{
// 创建交易记录,状态为等待支付
var transactionRecord = new SysAlipayTransaction
{
AppId = _option.AccountList.First().AppId,
OutTradeNo = input.OutTradeNo,
TotalAmount = input.TotalAmount.ToDecimal(),
TradeStatus = "WAIT_PAY", // 等待支付
CreateTime = DateTime.Now,
Subject = input.Subject,
Body = input.Body,
Remark = "等待用户支付"
};
_db.Insertable(transactionRecord).ExecuteCommand();
// 设置支付页面请求并组装业务参数model设置异步通知接收地址
AlipayTradeWapPayRequest request = new();
request.SetBizModel(new AlipayTradeWapPayModel()
{
Subject = input.Subject,
OutTradeNo = input.OutTradeNo,
TotalAmount = input.TotalAmount,
Body = input.Body,
ProductCode = "QUICK_WAP_WAY",
TimeExpire = input.TimeoutExpress
});
request.SetNotifyUrl(_option.NotifyUrl);
var alipayClient = _alipayClientList.First();
var response = alipayClient.SdkExecute(request);
if (response.IsError) throw Oops.Oh(response.SubMsg);
return $"{_option.ServerUrl}?{response.Body}";
}
/// <summary>
/// 交易预创建 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("交易预创建")]
[ApiDescriptionSettings(Name = "AlipayPreCreate"), HttpPost]
public string AlipayPreCreate(AlipayPreCreateInput input)
{
// 创建交易记录,状态为等待支付
var transactionRecord = new SysAlipayTransaction
{
AppId = _option.AccountList.First().AppId,
OutTradeNo = input.OutTradeNo,
TotalAmount = input.TotalAmount.ToDecimal(),
TradeStatus = "WAIT_PAY", // 等待支付
CreateTime = DateTime.Now,
Subject = input.Subject,
Remark = "等待用户支付"
};
_db.Insertable(transactionRecord).ExecuteCommand();
// 设置异步通知接收地址并组装业务参数model
AlipayTradePrecreateRequest request = new();
request.SetNotifyUrl(_option.NotifyUrl);
request.SetBizModel(new AlipayTradePrecreateModel()
{
Subject = input.Subject,
OutTradeNo = input.OutTradeNo,
TotalAmount = input.TotalAmount,
TimeoutExpress = input.TimeoutExpress
});
var alipayClient = _alipayClientList.First();
var response = alipayClient.CertificateExecute(request);
if (response.IsError) throw Oops.Oh(response.SubMsg);
return response.QrCode;
}
/// <summary>
/// 单笔转账到支付宝账户
/// https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer
/// </summary>
[NonAction]
public async Task<AlipayFundTransUniTransferResponse> Transfer(AlipayFundTransUniTransferInput input)
{
var account = _option.AccountList.FirstOrDefault(u => u.AppId == input.AppId) ?? throw Oops.Oh("未找到商户支付宝账号");
var alipayClient = _option.GetClient(account);
// 构造请求参数以调用接口
AlipayFundTransUniTransferRequest request = new();
AlipayFundTransUniTransferModel model = new()
{
BizScene = AlipayConst.BizScene,
ProductCode = AlipayConst.ProductCode,
OutBizNo = input.OutBizNo, // 商家订单
TransAmount = $"{input.TransAmount}:F2", // 订单总金额
OrderTitle = input.OrderTitle, // 业务标题
Remark = input.Remark, // 业务备注
PayeeInfo = new() // 收款方信息
{
CertType = input.CertType?.ToString(),
CertNo = input.CertNo,
Identity = input.Identity,
Name = input.Name,
IdentityType = input.IdentityType.ToString()
},
BusinessParams = input.PayerShowNameUseAlias ? "{\"payer_show_name_use_alias\":\"true\"}" : null
};
request.SetBizModel(model);
var response = alipayClient.CertificateExecute(request);
// 保存转账记录
await _db.Insertable(new SysAlipayTransaction
{
UserId = input.UserId,
AppId = input.AppId,
TradeNo = response.OrderId,
OutTradeNo = input.OutBizNo,
TotalAmount = response.Amount.ToDecimal(),
TradeStatus = response.Code == "10000" ? "SUCCESS" : "FAILED",
Subject = input.OrderTitle,
ErrorInfo = response.SubMsg,
Remark = input.Remark
}).ExecuteCommandAsync();
return response;
}
}

View File

@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIDszCCApugAwIBAgIQICQSIl9wqwjbcsS1A/zPajANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjQxMjIyMTE0NzAzWhcNMjUxMjIyMTE0NzAzWjCB
hDELMAkGA1UEBhMCQ04xHzAdBgNVBAoMFm10cXdudzEyODlAc2FuZGJveC5jb20xDzANBgNVBAsM
BkFsaXBheTFDMEEGA1UEAww65pSv5LuY5a6dKOS4reWbvSnnvZHnu5zmioDmnK/mnInpmZDlhazl
j7gtMjA4ODcyMTA1MzI1NzEzMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIPs9UeN
0Hhp0qFTXE51E/a2EQ7YmMI8dJdBLWP17Irzo1I1Zxw3BIsrxEsdPJSGr76oQlunEyWYNyiVsOaL
6KE8V1FK6uPSJr5BK5W9o3wKg3XX7BKdflyB4964uRNJOOPpqNi4vwY1ZZFVpSHrZJ0ZAthDHGYo
o3/zdEEIwEWRkVAtMC6pJa8K0qk3qyaqHRMzwO3r13Ex8q/7+kO1iOsQ5UFQwKeV/g6/NuyEW9BZ
6y59BehHQBqf51ZJcPa91Dc6va/C6McKe/OWd+km9EFwjab0L2GvwdRtrxUAikR0tghgvx3fAfJw
nkfdjTzYUi/umDuVJlyW6xg0p9cJwLcCAwEAAaMSMBAwDgYDVR0PAQH/BAQDAgTwMA0GCSqGSIb3
DQEBCwUAA4IBAQAoaQR/LYaBNMQ9pjEAHNaLdheebL6TbDNI8thKYAhMKFfeJ4ZNZPwqg2FnCfhf
eVJctLqJtxgzzl0vazVGeFJRwrMvJXkZsU9veRjLP1s4QUOO5NqjxkDNuQnFnQZBsySLWkM/+0f3
AcFImbXVUDuod/KnPZ6i2gbYNmaADAvYM8M87aqbvrfbLHBk6wtHR21l9MMu1BXyAlJn29k47xYj
XNRJo6292M4Y4fyGFxT7tC+kt+MA6WUTl4hB+PvuXJh26tUOEo6EcXAZekcDJNOLCioNTW9/ck4P
bj2Qxvouztndmd3zsT+flLiImEtGgRaClveKuUwrd9rzWLNkl2lw
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDszCCApugAwIBAgIQIBkIGbgVxq210KxLJ+YA/TANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxJTAjBgNVBAsMHENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IHRlc3QxNjA0BgNVBAMMLUFudCBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgUjEgdGVzdDAeFw0xOTA4MTkxMTE2MDBaFw0yNDA4MDExMTE2MDBaMIGRMQswCQYDVQQGEwJD
TjEbMBkGA1UECgwSQW50IEZpbmFuY2lhbCB0ZXN0MSUwIwYDVQQLDBxDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSB0ZXN0MT4wPAYDVQQDDDVBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IENsYXNzIDIgUjEgdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMh4FKYO
ZyRQHD6eFbPKZeSAnrfjfU7xmS9Yoozuu+iuqZlb6Z0SPLUqqTZAFZejOcmr07ln/pwZxluqplxC
5+B48End4nclDMlT5HPrDr3W0frs6Xsa2ZNcyil/iKNB5MbGll8LRAxntsKvZZj6vUTMb705gYgm
VUMILwi/ZxKTQqBtkT/kQQ5y6nOZsj7XI5rYdz6qqOROrpvS/d7iypdHOMIM9Iz9DlL1mrCykbBi
t25y+gTeXmuisHUwqaRpwtCGK4BayCqxRGbNipe6W73EK9lBrrzNtTr9NaysesT/v+l25JHCL9tG
wpNr1oWFzk4IHVOg0ORiQ6SUgxZUTYcCAwEAAaMSMBAwDgYDVR0PAQH/BAQDAgTwMA0GCSqGSIb3
DQEBCwUAA4IBAQBWThEoIaQoBX2YeRY/I8gu6TYnFXtyuCljANnXnM38ft+ikhE5mMNgKmJYLHvT
yWWWgwHoSAWEuml7EGbE/2AK2h3k0MdfiWLzdmpPCRG/RJHk6UB1pMHPilI+c0MVu16OPpKbg5Vf
LTv7dsAB40AzKsvyYw88/Ezi1osTXo6QQwda7uefvudirtb8FcQM9R66cJxl3kt1FXbpYwheIm/p
j1mq64swCoIYu4NrsUYtn6CV542DTQMI5QdXkn+PzUUly8F6kDp+KpMNd0avfWNL5+O++z+F5Szy
1CPta1D7EQ/eYmMP+mOQ35oifWIoFCpN6qQVBS/Hob1J/UUyg7BW
-----END CERTIFICATE-----

View File

@ -0,0 +1,88 @@
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIQICQSIV6QZN2y3Knbwe6r9TANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjQxMjIxMTEzOTMzWhcNMjUxMjI2MTEzOTMzWjBr
MQswCQYDVQQGEwJDTjEfMB0GA1UECgwWbXRxd253MTI4OUBzYW5kYm94LmNvbTEPMA0GA1UECwwG
QWxpcGF5MSowKAYDVQQDDCEyMDg4NzIxMDUzMjU3MTMyLTkwMjEwMDAxNDI2ODYzOTUwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxJhoamnjWZGQp+1kHXTyQgA2va0rTQv8ZvFggERZv
fOg2i5RYTBFLB0J1l3hj+23iBqzRTgzPxCLUTZUrj6WgwdDbXi11eE2wltVwmDHI1r3YdLC6KCQN
Jpv6SKSHs9JV6UDpzsHaJbsoi6NymYpkbFClLCh3MOcyha9Ju4B9n5mKze1GEDTNcAs1nGfSsH1E
wAWoe65iVM8pbTXTzjbEW/jJc2xbFT9RPRTihflkEf6p3kOpaPtgFmcUqdJ4BsldL6iWqbdR5JhT
mVKT8hDmsbW9KpPnCXWedxBZHmNiuOOCOeDhHGV3zFrZvIPJa7umAL+eweg0egN5RHP9tDAnAgMB
AAGjEjAQMA4GA1UdDwEB/wQEAwIE8DANBgkqhkiG9w0BAQsFAAOCAQEANPJelQG99aAZb+KXUE3Z
ES+ZjPD9WwCBWpfcjyDq6duEQc9Qo0B2vW6EunbOi2qhS0dJ3pgJFaRrbi72BqQavT1eVYHcfXeC
y8I3dI7oxn5BS944oiGTzaYTx6dJ8lXDmlQO8ULz+6/wBCkhr6TgKbjIOh7p3/B9NPcfL5z6OrbQ
fXRk439uRfjWlzohE9q4cAY5AYnHLZCgbHwRX+YusDmJCu+LS9hioNHjwJ9t/jXsjzEQ09sfzEHF
uPGKS4JGmzwqGp2Ly68MCzp7WOgEFBexYUio4suzKlZUcv1+HDi6fEosZ6Fr7Z3CEMwKelq8m2l7
jg83G0PECp5unR1Hcw==
-----END CERTIFICATE-----