这次是第二次对接微信公众号的支付了,第一次代码写的很乱,没有找现成的SDK,感觉里面乱七八糟的东西太多不够清爽,而我仅仅是做一个小功能,对接又不复杂干脆自己做。
不想重复做轮子的朋友可以参考一下:Senparc C# SDK
这里我只说一下公众号内支付,APP以及其他的支付,等我以后用得到的时候再总结。
1、拿到用户的openid
2、使用微信接口下单获取preorder_id
3、前端H5调用微信支付对话框
4、接收微信支付结果的通知
好了,现在开始具体过程介绍
1、公众号的appid,appsecret
登录微信公众平台,在开发一栏里找到基本配置
2、微信支付平台的商户id(mch_id), 支付key(这个是自己设置的)
每个用户在公众号里都有一个唯一的openid,公众号可以通过openid获取用户的信息,当然获取用户信息是需要用户同意的。但是只获取openid不需要用户同意。
这里没提到的内容可以参考官方文档,:微信网页授权
要获取openid,首先要获取code,然后根据code获取openid。这个过程是为了要用户授权,也就是下面的绿色页面。
获取code跳转到这个链接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
参数
appid:是公众号的唯一标识
redirect_uri:是授权后重定向的回调链接地址,请使用urlEncode对链接进行处理
response_type:返回类型,请填写code
scope:是应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
state:否 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
#wechat_redirect 是 无论直接打开还是做页面302重定向时候,必须带此参数
在这里我们scope参数使用snsapi_base,使用snsapi_userinfo 会弹出绿色页面,但是结果是一样的。
如果没出错的话,页面会跳转到指定的redirect_uri页面,并附带上code和state参数。
获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
参数
appid:公众号的唯一标识
secret:公众号的appsecret
code:填写第一步获取的code参数
grant_type:填写authorization_code
正确时返回的JSON数据包如下
{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }
看到这里面的openid这个参数没?这时就可以取用了,获取到的refresh_token可以保存起来,以后直接用refresh_token获取openid。
使用微信公众号支付必须在微信留个案底,需要把订单信息提交给微信,这时候就需要使用统一下单接口,官方文档
处于安全角度,使用这个接口需要对传输的信息加密,为了避免篡改,需要用到支付key,过程是这样的:
1、生成一个随机数:32位,可以创建一个Guid。
2、生成安全签名:把提交到微信的参数按照首字母字典排序(a-z),编辑成key=value&key=value…的形式,然后末尾加上key,用md5加个密。
3、创建提交的xml
4、使用post提交到微信接口
· 把要传的参数使用字典排序(除了sign):
SortedDictionary param = new SortedDictionary();
param.Add("appid", appid);
param.Add("attach", attach);
param.Add("body", body);
param.Add("mch_id", config.mchid);
param.Add("nonce_str", Guid.NewGuid().ToString().Replace("-", "").ToUpper());
param.Add("notify_url", notifyurl);
param.Add("openid", openid);
param.Add("out_trade_no", orderno);
param.Add("spbill_create_ip", System.Web.HttpContext.Current.Request.UserHostAddress);
param.Add("total_fee", ((Int32)(price * 100)).ToString());
param.Add("trade_type", "JSAPI");
· 参数生成字符串
private String GetUnEncyptKey(SortedDictionary<String, String> sort)
{
List<String> lstParam = new List<String>();
foreach (var item in sort)
{
lstParam.Add(String.Format("{0}={1}", item.Key, item.Value));
}
return String.Join("&", lstParam);
}
· MD5加密
///
/// 签名字符串
///
/// 需要签名的字符串
/// 密钥
/// 编码格式
/// 签名结果
public static string Sign(string prestr, string key, string _input_charset)
{
StringBuilder sb = new StringBuilder(32);
prestr = prestr +"&key="+ key;
MD5 md5 = new MD5CryptoServiceProvider();
byte[] t = md5.ComputeHash(Encoding.GetEncoding(_input_charset).GetBytes(prestr));
for (int i = 0; i < t.Length; i++)
{
sb.Append(t[i].ToString("x").PadLeft(2, '0'));
}
return sb.ToString();
}
· 把刚才生成的sign加入到SortedDictionary中然后生成xml:
param.Add("sign", "签名值");
///
/// 把提交的参数参数转换为xml
///
///
///
public static String GetPostXML(SortedDictionary<String, String> requestInfo)
{
List<String> xml = new List<String>();
xml.Add("" );
SortedDictionary<String, String> post_param = ExcludeEmpty(requestInfo);
foreach (var item in post_param)
{
if (item.Key == "detail")
{
xml.Add(String.Format("<{0}>", item.Key, item.Value));
}
else {
xml.Add(String.Format("<{0}>{1}{0}>", item.Key, item.Value));
}
}
xml.Add("");
return String.Join("", xml);
}
(微信的detail 是使用CDATA防止xml转义的,其他参数不能采用)
好了,把XML使用POST提交到接口吧,地址是:https://api.mch.weixin.qq.com/pay/unifiedorder
返回结果也是xml的,可以返序列化。使用这段代码可以直接反序列化成实体类:
///
/// 反序列化xml
///
/// XML字符串
///
public static T Deserialize(string xml)
{
try
{
String typename=typeof(T).Name;
xml = xml.Replace("" , "<" + typename + ">").Replace("", "" + typename + ">");
using (StringReader sr = new StringReader(xml))
{
XmlSerializer xmldes = new XmlSerializer(typeof(T));
return (T)xmldes.Deserialize(sr);
}
}
catch (Exception e)
{
return default(T);
}
}
好了,正常情况下就可以获取到prepay_id了。
请参考官方文档
对接过统一下单接口,这个过程就很简单了。按照这个文档做就好了。这里的随机字符串和签名都是重新生成的,生成方式和前面说的一样。
前端页面JS:
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": "wx8888888888888888", //公众号名称,由商户传入
"timeStamp": "1414561699", //时间戳,自1970年以来的秒数
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS", //随机串
"package": "prepay_id=123456789",
"signType": "MD5", //微信签名方式:
"paySign": "C380BEC2BFD727A4B6845133519F3AD6" //微信签名
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
//支付成功,后续自行处理
}
else
{
//支付取消,或者其他错误,自行处理
}
});
}
好了,到现在支付就完成了,下面还需要获取一下支付通知,最终确认一下支付状态。
请自行阅读一下,官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
简单说明一下文档中return_code 和result_code 分别表示通信结果和业务(支付)执行结果。收到参数后自己记录一下信息,尤其是支付单号transaction_id,然后再按照约定格式返回一个xml表示成功就可以了。
必须要注意的是,一定要对收到的参数进行签名,验证收到的消息来源是否来自微信,千万不可懒。因为加密所需的key只有你和微信知道,所以加密结果可能能对起来。如果key泄漏了会很危险,因为其他人可能会伪造一个结果,告诉你支付成功了。key可以在微信支付平台中修改。
这次对接过程还比较顺利,没有遇到什么坑,记得第一次做微信接口的时候,不知道是文档我没理解,还是其他什么原因,用了很长事件。当然只按照上面说的是不行的,这里只是提供一个思路和几个辅助的代码段,还需要按照你自己的业务逻辑进行改造,至于其他接口大同小异,不在叙述了。