最近微信支付莫名其妙的进行了升级,在微信开放平台提交的移动应用开发中微信支付,如果收到的是这样的邮件则无法使用在开放平台的移动应用开发的文档。因为邮件中少了2个关键的KEY:paySignKey
, partnerKey
。
一直询问支持,给的都是支付开发教程(微信商户平台版) ,研究了下,都是公众号的开发。
后面找到了这份文档,研究了一番,依旧觉得是公众号的,里面的统一下单需要个openid,这分明是微信公众号的开发。后面看到openid只是在公众号开发的时候才传递,所以决定按照这份文档一试。
按照文档中的业务流程,当进行到第三步的时候,文档这样说:
步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为prepay_id=wx20141009175351724b348a500087751557
但是我我找遍了所有地方,都没有说明这个package
具体的事例,以及参加签名的字段partnerId
是老文档中的描述,而且这里的字符串怎么突然有大小写了?后面参考了老文档,成功了。
3.5 添加 prepayid 再次签名 获取到 prepayid 后,将参数 appid、appkey、noncestr、package(注意:此处应置为 Sign=WXPay)、partnerid、prepayid、timestamp 签名后返回给 APP,签名方法跟 3.4 节 app_signature 说明一致。得到参数列表如下,通过这些参数即可在客户端调起支付。 { “appid”:“wxd930ea5d5a258f4f”, “noncestr”:“139042a4157a773f209847829d80894d”, “package”:“Sign=WXpay”; “partnerid”:“1900000109” “prepayid”:“1101000000140429eb40476f8896f4c9”, “sign”:“7ffecb600d7157c5aa49810d2d8f28bc2811827b”, “timestamp”:“1398746574” }
总结下开发:
- 先按照同一下单请求参数去获取
prepay_id
- 得到
prepay_id
, 参考上面的 3.5 添加 prepayid 再次签名
然后再吐槽下微信支付:新接口获取prepay_id
确实方便了很多,不需要去获取token、packge,请求与接收都有JSON
换成了XML
。但接口更新也不正式的声明下,文档也乱写,DEMO也没用完全开放出来,坑啊!
附上Python代码:
#!/usr/bin/env python
# coding=utf-8
"""
xml2json:https://github.com/hay/xml2json
log_debug, log_info 相当于print
"""
from flask import current_app
from aladin.helpers import log_debug, log_info
from hashlib import md5
import requests, time, json
from xml.etree import ElementTree
from xml2json import xml2json
import optparse
class WeiXinPay():
"""微信支付,返回回客户端需要参数
"""
def __init__(self, order_id, body, total_fee, nonce_str, spbill_create_ip='8.8.8.8'):
"""
:param order_id: 订单ID
:param body: 订单信息
:param total_fee: 订单金额
:param nonce_str: 32位内随机字符串
:param spbill_create_ip: 客户端请求IP地址
"""
self.params = {
'appid': current_app.config['APPID'],
'mch_id': current_app.config['MCHID'],
'nonce_str': nonce_str,
'body': body,
'out_trade_no': str(order_id),
'total_fee': str(int(total_fee)),
'spbill_create_ip': spbill_create_ip,
'trade_type': 'APP',
'notify_url': current_app.config['WEIXIN_NOTIFY_URL']
}
self.url = 'https://api.mch.weixin.qq.com/pay/unifiedorder' # 微信请求url
self.error = None
def key_value_url(self, value):
"""将将键值对转为 key1=value1&key2=value2
"""
key_az = sorted(value.keys())
pair_array = []
for k in key_az:
v = value.get(k, '').strip()
v = v.encode('utf8')
k = k.encode('utf8')
log_info('%s => %s' % (k,v))
pair_array.append('%s=%s' % (k, v))
tmp = '&'.join(pair_array)
log_info("key_value_url ==> %s " %tmp)
return tmp
def get_sign(self, params):
"""生成sign
"""
stringA = self.key_value_url(params)
stringSignTemp = stringA + '&key=' + current_app.config['APIKEY'] # APIKEY, API密钥,需要在商户后台设置
log_info("stringSignTemp ==> %s" % stringSignTemp)
sign = (md5(stringSignTemp).hexdigest()).upper()
params['sign'] = sign
log_info("sign ==> %s" % sign)
def get_req_xml(self):
"""拼接XML
"""
self.get_sign(self.params)
xml = "<xml>"
for k, v in self.params.items():
v = v.encode('utf8')
k = k.encode('utf8')
xml += '<' + k + '>' + v + '</' + k + '>'
xml += "</xml>"
log_info(xml)
return xml
def get_prepay_id(self):
"""
请求获取prepay_id
"""
xml = self.get_req_xml()
headers = {'Content-Type': 'application/xml'}
r = requests.post(self.url, data=xml, headers=headers)
log_info(r.text)
log_info("++++++++++++++++++++++++++")
re_xml = ElementTree.fromstring(r.text.encode('utf8'))
xml_status = re_xml.getiterator('result_code')[0].text
log_info("result_code ==> %s" % xml_status)
if xml_status != 'SUCCESS':
self.error = u"连接微信出错啦!"
return
prepay_id = re_xml.getiterator('prepay_id')[0].text
self.params['prepay_id'] = prepay_id
self.params['package'] = 'Sign=WXPay'
self.params['timestamp'] = str(int(time.time()))
def re_finall(self):
"""得到prepay_id后再次签名,返回给终端参数
"""
self.get_prepay_id()
if self.error:
return
sign_again_params = {
'appid': self.params['appid'],
'noncestr': self.params['nonce_str'],
'package': self.params['package'],
'partnerid': self.params['mch_id'],
'timestamp': self.params['timestamp'],
'prepayid': self.params['prepay_id']
}
self.get_sign(sign_again_params)
self.params['sign'] = sign_again_params['sign']
# 移除其他不需要返回参数
for i in self.params.keys():
if i not in [
'appid', 'mch_id', 'nonce_str',
'timestamp', 'sign', 'package', 'prepay_id']:
self.params.pop(i)
return self.params
class WeiXinResponse(WeiXinPay):
"""
微信签名验证
"""
def __init__(self, xml):
"""
:param xml: 支付成功回调的XML
"""
self.xml = xml
options = optparse.Values({"pretty": False})
self.xml_json = json.loads(xml2json(self.xml, options))['xml']
self.sign = self.xml_json.get('sign', '')
def verify(self):
"""验证签名"""
self.xml_json.pop('sign')
self.get_sign(self.xml_json)
if self.sign != self.xml_json['sign']:
log_info("signValue:%s != sing:%s" % (self.xml_json['sign'], self.sign))
return False
return True
不过,建议用nodejs写一个版本玩玩
@i5ting 恩,后面打算写个coffee的。
@youqingkui coffee真的很好用,哇哈哈
帅哥, "DEMO下载"里的"下载"链接好像都不能点的,你是怎么下载SDK的呢?
@zhousuper 打电话问过腾讯了,DEMO还在写中,只有被扫支付。所以只能看文档摸着过河,和参考原来的微信开放平台的移动应用开发https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN
@youqingkui 非常感谢,没想到你还真回了,你的信息对我挺有用。
请问nonce_str 这个随机码是不是,获取prepay_id 与 第二次签名时 。都是用同一个。
@lun324 nonce_str: 32位内随机字符串, 是同一个。