关于微信小程序支付功能,他的官方文档真的写的很清楚了!但是整理吸收一下还是不错的
官网呈上:https://developers.weixin.qq.com/miniprogram/dev/api/wx.requestPayment.html
核心流程:下面的图和交互流程描述写的真是不能再清晰了
商户系统和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,api参见公共api【查询订单API】
二、根据以上流程一一拆分一下:
(1)小程序内调用登录接口,获取到用户的openid:这个就不多赘述了,凡是做小程序的,肯定会有登录流程,也会存储相关的openid,不是支付的重点,有兴趣异步官网:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
(2)商户server调用支付统一下单,api参见公共api【统一下单API】详细官网https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1:这个就有些说道了,不过看完文档就一步了然了,无非是调用微信的接口,传递一些参数,如果要说仔细说的话,就是参数的获取以及加密方式了,下面会一一分解
- 统一接口
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信分配的小程序ID |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
签名类型 | sign_type | 否 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 |
商品描述 | body | 是 | String(128) | 腾讯充值中心-QQ会员充值 |
商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商品详情 | detail | 否 | String(6000) | 商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传,详见“单品优惠参数说明” | |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号 |
标价币种 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 |
标价金额 | total_fee | 是 | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和H5支付提交用户端ip,Native支付填调用微信支付API的机器IP。 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 |
订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 建议:最短失效时间间隔大于1分钟 |
订单优惠标记 | goods_tag | 否 | String(32) | WXG | 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | 小程序取值如下:JSAPI,详细说明见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | 上传此参数no_credit--可限制用户不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。 |
电子发票入口开放标识 | receipt | 否 | String(8) | Y | Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效 |
最后我们要的结果是xml格式
<xml> <appid>wx2421b1c4370ec43b</appid> <attach>支付测试</attach> <body>JSAPI支付测试</body> <mch_id>10000100</mch_id> <detail><![CDATA[{ "goods_detail":[ { "goods_id":"iphone6s_16G", "wxpay_goods_id":"1001", "goods_name":"iPhone6s 16G", "quantity":1, "price":528800, "goods_category":"123456", "body":"苹果手机" }, { "goods_id":"iphone6s_32G", "wxpay_goods_id":"1002", "goods_name":"iPhone6s 32G", "quantity":1, "price":608800, "goods_category":"123789", "body":"苹果手机" } ] }]]></detail> <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str> <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url> <openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid> <out_trade_no>1415659990</out_trade_no> <spbill_create_ip>14.23.150.211</spbill_create_ip> <total_fee>1</total_fee> <trade_type>JSAPI</trade_type> <sign>0CB01533B8C1EF103065174F50BCA001</sign> </xml>
借鉴了前人优秀代码整理如下
请求支付,前提已知参数
appid = ‘wxe09634ee0e363dcb‘ # 小程序ID mch_id = ‘1111111111‘ # 商户号 total_fee = ‘1‘ # 总金额 spbill_create_ip = ‘111.111.111.11‘ # 终端IP notify_url = ‘https://abc.abcdefg.com/wxpay/notify‘ # 通知地址 trade_type = ‘JSAPI‘ # 交易类型 merchant_key = ‘00000000000000000000000000000000‘ # 商户KEY
class CreatePay(HTTPMethodView): async def post(self,request): ‘‘‘ 请求支付 :return: ‘‘‘ data = { ‘appid‘: appid, # 微信分配的小程序ID ‘mch_id‘: mch_id, # 微信支付分配的商户号 ‘nonce_str‘: get_nonce_str(), # uuid获取随机字符串 ‘body‘: ‘测试‘, # 商品描述 ‘out_trade_no‘: str(int(time.time())), # 商户订单号 ‘total_fee‘: ‘1‘, ‘spbill_create_ip‘: spbill_create_ip, # 订单总金额,单位为分,详见支付金额 ‘notify_url‘: notify_url, # 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数 ‘attach‘: ‘{"msg": "北京总店"}‘, # 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 ‘trade_type‘: trade_type, # 小程序取值如下:JSAPI ‘openid‘: ‘1111111111111111111111‘ # trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识 } wxpay = WxPay(merchant_key, **data) pay_info = wxpay.get_pay_info() if pay_info: return text(pay_info) # 把支付相关信息返回给用户确认,用户确认后,前端直接调用下面的微信接口,支付成功后返回用户信息, # 并且给商户后台推送消息,商户后台更新支付状态 """ wx.requestPayment( { ‘timeStamp‘: ‘‘, ‘nonceStr‘: ‘‘, ‘package‘: ‘‘, ‘signType‘: ‘MD5‘, ‘paySign‘: ‘‘, ‘success‘:function(res){}, ‘fail‘:function(res){}, ‘complete‘:function(res){} }) """ return text({‘errcode‘: 40001, ‘errmsg‘: ‘请求支付失败‘}) WxPayBP.add_route(CreatePay.as_view(),‘createPay‘)
调用的方法Utils.py
def get_nonce_str(): ‘‘‘ 获取随机字符串 :return: ‘‘‘ return str(uuid.uuid4()).replace(‘-‘, ‘‘) def dict_to_xml(dict_data): ‘‘‘ dict to xml :param dict_data: :return: <xml> <appid>wxd930ea5d5a258f4f</appid> <mch_id>10000100</mch_id> <device_info>1000</device_info> <body>test</body> <nonce_str>ibuaiVcKdpRxkhJA</nonce_str> <sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign> </xml> :description: 微信服务器与商户服务器之间采用 XML 格式进行交互,这就涉及到与语言原生数据类型进行转换以方便处理。 交互的数据参数都是 key-value 的形式,因此在 Python 中使用字典会更加方便。 而要解析 XML,也有一大把第三方库供使用,比如 BeautifulSoup。 ‘‘‘ xml = ["<xml>"] for k, v in dict_data.iteritems(): xml.append("<{0}>{1}</{0}>".format(k, v)) xml.append("</xml>") return "".join(xml) def xml_to_dict(xml_data): ‘‘‘ xml to dict :param xml_data: :return: ‘‘‘ xml_dict = {} root = ET.fromstring(xml_data) for child in root: xml_dict[child.tag] = child.text return xml_dict class WxPay(object): def __init__(self, merchant_key, *args, **kwargs): self.url = ‘https://api.mch.weixin.qq.com/pay/unifiedorder‘ # 统一下单接口 self.merchant_key = merchant_key # 商户平台设置的密钥key self.pay_data = kwargs # 下单参数 super(WxPay, self).__init__() def create_sign(self, pay_data): ‘‘‘ 生成签名 :return: Reference document:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3 第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下: stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"; 第二步:拼接API密钥: stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式 sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式 ‘‘‘ stringA = ‘&‘.join(["{0}={1}".format(k, pay_data.get(k))for k in sorted(pay_data)]) stringSignTemp = ‘{0}&key={1}‘.format(stringA, self.merchant_key) sign = hashlib.md5(stringSignTemp).hexdigest() # 选择md5签名方式 return sign.upper() def get_pay_info(self): ‘‘‘ 获取支付信息 :param xml_data: :return: ‘‘‘ sign = self.create_sign(self.pay_data) # 生成签名 self.pay_data[‘sign‘] = sign # 更新统一下单接口参数,加上签名 xml_data = dict_to_xml(self.pay_data) # response = request(url=self.url, data=xml_data) """ 返回数据类型 <xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <appid><![CDATA[wx2421b1c4370ec43b]]></appid> <mch_id><![CDATA[10000100]]></mch_id> <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str> <openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid> <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign> <result_code><![CDATA[SUCCESS]]></result_code> <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id> <trade_type><![CDATA[JSAPI]]></trade_type> </xml> """ # 以下字段在return_code 和result_code都为SUCCESS的时候有返回 prepay_id if response: prepay_id = xml_to_dict(response).get(‘prepay_id‘) paySign_data = { ‘appId‘: self.pay_data.get(‘appid‘), ‘timeStamp‘: self.pay_data.get(‘out_trade_no‘), ‘nonceStr‘: self.pay_data.get(‘nonce_str‘), ‘package‘: ‘prepay_id={0}‘.format(prepay_id), ‘signType‘: ‘MD5‘ } # 再次签名 """ paySign = MD5(appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS &package=prepay_id=wx2017033010242291fcfe0db70013231072 &signType=MD5 &timeStamp=1490840662 &key=qazwsxedcrfvtgbyhnujmikolp111111) = 22D9B4E54AB1950F51E0649E8810ACD6 """ paySign = self.create_sign(paySign_data) paySign_data.pop(‘appId‘) paySign_data[‘paySign‘] = paySign return paySign_data return False
代码里需要注意一下就是create_sign函数,是按照官方文档要求,生成的签名 :https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
举例:
(2.1)假设传送的参数如下:
appid: wxd930ea5d5a258f4f mch_id: 10000100 device_info: 1000 body: test nonce_str: ibuaiVcKdpRxkhJA
(2.2)
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
第二步:拼接API密钥:
stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式 sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式
(2.3)最终得到最终发送的数据:
<xml> <appid>wxd930ea5d5a258f4f</appid> <mch_id>10000100</mch_id> <device_info>1000</device_info> <body>test</body> <nonce_str>ibuaiVcKdpRxkhJA</nonce_str> <sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign> </xml>
最后整理出来就是Utils.py中create_sign函数
def create_sign(self, pay_data): ‘‘‘ 生成签名 :pay_data:{ ‘appid‘: appid, # 微信分配的小程序ID ‘mch_id‘: mch_id, # 微信支付分配的商户号 ‘nonce_str‘: get_nonce_str(), # uuid获取随机字符串 ‘body‘: ‘测试‘, # 商品描述 ‘out_trade_no‘: str(int(time.time())), # 商户订单号 ‘total_fee‘: ‘1‘, ‘spbill_create_ip‘: spbill_create_ip, # 订单总金额,单位为分,详见支付金额 ‘notify_url‘: notify_url, # 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数 ‘attach‘: ‘{"msg": "北京总店"}‘, # 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 ‘trade_type‘: trade_type, # 小程序取值如下:JSAPI ‘openid‘: ‘1111111111111111111111‘ # trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识 "sign":"" } :return: Reference document:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3 第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下: stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"; 第二步:拼接API密钥: stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式 sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式 ‘‘‘ stringA = ‘&‘.join(["{0}={1}".format(k, pay_data.get(k))for k in sorted(pay_data)]) stringSignTemp = ‘{0}&key={1}‘.format(stringA, self.merchant_key) sign = hashlib.md5(stringSignTemp).hexdigest() # 选择md5签名方式 return sign.upper()
(3) 商户server调用再次签名,api参见公共api【再次签名】
-
小程序调起支付数据签名字段列表:方式跟上面统一下单接口签名方法一样,只是参数不一样罢了
字段名 变量名 必填 类型 示例值 描述 小程序ID appId 是 String wxd678efh567hg6787 微信分配的小程序ID 时间戳 timeStamp 是 String 1490840662 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 随机串 nonceStr 是 String 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法 数据包 package 是 String prepay_id=wx2017033010242291fcfe0db70013231072 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072 签名方式 signType 是 String MD5 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 -
举例如下:
paySign = MD5(appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&package=prepay_id=wx2017033010242291fcfe0db70013231072&signType=MD5&timeStamp=1490840662&key=qazwsxedcrfvtgbyhnujmikolp111111) = 22D9B4E54AB1950F51E0649E8810ACD6
(4)、商户server接收支付通知,api参见公共api【支付结果通知API】:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7
支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
接口链接:
该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
(5)、商户server查询支付结果,api参见公共api【查询订单API】:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2
后续补充.........