SysAlipayService.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
  2. //
  3. // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
  4. //
  5. // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
  6. using Aop.Api;
  7. using Aop.Api.Domain;
  8. using Aop.Api.Request;
  9. using Aop.Api.Response;
  10. using Aop.Api.Util;
  11. using Microsoft.AspNetCore.Hosting;
  12. using NewLife.Reflection;
  13. namespace Admin.NET.Core.Service;
  14. /// <summary>
  15. /// 支付宝支付服务 🧩
  16. /// </summary>
  17. [ApiDescriptionSettings(Order = 240)]
  18. public class SysAlipayService : IDynamicApiController, ITransient
  19. {
  20. private readonly IWebHostEnvironment _webHostEnvironment;
  21. private readonly SysConfigService _sysConfigService;
  22. private readonly List<IAopClient> _alipayClientList;
  23. private readonly IHttpContextAccessor _httpContext;
  24. private readonly AlipayOptions _option;
  25. private readonly ISqlSugarClient _db;
  26. public SysAlipayService(
  27. ISqlSugarClient db,
  28. IHttpContextAccessor httpContext,
  29. SysConfigService sysConfigService,
  30. IWebHostEnvironment webHostEnvironment,
  31. IOptions<AlipayOptions> alipayOptions)
  32. {
  33. _db = db;
  34. _httpContext = httpContext;
  35. _sysConfigService = sysConfigService;
  36. _option = alipayOptions.Value;
  37. _webHostEnvironment = webHostEnvironment;
  38. // 初始化支付宝客户端列表
  39. _alipayClientList = [];
  40. foreach (var account in _option.AccountList) _alipayClientList.Add(_option.GetClient(account));
  41. }
  42. /// <summary>
  43. /// 获取授权信息 🔖
  44. /// </summary>
  45. /// <param name="input"></param>
  46. /// <returns></returns>
  47. [NonUnify]
  48. [AllowAnonymous]
  49. [DisplayName("获取授权信息")]
  50. [ApiDescriptionSettings(Name = "AuthInfo"), HttpGet]
  51. public ActionResult GetAuthInfo([FromQuery] AlipayAuthInfoInput input)
  52. {
  53. var type = input.UserId?.Split('-').FirstOrDefault().ToInt();
  54. var userId = input.UserId?.Split('-').LastOrDefault().ToLong();
  55. var account = _option.AccountList.FirstOrDefault();
  56. var alipayClient = _alipayClientList.First();
  57. // 当前网页接口地址
  58. var currentUrl = $"{_option.AppAuthUrl}{_httpContext.HttpContext!.Request.Path}?userId={input.UserId}";
  59. if (string.IsNullOrEmpty(input.AuthCode))
  60. {
  61. // 重新授权
  62. var url = $"{_option.AuthUrl}?app_id={account!.AppId}&scope=auth_user&redirect_uri={currentUrl}";
  63. return new RedirectResult(url);
  64. }
  65. // 组装授权请求参数
  66. AlipaySystemOauthTokenRequest request = new()
  67. {
  68. GrantType = AlipayConst.GrantType,
  69. Code = input.AuthCode
  70. };
  71. AlipaySystemOauthTokenResponse response = alipayClient.CertificateExecute(request);
  72. // token换取用户信息
  73. AlipayUserInfoShareRequest infoShareRequest = new();
  74. AlipayUserInfoShareResponse info = alipayClient.CertificateExecute(infoShareRequest, response.AccessToken);
  75. // 记录授权信息
  76. var entity = _db.Queryable<SysAlipayAuthInfo>().First(u =>
  77. (!string.IsNullOrWhiteSpace(u.UserId) && u.UserId == info.UserId) ||
  78. (!string.IsNullOrWhiteSpace(u.OpenId) && u.OpenId == info.OpenId)) ?? new();
  79. entity.Copy(info, excludes: [nameof(SysAlipayAuthInfo.Gender), nameof(SysAlipayAuthInfo.Age)]);
  80. entity.Age = int.Parse(info.Age);
  81. entity.Gender = info.Gender switch
  82. {
  83. "m" => GenderEnum.Male,
  84. "f" => GenderEnum.Female,
  85. _ => GenderEnum.Unknown
  86. };
  87. entity.AppId = account!.AppId;
  88. if (entity.Id <= 0) _db.Insertable(entity).ExecuteCommand();
  89. else _db.Updateable(entity).ExecuteCommand();
  90. // 执行完,重定向到指定界面
  91. //var authPageUrl = _sysConfigService.GetConfigValueByCode<string>(ConfigConst.AlipayAuthPageUrl + type).Result;
  92. //return new RedirectResult(authPageUrl);
  93. return new RedirectResult(_option.AppAuthUrl + "/index.html");
  94. }
  95. /// <summary>
  96. /// 支付回调 🔖
  97. /// </summary>
  98. /// <returns></returns>
  99. [AllowAnonymous]
  100. [DisplayName("支付回调")]
  101. [ApiDescriptionSettings(Name = "Notify"), HttpPost]
  102. public string Notify()
  103. {
  104. SortedDictionary<string, string> sorted = [];
  105. foreach (string key in _httpContext.HttpContext!.Request.Form.Keys)
  106. sorted.Add(key, _httpContext.HttpContext.Request.Form[key]);
  107. var account = _option.AccountList.FirstOrDefault();
  108. string alipayPublicKey = Path.Combine(_webHostEnvironment.ContentRootPath, account!.AlipayPublicCertPath!.Replace('/', '\\').TrimStart('\\'));
  109. bool signVerified = AlipaySignature.RSACertCheckV1(sorted, alipayPublicKey, "UTF-8", account.SignType); // 调用SDK验证签名
  110. if (!signVerified) throw Oops.Oh("交易失败");
  111. // 更新交易记录
  112. var outTradeNo = sorted.GetValueOrDefault("out_trade_no");
  113. var transaction = _db.Queryable<SysAlipayTransaction>().First(x => x.OutTradeNo == outTradeNo) ?? throw Oops.Oh("交易记录不存在");
  114. transaction.TradeNo = sorted.GetValueOrDefault("trade_no");
  115. transaction.TradeStatus = sorted.GetValueOrDefault("trade_status");
  116. transaction.FinishTime = sorted.ContainsKey("gmt_payment") ? DateTime.Parse(sorted.GetValueOrDefault("gmt_payment")) : null;
  117. transaction.BuyerLogonId = sorted.GetValueOrDefault("buyer_logon_id");
  118. transaction.BuyerUserId = sorted.GetValueOrDefault("buyer_user_id");
  119. transaction.SellerUserId = sorted.GetValueOrDefault("seller_id");
  120. transaction.Remark = sorted.GetValueOrDefault("remark");
  121. _db.Updateable(transaction).ExecuteCommand();
  122. return "success";
  123. }
  124. /// <summary>
  125. /// 统一收单下单并支付页面接口 🔖
  126. /// </summary>
  127. /// <param name="input"></param>
  128. /// <returns></returns>
  129. [DisplayName("统一收单下单并支付页面接口")]
  130. [ApiDescriptionSettings(Name = "AlipayTradePagePay"), HttpPost]
  131. public string AlipayTradePagePay(AlipayTradePagePayInput input)
  132. {
  133. // 创建交易记录,状态为等待支付
  134. var transactionRecord = new SysAlipayTransaction
  135. {
  136. AppId = _option.AccountList.First().AppId,
  137. OutTradeNo = input.OutTradeNo,
  138. TotalAmount = input.TotalAmount.ToDecimal(),
  139. TradeStatus = "WAIT_PAY", // 等待支付
  140. CreateTime = DateTime.Now,
  141. Subject = input.Subject,
  142. Body = input.Body,
  143. Remark = "等待用户支付"
  144. };
  145. _db.Insertable(transactionRecord).ExecuteCommand();
  146. // 设置支付页面请求,并组装业务参数model,设置异步通知接收地址
  147. AlipayTradeWapPayRequest request = new();
  148. request.SetBizModel(new AlipayTradeWapPayModel()
  149. {
  150. Subject = input.Subject,
  151. OutTradeNo = input.OutTradeNo,
  152. TotalAmount = input.TotalAmount,
  153. Body = input.Body,
  154. ProductCode = "QUICK_WAP_WAY",
  155. TimeExpire = input.TimeoutExpress
  156. });
  157. request.SetNotifyUrl(_option.NotifyUrl);
  158. var alipayClient = _alipayClientList.First();
  159. var response = alipayClient.SdkExecute(request);
  160. if (response.IsError) throw Oops.Oh(response.SubMsg);
  161. return $"{_option.ServerUrl}?{response.Body}";
  162. }
  163. /// <summary>
  164. /// 交易预创建 🔖
  165. /// </summary>
  166. /// <param name="input"></param>
  167. /// <returns></returns>
  168. [DisplayName("交易预创建")]
  169. [ApiDescriptionSettings(Name = "AlipayPreCreate"), HttpPost]
  170. public string AlipayPreCreate(AlipayPreCreateInput input)
  171. {
  172. // 创建交易记录,状态为等待支付
  173. var transactionRecord = new SysAlipayTransaction
  174. {
  175. AppId = _option.AccountList.First().AppId,
  176. OutTradeNo = input.OutTradeNo,
  177. TotalAmount = input.TotalAmount.ToDecimal(),
  178. TradeStatus = "WAIT_PAY", // 等待支付
  179. CreateTime = DateTime.Now,
  180. Subject = input.Subject,
  181. Remark = "等待用户支付"
  182. };
  183. _db.Insertable(transactionRecord).ExecuteCommand();
  184. // 设置异步通知接收地址,并组装业务参数model
  185. AlipayTradePrecreateRequest request = new();
  186. request.SetNotifyUrl(_option.NotifyUrl);
  187. request.SetBizModel(new AlipayTradePrecreateModel()
  188. {
  189. Subject = input.Subject,
  190. OutTradeNo = input.OutTradeNo,
  191. TotalAmount = input.TotalAmount,
  192. TimeoutExpress = input.TimeoutExpress
  193. });
  194. var alipayClient = _alipayClientList.First();
  195. var response = alipayClient.CertificateExecute(request);
  196. if (response.IsError) throw Oops.Oh(response.SubMsg);
  197. return response.QrCode;
  198. }
  199. /// <summary>
  200. /// 单笔转账到支付宝账户
  201. /// https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer
  202. /// </summary>
  203. [NonAction]
  204. public async Task<AlipayFundTransUniTransferResponse> Transfer(AlipayFundTransUniTransferInput input)
  205. {
  206. var account = _option.AccountList.FirstOrDefault(u => u.AppId == input.AppId) ?? throw Oops.Oh("未找到商户支付宝账号");
  207. var alipayClient = _option.GetClient(account);
  208. // 构造请求参数以调用接口
  209. AlipayFundTransUniTransferRequest request = new();
  210. AlipayFundTransUniTransferModel model = new()
  211. {
  212. BizScene = AlipayConst.BizScene,
  213. ProductCode = AlipayConst.ProductCode,
  214. OutBizNo = input.OutBizNo, // 商家订单
  215. TransAmount = $"{input.TransAmount}:F2", // 订单总金额
  216. OrderTitle = input.OrderTitle, // 业务标题
  217. Remark = input.Remark, // 业务备注
  218. PayeeInfo = new() // 收款方信息
  219. {
  220. CertType = input.CertType?.ToString(),
  221. CertNo = input.CertNo,
  222. Identity = input.Identity,
  223. Name = input.Name,
  224. IdentityType = input.IdentityType.ToString()
  225. },
  226. BusinessParams = input.PayerShowNameUseAlias ? "{\"payer_show_name_use_alias\":\"true\"}" : null
  227. };
  228. request.SetBizModel(model);
  229. var response = alipayClient.CertificateExecute(request);
  230. // 保存转账记录
  231. await _db.Insertable(new SysAlipayTransaction
  232. {
  233. UserId = input.UserId,
  234. AppId = input.AppId,
  235. TradeNo = response.OrderId,
  236. OutTradeNo = input.OutBizNo,
  237. TotalAmount = response.Amount.ToDecimal(),
  238. TradeStatus = response.Code == "10000" ? "SUCCESS" : "FAILED",
  239. Subject = input.OrderTitle,
  240. ErrorInfo = response.SubMsg,
  241. Remark = input.Remark
  242. }).ExecuteCommandAsync();
  243. return response;
  244. }
  245. }