Openresty开发微信支付功能
2016-06-02 18:01
453 查看
Openresty开发微信支付功能
——服务器开发工程师 蔡剑彬 caijianbin668943@163.com
一、开发环境
1、硬件环境:1核/1G内存/8G硬盘/1M宽带的腾讯云服务器
2、软件环境:CentOS7操作系统、Openresty 1.9.7.2、LuaXml_101012、redis
二、官方流程时序图
图1 微信支付流程时序图
第一眼看下去,觉得好复杂的,尤其是平时比较少接触时序图的我,但仔细看看,发现服务器需要注意的其实就只有4个标了红色的地方,下面将逐一进行详解。
三、生成图文消息链接或二维码
当用户点击商品时,服务器需要返回该商品的详细信息,标明商品有多好用、价格有多划算、已经有多少人买了等等,以便哄用户点击右下角那个“立即购买”按钮(“加入购物车”按钮也算成功了一半)。总而言之就是需要生成商户的订单,这个部分我就不详述,其实也就是创建个订单号,把商品id、订单id之类的信息放到redis中(用其他缓存数据库我也没意见的),顺便设个expire time,后面用户付钱了才把订单信息放到mysql去。
接下来就是讨论下我们的重点——“付钱的过程”。
四、商户server调用统一下单接口请求订单
微信官方文档:统一下单API。
根据官方文档说的,就是把所有所有必填项,以及在某些选项下选填项也变成必填项的项全部组成在xml中,发到微信服务器。他们说的十分简单,然而这只是站在他们已经懂的立场上觉得简单而已,事实并不是这样。
1、微信支付文档的坑
有以下几样事项文档中是没有任何说明的,也是最多人踩的坑:
1) 文档中没有提到访问https://api.mch.weixin.qq.com/pay/unifiedorder是通过POST方式;
2) 所有字段,除了总金额(total_fee)是int类型,其余都是string类型。笔者当时给随机字符串(nonce_str)赋了一个随机数,然后就发生了十分诡异的事情!在Android中可以调试通过,而Ios中却提示“config失败”!后面都转成字符串就可以了;
3) “签名失败”这个问题估计是最多人蛋碎的地方了。看了官方的签名算法后,第一感觉是不是就直接拿appid、mch_id、device_info、body和nonce_str参与MD5加密就可以了呢?然而并不是,而是所有需要放到xml中上传的字段都要参与MD5加密!而且所有字段需要按照字母升序排列,不仅仅是MD5加密时需要排序,连xml中也要按这个顺序排序,sign放到最下面;
4) 参与MD5加密的字符串utf8编码的问题,笔者用的vim码字,在终端菜单->Terminal->Set Character Encoding->Unicode (UTF=8)即可;
5) “调用支付JSAP缺少参数:$key0$”,这个问题就充分表现了微信文档是如何坑的。因为无论是后台还是前端没有做好,都是返回的同一个错误,很难定位是谁的问题。大多数人是因为后台生成签名时,时间戳写成”timestamp=xxx”导致问题出现,改成”timeStamp=xxx”就好。当我一次又一次核对了后台数据,真的确定没有问题了,才让前端看看是否前端导致的。最后发现原来是前端少传了prepay_id,添加上即可;
2、如何使用LuaXML库
避开了上面5个坑,接下来的工作就好多了,除了LuaXml这个坑之外,那就先讲讲LuaXml吧。
从官网上下载的LuaXml_101012.zip,解压后里面的LuaXML_lib.so并不能直接使用,否则会报“wrong ELF calss : ELFCLASS32”的错误,因为笔者的操作系统是CentOS7,64位机,用32位的bin是不行的,所以需要重新编译。
在目录下直接make,会提示找不到lua源码,查看Makefile,里面有这样两句:
图2 LuaXml Makefile截图
下载对应版本的lua源码放在与LuaXml文件夹的同一目录下,再次编译。
如果编译提示”cannot find -llua”那么就表明找不到lua库,Makefile中有这句
,由于centos7是64位,所以安装lua的时候,lua库的路径是”/lib64/liblua-5.1.so”,此时做个软连接”ln -s /lib64/liblua-5.1.so /lib/liblua.so”,接着再编译即可。
编译完后,把LuaXML_lib.so和LuaXml.lua放入path_to_openresty/LuaJIT/lib/lua/5.1 和path_to_openresty/LuaJIT/share/lua/5.1中:
cp -r LuaXML_lib.so path_to_openresty/LuaJIT/lib/lua/5.1/
cp -r LuaXML_lib.so path_to_openresty/LuaJIT/share/lua/5.1/
cp -r LuaXml.lua path_to_openresty/LuaJIT/lib/lua/5.1/
cp -r LuaXml.lua path_to_openresty/LuaJIT/share/lua/5.1/
至此,LuaXML库就可以使用了,当然还要注意下面一个坑:
在很多示例代码中,require "LuaXml"之后便可以直接使用其全局变量xml了,事实上确实是可以直接使用,但是只能使用一次,而第二次访问接口是,会报错“attempt to index global 'xml' (a nil value)”!详情可以点这里(需要翻墙),解决办法便是在require “LuaXml”代码后面加插一行代码xml = require “xml”,记住千万不能用local xml = require “xml”,否则还是没有效果!
3、调用统一下单API
没有什么比直接上代码更具直观性了,那就上吧。(代码风格不大好,求轻喷)
五、总结
微信支付的文档十分之多坑,不google根本弄不出来,需要给予耐心慢慢磨,不要省时间,文档中的每一行都要看,不然会耗掉更多的时间!
——服务器开发工程师 蔡剑彬 caijianbin668943@163.com
一、开发环境
1、硬件环境:1核/1G内存/8G硬盘/1M宽带的腾讯云服务器
2、软件环境:CentOS7操作系统、Openresty 1.9.7.2、LuaXml_101012、redis
二、官方流程时序图
图1 微信支付流程时序图
第一眼看下去,觉得好复杂的,尤其是平时比较少接触时序图的我,但仔细看看,发现服务器需要注意的其实就只有4个标了红色的地方,下面将逐一进行详解。
三、生成图文消息链接或二维码
当用户点击商品时,服务器需要返回该商品的详细信息,标明商品有多好用、价格有多划算、已经有多少人买了等等,以便哄用户点击右下角那个“立即购买”按钮(“加入购物车”按钮也算成功了一半)。总而言之就是需要生成商户的订单,这个部分我就不详述,其实也就是创建个订单号,把商品id、订单id之类的信息放到redis中(用其他缓存数据库我也没意见的),顺便设个expire time,后面用户付钱了才把订单信息放到mysql去。
接下来就是讨论下我们的重点——“付钱的过程”。
四、商户server调用统一下单接口请求订单
微信官方文档:统一下单API。
根据官方文档说的,就是把所有所有必填项,以及在某些选项下选填项也变成必填项的项全部组成在xml中,发到微信服务器。他们说的十分简单,然而这只是站在他们已经懂的立场上觉得简单而已,事实并不是这样。
1、微信支付文档的坑
有以下几样事项文档中是没有任何说明的,也是最多人踩的坑:
1) 文档中没有提到访问https://api.mch.weixin.qq.com/pay/unifiedorder是通过POST方式;
2) 所有字段,除了总金额(total_fee)是int类型,其余都是string类型。笔者当时给随机字符串(nonce_str)赋了一个随机数,然后就发生了十分诡异的事情!在Android中可以调试通过,而Ios中却提示“config失败”!后面都转成字符串就可以了;
3) “签名失败”这个问题估计是最多人蛋碎的地方了。看了官方的签名算法后,第一感觉是不是就直接拿appid、mch_id、device_info、body和nonce_str参与MD5加密就可以了呢?然而并不是,而是所有需要放到xml中上传的字段都要参与MD5加密!而且所有字段需要按照字母升序排列,不仅仅是MD5加密时需要排序,连xml中也要按这个顺序排序,sign放到最下面;
4) 参与MD5加密的字符串utf8编码的问题,笔者用的vim码字,在终端菜单->Terminal->Set Character Encoding->Unicode (UTF=8)即可;
5) “调用支付JSAP缺少参数:$key0$”,这个问题就充分表现了微信文档是如何坑的。因为无论是后台还是前端没有做好,都是返回的同一个错误,很难定位是谁的问题。大多数人是因为后台生成签名时,时间戳写成”timestamp=xxx”导致问题出现,改成”timeStamp=xxx”就好。当我一次又一次核对了后台数据,真的确定没有问题了,才让前端看看是否前端导致的。最后发现原来是前端少传了prepay_id,添加上即可;
2、如何使用LuaXML库
避开了上面5个坑,接下来的工作就好多了,除了LuaXml这个坑之外,那就先讲讲LuaXml吧。
从官网上下载的LuaXml_101012.zip,解压后里面的LuaXML_lib.so并不能直接使用,否则会报“wrong ELF calss : ELFCLASS32”的错误,因为笔者的操作系统是CentOS7,64位机,用32位的bin是不行的,所以需要重新编译。
在目录下直接make,会提示找不到lua源码,查看Makefile,里面有这样两句:
图2 LuaXml Makefile截图
下载对应版本的lua源码放在与LuaXml文件夹的同一目录下,再次编译。
如果编译提示”cannot find -llua”那么就表明找不到lua库,Makefile中有这句
,由于centos7是64位,所以安装lua的时候,lua库的路径是”/lib64/liblua-5.1.so”,此时做个软连接”ln -s /lib64/liblua-5.1.so /lib/liblua.so”,接着再编译即可。
编译完后,把LuaXML_lib.so和LuaXml.lua放入path_to_openresty/LuaJIT/lib/lua/5.1 和path_to_openresty/LuaJIT/share/lua/5.1中:
cp -r LuaXML_lib.so path_to_openresty/LuaJIT/lib/lua/5.1/
cp -r LuaXML_lib.so path_to_openresty/LuaJIT/share/lua/5.1/
cp -r LuaXml.lua path_to_openresty/LuaJIT/lib/lua/5.1/
cp -r LuaXml.lua path_to_openresty/LuaJIT/share/lua/5.1/
至此,LuaXML库就可以使用了,当然还要注意下面一个坑:
在很多示例代码中,require "LuaXml"之后便可以直接使用其全局变量xml了,事实上确实是可以直接使用,但是只能使用一次,而第二次访问接口是,会报错“attempt to index global 'xml' (a nil value)”!详情可以点这里(需要翻墙),解决办法便是在require “LuaXml”代码后面加插一行代码xml = require “xml”,记住千万不能用local xml = require “xml”,否则还是没有效果!
3、调用统一下单API
没有什么比直接上代码更具直观性了,那就上吧。(代码风格不大好,求轻喷)
[code=language-lua]-- 获取配置信息 local get_args = ngx.req.get_uri_args() local timestamp = tostring(get_args["timestamp"]) -- 微信返回的时间戳 local nonce_str = tostring(get_args["nonce_str"]) -- 微信返回的随机字符串 local body = "商品描述" -- 商品描述 local detail = "活动详情" -- 商品详情 local attach = "附加信息" -- 附加信息,判断当前的订单类型 local product_id = "G123456789" -- trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义 local appid = "123456789" -- 公众号ID local mch_id = "123456789" -- 商户号 local device_info = "WEB" -- 设备号 local out_trade_no = "O123456789" -- 商户订单号 local total_fee = 100 -- 总价,这里1是代表1分钱,只能是整数 local spbill_create_ip = "192.168.1.1" -- 终端IP local notify_url = "www.test.com/wechat/wxpay" -- 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数 local trade_type = "JSAPI" -- 取值如下:JSAPI,NATIVE,APP local fee_type = "CNY" -- 货币类型 local time_start = "20091225091010" -- 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010 local time_expire = "20101225091010" -- 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010 local goods_tag = "WXG" -- 商品标志 local limit_pay = "no_credit" -- 指定支付方式,no_credit--指定不能使用信用卡支付 local openid = “0123456789” -- trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识 -- 生成签名 local sign_str_a = "appid="..appid .."&attach="..attach .."&body="..body .."&detail="..detail .."&device_info="..device_info .."&mch_id="..mch_id .."&nonce_str="..nonce_str .."¬ify_url="..notify_url .."&openid="..openid .."&out_trade_no="..out_trade_no .."&spbill_create_ip="..spbill_create_ip .."&timeStamp="..timestamp .."&total_fee="..total_fee .."&trade_type="..trade_type local sign_str_b = sign_str_a.."&key="..”微信支付平台设置的key” local resty_md5 = require "resty.md5" local md5 = resty_md5:new() local resty_str = require "resty.string" md5:update(sign_str_b) local digest = md5:final() local sign = string.upper(resty_str.to_hex(digest)) -- 签名 -- 生成xml文件 require "LuaXml" xml = require "xml" local xml_data = xml.new("xml") xml_data:append("appid")[1] = appid xml_data:append("attach")[1] = attach xml_data:append("body")[1] = body xml_data:append("detail")[1] = detail xml_data:append("device_info")[1] = device_info --xml_data:append("fee_type")[1] = fee_type --xml_data:append("goods_tag")[1] = goods_tag --xml_data:append("limit_pay")[1] = limit_pay xml_data:append("mch_id")[1] = mch_id xml_data:append("nonce_str")[1] = nonce_str xml_data:append("notify_url")[1] = notify_url xml_data:append("openid")[1] = openid xml_data:append("out_trade_no")[1] = out_trade_no --xml_data:append("product_id")[1] = product_id xml_data:append("spbill_create_ip")[1] = spbill_create_ip --xml_data:append("time_start")[1] = time_start --xml_data:append("time_expire")[1] = time_expire xml_data:append("timeStamp")[1] = timestamp xml_data:append("total_fee")[1] = total_fee xml_data:append("trade_type")[1] = trade_type xml_data:append("sign")[1] = sign -- 调用统一下单接口 local http = require "resty.http" local hc = http:new() local xml_str = xml.str(xml_data) -- change to xml string local res, err = hc:request_uri("https://api.mch.weixin.qq.com/pay/unifiedorder", { method = "POST", body = xml_str, headers = { ["Content-Type"] = "text/xml", }, ssl_verify = false }) -- 解析微信服务器返回的结果 local return_xml_data = xml.eval(res.body) local return_xml_table = {} for key, val in pairs(return_xml_data) do if key > 0 then if type(val) == 'table' then return_xml_table[val[0]] = val[1] end end end if return_xml_table["return_code"] == "SUCCESS" then local result_table = {} result_table["timeStamp"] = timestamp result_table["nonceStr"] = nonce_str --return_xml_table["nonce_str"] result_table["package"] = "prepay_id="..return_xml_table["prepay_id"] result_table["signType"] = "MD5" --result_table["paySign"] = return_xml_table["sign"] result_table["trade_type"] = return_xml_table["trade_type"] local paySign_str = "appId="..appid .."&nonceStr="..result_table["nonceStr"] .."&package="..result_table["package"] .."&signType="..result_table["signType"] .."&timeStamp="..result_table["timeStamp"] .."&key="..”微信支付平台设置的key” local md5 = resty_md5:new() -- 这里需要重新new一个md5,否则update的时候会将上面的内容也加进来,导致错误 md5:update(paySign_str) digest = md5:final() local paySign = string.upper(resty_str.to_hex(digest)) -- 签名 result_table["paySign"] = paySign ngx.say(rfc:format_to_json(200, result_table)) else ngx.say(rfc:format_to_json(500, "Failed to get unified_order : "..return_xml_table["return_msg"])) end
五、总结
微信支付的文档十分之多坑,不google根本弄不出来,需要给予耐心慢慢磨,不要省时间,文档中的每一行都要看,不然会耗掉更多的时间!
相关文章推荐
- 接入微信JSAPI被扫码支付--JAVA
- 2 微信开发者中心
- JS与OC的交互 WebViewJavaScriptBridge WEB微信支付
- 微信支付开发(7) H5支付
- 微信自定义菜单集成多客服功能的方法与总结
- 微信浏览器强制使用
- 微信公众号开发之加密解密
- 微信公众号(justjavac)正式接受外部投稿,所有打赏金额归投稿者
- 微信分享功能,安卓手机分享图片不显示
- 我有几个粽子,和一个故事
- javascript获取wx.config内部字段解决微信分享
- 绑定微信公众平台提示“请求URL超时…
- 微信用户快赶上QQ了,达到了7.62亿
- 南通大学教务管理系统微信公众号体验
- 绑定微信公众平台提示“请求URL超时”解决办法
- 关于微信公众号支付tips
- 分享自动抓取缩略图相关文章(微信也有效)
- 微信JSSDK上传多张图片
- 一步一步实现iOS微信自动抢红包(非越狱)
- 微信接口开发缓存全局token,ticket