using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; using Newtonsoft.Json; namespace Mtxfw.Utility { public class HuifuPaymentService { private string _sysId; private string _productId; private string _huifuId; private string _privateKey; private string _apiUrl; /// /// 构造函数 /// /// 系统号 /// 产品号 /// 商户号 /// 私钥 /// API地址 public HuifuPaymentService(string sysId, string productId, string huifuId, string privateKey, string apiUrl = "https://api.huifu.com/v3/trade/payment/jspay") { _sysId = sysId; _productId = productId; _huifuId = huifuId; _privateKey = privateKey; _apiUrl = apiUrl; } /// /// 记录异常到文件 /// /// 异常对象 /// 方法名 private void LogException(Exception ex, string methodName) { try { string logFileName = "huifu_pay_error_log_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt"; string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", logFileName); // 确保logs目录存在 Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs")); // 记录异常信息 File.WriteAllText(logPath, "===== 异常时间: " + DateTime.Now.ToString() + " =====\n"); File.AppendAllText(logPath, "===== 方法名: " + methodName + " =====\n"); File.AppendAllText(logPath, "===== 异常类型: " + ex.GetType().FullName + " =====\n"); File.AppendAllText(logPath, "===== 异常消息: =====\n" + ex.Message + "\n"); File.AppendAllText(logPath, "===== 堆栈跟踪: =====\n" + ex.StackTrace + "\n"); if (ex.InnerException != null) { File.AppendAllText(logPath, "===== 内部异常: =====\n" + ex.InnerException.Message + "\n"); File.AppendAllText(logPath, "===== 内部异常堆栈: =====\n" + ex.InnerException.StackTrace + "\n"); } } catch (Exception logEx) { // 日志记录失败时,静默处理 } } /// /// 发起支付请求 /// /// 请求流水号 /// 商品描述 /// 交易类型 /// 交易金额 /// 微信参数集合 /// 交易有效期 /// 异步通知地址 /// 支付响应 public HuifuPayResponse Pay(string reqSeqId, string goodsDesc, string tradeType, string transAmt, string wxData, string timeExpire = null, string notifyUrl = null) { try { // 构建请求数据 var requestData = new HuifuPayRequest { SysId = _sysId, ProductId = _productId, Data = new HuifuPayRequestData { ReqDate = DateTime.Now.ToString("yyyyMMdd"), ReqSeqId = reqSeqId, HuifuId = _huifuId, GoodsDesc = goodsDesc, TradeType = tradeType, TransAmt = transAmt, TimeExpire = timeExpire ?? DateTime.Now.AddMinutes(30).ToString("yyyyMMddHHmmss"), WxData = wxData, NotifyUrl = notifyUrl } }; // 生成签名 string dataJson = JsonConvert.SerializeObject(requestData.Data); requestData.Sign = GenerateSign(dataJson, _privateKey); // 序列化请求 string requestJson = JsonConvert.SerializeObject(requestData); // 发送请求 string responseJson = SendRequest(_apiUrl, requestJson); // 反序列化响应 return JsonConvert.DeserializeObject(responseJson); } catch (Exception ex) { LogException(ex, "Pay"); throw new Exception("支付请求失败: " + ex.Message, ex); } } /// /// 生成签名 /// /// 数据JSON字符串 /// 密钥 /// 签名结果 private string GenerateSign(string dataJson, string privateKey) { try { // 移除私钥中的换行符和空格 privateKey = privateKey.Replace("\r", "").Replace("\n", "").Replace(" ", ""); // 转换私钥格式 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(ConvertPrivateKeyToXml(privateKey)); // 生成签名 byte[] dataBytes = Encoding.UTF8.GetBytes(dataJson); byte[] signatureBytes = rsa.SignData(dataBytes, new SHA256CryptoServiceProvider()); return Convert.ToBase64String(signatureBytes); } catch (Exception ex) { LogException(ex, "GenerateSign"); throw new Exception("生成签名失败: " + ex.Message, ex); } } /// /// 将Java格式的私钥转换为XML格式 /// /// Java格式私钥 /// XML格式私钥 private string ConvertPrivateKeyToXml(string privateKey) { try { // 移除私钥中的头部和尾部 privateKey = privateKey.Replace("-----BEGIN PRIVATE KEY-----", ""); privateKey = privateKey.Replace("-----END PRIVATE KEY-----", ""); privateKey = privateKey.Trim(); // 解码Base64字符串 byte[] keyBytes = Convert.FromBase64String(privateKey); // 使用BouncyCastle解析私钥 Org.BouncyCastle.Asn1.Asn1Sequence sequence = Org.BouncyCastle.Asn1.Asn1Sequence.GetInstance(keyBytes); // 提取RSA私钥参数 Org.BouncyCastle.Asn1.DerInteger modulus, publicExponent, privateExponent, prime1, prime2, exponent1, exponent2, coefficient; // 检查私钥格式 if (sequence.Count == 3) // PKCS#8格式 { // 版本号 Org.BouncyCastle.Asn1.DerInteger version = (Org.BouncyCastle.Asn1.DerInteger)sequence[0]; // 算法标识符 Org.BouncyCastle.Asn1.Asn1Sequence algorithmId = (Org.BouncyCastle.Asn1.Asn1Sequence)sequence[1]; // 私钥数据 Org.BouncyCastle.Asn1.DerOctetString privateKeyOctet = (Org.BouncyCastle.Asn1.DerOctetString)sequence[2]; // 解析私钥数据 Org.BouncyCastle.Asn1.Asn1Sequence privateKeySequence = Org.BouncyCastle.Asn1.Asn1Sequence.GetInstance(privateKeyOctet.GetOctets()); // 提取RSA参数 modulus = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[1]; publicExponent = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[2]; privateExponent = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[3]; prime1 = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[4]; prime2 = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[5]; exponent1 = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[6]; exponent2 = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[7]; coefficient = (Org.BouncyCastle.Asn1.DerInteger)privateKeySequence[8]; } else if (sequence.Count == 9) // 直接的RSA私钥格式 { // 提取RSA参数 modulus = (Org.BouncyCastle.Asn1.DerInteger)sequence[1]; publicExponent = (Org.BouncyCastle.Asn1.DerInteger)sequence[2]; privateExponent = (Org.BouncyCastle.Asn1.DerInteger)sequence[3]; prime1 = (Org.BouncyCastle.Asn1.DerInteger)sequence[4]; prime2 = (Org.BouncyCastle.Asn1.DerInteger)sequence[5]; exponent1 = (Org.BouncyCastle.Asn1.DerInteger)sequence[6]; exponent2 = (Org.BouncyCastle.Asn1.DerInteger)sequence[7]; coefficient = (Org.BouncyCastle.Asn1.DerInteger)sequence[8]; } else { throw new Exception("不支持的私钥格式,序列长度: " + sequence.Count); } // 构建XML格式私钥 string xmlPrivateKey = $@" {Convert.ToBase64String(modulus.Value.ToByteArrayUnsigned())} {Convert.ToBase64String(publicExponent.Value.ToByteArrayUnsigned())}

{Convert.ToBase64String(prime1.Value.ToByteArrayUnsigned())}

{Convert.ToBase64String(prime2.Value.ToByteArrayUnsigned())} {Convert.ToBase64String(exponent1.Value.ToByteArrayUnsigned())} {Convert.ToBase64String(exponent2.Value.ToByteArrayUnsigned())} {Convert.ToBase64String(coefficient.Value.ToByteArrayUnsigned())} {Convert.ToBase64String(privateExponent.Value.ToByteArrayUnsigned())}
"; return xmlPrivateKey; } catch (Exception ex) { LogException(ex, "ConvertPrivateKeyToXml"); throw new Exception("私钥格式转换失败: " + ex.Message, ex); } } /// /// 将Java格式的公钥转换为XML格式 /// /// Java格式公钥 /// XML格式公钥 private string ConvertPublicKeyToXml(string publicKey) { try { // 移除公钥中的头部和尾部 publicKey = publicKey.Replace("-----BEGIN PUBLIC KEY-----", ""); publicKey = publicKey.Replace("-----END PUBLIC KEY-----", ""); publicKey = publicKey.Trim(); // 解码Base64字符串 byte[] keyBytes = Convert.FromBase64String(publicKey); // 使用BouncyCastle解析公钥 Org.BouncyCastle.Asn1.Asn1Sequence sequence = Org.BouncyCastle.Asn1.Asn1Sequence.GetInstance(keyBytes); // 提取RSA公钥参数 Org.BouncyCastle.Asn1.DerInteger modulus, publicExponent; if (sequence.Count == 2) // X.509格式 { // 算法标识符 Org.BouncyCastle.Asn1.Asn1Sequence algorithmId = (Org.BouncyCastle.Asn1.Asn1Sequence)sequence[0]; // 公钥数据 Org.BouncyCastle.Asn1.DerBitString publicKeyBitString = (Org.BouncyCastle.Asn1.DerBitString)sequence[1]; // 解析公钥数据 Org.BouncyCastle.Asn1.Asn1Sequence publicKeySequence = Org.BouncyCastle.Asn1.Asn1Sequence.GetInstance(publicKeyBitString.GetBytes()); // 提取RSA参数 modulus = (Org.BouncyCastle.Asn1.DerInteger)publicKeySequence[0]; publicExponent = (Org.BouncyCastle.Asn1.DerInteger)publicKeySequence[1]; } else { throw new Exception("不支持的公钥格式,序列长度: " + sequence.Count); } // 构建XML格式公钥 string xmlPublicKey = $@" {Convert.ToBase64String(modulus.Value.ToByteArrayUnsigned())} {Convert.ToBase64String(publicExponent.Value.ToByteArrayUnsigned())} "; return xmlPublicKey; } catch (Exception ex) { LogException(ex, "ConvertPublicKeyToXml"); throw new Exception("公钥格式转换失败: " + ex.Message, ex); } } /// /// 验证签名 /// /// 数据JSON字符串 /// 签名 /// 公钥 /// 签名是否有效 public bool VerifySign(string dataJson, string signature, string publicKey) { try { // 移除公钥中的换行符和空格 publicKey = publicKey.Replace("\r", "").Replace("\n", "").Replace(" ", ""); // 转换公钥格式 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(ConvertPublicKeyToXml(publicKey)); // 验证签名 byte[] dataBytes = Encoding.UTF8.GetBytes(dataJson); byte[] signatureBytes = Convert.FromBase64String(signature); return rsa.VerifyData(dataBytes, new SHA256CryptoServiceProvider(), signatureBytes); } catch (Exception ex) { LogException(ex, "VerifySign"); throw new Exception("验证签名失败: " + ex.Message, ex); } } /// /// 发送HTTP请求 /// /// 请求地址 /// 请求JSON /// 响应结果 private string SendRequest(string url, string requestJson) { string responseJson = ""; string logFileName = "huifu_pay_log_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt"; string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", logFileName); try { // 确保logs目录存在 Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs")); // 记录请求数据 File.WriteAllText(logPath, "===== 请求时间: " + DateTime.Now.ToString() + " =====\n"); File.AppendAllText(logPath, "===== 请求URL: " + url + " =====\n"); File.AppendAllText(logPath, "===== 请求数据: =====\n" + requestJson + "\n\n"); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/json"; request.Timeout = 30000; // 30秒超时 // 写入请求数据 using (StreamWriter writer = new StreamWriter(request.GetRequestStream())) { writer.Write(requestJson); } // 获取响应 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { responseJson = reader.ReadToEnd(); // 记录响应数据 File.AppendAllText(logPath, "===== 响应时间: " + DateTime.Now.ToString() + " =====\n"); File.AppendAllText(logPath, "===== 响应数据: =====\n" + responseJson + "\n"); return responseJson; } } catch (WebException ex) { if (ex.Response != null) { using (StreamReader reader = new StreamReader(ex.Response.GetResponseStream(), Encoding.UTF8)) { string errorResponse = reader.ReadToEnd(); // 记录错误响应 File.AppendAllText(logPath, "===== 错误时间: " + DateTime.Now.ToString() + " =====\n"); File.AppendAllText(logPath, "===== 错误响应: =====\n" + errorResponse + "\n"); LogException(ex, "SendRequest"); throw new Exception("HTTP请求失败: " + errorResponse, ex); } } // 记录异常 File.AppendAllText(logPath, "===== 异常时间: " + DateTime.Now.ToString() + " =====\n"); File.AppendAllText(logPath, "===== 异常信息: =====\n" + ex.ToString() + "\n"); LogException(ex, "SendRequest"); throw new Exception("HTTP请求失败: " + ex.Message, ex); } catch (Exception ex) { // 记录其他异常 File.AppendAllText(logPath, "===== 异常时间: " + DateTime.Now.ToString() + " =====\n"); File.AppendAllText(logPath, "===== 异常信息: =====\n" + ex.ToString() + "\n"); LogException(ex, "SendRequest"); throw new Exception("发送请求失败: " + ex.Message, ex); } } } #region 请求响应模型 public class HuifuPayRequest { [JsonProperty("sys_id")] public string SysId { get; set; } [JsonProperty("product_id")] public string ProductId { get; set; } [JsonProperty("sign")] public string Sign { get; set; } [JsonProperty("data")] public HuifuPayRequestData Data { get; set; } } public class HuifuPayRequestData { [JsonProperty("req_date")] public string ReqDate { get; set; } [JsonProperty("req_seq_id")] public string ReqSeqId { get; set; } [JsonProperty("huifu_id")] public string HuifuId { get; set; } [JsonProperty("goods_desc")] public string GoodsDesc { get; set; } [JsonProperty("trade_type")] public string TradeType { get; set; } [JsonProperty("trans_amt")] public string TransAmt { get; set; } [JsonProperty("time_expire")] public string TimeExpire { get; set; } [JsonProperty("wx_data")] public string WxData { get; set; } [JsonProperty("notify_url")] public string NotifyUrl { get; set; } } public class HuifuPayResponse { [JsonProperty("code")] public string Code { get; set; } [JsonProperty("msg")] public string Msg { get; set; } [JsonProperty("data")] public HuifuPayResponseData Data { get; set; } [JsonProperty("sign")] public string Sign { get; set; } } public class HuifuPayResponseData { [JsonProperty("resp_code")] public string RespCode { get; set; } [JsonProperty("resp_desc")] public string RespDesc { get; set; } [JsonProperty("pay_info")] public string PayInfo { get; set; } [JsonProperty("out_trans_id")] public string OutTransId { get; set; } [JsonProperty("party_order_id")] public string PartyOrderId { get; set; } } #endregion }