问题背景
最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程、踩坑过程与最终具体实现。
微信支付APP下单流程
根据微信官方文档: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_2.shtml
下单流程如下:
和支付宝不同,微信多了一个预付单的概念,这里把APP下单实际分为四大部分,其中包含请求微信后端需要的首次签名和需要返回给APP的二次支付信息签名--这里踩一个小坑,流程图中并没把第二次签名支付信息需要返回给APP的步骤画出来(即下面的步骤6.5),因而一开始误以为只需要返回prepay_id给客户端,导致校验失败。
一. 对应步骤1~4,APP 请求业务后端,业务后台进行V3签名后,请求微信后端生成预付单prepay_id
二. 对应步骤5~6.5,业务后端收到微信后端返回prepay_id,将支付相关参数打包进行二次签名后返回给APP,这里相比流程图多了一个6.5--即业务后端返回签名支付信息到APP
三. 对应步骤7~18,APP收到业务后端返回签名支付信息后调起SDK发起支付请求,收到同步消息结果通知
四. 对应步骤19~22,APP查询业务后端,业务后端通过回调通知或直接查询微信后端返回最终支付结果
代码实现
首次签名逻辑
第一次请求生成预付单号的签名文档为:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml, 共5个部分参与签名,其组成格式为:
HTTP请求方法\\nURL\\n请求时间戳\\n请求随机串\\n请求报文主体\\n
对应签名代码:
class WechatPayDALBase(object):
def __init__(self, mch_appid, mchid, v3key, serial_no, client_key):
self.mch_appid = mch_appid
self.mchid = mchid
self.v3key = v3key
# serial_no可通过openssl直接获取, 例: openssl x509 -in 1900009191_20180326_cert.pem -noout -serial
self.serial_no = serial_no
with open(client_key, \'r\') as ifile:
pkey = RSA.importKey(ifile.read())
self.signer = pkcs1_15.new(pkey)
def compute_sign_v3(self, method, url, body):
\'\'\'
V3签名逻辑
\'\'\'
ts = int(time.time())
nonce = self.generate_nonce()
uparts= parse_url(url)
ustr = uparts.path + (\'?{}\'.format(uparts.query) if uparts.query else \'\')
content = \'{}\\n{}\\n{}\\n{}\\n{}\\n\'.format(method, ustr, ts, nonce, body)
digest = SHA256.new(content.encode(\'utf-8\'))
sign_v = base64.b64encode(self.signer.sign(digest)).decode(\'utf-8\')
sign_str = \'serial_no=\"{}\",mchid=\"{}\",timestamp=\"{}\",nonce_str=\"{}\",signature=\"{}\"\'.format(
self.serial_no, self.mchid, ts, nonce, sign_v)
return sign_str
def make_headers_v3(self, url, headers=None, body=\'\', method=\'GET\'):
\'\'\'
微信支付V3版本签名header生成函数
\'\'\'
if not headers:
headers = {}
headers[\'Accept\'] = \'application/json\'
sign = self.compute_sign_v3(method, url, body)
auth_info = \'WECHATPAY2-SHA256-RSA2048 {}\'.format(sign)
headers[\'Authorization\'] = auth_info
return headers
def generate_nonce(self):
rnd = int(time.time()) + random.randint(100000, 1000000)
nonce = hashlib.md5(str(rnd).encode()).hexdigest()[:16]
return nonce
二次签名逻辑
由业务后端返回给APP的二次签名信息文档为:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
共4个部分参与签名,其组成格式为:
应用id\\n时间戳\\n随机字符串\\n预支付交易会话ID\\n
返回签名支付信息的对应代码:
def get_pay_sign_info(self, prepay_id):
ts = int(time.time())
nonce = self.generate_nonce()
content = \'{}\\n{}\\n{}\\n{}\\n\'.format(self.mch_appid, ts, nonce, prepay_id)
digest = SHA256.new(content.encode(\'utf-8\'))
sign_v = base64.b64encode(self.signer.sign(digest)).decode(\'utf-8\')
return {
\'appid\': self.mch_appid,
\'partnerid\': self.mchid,
\'timestamp\': str(ts),
\'noncestr\': nonce,
\'prepay_id\': prepay_id,
\'package\': \'Sign=WXPay\',
\'sign\': sign_v,
}
业务后端查询订单详情
文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml
代码如下:
def query_order(self, out_trade_no):
\'\'\'
查询指定订单信息
\'\'\'
url = f\'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={self.mchid}\'
headers = self.make_headers_v3(url)
rsp = requests.get(url, headers=headers)
pay_logger.info(\'out_trade_no:{}, rsp:{}|{}\'.format(out_trade_no, rsp.status_code, rsp.text))
rdct = rsp.json()
return rdct
业务后端调用APP下单API
文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
代码如下:
def create_order_info(self, data, callback_url):
\'\'\'
创建微信预支付订单, 注意包含两次签名过程:
首次签名用于请求微信后端获取prepay_id
二次签名信息返回客户端用于调起SDK支付
\'\'\'
url = \'https://api.mch.weixin.qq.com/v3/pay/transactions/app\'
ndt = datetime.now()
out_trade_no = self.generate_partner_trade_no(ndt)
data = {
\'mchid\': self.mchid,
\'out_trade_no\': out_trade_no,
\'appid\': self.mch_appid,
\'description\': data[\'subject\'],
\'notify_url\': callback_url,
\'amount\': {
\'currency\': \'CNY\',
\'total\': int(data[\'price\']),
},
\'time_expire\': (ndt + timedelta(minutes=5)).strftime(\'%Y-%m-%dT%H:%M:%S+08:00\')
}
jdata = json.dumps(data, separators=[\',\', \':\'])
headers = {\'Content-Type\': \'application/json\'}
# 第一次签名, 直接请求微信后端
headers = self.make_headers_v3(url, headers=headers, body=jdata, method=\'POST\')
rsp = requests.post(url, headers=headers, data=jdata)
pay_logger.info(\'rsp:{}|{}\'.format(rsp.status_code, rsp.text))
rdct = rsp.json()
# 第二次签名, 返回给客户端调用
sign_info = self.get_pay_sign_info(rdct[\'prepay_id\'])
return sign_info
源码地址
试水代码开源,把相关代码分享在了github:https://github.com/liuzhi67/wechat-pay-python
转载请注明出处,原文地址:https://www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html
签名:拥抱开源,拥抱自由
来源:https://www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html
本站部分图文来源于网络,如有侵权请联系删除。