撸一撸腾讯的微信支付(C#)
一、前言
以往网上支付都是支付宝的天下,随着微信用户群的日益增多(其实,到现在我也不理解微信为嘛那么火,功能还没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;//录入自己申请的公众号ID
string 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*).ToString("", CultureInfo.InvariantCulture);
//调用微信API的IP
string 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().ToString("yyyyMMddHHmmss");
//string time_expire = "20160503114957";
静态类WXPayUlities的CreateRandomStr()方法用于生成随机字符串:
public static string CreateRandomStr(int length = )
{
var guid = Guid.NewGuid();
if (length <= || length > )
{
return guid.ToString("N");
}
else
{
return guid.ToString("N").Remove(, 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();
for (int i = ; i < t.Length; i++)
{
sb.Append(t[i].ToString("x").PadLeft(, ''));
}
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 ( == 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, );
//回传数据为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 = ;
//二维码版本号
endocder.QRCodeVersion = ;
//纠错等级
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 = )
{
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);//公众账号ID
inputObj.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; }
撸一撸腾讯的微信支付(C#)的更多相关文章
- Cordova - 彻底搞定安卓中的微信支付插件!
Cordova:8.0.0 Android studio:3.2.1 cordova-plugin-adam-wechat : 3.0.6 你看到这个标题肯定会惊讶,一个Cordova的微信支付插件, ...
- WhatsApp值160亿美元,腾讯推大众点评微信支付!
腾讯前脚刚入股大众点评,FB后脚就将斥资160亿美元收购WhatsApp(40亿美元现金和120亿美元股票). 为什么WhatsApp值160亿美元?这是什么东东呢?WhatsApp这款服务可以帮助用 ...
- 腾讯微信支付,程序员是如何让jQuery代码付钱的
微信支付和支付宝支付已经是我们生活中不可确实的两个金融软件了,也是必备的,小编认为小钱用微信,大钱用支付宝. 下面这个图是我们生活中用腾讯微信支付平台的最后一个页面,大家想不想知道这个页面是如果做出来 ...
- 微信支付之统一下单--JAVA版
都说微信支付有些坑,都抱怨微信支付的文档太烂,一会APPId,一会商户id,还有appsecret,支付API秘钥让你傻傻分不清楚,还有这里大写那里小写,几种标准,让你眼花缭乱.没错,这就是很多技术团 ...
- 微信支付之01------获取订单微信支付二维码的接口------Java实现
[ 前言:以前写过一个获取微信二维码支付的接口,发现最近公司新开的项目会经常用到,现在我又翻出代码看了一遍,觉得还是把整个代码流程记下来的好 ] 借鉴博客: 他这篇博客写得不错,挺全的:https:/ ...
- iOS开发微信支付
现在基本所有的App都会接入支付宝支付以及微信支付,也有很多第三方提供给你 SDK帮你接入,但是这种涉及到支付的东西还是自己服务器搞来的好一些,其实搞懂了 逻辑非常的简单,下面直接给大家说说下基本流程 ...
- 微信小程序web-view(webview) 嵌套H5页面 唤起微信支付的实现方案
场景:小程序页面有一个web-view组件,组件嵌套的H5页面,要唤起微信支付. 先讲一下我的项目,首先我是自己开发的一个H5触屏版的商城系统,里面含有购物车,订单支付等功能.然后刚开始,我们公众号里 ...
- php 微信支付企业付款
1.所需参数 字段名 变量名 必填 示例值 类型 描述 公众账号appid mch_appid 是 wx8888888888888888 String 公众号的appId 商户号 mchid 是 19 ...
- 微信支付 composer 方法 --- 实测有效
<h1 align="center">Pay</h1> <p align="center"> <a href=&quo ...
随机推荐
- Ubuntu 14.04 – How to install xrdp in Ubuntu 14.04
http://c-nergy.be/blog/?p=5305 Hello World, Ubuntu 14.04 has been released on April 17th 2014 and we ...
- 通过 XML HTTP 加载 XML 文件
新建一个.aspx文件 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="02-通 ...
- Winfrom 基于TCP的Socket 编程
基于TCP的Socket基础例子 服务端的代码 public partial class Form1 : Form { public Form1() { InitializeComponent(); ...
- Silverlight 使用IsolatedStorage新建XML文件,并且用LINQ查询XML
代码 Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-- ...
- 使用虚拟机在ubuntu下搭建mongoDB开发环境和简单增删改查操作
最近在折腾mongodb和nodejs,在imooc上找了一个mongodb的入门教程,跟着里面一步一步的走,下面记录下我操作的步骤和遇到的问题. 课程地址:http://www.imooc.com/ ...
- php怎么解析utf-8带BOM编码的json数据,php解析json数据返回NULL
今天遇到一个问题,json_decode解析json数据返回null,试了各种方法都不行,最后发现,原来是json文件编码的问题. 当json_decode解析utf-8带BOM格式的json数据时, ...
- Ubuntu 14.04 安装flash插件
分别tar.gz和apt-get方法 第一种: adboe官网下载tar.gz,进入terminal 1.解压缩包,输入命令“tar -zxvf 软件包名字” 2.拷贝插件到mozilla目录,输入命 ...
- Laravel 5 基础(八)- 模型、控制器、视图基础流程
添加路由 Route::get('artiles', 'ArticlesController@index'); 创建控制器 php artisan make:controller ArticlesCo ...
- jquery Mobile应用第2课《构建跨平台APP:jQuery Mobile移动应用实战》连载二(简单的QWER键盘)
在jQuery Mobile的布局中,控件大多都是单独占据页面中的一行,按钮自然也不例外,但是仍然有一些方法能够让多个按钮组成一行,比如说在范例6-5中就利用按钮分组的方法使4个按钮并列在一行中,如图 ...
- Java入门到精通——基础篇之面向对象
一.概述. Java属于面向对象的一种语言,因为Java是面向对象的语言所以这个语言的诞生需要有五个基本特性: 1)万物皆为对象. 2)程序是对象的集合. 3)每个对象都有自己的由其他对象所构成的存储 ...