// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! using Admin.NET.Core.Service.Alipay; 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; /// /// 支付宝支付服务 🧩 /// [ApiDescriptionSettings(Order = 240)] public class AlipayService : IDynamicApiController, ITransient { private readonly UserManager _userManager; private readonly SysConfigService _sysConfigService; private readonly AlipayOptions _alipayOptions; private readonly IHttpContextAccessor _httpContext; private readonly IWebHostEnvironment _webHostEnvironment; private readonly IAopClient _alipayClient; public AlipayService(UserManager userManager, SysConfigService sysConfigService, IOptions alipayOptions, IHttpContextAccessor httpContext, IWebHostEnvironment webHostEnvironment) { _userManager = userManager; _sysConfigService = sysConfigService; _alipayOptions = alipayOptions.Value; _httpContext = httpContext; _webHostEnvironment = webHostEnvironment; // 初始化支付宝客户端 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) }); } /// /// 获取授权信息 🔖 /// /// /// [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 App.GetServices()) if (notify.ScanCallback(type, userId, info)) pass = true; if (!pass) throw Oops.Oh("未处理的授权逻辑"); // 执行完,重定向到指定界面 var authPageUrl = _sysConfigService.GetConfigValueByCode(ConfigConst.AlipayAuthPageUrl + type).Result; return new RedirectResult(authPageUrl); } /// /// 支付回调 🔖 /// /// [AllowAnonymous] [DisplayName("支付回调")] [ApiDescriptionSettings(Name = "Notify"), HttpPost] public string Notify() { SortedDictionary 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 App.GetServices()) if (notify.TopUpCallback(type, tradeNo)) isError = false; if (isError) throw Oops.Oh("交易失败"); } return "success"; } /// /// 统一收单下单并支付页面接口 🔖 /// /// /// [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}"; } /// /// 交易预创建 🔖 /// /// /// [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; } /// /// 单笔转账到支付宝账户 /// https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer /// [NonAction] public Task 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); } }