Files
RenyiServer/Mtxfw.Utility/HuifuPaymentService.cs
2026-03-09 00:13:46 +08:00

486 lines
20 KiB
C#
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="sysId">系统号</param>
/// <param name="productId">产品号</param>
/// <param name="huifuId">商户号</param>
/// <param name="privateKey">私钥</param>
/// <param name="apiUrl">API地址</param>
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;
}
/// <summary>
/// 记录异常到文件
/// </summary>
/// <param name="ex">异常对象</param>
/// <param name="methodName">方法名</param>
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)
{
// 日志记录失败时,静默处理
}
}
/// <summary>
/// 发起支付请求
/// </summary>
/// <param name="reqSeqId">请求流水号</param>
/// <param name="goodsDesc">商品描述</param>
/// <param name="tradeType">交易类型</param>
/// <param name="transAmt">交易金额</param>
/// <param name="wxData">微信参数集合</param>
/// <param name="timeExpire">交易有效期</param>
/// <param name="notifyUrl">异步通知地址</param>
/// <returns>支付响应</returns>
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<HuifuPayResponse>(responseJson);
}
catch (Exception ex)
{
LogException(ex, "Pay");
throw new Exception("支付请求失败: " + ex.Message, ex);
}
}
/// <summary>
/// 生成签名
/// </summary>
/// <param name="dataJson">数据JSON字符串</param>
/// <param name="privateKey">密钥</param>
/// <returns>签名结果</returns>
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);
}
}
/// <summary>
/// 将Java格式的私钥转换为XML格式
/// </summary>
/// <param name="privateKey">Java格式私钥</param>
/// <returns>XML格式私钥</returns>
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 = $@"<RSAKeyValue>
<Modulus>{Convert.ToBase64String(modulus.Value.ToByteArrayUnsigned())}</Modulus>
<Exponent>{Convert.ToBase64String(publicExponent.Value.ToByteArrayUnsigned())}</Exponent>
<P>{Convert.ToBase64String(prime1.Value.ToByteArrayUnsigned())}</P>
<Q>{Convert.ToBase64String(prime2.Value.ToByteArrayUnsigned())}</Q>
<DP>{Convert.ToBase64String(exponent1.Value.ToByteArrayUnsigned())}</DP>
<DQ>{Convert.ToBase64String(exponent2.Value.ToByteArrayUnsigned())}</DQ>
<InverseQ>{Convert.ToBase64String(coefficient.Value.ToByteArrayUnsigned())}</InverseQ>
<D>{Convert.ToBase64String(privateExponent.Value.ToByteArrayUnsigned())}</D>
</RSAKeyValue>";
return xmlPrivateKey;
}
catch (Exception ex)
{
LogException(ex, "ConvertPrivateKeyToXml");
throw new Exception("私钥格式转换失败: " + ex.Message, ex);
}
}
/// <summary>
/// 将Java格式的公钥转换为XML格式
/// </summary>
/// <param name="publicKey">Java格式公钥</param>
/// <returns>XML格式公钥</returns>
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 = $@"<RSAKeyValue>
<Modulus>{Convert.ToBase64String(modulus.Value.ToByteArrayUnsigned())}</Modulus>
<Exponent>{Convert.ToBase64String(publicExponent.Value.ToByteArrayUnsigned())}</Exponent>
</RSAKeyValue>";
return xmlPublicKey;
}
catch (Exception ex)
{
LogException(ex, "ConvertPublicKeyToXml");
throw new Exception("公钥格式转换失败: " + ex.Message, ex);
}
}
/// <summary>
/// 验证签名
/// </summary>
/// <param name="dataJson">数据JSON字符串</param>
/// <param name="signature">签名</param>
/// <param name="publicKey">公钥</param>
/// <returns>签名是否有效</returns>
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);
}
}
/// <summary>
/// 发送HTTP请求
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="requestJson">请求JSON</param>
/// <returns>响应结果</returns>
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
}