using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Gateway.Payment.Shared; using System; using System.Collections.Generic; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Net; using System.Net.Security; using System.IO; namespace PaymentGateway.GatewayApp { public class TongLianAllInPayV1Processor : IPaymentProcessor { private IServiceProvider services; private ILogger logger; public TongLianAllInPayV1Processor(IServiceProvider services) { this.services = services; var loggerFactory = services.GetRequiredService(); this.logger = loggerFactory.CreateLogger("DynamicPrivate_Gateway.Payment"); } public async Task Cancel(PaymentOrder order) { return new GenericProcessResponse() { AllInPayResponse = null }; } public async Task Process(PaymentOrder order) { order.TradeStatus = TradeStatusEnum.PAYERROR; var response = new Dictionary(); try { response = QrPay(order); if (IsSuccessful(response)) { order.TradeStatus = TradeStatusEnum.SUCCESS; } if (response != null && response.ContainsKey("trxstatus") && response["trxstatus"] == "2000") { this.logger.LogDebug($"Continuous query trx result is ongoing for order: {order.ToSimpleLogString()}"); // 当结果码为“2000”时,商户系统可设置间隔时间(建议10秒)重新查询支付结果,直到支付成功或超时(建议40秒) var config = (TongLianPayConfigV1)order.Config; response = await QueryTrxResult(order, config.queryInterval, config.queryTimeout); this.logger.LogDebug($" Continuous query trx result has done for order: {order.ToSimpleLogString()} with response: {response}"); if (IsSuccessful(response)) { order.TradeStatus = TradeStatusEnum.SUCCESS; } } LogResponse(response); } catch (Exception ex) { this.logger.LogError($"Request AllInPay exceptioned: {ex.ToString()}"); } return new GenericProcessResponse() { AllInPayResponse = response }; } private async Task> QueryTrxResult(PaymentOrder order, int interval = 10, int dueTime = 60) { this.logger.LogDebug($"AllInPay QueryTrxResult started, query interval = {interval}, dueTime = {dueTime}"); var reponse = new GenericProcessResponse() { AllInPayResponse = new Dictionary() }; var cts = new CancellationTokenSource(); try { await Task.WhenAny(PeriodicTaskV1.Run(Query, QuitQuerying, order, reponse, cts, TimeSpan.FromSeconds(interval)), Task.Delay(TimeSpan.FromSeconds(dueTime))); cts.Cancel(); } catch (Exception ex) { this.logger.LogError($"QueryTrxResult exceptioned, msg {ex}"); } return reponse.AllInPayResponse; } public async Task Query(PaymentOrder order) { order.TradeStatus = TradeStatusEnum.PAYERROR; var response = DoQuery(order); if (IsSuccessful(response)) { order.TradeStatus = TradeStatusEnum.SUCCESS; } LogResponse(response); return new GenericProcessResponse() { AllInPayResponse = response }; } public async Task Query(PaymentOrder order, GenericProcessResponse reponse, CancellationTokenSource cts) { var response = await Query(order); if (IsSuccessful(reponse.AllInPayResponse)) { cts.Cancel(); } return response; } public async Task Return(PaymentOrder order) { order.TradeStatus = TradeStatusEnum.PAYERROR; var response = DoCancel(order); if (IsSuccessful(response)) { order.TradeStatus = TradeStatusEnum.SUCCESS; } if (response != null && response.ContainsKey("trxstatus") && response["trxstatus"] == "2000") { //当结果码为“2000”时,商户系统可设置间隔时间(建议10秒)重新查询支付结果,直到支付成功或超时(建议40秒) var config = (TongLianPayConfigV1)order.Config; this.logger.LogDebug($"Continuous query trx result is ongoing for order: {order.ToSimpleLogString()}"); response = await QueryTrxResult(order, config.queryInterval, config.queryTimeout); this.logger.LogDebug($" Continuous query trx result has done for order: {order.ToSimpleLogString()} with response: {response}"); if (IsSuccessful(response)) { order.TradeStatus = TradeStatusEnum.SUCCESS; } } LogResponse(response); return new GenericProcessResponse() { AllInPayResponse = response }; } public Dictionary Pay(PaymentOrder order) { var config = (TongLianPayConfigV1)order.Config; var paramDic = BuildBasicParam(config); paramDic.Add("trxamt", Convert.ToInt32(order.NetAmount * 100).ToString()); paramDic.Add("reqsn", order.BillNumber); paramDic.Add("paytype", "W01"); paramDic.Add("body", order.Title); paramDic.Add("remark", ""); paramDic.Add("acct", ""); paramDic.Add("authcode", order.AuthCode); paramDic.Add("notify_url", config.notifyUrl); paramDic.Add("limit_pay", ""); paramDic.Add("idno", ""); paramDic.Add("truename", ""); paramDic.Add("asinfo", ""); paramDic.Add("sign", AllInPayAppUtil.signParam(paramDic, config.appKey)); return DoRequest(paramDic, "/pay", config); } public Dictionary QrPay(PaymentOrder order) { if (string.IsNullOrEmpty(order.AuthCode)) throw new ArgumentException("Must provide AuthCode for TongLianPayV1"); if (string.IsNullOrEmpty(order.BillNumber)) throw new ArgumentException("Must provide BillNumber for TongLianPayV1"); var config = (TongLianPayConfigV1)order.Config; var paramDic = BuildBasicParam(config); paramDic.Add("trxamt", Convert.ToInt32(order.NetAmount * 100).ToString()); paramDic.Add("reqsn", order.BillNumber); paramDic.Add("paytype", "W01"); paramDic.Add("body", order.Title); paramDic.Add("remark", ""); paramDic.Add("acct", ""); paramDic.Add("authcode", order.AuthCode); paramDic.Add("notify_url", config.notifyUrl); paramDic.Add("limit_pay", ""); paramDic.Add("idno", ""); paramDic.Add("truename", ""); paramDic.Add("asinfo", ""); paramDic.Add("sign", AllInPayAppUtil.signParam(paramDic, config.appKey)); return DoRequest(paramDic, "/scanqrpay", config); } public Dictionary DoCancel(PaymentOrder order) { if (string.IsNullOrEmpty(order.BillNumber)) throw new ArgumentException("Must provide BillNumber for cancel a trx from remote payment provider"); var config = (TongLianPayConfigV1)order.Config; var paramDic = BuildBasicParam(config); paramDic.Add("trxamt", Convert.ToInt32(order.NetAmount * 100).ToString()); paramDic.Add("reqsn", DateTime.Now.ToFileTime().ToString()); paramDic.Add("oldtrxid", ""); paramDic.Add("oldreqsn", order.BillNumber); paramDic.Add("sign", AllInPayAppUtil.signParam(paramDic, config.appKey)); return DoRequest(paramDic, "/cancel", config); } public Dictionary Refund(PaymentOrder order) { if (string.IsNullOrEmpty(order.BillNumber)) throw new ArgumentException("Must provide BillNumber for TongLianPayV1"); var config = (TongLianPayConfigV1)order.Config; var paramDic = BuildBasicParam(config); paramDic.Add("trxamt", Convert.ToInt32(order.NetAmount * 100).ToString()); paramDic.Add("reqsn", DateTime.Now.ToFileTime().ToString()); paramDic.Add("oldtrxid", ""); paramDic.Add("oldreqsn", order.BillNumber); paramDic.Add("sign", AllInPayAppUtil.signParam(paramDic, config.appKey)); return DoRequest(paramDic, "/refund", config); } public Dictionary DoQuery(PaymentOrder order) { //String reqsn, String trxid var config = (TongLianPayConfigV1)order.Config; var paramDic = BuildBasicParam(config); paramDic.Add("reqsn", order.BillNumber); paramDic.Add("trxid", ""); paramDic.Add("sign", AllInPayAppUtil.signParam(paramDic, config.appKey)); return DoRequest(paramDic, "/query", config); } private Dictionary BuildBasicParam(TongLianPayConfigV1 config) { var paramDic = new Dictionary(); paramDic.Add("cusid", config.cusId); paramDic.Add("appid", config.appId); paramDic.Add("version", config.apiVersion); paramDic.Add("randomstr", DateTime.Now.ToFileTime().ToString()); return paramDic; } private Dictionary DoRequest(Dictionary param, string url, TongLianPayConfigV1 config) { var rsp = CreatePostHttpResponse(config.apiUrl + url, param, Encoding.UTF8); var rspDic = (Dictionary)System.Text.Json.JsonSerializer.Deserialize(rsp, typeof(Dictionary)); if ("SUCCESS".Equals(rspDic["retcode"], StringComparison.OrdinalIgnoreCase))//验签 { var signRsp = rspDic["sign"]; rspDic.Remove("sign"); var sign = AllInPayAppUtil.signParam(rspDic, config.appKey); if (signRsp.Equals(sign)) { return rspDic; } else throw new Exception("验签失败"); } else if ("FAIL".Equals(rspDic["retcode"], StringComparison.OrdinalIgnoreCase)) { return rspDic; } else { this.logger.LogError("Unexpected response from remote payment gateway, will try to logging it below:"); LogResponse(rspDic); throw new Exception(rspDic["retmsg"]); } } private static readonly string DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"; private static bool CheckValidationResult(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors errors) { return true; //总是接受 } public static String CreatePostHttpResponse(string url, IDictionary parameters, Encoding charset) { HttpWebRequest request = null; //HTTPSQ请求 ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); request = WebRequest.Create(url) as HttpWebRequest; request.ProtocolVersion = HttpVersion.Version10; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.UserAgent = DefaultUserAgent; //如果需要POST数据 if (!(parameters == null || parameters.Count == 0)) { StringBuilder buffer = new StringBuilder(); int i = 0; foreach (string key in parameters.Keys) { if (i > 0) { buffer.AppendFormat("&{0}={1}", key, parameters[key]); } else { buffer.AppendFormat("{0}={1}", key, parameters[key]); } i++; } byte[] data = charset.GetBytes(buffer.ToString()); using (Stream stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } } Stream outstream = request.GetResponse().GetResponseStream(); //获取响应的字符串流 StreamReader sr = new StreamReader(outstream); //创建一个stream读取流 return sr.ReadToEnd(); } private void LogResponse(Dictionary rspDic) { var rsp = "请求返回数据:\n"; if (rspDic != null) { foreach (var item in rspDic) { rsp += item.Key + "-----" + item.Value + ";\n"; } } this.logger.LogInformation($"AllInPay recieved response: {rsp}"); } private bool IsSuccessful(Dictionary response) { if (response != null && response.Count > 0) { if (response.ContainsKey("retcode") && response["retcode"] == "SUCCESS") { if (response.ContainsKey("trxstatus") && response["trxstatus"] == "0000") { return true; } } } return false; } private bool PayFailed(Dictionary response) { if (response != null && response.Count > 0) { if (response.ContainsKey("retcode") && response["retcode"] == "SUCCESS") { if (response.ContainsKey("trxstatus") && response["trxstatus"] == "3045") { return true; } } } return false; } private bool QuitQuerying(Dictionary response) { return IsSuccessful(response) || PayFailed(response); } public Task UnifiedOrder(PaymentOrder order) { throw new NotImplementedException(); } public Task Query(PaymentOrder order, int count, int interval) { throw new NotImplementedException(); } } public class PeriodicTaskV1 { public static async Task Run(Func> process, Func, bool> predicate, PaymentOrder order, GenericProcessResponse response, CancellationTokenSource cts, TimeSpan period) { while (!cts.Token.IsCancellationRequested) { if (!cts.Token.IsCancellationRequested) { response = await process(order, response, cts); if (response != null && predicate(response.AllInPayResponse)) return; } await Task.Delay(period, cts.Token); } } } }