微信app支付(V3)
最近项目需要接入微信APP支付,原项目服务器是有相关代码的,不过是V2版本的支付。
服务器请求微信预下单成功后,计算出了签名值发给前端,结果客户端拉起支付提示【支付签名验证失败】。
客户端百度找了各种资料,无奈V2版本的资料太少,现在网上很多是V3的资料,最后决定再接入V3版本。
正文
2022年8月23日19:03:01 已更新增加内置库加解密,不再愚蠢地调用shell和python脚本了….可以拉到最下面)
首先是服务器下单,按照官方文档设置好参数。
请求参数加密
加密方式,官方给出的案例是获取微信支付平台证书。
大致看了一下,当我看到SHA256 with RSA签名的时候心凉了,我们服务器没有这个库,不碍事,直接调用shell命令(事实上后面很多签名都是这样做的)
直接上lua代码
local function syscmd(cmd)
skynet.error("syscmd. cmd:", cmd)
local pfile = io.popen(cmd)
local ret = pfile:read("a")
pfile:close()
return ret
end
核心代码
local content = {
appid = 这里填你的引用ID, *-- 应用ID*
mchid = 这里填你的商户ID, *-- 商户ID*
description = "游戏道具", *-- 描述*
out_trade_no = "服务器自己用的订单号", *-- 商户系统内部订单号*
amount = {
total = "道具价格", *--单位分*
currency = "CNY",
},
notify_url = "回调地址,必须用HTTPS", *-- 支付成功通知*
}
local noncestr = random_string_32() *--生成sui 字符串*
local timestamp = tostring(os.time()) *-- 时间戳*
local header_sign_str = string.format([[%s\n%s\n%s\n%s\n%s\n]],
"POST","/v3/pay/transactions/app",timestamp,noncestr,cjson.encode(content)
)
*-- 执行shell,apiclient_key.pem这个文件自己到微信商户后台下载*
local header_sign2 = syscmd("echo -n -e '" .. header_sign_str .. "' | openssl dgst -sha256 -sign apiclient_key.pem | openssl base64 -A")
local header = {
["Authorization"] = 'WECHATPAY2-SHA256-RSA2048 mchid="' ..
商户ID .. '",serial_no="这里填商户证书序列号",'
.. 'nonce_str="' .. noncestr .. '",signature="' .. header_sign2
.. '",timestamp="' .. timestamp .. '"',
["Content-Type"] = "application/json",*--这里必填*
["Accept"] = "application/json",*--这里必填*
["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"*--这里必填*
}
local status, body = httpc.request("POST", "https://api.mch.weixin.qq.com", '/v3/pay/transactions/app', nil, header, cjson.encode(content))
print("=====================",status,body)
(”User-Agent”字段随便百度填的。。如果不填,回调会提示缺少”Accept” 或”User-Agent”)
不出意外,这应该就下单成功了,成功后会获得一个prepay_id,再次加密拿给客户端拉起支付,代码如下
local info = cjson.decode(body)
info.out_trade_no = orderNo
if info.prepay_id and info.prepay_id~="" then
local order = {
appid = game.wx_appid,
mchid = game.wx_mch_id,
noncestr = noncestr,
timestamp = timestamp, *-- 时间戳*
prepayid = info.prepay_id
}
local client_sign_str = string.format('%s\n%s\n%s\n%s\n',order.appid,order.timestamp,order.noncestr,info.prepay_id)
order.sign = syscmd("echo -n -e '" .. client_sign_str .. "' | openssl dgst -sha256 -sign apiclient_key.pem | openssl base64 -A")
local t = {
out_trade_no = orderNo,
orderStr = cjson.encode(order),
plat = arg.platform,
}
return t
else
info.err = "request wx failed"
return info
end
微信回调
官方文档在这里
收到的是json,判断一下body中event_type字段是否为”TRANSACTION.SUCCESS”,否则为支付失败
这里解密用的签名是我调用的python脚本…(官方解密)
local function get_sign(header,body)
local info = cjson.decode(body).resource
*-- util.print_r(info)*
local V3_key = "这里填自己商户V3key"
local ret = syscmd("python aead_aes_256_gcm.py recharge " .. V3_key .. " " .. (info.nonce or "") .. " " .. (info.ciphertext or "") .. " " .. (info.associated_data or ""))
print("执行结果:")
skynet.error("执行结果:",ret)
if not ret or ret == "" then
skynet.error("订单解密失败")
return false
end
local real_order = cjson.decode(ret)
return true,real_order
end
这里解密后得到充值数据,通知游戏服充值成功。
aead_aes_256_gcm.py文件如下
# -*- coding: UTF-8 -*-`
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
import subprocess
import sys,io
def decrypt(tp,key,nonce, ciphertext, associated_data):
key_bytes = str.encode(key)
nonce_bytes = str.encode(nonce)
ad_bytes = str.encode(associated_data)
data = base64.b64decode(ciphertext)
aesgcm = AESGCM(key_bytes)
print(aesgcm.decrypt(nonce_bytes, data, ad_bytes))`
if __name__ == '__main__':
decrypt(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4],sys.argv[5])
PS:
也许应该先对回调进行签名验证后再做下一步操作?有点麻烦,直接解密算球,有人伪造就认了吧…
2022年8月23日19:03:45更新
使用内置C库加解密
这几天思前想后,用shell和python脚本来加解密代码实在丑陋,就找了各种大佬咨询加密库,最终还是找到了。
服务器下单
修改为
local header_sign2 = cfadmin_crypt_rsa.rsa_sign(header_sign_str,wechat_pay_v3_apiclient_pem_data,"sha256")
给客户的加密
order.sign = cfadmin_crypt_rsa.rsa_sign(client_sign_str,wechat_pay_v3_apiclient_pem_data,"sha256")
支付回调解密
local c_ret = cfadmin_crypt_aes.aes_256_gcm_decrypt(V3_key,crypt.base64decode(info.ciphertext),info.nonce,(info.associated_data or ""))