PaymentService.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. using System.Collections.Concurrent;
  2. using System.Text;
  3. using System;
  4. using System.Web;
  5. using WingServerCommon.Service;
  6. using WingInterfaceLibrary.Interface;
  7. using System.Threading.Tasks;
  8. using WingInterfaceLibrary.DTO.Payment;
  9. using WingInterfaceLibrary.Request.Payment;
  10. using WingInterfaceLibrary.Result;
  11. using System.Collections.Generic;
  12. using WingInterfaceLibrary.Interface.DBInterface;
  13. using WingInterfaceLibrary.DB.Request;
  14. using WingServerCommon.Log;
  15. using WingPaymentService.Common;
  16. using WingInterfaceLibrary.Enum;
  17. using WingInterfaceLibrary.Request;
  18. using WingPaymentService.Common.WeChat;
  19. using WingInterfaceLibrary.Request.Courses;
  20. using WingServerCommon.Config;
  21. using WingServerCommon.Config.Parameters;
  22. namespace WingPaymentService.Service
  23. {
  24. public class PaymentService : JsonRpcService, IPaymentService
  25. {
  26. private string _alipayShortBaseUrl => EnvironmentConfigManager.GetParammeter<StringParameter>("Alipay", "PayShortBaseUrl").Value;
  27. private string _wechatShortBaseUrl => EnvironmentConfigManager.GetParammeter<StringParameter>("WeChat", "PayShortBaseUrl").Value;
  28. private string _alipayPayReturnUrl => EnvironmentConfigManager.GetParammeter<StringParameter>("Alipay", "PayReturnUrl").Value;
  29. private string _wechatPayReturnUrl => EnvironmentConfigManager.GetParammeter<StringParameter>("WeChat", "PayReturnUrl").Value;
  30. private bool _alipayPayEnable => EnvironmentConfigManager.GetParammeter<BoolParameter>("Alipay", "Enable").Value;
  31. private bool _wechatPayEnable => EnvironmentConfigManager.GetParammeter<BoolParameter>("WeChat", "Enable").Value;
  32. private IEducationService _educationService;
  33. private IAuthenticationService _authenticationService;
  34. private IPaymentDBService _paymentDBService;
  35. private AlipayCommon _alipayCommon;
  36. private WeChatCommon _weChatCommon;
  37. private ConcurrentDictionary<string, string> _recordRelation = new();
  38. public override void Load(JsonRpcClientPool jsonRpcClientPool)
  39. {
  40. _alipayCommon = new AlipayCommon();
  41. _weChatCommon = new WeChatCommon();
  42. base.Load(jsonRpcClientPool);
  43. _paymentDBService = GetProxy<IPaymentDBService>();
  44. _authenticationService = GetProxy<IAuthenticationService>();
  45. _educationService = GetProxy<IEducationService>();
  46. }
  47. /// <summary>
  48. /// 购买接口
  49. /// </summary>
  50. /// <param name="request"></param>
  51. /// <returns></returns>
  52. public async Task<CreateOrderResultDTO> CreateOrderAsync(CreateOrderRequest request)
  53. {
  54. var dbRequest = new CreateOrderDBRequest();
  55. dbRequest.OrderAmount = request.OrderAmount;
  56. dbRequest.OrderTitle = request.OrderTitle;
  57. dbRequest.OrderType = request.OrderType;
  58. if (string.IsNullOrWhiteSpace(request.OrderUserCode))
  59. {
  60. request.OrderUserCode = await ValidateTokenAsync(request.Token);
  61. if (string.IsNullOrWhiteSpace(request.OrderUserCode))
  62. {
  63. ThrowCustomerException(CustomerRpcCode.UserNotFind, "UserNotFind");
  64. }
  65. }
  66. dbRequest.OrderUserCode = request.OrderUserCode;
  67. dbRequest.PayStatus = PayStatusEnum.NoPay;
  68. dbRequest.PayTime = DateTime.UtcNow;
  69. dbRequest.ProductCode = request.ProductCode;
  70. dbRequest.TPORderCode = request.TPORderCode;
  71. return await _paymentDBService.CreateOrderAsync(dbRequest);
  72. }
  73. /// <summary>
  74. /// 订单详情接口
  75. /// </summary>
  76. /// <param name="request"></param>
  77. /// <returns></returns>
  78. public async Task<OrderDetailDTO> GetOrderDetailAsync(GetOrderDetailRequest request)
  79. {
  80. return await _paymentDBService.GetOrderDetailAsync(new GetOrderDetailDBRequest { OrderCode = request.OrderCode });
  81. }
  82. /// <summary>
  83. /// 订单列表接口
  84. /// </summary>
  85. /// <param name="request"></param>
  86. /// <returns></returns>
  87. public async Task<PageResult<OrderDetailDTO>> GetOrderPagesAsync(GetOrderPagesRequest request)
  88. {
  89. return await _paymentDBService.GetOrderPagesAsync(new GetOrderPagesDBRequest
  90. {
  91. CurrentPage = request.PageIndex,
  92. PageSize = request.PageSize,
  93. KeyWord = request.KeyWord
  94. });
  95. }
  96. /// <summary>
  97. /// 发起支付请求
  98. /// </summary>
  99. /// <param name="request"></param>
  100. /// <returns></returns>
  101. public async Task<PayInfoDTO> GoToPayAsync(GoToPayRequest request)
  102. {
  103. if (!string.IsNullOrWhiteSpace(request.OrderCode))
  104. {
  105. var orderInfo = await _paymentDBService.GetOrderDetailAsync(new GetOrderDetailDBRequest { OrderCode = request.OrderCode });
  106. if (orderInfo != null && orderInfo.OrderAmount > 0)
  107. {
  108. string recordCode = Guid.NewGuid().ToString("N");
  109. (string payUrl, string recordContent) payContent = (string.Empty, string.Empty);
  110. var payUrlRequest = new InsertPayUrlDBRequest();
  111. InsertPayUrlDBResult payUrlResult = null;
  112. switch (request.PayType)
  113. {
  114. case PayTypeEnum.Alipay_PAGE:
  115. _recordRelation.AddOrUpdate(recordCode, request.OrderCode, (a, b) => { return request.OrderCode; });
  116. payContent = _alipayCommon.AlipayPagePay(orderInfo.OrderTitle, orderInfo.OrderAmount, recordCode, _alipayPayReturnUrl);
  117. payUrlRequest.PaymentOrderCode = request.OrderCode;
  118. payUrlRequest.PayLink = payContent.payUrl;
  119. payUrlRequest.PayShortLink = string.Format(_alipayShortBaseUrl, payUrlRequest.PaymentOrderCode);
  120. payUrlResult = await _paymentDBService.InsertPayUrlAsync(payUrlRequest);
  121. if (payUrlResult.IsSuccess)
  122. {
  123. payContent.payUrl = payUrlResult.PayShortUrl;
  124. }
  125. break;
  126. case PayTypeEnum.Alipay_WAP:
  127. _recordRelation.AddOrUpdate(recordCode, request.OrderCode, (a, b) => { return request.OrderCode; });
  128. payContent = _alipayCommon.AlipayWapPay(orderInfo.OrderTitle, orderInfo.OrderAmount, recordCode, _alipayPayReturnUrl);
  129. payUrlRequest.PaymentOrderCode = request.OrderCode;
  130. payUrlRequest.PayLink = payContent.payUrl;
  131. payUrlRequest.PayShortLink = string.Format(_alipayShortBaseUrl, payUrlRequest.PaymentOrderCode);
  132. payUrlResult = await _paymentDBService.InsertPayUrlAsync(payUrlRequest);
  133. if (payUrlResult.IsSuccess)
  134. {
  135. payContent.payUrl = payUrlResult.PayShortUrl;
  136. }
  137. break;
  138. case PayTypeEnum.WeChat_PAGE:
  139. payContent = await _weChatCommon.GetWebPayUrlAsync(orderInfo.OrderTitle, orderInfo.OrderAmount, orderInfo.Code, recordCode, string.Empty, string.Empty);
  140. payUrlRequest.PaymentOrderCode = request.OrderCode;
  141. payUrlRequest.PayLink = payContent.payUrl;
  142. payUrlRequest.PayShortLink = string.Format(_wechatShortBaseUrl, payUrlRequest.PaymentOrderCode);
  143. payUrlResult = await _paymentDBService.InsertPayUrlAsync(payUrlRequest);
  144. if (payUrlResult.IsSuccess)
  145. {
  146. payContent.payUrl = payUrlResult.PayShortUrl;
  147. }
  148. break;
  149. case PayTypeEnum.WeChat_WAP:
  150. payContent = await _weChatCommon.GetWapPayUrlAsync(orderInfo.OrderTitle, orderInfo.OrderAmount, orderInfo.Code, recordCode, string.Empty, string.Empty);
  151. payUrlRequest.PaymentOrderCode = request.OrderCode;
  152. payUrlRequest.PayLink = payContent.payUrl;
  153. payUrlRequest.PayShortLink = string.Format(_wechatShortBaseUrl, payUrlRequest.PaymentOrderCode);
  154. payUrlResult = await _paymentDBService.InsertPayUrlAsync(payUrlRequest);
  155. if (payUrlResult.IsSuccess)
  156. {
  157. payContent.payUrl = payUrlResult.PayShortUrl;
  158. }
  159. break;
  160. }
  161. await CreatePayStatusReq(recordCode, payContent.recordContent);
  162. if (!string.IsNullOrWhiteSpace(payContent.payUrl))
  163. {
  164. return new PayInfoDTO
  165. {
  166. IsSuccess = true,
  167. PayUrl = payContent.payUrl
  168. };
  169. }
  170. }
  171. }
  172. return new PayInfoDTO
  173. {
  174. IsSuccess = false,
  175. PayUrl = string.Empty
  176. };
  177. }
  178. /// <summary>
  179. /// 支付后回调
  180. /// </summary>
  181. /// <param name="src">来源</param>
  182. /// <param name="request">请求信息</param>
  183. /// <returns></returns>
  184. public async Task<PayCallbackDTO> PayCallbackAsync(string src, string request)
  185. {
  186. bool isSuccess = false;
  187. try
  188. {
  189. switch (src)
  190. {
  191. case "alipay":
  192. isSuccess = await AlipayCallback(request);
  193. break;
  194. case "wechat":
  195. isSuccess = await WeChatCallback(request);
  196. break;
  197. }
  198. return new PayCallbackDTO { IsSuccess = isSuccess };
  199. }
  200. catch (Exception ex)
  201. {
  202. Logger.WriteLineError($"PayCallback error {ex}");
  203. return new PayCallbackDTO { IsSuccess = false };
  204. }
  205. }
  206. /// <summary>
  207. /// 获取可支付列表
  208. /// </summary>
  209. /// <returns></returns>
  210. public async Task<PayableTypeDTO> GetPayableTypes()
  211. {
  212. var payTypes = new List<string>();
  213. if (_alipayPayEnable)
  214. {
  215. payTypes.Add("alipay");
  216. }
  217. if (_wechatPayEnable)
  218. {
  219. payTypes.Add("wechat");
  220. }
  221. var result = new PayableTypeDTO();
  222. result.PayTypes = payTypes;
  223. return await Task.FromResult(result);
  224. }
  225. /// <summary>
  226. /// 支付宝回调
  227. /// </summary>
  228. /// <param name="request"></param>
  229. /// <returns></returns>
  230. private async Task<bool> AlipayCallback(string request)
  231. {
  232. Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
  233. var uriParams = HttpUtility.UrlDecode(request);
  234. if (uriParams != null)
  235. {
  236. var paramsArr = uriParams.Split('&');
  237. foreach (string param in paramsArr)
  238. {
  239. var keyValue = param.Split('=');
  240. var key = keyValue[0];
  241. var value = keyValue.Length > 1 ? keyValue[1] : string.Empty;
  242. keyValuePairs.Add(key, value);
  243. }
  244. }
  245. var recordCode = keyValuePairs.GetValueOrDefault("out_trade_no");
  246. _recordRelation.TryGetValue(recordCode, out string orderCode);
  247. var totalAmount = keyValuePairs.GetValueOrDefault("total_amount");
  248. var payStatus = keyValuePairs.GetValueOrDefault("trade_status") == "TRADE_SUCCESS" ? WingInterfaceLibrary.Enum.PayStatusEnum.Paid : WingInterfaceLibrary.Enum.PayStatusEnum.NoPay;
  249. var payTime = DateTime.UtcNow;
  250. var success = await PayRecordWriteRes(orderCode, recordCode, totalAmount, payStatus, payTime, request);
  251. if (success)
  252. {
  253. _recordRelation.TryRemove(recordCode, out _);
  254. }
  255. return success;
  256. }
  257. /// <summary>
  258. /// 微信回调
  259. /// </summary>
  260. /// <param name="request"></param>
  261. /// <returns></returns>
  262. private async Task<bool> WeChatCallback(string request)
  263. {
  264. //转换数据格式, 取消签名校验,签名调用方自己调用,兼容支付和退款
  265. WxPayData data = new WxPayData();
  266. var paramValues = data.FromXml(request, false);//回调不用签名校验
  267. var payOutTradeNo = paramValues.GetValueOrDefault("out_trade_no")?.ToString();
  268. var totalAmount = (double.Parse(paramValues.GetValueOrDefault("total_fee")?.ToString()) / 100).ToString("0.00");
  269. var payRecordCode = paramValues.GetValueOrDefault("pay_record_code")?.ToString();
  270. var payStatus = paramValues.GetValueOrDefault("result_code")?.ToString() == "SUCCESS" ? PayStatusEnum.Paid : PayStatusEnum.NoPay;
  271. var payTime = DateTime.UtcNow;
  272. return await PayRecordWriteRes(payOutTradeNo, payRecordCode, totalAmount, payStatus, payTime, request);
  273. }
  274. /// <summary>
  275. /// 支付记录
  276. /// </summary>
  277. /// <param name="orderCode">订单编码</param>
  278. /// <param name="payRecordCode">支付记录编码</param>
  279. /// <param name="totalAmount">支付金额</param>
  280. /// <param name="payStatus">支付状态</param>
  281. /// <param name="payTime">支付时间</param>
  282. /// <param name="request">支付原始请求</param>
  283. /// <returns></returns>
  284. private async Task<bool> PayRecordWriteRes(string orderCode, string payRecordCode, string totalAmount, PayStatusEnum payStatus, DateTime payTime, string res)
  285. {
  286. if (string.IsNullOrWhiteSpace(payRecordCode))
  287. {
  288. Logger.WriteLineError("Pay record code is empty");
  289. return false;
  290. }
  291. await UpdatePayStatusRes(payRecordCode, orderCode, payStatus, res, payTime);
  292. if (string.IsNullOrWhiteSpace(orderCode))
  293. {
  294. Logger.WriteLineError("Order code is empty");
  295. return false;
  296. }
  297. if (payStatus == PayStatusEnum.Paid)
  298. {
  299. return await UpdateOrderStatus(orderCode, payStatus, totalAmount, payTime);
  300. }
  301. return false;
  302. }
  303. private async Task<bool> UpdateOrderStatus(string orderCode, PayStatusEnum payStatus, string totalAmount, DateTime payTime)
  304. {
  305. var orderInfo = await _paymentDBService.GetOrderDetailAsync(new GetOrderDetailDBRequest { OrderCode = orderCode });
  306. if (orderInfo.OrderAmount.ToString("0.00") != totalAmount)
  307. {
  308. Logger.WriteLineError($"OrderCode:{orderInfo.Code} pay status {payStatus}, total amount error, order amount {orderInfo.OrderAmount.ToString("0.00")} paid amount {totalAmount}");
  309. return false;
  310. }
  311. var result = await _paymentDBService.UpdateOrderAsync(new UpdateOrderDBRequest
  312. {
  313. OrderCode = orderCode,
  314. PayStatus = payStatus
  315. });
  316. await _educationService.PaymentCallbackAsync(new PaymentCallbackRequest
  317. {
  318. PaymentOrderCode = orderCode,
  319. PayStatus = payStatus,
  320. PayTime = payTime
  321. });
  322. return result.IsSuccess;
  323. }
  324. private async Task<CreatePaymentRecordResultDTO> CreatePayStatusReq(string paymentRecordCode, string payReqContent)
  325. {
  326. var createRecordInfo = new CreatePaymentRecordDBRequest();
  327. createRecordInfo.Code = paymentRecordCode;
  328. createRecordInfo.PayReqContent = payReqContent;
  329. return await _paymentDBService.CreatePaymentRecordAsync(createRecordInfo);
  330. }
  331. private async Task<UpdatePaymentRecordResultDTO> UpdatePayStatusRes(string paymentRecordCode, string orderCode, PayStatusEnum payStatus, string payResContent, DateTime payTime)
  332. {
  333. var updateRecordInfo = new UpdatePaymentRecordDBRequest();
  334. updateRecordInfo.Code = paymentRecordCode;
  335. updateRecordInfo.OrderCode = orderCode;
  336. updateRecordInfo.PayTime = payTime;
  337. updateRecordInfo.PayStatus = payStatus;
  338. updateRecordInfo.PayResContent = payResContent;
  339. return await _paymentDBService.UpdatePaymentRecordAsync(updateRecordInfo);
  340. }
  341. /// <summary>
  342. /// 客户端用户验证
  343. /// </summary>
  344. /// <param name="request"></param>
  345. /// <returns>User Code</returns>
  346. private async Task<string> ValidateTokenAsync(string token)
  347. {
  348. var request = new TokenRequest() { Token = token };
  349. var result = await _authenticationService.GetTokenAsync(request);
  350. return result.ClientId; //return User code
  351. }
  352. private void ThrowCustomerException(CustomerRpcCode customerRpcCodeEnum, string msg)
  353. {
  354. ThrowRpcException((int)customerRpcCodeEnum, msg);
  355. }
  356. }
  357. }