微信V3支付版本中,如何获取正确的签名方法?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1524个文字,预计阅读时间需要7分钟。
问题背景:最近接入微信支付,微信官方并未提供Python版本的SDK,因此只能根据文档手动实现。以下记录微信支付的整体流程、踩坑过程与最终实现。
微信支付整体流程:
1.用户在APP中选择微信支付。
2.APP调用微信支付API生成预支付交易会话标识。
3.微信返回预支付交易会话标识。
4.APP将预支付交易会话标识展示给用户,用户确认支付。
5.微信后台处理支付,并返回支付结果。
6.APP根据支付结果更新订单状态。
踩坑过程:
1.生成预支付交易会话标识时,签名算法错误,导致微信返回错误。
2.调用微信支付API时,参数错误,导致微信返回错误。
3.获取支付结果时,解析返回数据错误,导致无法正确获取支付结果。
最终实现:
1.仔细阅读微信支付文档,确保API调用参数正确。
2.使用Python的hashlib库生成签名,确保签名算法正确。
3.使用Python的json库解析微信返回的数据,确保数据解析正确。
以下为微信支付代码示例:
python
import hashlibimport jsondef generate_sign(params, key): sorted_params=sorted(params.items()) sign_str='&'.join(['{}={}'.format(k, v) for k, v in sorted_params]) sign_str +='&key=' + key sign=hashlib.md5(sign_str.encode()).hexdigest() return sign
def pay(appid, mch_id, nonce_str, body, out_trade_no, total_fee, spbill_create_ip, notify_url, key): params={ 'appid': appid, 'mch_id': mch_id, 'nonce_str': nonce_str, 'body': body, 'out_trade_no': out_trade_no, 'total_fee': total_fee, 'spbill_create_ip': spbill_create_ip, 'notify_url': notify_url, 'trade_type': 'APP' } sign=generate_sign(params, key) params['sign']=sign data=json.dumps(params) return data
问题背景最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程、踩坑过程与最终具体实现。
微信支付APP下单流程根据微信官方文档: 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查询业务后端,业务后端通过回调通知或直接查询微信后端返回最终支付结果
第一次请求生成预付单号的签名文档为: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的二次签名信息文档为: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,
}
业务后端查询订单详情
文档地址:pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml
代码如下:
def query_order(self, out_trade_no):
'''
查询指定订单信息
'''
url = f'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
文档地址:pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
代码如下:
def create_order_info(self, data, callback_url):
'''
创建微信预支付订单, 注意包含两次签名过程:
首次签名用于请求微信后端获取prepay_id
二次签名信息返回客户端用于调起SDK支付
'''
url = '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:github.com/liuzhi67/wechat-pay-python
转载请注明出处,原文地址:www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html
签名:拥抱开源,拥抱自由本文共计1524个文字,预计阅读时间需要7分钟。
问题背景:最近接入微信支付,微信官方并未提供Python版本的SDK,因此只能根据文档手动实现。以下记录微信支付的整体流程、踩坑过程与最终实现。
微信支付整体流程:
1.用户在APP中选择微信支付。
2.APP调用微信支付API生成预支付交易会话标识。
3.微信返回预支付交易会话标识。
4.APP将预支付交易会话标识展示给用户,用户确认支付。
5.微信后台处理支付,并返回支付结果。
6.APP根据支付结果更新订单状态。
踩坑过程:
1.生成预支付交易会话标识时,签名算法错误,导致微信返回错误。
2.调用微信支付API时,参数错误,导致微信返回错误。
3.获取支付结果时,解析返回数据错误,导致无法正确获取支付结果。
最终实现:
1.仔细阅读微信支付文档,确保API调用参数正确。
2.使用Python的hashlib库生成签名,确保签名算法正确。
3.使用Python的json库解析微信返回的数据,确保数据解析正确。
以下为微信支付代码示例:
python
import hashlibimport jsondef generate_sign(params, key): sorted_params=sorted(params.items()) sign_str='&'.join(['{}={}'.format(k, v) for k, v in sorted_params]) sign_str +='&key=' + key sign=hashlib.md5(sign_str.encode()).hexdigest() return sign
def pay(appid, mch_id, nonce_str, body, out_trade_no, total_fee, spbill_create_ip, notify_url, key): params={ 'appid': appid, 'mch_id': mch_id, 'nonce_str': nonce_str, 'body': body, 'out_trade_no': out_trade_no, 'total_fee': total_fee, 'spbill_create_ip': spbill_create_ip, 'notify_url': notify_url, 'trade_type': 'APP' } sign=generate_sign(params, key) params['sign']=sign data=json.dumps(params) return data
问题背景最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程、踩坑过程与最终具体实现。
微信支付APP下单流程根据微信官方文档: 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查询业务后端,业务后端通过回调通知或直接查询微信后端返回最终支付结果
第一次请求生成预付单号的签名文档为: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的二次签名信息文档为: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,
}
业务后端查询订单详情
文档地址:pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml
代码如下:
def query_order(self, out_trade_no):
'''
查询指定订单信息
'''
url = f'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
文档地址:pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
代码如下:
def create_order_info(self, data, callback_url):
'''
创建微信预支付订单, 注意包含两次签名过程:
首次签名用于请求微信后端获取prepay_id
二次签名信息返回客户端用于调起SDK支付
'''
url = '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:github.com/liuzhi67/wechat-pay-python
转载请注明出处,原文地址:www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html
签名:拥抱开源,拥抱自由
