一、前言

以往网上支付都是支付宝的天下,随着微信用户群的日益增多(其实,到现在我也不理解微信为嘛那么火,功能还没QQ强大,或许是公众号的原因?),先如今不上个微信支付你都不好意思说你系统支持在线支付。在之前研究过Nopcommerc支付宝支付插件,对这类第三方支付流程心中大概明白,但实际开发下来发现微信支付当中需要的注意点还不少。总体上,微信支付文档不及支付宝文档,按照后者demo一步步就能完成支付功能。

二、微信支付流程

微信支持以四种方式完成支付,分别是刷卡支付、公众号支付、扫码支付、APP支付。不同支付方式功能实现存在一定差异,具体体现在请求参数、回传参数及商户系统与微信支付中心交互流程等。本文仅研究扫码支付,并且仅介绍扫码支付中的模式二(以下简称模式二)。

下图是微信官方给出的模式二支付流程,详细描述可以参见(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5)。

可以将微信支付流程描述如下:

1、准备工作。模式二支付,开发者需要事先申请公众号,申请通过后的公众号还需要申请开通微信支付,具体开通流程不知道的可以自行百度。微信支付开通成功后会得到三个参数:AppId(公众帐号ID)、MchId(商户号)、Key(用于签名)。

2、组织请求参数。与支付宝支付一样,商户系统需要将支付相关信息(如订单号、支付金额、回调地址等参数发送到微信支付系统),因此这一步需要结合微信官方文档,根据自己系统组织相关参数的获取办法。详细的请求参数参看官方文档,本文中使用的请求参数列举在下表中。

参数名称 参数描述 注意事项
appid 公众帐号ID 必须
MchId 商户号 必须
Key 参与签名生成 必须
out_trade_no 订单号 必须。商户系统订单号。
total_fee 支付总金额 必须。单位为人民币最小单位分
trade_type 交易类型

必须。可能取值为字符串 JSAPI,NATIVE,APP,对应不同的支付模式。

JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app;刷卡支付交易类型为MICROPAY,但与其它支付方式调用接口不一致,所以不讨论

notify_url 通知地址 必须。该地址为商户系统响应微信支付系统回传支付结果的Action地址,不能为本地地址(如127.0.0.1),不能携带参数。微信会重复向该地址发送支付结果,直到商户系统给微信支付系统发送“已收到”信息后才停止发送。
nonce_str 随机字符串 必须。不长于32位的字符串,用于参与签名的生成。
spbill_create_ip 终端IP 必须。NATIVE支付方式就是调用微信支付API的终端IP。
body 商品简单描述 必须。官方说该字段须严格按照规范传递,但实测不按官方规范上传也没事。该字段会显示在用户支付界面上,建议还是规范书写。
time_start 交易起始时间 不必须。一般与订单过期时间一起使用,表明本次请求返回的二维码的支付有效时间。
time_expire 交易结束时间 不必须。失效时间必须大于五分钟。
sign 签名 必须。签名参数,具体生成方法见官网https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3

3、发送请求参数。向微信远程相应接口地址https://api.mch.weixin.qq.com/pay/unifiedorder  发送支付请求,该地址会返回针对当前请求的相应信息,具体返回信息参见官方文档。我们需要从中提取出二维码链接,生成二维码图片推送给用户。

4.  当用户扫码完成(与商户系统无关,客户在个人手机的微信端完成操作)微信支付系统会给notify_url设定的地址发送支付结果,商户系统接收到微信回复的信息后 更新系统的订单支付状态信息,同时给微信回馈:“我晓得喽,支付完成,不要再发了”。

5、重定向操作,将用户导向至某一界面,告知支付结果并提示下一步操作。

三 功能实现

1、请求参数的准备。

  //响应的远程url地址string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";string appid = _WeiXinPaymentSettings.AppId;//录入自己申请的公众号IDstring mch_Id = _WeiXinPaymentSettings.MchId;string key = _WeiXinPaymentSettings.Key;//商户订单号string out_trade_no = postProcessPaymentRequest.Order.Id.ToString() +"PAY"+ DateTime.Now.ToString("yyyyMMddHHmmss");//发现支付订单号为一位数字时,回返回请求失败信息,所以加入时间字符串,字符串PAY为split出实际订单号做准备  当然此处可以根据自身系统设计//支付总金额string total_fee = (postProcessPaymentRequest.Order.OrderTotal*100).ToString("0", CultureInfo.InvariantCulture);//调用微信API的IPstring spbill_create_ip = _webHelper.GetCurrentIpAddress();//交易类型string trade_type = "NATIVE";//产品Id 这里使用订单号string product_id = out_trade_no;//接收微信支付异步通知回调地址 因为是开发Nop插件,所以此处为插件响应请求的地址string notify_url = _webHelper.GetStoreLocation(false) + "Plugins/PaymentWeiXinPay/Notify";//商品或支付单简要描述string body = "TEST缴费";//随机数字符串string nonce_str = WXPayUlities.CreateRandomStr();  //订单(指代微信订单)生成时间var now = DateTime.Now;string time_start = DateTime.Now.ToString("yyyyMMddHHmmss");//string time_start = "20160503114458";//订单失效时间 此处为开始时间后五分钟string time_expire = now.AddMinutes(5).ToString("yyyyMMddHHmmss");//string time_expire = "20160503114957";

静态类WXPayUlities的CreateRandomStr()方法用于生成随机字符串:
    public static string CreateRandomStr(int length = 0){var guid = Guid.NewGuid();if (length <= 0 || length > 32){return guid.ToString("N");}else{return guid.ToString("N").Remove(0, length);}}

2、组织请求参数  向微信支付系统发送的支付请求按key-value的形式组织,并且要求所有非空参数值的参数按照参数名ASCII码从小到大排序。这里使用C#的SortedDictionary类型对参数进行排序。
                 var res = new SortedDictionary<string, object>();res.SetValue("appid", appid);res.SetValue("mch_id", mch_Id);res.SetValue("out_trade_no", out_trade_no);res.SetValue("total_fee", total_fee);res.SetValue("spbill_create_ip", spbill_create_ip);res.SetValue("trade_type", trade_type);res.SetValue("product_id", product_id);res.SetValue("notify_url", notify_url);res.SetValue("body", body);res.SetValue("nonce_str", nonce_str);res.SetValue("time_start", time_start);res.SetValue("time_expire", time_expire);

   签名生成的通用步骤如下:

  第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    特别注意以下重要规则:

    ◆ 参数名ASCII码从小到大排序(字典序);

    ◆ 如果参数的值为空不参与签名;

    ◆ 参数名区分大小写;

    ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。

    ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

  第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

    举例:

    假设传送的参数如下:

    appid: wxd930ea5d5a258f4f

    mch_id: 10000100

    device_info: 1000

    body: test

    nonce_str: ibuaiVcKdpRxkhJA

    第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:

    stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";

    第二步:拼接API密钥:

    stringSignTemp="stringA&key=192006250b4c09247ec02edce69f6a2d"

    sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7"

    最终得到最终发送的数据:

    <xml>

    <appid>wxd930ea5d5a258f4f</appid>

    <mch_id>10000100</mch_id>

    <device_info>1000<device_info>

    <body>test</body>

    <nonce_str>ibuaiVcKdpRxkhJA</nonce_str>

    <sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>

    <xml>

因此可以通过一个拓展string的拓展方法生成签名:

     public static string CreateSign(this string str, string key){string t = str + "&key=" + key;return GetMD5(t, "utf-8").ToUpper();}public static string GetMD5(string Input, string Input_charset){MD5 md5 = new MD5CryptoServiceProvider();byte[] t = md5.ComputeHash(Encoding.GetEncoding(Input_charset).GetBytes(Input));StringBuilder sb = new StringBuilder(32);for (int i = 0; i < t.Length; i++){sb.Append(t[i].ToString("x").PadLeft(2, '0'));}return sb.ToString();}

   将SortedDictionary类型转化为URL key-value对
 public static string ToUrl(this SortedDictionary<string, object> source){string buff = "";foreach (KeyValuePair<string, object> pair in source){if (pair.Value == null){throw new Exception("m_values内部含有值为null的字段!");}if (pair.Key != "sign" && pair.Value.ToString() != ""){buff += pair.Key + "=" + pair.Value + "&";}}buff = buff.Trim('&');return buff;}

至此,我们将SortedDictionary转为为url key-value后使用商户的Key可以完成签名字符串的创建

                 //签名string sign = res.ToUrl().CreateSign(key);

值得注意的是,微信支付系统与商务系统之间的交互都为XML格式,我们需要两个方法,实现XML与URL之间的互相转换
   /// <summary>/// 将参数列表转化为XML/// </summary>/// <param name="m_values"></param>/// <returns></returns>public static string ToXml(this SortedDictionary<string, object> m_values){//数据为空时不能转化为xml格式if (0 == m_values.Count){throw new Exception("m_values数据为空!");}string xml = "<xml>";foreach (KeyValuePair<string, object> pair in m_values){//字段值不能为null,会影响后续流程if (pair.Value == null){throw new Exception("m_values内部含有值为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类型不能含有其他数据类型
                {throw new Exception("m_values字段数据类型错误!");}}xml += "</xml>";return xml;}/// <summary>/// 将xml转为WxPayData对象并返回对象内部的数据/// </summary>/// <param name="xml">待提取的XML</param>/// <param name="locolkey">本地Key</param>/// <returns></returns>public static SortedDictionary<string, object> FromXml(this string xml, string locolkey){var values = new SortedDictionary<string, object>();if (string.IsNullOrEmpty(xml)){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;values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
            }try{if ((string)values["return_code"] != "SUCCESS"){return values;}}catch (Exception ex){throw new Exception(ex.Message);}return values;}

3 、发送支付请求。

我们通过C#的HttpWebRequest类向微信支付接口发送POST的支付请求。这里就不贴实现代码了,需要注意网络延迟,请求的超市处理。

4、处理请求结果

这一步,我们需要从微信返回的XML中获取微信返回的二维码链接

               //向微信发送Post请求并得到二维码链接string response = HttpService.Post(xml, url, 6);//回传数据为XML结构 提取出二维码生成链接var p = response.FromXml(key);//返回结果判断 必须为return_code=SUCCESS 结合自己业务情况处理//接受微信返回的签名 并本地生成签名完成校验!//验证签名var t=p.CheckSign(key);/// <summary>/// 检查签名是否匹配/// </summary>/// <param name="values">接收到的回传参数</param>/// <returns>错误抛出异常,正确返回true</returns>public static bool CheckSign(this SortedDictionary<string, object> values, string key){//如果没有设置签名,则跳过检测if (!IsSet(values, "sign")){throw new Exception("签名存在但不合法!");}//如果设置了签名但是签名为空,则抛异常else if (GetValue(values, "sign") == null || GetValue(values, "sign").ToString() == ""){throw new Exception("签名存在但不合法!");}//获取接收到的签名string return_sign = GetValue(values, "sign").ToString();//在本地计算新的签名string cal_sign = CreateSign(ToUrl(values), key);if (cal_sign == return_sign){return true;}throw new Exception("签名验证错误!");}

经过必要的验证后,我们可以提取二维码链接并生成二维码图片。这里引用了程序集ThoughtWorks.QRCode.dll 来生成二维码图片

               //提取二维码链接var code_Url = p.GetValue("code_url").ToString();//得到二维码图片的本地路径var v = CreateQRCode(code_Url);/// <summary>/// 生成二维码/// </summary>/// <param name="result_Code">微信返回的url</param>/// <returns>返回二维码图片地址</returns>protected string CreateQRCode(string result_Code){QRCodeEncoder endocder = new QRCodeEncoder();//二维码背景颜色endocder.QRCodeBackgroundColor = System.Drawing.Color.White;//二维码编码方式endocder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE;//每个小方格的宽度endocder.QRCodeScale = 10;//二维码版本号endocder.QRCodeVersion = 5;//纠错等级endocder.QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M;//将json川做成二维码Bitmap bitmap = endocder.Encode(result_Code);//图片存储路径string strSaveDir = _webHelper.MapPath("path");if (!Directory.Exists(strSaveDir)){Directory.CreateDirectory(strSaveDir);}var randomId = Guid.NewGuid();string strSavePath = Path.Combine(strSaveDir, randomId + ".png");if (!System.IO.File.Exists(strSavePath)){bitmap.Save(strSavePath);return Path.Combine("path", randomId + ".png"); }else{throw new NopException("存在同名的微信支付二维码");}}  

至此,微信支付流程完成了大半,当用户扫码完成支付后,我们的回调地址对应的方法需要结合业务需要作出如订单状态修改为已支付等相应处理,这里不再贴代码。但值得一提的是,为了安全起见,回调地址中有必要加上回传订单的查询。

  //查询订单protected bool QueryOrder(string transaction_id){var res = new SortedDictionary<string, object>();res.SetValue("transaction_id", transaction_id);var result = OrderQuery(res);if (result.GetValue("return_code").ToString() == "SUCCESS" &&result.GetValue("result_code").ToString() == "SUCCESS"){return true;}else{return false;}}public SortedDictionary<string, object> OrderQuery(SortedDictionary<string, object> inputObj, int timeOut = 6){string url = "https://api.mch.weixin.qq.com/pay/orderquery";//检测必填参数if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id")){throw new Exception("订单查询接口中,out_trade_no、transaction_id至少填一个!");}inputObj.SetValue("appid", _WeiXinPaymentSettings.AppId);//公众账号IDinputObj.SetValue("mch_id", _WeiXinPaymentSettings.MchId);//商户号inputObj.SetValue("nonce_str", WXPayUlities.CreateRandomStr());//随机字符串inputObj.SetValue("sign", inputObj.ToUrl().CreateSign(_WeiXinPaymentSettings.Key));//签名string xml = inputObj.ToXml();string response = HttpService.Post(xml, url, timeOut);//调用HTTP通信接口提交数据var responseSorted = response.FromXml(_WeiXinPaymentSettings.Key);var t = responseSorted.CheckSign(_WeiXinPaymentSettings.Key);if (t == false)throw new Exception("回传签名验证失败");return responseSorted;}

 

转载于:https://www.cnblogs.com/Jnw-qianxi/p/6013959.html

撸一撸腾讯的微信支付(C#)相关推荐

  1. 微信支付原总经理吴毅:离开腾讯,如何再战蚂蚁金服?

    微信支付原总经理吴毅:离开腾讯,如何再战蚂蚁金服?(转) 来源:企鹅生态 5月4日,腾讯宣布微信支付总经理吴毅离职,随后分期乐确认这位微信红包缔造者将加盟并出任总裁.吴毅是近一年来腾讯走出的最知名的管 ...

  2. 微信支付在巴黎发布“智慧生活零时差”全球战略

    中新社巴黎1月22日电 (记者 李洋)当地时间1月22日,微信支付在巴黎发布了以"智慧生活零时差"为主题的全球战略,以跨境支付为依托,更好地连接境外商业与中国游客,为处于不同时区的 ...

  3. h5调微信支付 unkonw url_聚合支付系统设计(一)

    产品概述与整体设计 背景 如今,网购已经渗透到人们日常生活中的方方面面,做为网购的载体,互联网电商平台发展如火如荼,支付功能做为其不可或缺的一部分,实现起来,也有各种各样的方案.根据自己有限的认知,我 ...

  4. 微信“支付”页全国多地上线“出行服务”,已覆盖108城

    近期,微信"支付"页面新增"出行服务"入口,作为一站式的出行服务平台,"出行服务"提供覆盖车主服务.公交出行.打车租车等多场景出行服务,目前 ...

  5. 微信支付 postman_微信上线新功能,马化腾都在玩!快查查你多少分?

    尽管在移动支付领域与支付宝打得难解难分,但微信支付在信用体系建设方面的进展却慢了对手不只半拍,当芝麻分已经深入日常生活各种使用场景时,腾讯的"微信支付分"终于在6月3日晚间正式上线 ...

  6. 微信支付与支付宝钱包的竞争分析

    微信支付与支付宝钱包的竞争分析 NO1: 十九世纪七十年代起,"物竞天择,适者生存,优胜劣汰"已逐渐成为现代生物学的口号.而今,不知不觉中,它似乎也成了当代社会学的口号.罗素说:& ...

  7. iOS微信支付的步骤以及问题描述

    本博客转自:http://www.jianshu.com/p/22a1ceee4d36 一.微信支付的步骤: 1.在微信开放平台注册.微信开放平台 2.创建一个App应用.(需要填写App的资料,审核 ...

  8. 微信支付分正式上线:马化腾在朋友圈四处挑战,10分钟后被打败

    Python实战社群 Java实战社群 长按识别下方二维码,按需求添加 扫码关注添加客服 进Python社群▲ 扫码关注添加客服 进Java社群▲ 作者丨周小白 来源丨TechWeb(TechWeb) ...

  9. 腾讯微信支付支持全量商家小程序场景开通云闪付功能

    为持续推进支付生态开放及合作,腾讯宣布与银联云闪付互联互通再次取得重大进展: 微信支付于12月15日开始逐步升级商家小程序支持云闪付付款功能,微信商家小程序已全面开通云闪付.该功能升级后,用户即可在已 ...

最新文章

  1. es6常用语法和特性
  2. 【AutoML】强化学习如何用于模型蒸馏?
  3. triplet loss 在深度学习中主要应用在什么地方?有什么明显的优势?
  4. linux内存分配器类型,内核早期内存分配器:memblock
  5. 视频分辨率和音频质量_高分辨率音频的声音是否比CD质量好?
  6. python画直方图-从零开始学Python【13】--matplotlib(直方图)
  7. 四面体体积公式 hdu 1411
  8. docker使用阿里云的镜像加速器的地址
  9. 金边富贵竹的养护方法
  10. 贼好玩!我用Python写了一个AI玩星际争霸2!
  11. 2022最新阿里云域名注册和续费优惠口令及使用方法
  12. desmos绘制心形图案
  13. 国外问卷调查这个项目可以做吗?
  14. 多可系统中如何修改注册信息
  15. Select 选择器
  16. \ddd \xhh
  17. 人工智能 | ShowMeAI资讯日报 #2022.06.15
  18. python dataframe取一列_pandas取dataframe特定行/列
  19. Unity之自动生成预制体脚本
  20. 一、翻越前端的三座大山——继承/原型链

热门文章

  1. Keil5使用AC6编译器
  2. android广告轮播无限
  3. JavaScript 判断是否是数字 isFinite() Number.isFinite()
  4. html做成小程序,HTML-简单表单制作-表单制作-小程序表单制作
  5. PD虚拟机如何设置内存和CPU核心数
  6. .7z.001,.7z.002这样的文件如何解压
  7. Windows Dll 动态加载
  8. 计算机五个盘,我的电脑分区分了5个盘。怎么办?
  9. 风吹柳叶(Curtain creeper)
  10. MySQL性能优化的最佳20+条经验