您的位置:首页 > 移动开发 > 微信开发

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

没有什么比直接上代码更具直观性了,那就上吧。(代码风格不大好,求轻喷)

[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根本弄不出来,需要给予耐心慢慢磨,不要省时间,文档中的每一行都要看,不然会耗掉更多的时间!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: