NewGaoKaoApi/PaymentSDK/WeiPay/WxPayData.cs

396 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace PaymentSDK
{
/// <summary>
/// 微信支付协议接口数据类所有的API接口通信都依赖这个数据结构
/// 在调用接口之前先填充各个字段的值,然后进行接口通信,
/// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
/// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
/// </summary>
public class WxPayData
{
public WxPayData()
{
}
//采用排序的Dictionary的好处是方便对数据包进行签名不用再签名之前再做一次排序
private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();
/**
* 设置某个字段的值
* @param key 字段名
* @param value 字段值
*/
public void SetValue(string key, object value)
{
m_values[key] = value;
}
/**
* 根据字段名获取某个字段的值
* @param key 字段名
* @return key对应的字段值
*/
public object GetValue(string key)
{
object o = null;
m_values.TryGetValue(key, out o);
return o;
}
/**
* 判断某个字段是否已设置
* @param key 字段名
* @return 若字段key已被设置则返回true否则返回false
*/
public bool IsSet(string key)
{
object o = null;
m_values.TryGetValue(key, out o);
if (null != o)
return true;
else
return false;
}
/**
* @将Dictionary转成xml
* @return 经转换得到的xml串
* @throws WePayException
**/
public string ToXml()
{
//数据为空时不能转化为xml格式
if (0 == m_values.Count)
{
//NLogHelper.WriteError("WxPayData数据为空!",DevAuthorNameEnum.Michael,false);
throw new Exception("WxPayData数据为空!");
}
string xml = "<xml>";
foreach (KeyValuePair<string, object> pair in m_values)
{
//字段值不能为null会影响后续流程
if (pair.Value == null)
{
// NLogHelper.WriteError("WxPayData内部含有值为null的字段!", DevAuthorNameEnum.Michael, false);
throw new Exception("WxPayData内部含有值为null的字段!");
}
if (pair.Value.GetType() == typeof(int))
{
xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
}
else if (pair.Value.GetType() == typeof(string))
{
xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
}
else//除了string和int类型不能含有其他数据类型
{
// NLogHelper.WriteError("WxPayData字段数据类型错误!", DevAuthorNameEnum.Michael, false);
throw new Exception("WxPayData字段数据类型错误!");
}
}
xml += "</xml>";
return xml;
}
/**
* @将xml转为WxPayData对象并返回对象内部的数据
* @param string 待转换的xml串
* @return 经转换得到的Dictionary
* @throws WePayException
*/
public SortedDictionary<string, object> FromXml(string xml)
{
if (string.IsNullOrEmpty(xml))
{
// NLogHelper.WriteError("将空的xml串转换为WxPayData不合法!", DevAuthorNameEnum.Michael, false);
throw new Exception("将空的xml串转换为WxPayData不合法!");
}
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
XmlNodeList nodes = xmlNode.ChildNodes;
foreach (XmlNode xn in nodes)
{
XmlElement xe = (XmlElement)xn;
m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
}
try
{
//2015-06-29 错误是没有签名
if (m_values["return_code"] != "SUCCESS")
{
return m_values;
}
CheckSign();//验证签名,不通过会抛异常
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return m_values;
}
/**
* @Dictionary格式转化成url参数格式
* @ return url格式串, 该串不包含sign字段值
*/
public string ToUrl()
{
string buff = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if (pair.Value == null)
{
// NLogHelper.WriteError("WxPayData内部含有值为null的字段!", DevAuthorNameEnum.Michael, false);
throw new Exception("WxPayData内部含有值为null的字段!");
}
if (pair.Key != "sign" && pair.Value.ToString() != "")
{
buff += pair.Key + "=" + pair.Value + "&";
}
}
buff = buff.Trim('&');
return buff;
}
/**
* @Dictionary格式化成Json
* @return json串数据
*/
public string ToJson()
{
string jsonStr = Newtonsoft.Json.JsonConvert.SerializeObject(m_values);
return jsonStr;
}
/**
* @values格式化成能在Web页面上显示的结果因为web页面上不能直接输出xml格式的字符串
*/
public string ToPrintStr()
{
string str = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if (pair.Value == null)
{
// NLogHelper.WriteError("WxPayData内部含有值为null的字段!", DevAuthorNameEnum.Michael, false);
throw new Exception("WxPayData内部含有值为null的字段!");
}
str += string.Format("{0}={1}<br>", pair.Key, pair.Value.ToString());
}
// NLogHelper.WriteInfo("Print in Web Page : " + str, DevAuthorNameEnum.Michael, false);
return str;
}
/**
* @生成签名,详见签名生成算法
* @return 签名, sign字段不参加签名
*/
public string MakeSign()
{
//转url格式
string str = ToUrl();
//在string后加入API KEY
str += "&key=" + "{key}";
// NLogHelper.WriteInfo("Signurl" + str,DevAuthorNameEnum.Michael,false);
//MD5加密
var md5 = MD5.Create();
var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
var sb = new StringBuilder();
foreach (byte b in bs)
{
sb.Append(b.ToString("x2"));
}
//所有字符转为大写
return sb.ToString().ToUpper();
}
/**
*
* 检测签名是否正确
* 正确返回true错误抛异常
*/
public bool CheckSign()
{
//如果没有设置签名,则跳过检测
if (!IsSet("sign"))
{
// Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
throw new Exception("WxPayData签名存在但不合法!");
}
//如果设置了签名但是签名为空,则抛异常
else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
{
//Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
throw new Exception("WxPayData签名存在但不合法!");
}
//获取接收到的签名
string return_sign = GetValue("sign").ToString();
//在本地计算新的签名
string cal_sign = MakeSign();
if (cal_sign == return_sign)
{
return true;
}
//Log.Error(this.GetType().ToString(), "WxPayData签名验证错误!");
throw new Exception("WxPayData签名验证错误!");
}
/**
* @获取Dictionary
*/
public SortedDictionary<string, object> GetValues()
{
return m_values;
}
/**
*
* 统一下单
* @param WxPaydata inputObj 提交给统一下单API的参数
* @param int timeOut 超时时间
* @throws WePayException
* @return 成功时返回,其他抛异常
*/
public static WxPayData UnifiedOrder(WxPayData inputObj, int timeOut = 60)
{
string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//检测必填参数
if (!inputObj.IsSet("out_trade_no"))
{
throw new Exception("缺少统一支付接口必填参数out_trade_no");
}
else if (!inputObj.IsSet("body"))
{
throw new Exception("缺少统一支付接口必填参数body");
}
else if (!inputObj.IsSet("total_fee"))
{
throw new Exception("缺少统一支付接口必填参数total_fee");
}
else if (!inputObj.IsSet("trade_type"))
{
throw new Exception("缺少统一支付接口必填参数trade_type");
}
//关联参数
if (inputObj.GetValue("trade_type").ToString() == "JSAPI" && !inputObj.IsSet("openid"))
{
throw new Exception("统一支付接口中缺少必填参数openidtrade_type为JSAPI时openid为必填参数");
}
if (inputObj.GetValue("trade_type").ToString() == "NATIVE" && !inputObj.IsSet("product_id"))
{
throw new Exception("统一支付接口中缺少必填参数product_idtrade_type为JSAPI时product_id为必填参数");
}
//异步通知url未设置则使用配置文件中的url
if (!inputObj.IsSet("notify_url"))
{
inputObj.SetValue("notify_url", WeiXinPayConfig.NotifyUrl);//异步通知url
}
inputObj.SetValue("appid", WeiXinPayConfig.APP_ID);//公众账号ID
inputObj.SetValue("mch_id", WeiXinPayConfig.MCHID);//商户号
inputObj.SetValue("spbill_create_ip", WeiXinPayConfig.IP);//终端ip
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
//签名
inputObj.SetValue("sign", inputObj.MakeSign());
string xml = inputObj.ToXml();
var start = DateTime.Now;
// Log.Info("XcxPayApi", "UnfiedOrder request : " + xml);
string response = HttpPost(xml, url, "application/xml", timeOut);
//Log.Info("XcxPayApi", "UnfiedOrder response : " + response);
// WebHookHelper.WebHookmarkdownSend(response);
var end = DateTime.Now;
int timeCost = (int)((end - start).TotalMilliseconds);
WxPayData result = new WxPayData();
result.FromXml(response);
// ReportCostTime(url, timeCost, result);//测速上报网络不好时使用
return result;
}
/// <summary>
/// 生成随机数
/// </summary>
/// <returns></returns>
public static string GenerateNonceStr()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
/// <summary>
/// POST请求
/// </summary>
/// <param name="postData"></param>
/// <param name="url"></param>
/// <param name="contentType">application/xml、application/json、application/text、application/x-www-form-urlencoded</param>
/// <param name="timeOut"></param>
/// <param name="headers"></param>
/// <returns></returns>
public static string HttpPost(string postData, string url, string contentType = null, int timeOut = 30, Dictionary<string, string> headers = null)
{
postData = postData ?? "";
var httpClientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true
};
using (HttpClient httpClient = new HttpClient(httpClientHandler))
{
if (headers != null)
{
foreach (var header in headers)
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
using (HttpContent client = new StringContent(postData, Encoding.UTF8))
{
if (contentType != null)
client.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
HttpResponseMessage response = httpClient.PostAsync(url, client).Result;
return response.Content.ReadAsStringAsync().Result;
}
}
}
}
}