这里最要介绍一下微信的刷卡支付开发,至于微信的网页jsapi、原生支付natvie等等的可以参考一下官方网站,其实底层逻辑都差不多,只是严重过程跟步骤不一样而已。
微信的刷卡支付流程,可以查看官方网站的介绍,但是这里我想说一下自己的理解:
1.微信用户可以打开自己的微信的钱包,看到付款,然后点击进去就是一个条形码跟一个二维码,条形码是给商户收银台用扫描枪扫描的,另外就是用微信扫描的二维码,这里只是涉及到条形码的扫描支付,因为我这里主要针对商户支付。
2.条形码扫描后会获取到一串认证数字,其实这串数字就是条形码上面的那一串数字,但是这是主要服务器要推送的信息,用户要来也没有用
3.收银台扫描收到的一串数字,发送到后端服务器,后端服务器也就是opernerp程序接收到了这串认证数字,就开始拼凑xml信息,并发送到微信的验证url去
4.步骤3完了,这里有两种情况,第一种情况是免密码支付,也就是前台收银扫描认证数字后,openerp直接发送要严重的xml数据到微信后台,如果严重正确了,这种情况下是直接支付的,不需要询问用户,第二种情况就是当支付不大于1000块或者当天不超过5次支付或者不是出现了后端程序跟微信服务器通信故障的话,就要微信客户端的用户输入支付密码,同时后端服务器的也会根据当前用户输入密码这个过程中,隔时间去查询微信服务器验证当前微信用户支付流程是否已经正确完成了,一旦查询出用户支付成功,我们可以根据微信服务器返回的交易代码做相应的处理。
main.py # 主要是生产内部访问url,由前端调用
# -*- coding: utf-8 -*-省略一大推代码@http.route([‘/shop_wechat/payment/jsapi/order‘], type=‘json‘, auth=‘none‘) # 微信网页支付一大堆代码 @http.route([‘/shop_wechat/payment/native/order‘], type=‘json‘, auth=‘user‘) # 微信二维码支付一大推代码 @http.route([‘/shop_wechat/payment/scan/order‘], type=‘json‘, auth=‘none‘) # 微信刷卡支付def wechat_scan_order(self, **post):ret_data = {‘return_code‘: "FAIL", ‘return_msg‘: ""}cr = request.cr uid = SUPERUSER_ID # 当前用户idcontext = request.context pay_record = request.registry[‘payment.record‘] # 注册addons里面的payment_record.models.payment_record.py # 里面的class PaymentRecordrecord = {}record[‘ref_id‘] = str(post[‘orderID‘])record[‘type‘] = ‘微信‘record[‘sub_type‘] = ‘微信刷卡支付‘record[‘fee‘] = str(post[‘money‘])record_id = pay_record.createrecord(cr, uid, record) # return record[‘record_id‘]req_data = dict()req_data[‘out_trade_no‘] = record_id req_data[‘total_fee‘] = record[‘fee‘]req_data[‘auth_code‘] = str(post[‘auth_code‘])req_data[‘device_info‘] = str(post.get(‘device_info‘, ‘‘))req_data[‘openid‘] = request.session.wechat_openid # 其实在完成后的支付打印信息来看,openid是空值,但是为了统一代码就保留了req_data[‘product_id‘] = str(post.get(‘product_id‘, ‘‘)) # 其实在完成后的支付打印信息来看,product_id空值req_data[‘body‘] = ‘订单:‘+ str(post[‘orderID‘])req_data[‘trade_type‘] = ‘WeiXinScan‘req_data[‘spbill_create_ip‘] = ‘192.168.1.100‘req_data[‘notify_url‘] = ‘/payment/wechat/notify/‘ # 微信支付后的返回信息的接收地址req_data[‘attach‘] = request.session.db # 读取会话中使用的数据库 if req_data[‘out_trade_no‘] and req_data[‘total_fee‘]: # 如果有商品订单号并且有总金额err, res = request.registry[‘payment.acquirer‘].wechat_get_pro_order(cr, uid, req_data) # 把req_data传到# wechat_get_pro_order方法处理,并返回err,resform_data = {‘out_trade_no‘: post[‘orderID‘],‘trade_status‘: ‘Draft‘,‘payment_type‘: req_data[‘trade_type‘],‘fees‘: req_data[‘total_fee‘],‘openid‘: req_data[‘openid‘],}tx = Nonerequest.registry[‘payment.transaction‘].wechat_form_validate(cr, uid, tx, form_data)if err:ret_data[‘return_msg‘] = err form_data[‘trade_status‘] = ‘Error‘request.registry[‘payment.transaction‘].wechat_form_validate(cr, uid, tx, form_data)else:reference = post[‘orderID‘]if reference:tx_ids = request.registry[‘payment.transaction‘].search(cr, uid, [(‘reference‘, ‘=‘, reference)])if tx_ids:tx = request.registry[‘payment.transaction‘].browse(cr, uid, tx_ids[0], context=context)if tx.state == ‘draft‘:form_data[‘trade_status‘] = ‘Completed‘request.registry[‘payment.transaction‘].wechat_form_validate(cr, uid, tx, form_data)ret_data[‘return_code‘] = res.get(‘result_code‘)ret_data[‘return_msg‘] = res.get(‘return_msg‘)else:ret_data[‘return_msg‘] = u‘参数错误!商品订单号以及金额没有‘print ret_data[‘return_msg‘]print ret_datareturn ret_data def wechat_validate_data(self, **kwargs):res = Falsepost = kwargs.get(‘post‘)request.session.db = post.get(‘attach‘)cr = request.cr uid = SUPERUSER_ID _KEY = request.registry[‘payment.acquirer‘]._get_wechat_partner_key(cr, uid)_, prestr = util.params_filter(post)mysign = util.build_mysign(prestr, _KEY, ‘MD5‘)if mysign != post.get(‘sign‘):return ‘false‘ pay_record = request.registry[‘payment.record‘]record = {}record[‘record_id‘] = post.get(‘out_trade_no‘)ref_id = pay_record.paysucess(cr, uid, record) reference = ref_id post[‘out_trade_no‘] = reference transaction_id = post.get(‘transaction_id‘)openid = post.get(‘openid‘)fees = post.get(‘cash_fee‘)tx = None if reference:tx_ids = request.registry[‘payment.transaction‘].search(cr, uid, [(‘reference‘, ‘=‘, reference)])if tx_ids:tx = request.registry[‘payment.transaction‘].browse(cr, uid, tx_ids[0])if tx.state == ‘pending‘:post[‘transaction_id‘] = transaction_id post[‘openid‘] = openid post[‘trade_status‘] = ‘Completed‘post[‘fees‘] = fees res = request.registry[‘payment.transaction‘].wechat_form_validate(cr, uid, tx, post)return res
这里要说明一下刷卡支付要相关要传送的xml数据内容跟格式
1.提交刷卡支付API
接口地址:https://api.mch.weixin.qq.com/pay/micropay
发送的xml数据(举例):
<xml>
<appid>wx2421b1c4370ec43b</appid>
<attach>订单额外描述</attach>
<auth_code>120269300684844649</auth_code>
<body>刷卡支付测试</body>
<device_info>1000</device_info>
<goods_tag></goods_tag>
<mch_id>10000100</mch_id>
<nonce_str>8aaee146b1dee7cec9100add9b96cbe2</nonce_str>
<out_trade_no>1415757673</out_trade_no>
<spbill_create_ip>14.17.22.52</spbill_create_ip>
<time_expire></time_expire>
<total_fee>1</total_fee>
<sign>C29DB7DB1FD4136B84AE35604756362C</sign>
</xml>
2.如果是需要密码支付的话,就要查询微信服务器
查询接口地址:https://api.mch.weixin.qq.com/pay/orderquery
xml数据:(举个栗子)
<xml>
<appid>wx2421b1c4370ec43b</appid>
<mch_id>10000100</mch_id>
<nonce_str>ec2316275641faa3aacf3cc599e8730f</nonce_str>
<transaction_id>1008450740201411110005820873</transaction_id>
<sign>FDD167FAA73459FD921B144BAF4F4CA2</sign>
</xml>
3.还有一个撤单的流程,因为需要证书所以没有办法去做。
wechat.py # 主要处理跟微信后台的xml数据通信,并根据相关返回的信息做相应的处理
# -*- coding: utf-8 -*-import base64省略一大堆import def WechatScanOrder(self, cr, uid, params, context=None):"""统一下单"""conf_ids = self.search(cr, uid, [(‘name‘, ‘=‘, ‘wechat‘)], context=context) # 查询payment.acquirer的# 数据库表payment_acquirer的name字段等于 wechat的记录listif len(conf_ids) != 1:return "配置错误,微信支付模块没有安装",None acquirer = self.browse(cr, uid, conf_ids[0], context=context)if self._wechat_required(params): # 检测是否有相关微信传输要求的字段params[‘appid‘] = acquirer.wechat_app_id params[‘mch_id‘] = int(acquirer.wechat_partner_account)params[‘sub_mch_id‘] = ‘‘params[‘nonce_str‘] = util.random_str()params[‘total_fee‘] = str(int(params[‘total_fee‘] * 100))del params[‘trade_type‘] # 刷卡支付不需要支付类型del params[‘product_id‘] # 刷卡支付不需要商品号del params[‘openid‘] # 刷卡支付不需要用户标示_, prestr = util.params_filter(params) # params_filter将数据转换成UTF-8,估计是预防中文乱码情况params[‘sign‘] = util.build_mysign(prestr, acquirer.wechat_partner_key, ‘MD5‘) # 生产sign签名retData = self._post(self._get_wechat_urls(cr, uid)[‘WEIXIN_SCAN_PAY_URL‘], util.buildXml(params)) if retData.get(‘return_code‘) == ‘SUCCESS‘ and retData.get(‘result_code‘) != ‘SUCCESS‘:if retData.get(‘err_code‘) == ‘USERPAYING‘:to_check_data = {‘appid‘: acquirer.wechat_app_id,‘mch_id‘: int(acquirer.wechat_partner_account),‘out_trade_no‘: params[‘out_trade_no‘],‘nonce_str‘: util.random_str()}# to_check_data是在判断retDatade错误代码后(刷卡密码支付会产生),往微信的查询入口去查询当前状态返回的信息_, prestr = util.params_filter(to_check_data)to_check_data[‘sign‘] = util.build_mysign(prestr, acquirer.wechat_partner_key, ‘MD5‘)time.sleep(3)t = 0while t < 30:time.sleep(10)check_data = self._post(self._get_wechat_urls(cr, uid)[‘CHECK_WEIXIN_SCAN_PAY_URL‘], util.buildXml(to_check_data))if check_data.get(‘return_code‘) == ‘SUCCESS‘ and check_data.get(‘result_code‘) == ‘SUCCESS‘:check_state = check_data.get(‘trade_state‘)elif check_data.get(‘return_code‘) == ‘SUCCESS‘ and check_data.get(‘result_code‘) != ‘SUCCESS‘:check_state = check_data.get(‘return_msg‘)else:check_state = u‘微信支付查询出现问题或者用户没有输入密码超过30秒!‘ if check_state == ‘SUCCESS‘:break t += 10if check_state == ‘SUCCESS‘:return None, check_dataelse:# 超时撤单代码to_cancel_data = {‘appid‘: acquirer.wechat_app_id,‘mch_id‘: int(acquirer.wechat_partner_account),‘out_trade_no‘: params[‘out_trade_no‘],‘nonce_str‘: util.random_str()}_, prestr = util.params_filter(to_cancel_data)to_cancel_data[‘sign‘] = util.build_mysign(prestr, acquirer.wechat_partner_key, ‘MD5‘)# 调用撤单方法cancel_state = self.to_cancel_scanpay_process(cr, uid, to_cancel_data, context)if cancel_state == ‘SUCCESS‘:return check_state + ‘,并且撤单成功,请重新生产支付单!‘, Noneelif cancel_state == ‘FAIL‘:return check_state + ‘,并且撤单失败!请求技术人员支持.‘, Noneelse:try:return wechat_error_codes[check_state]+‘但是已经支付超时,如果有发生扣款情况,请返回扣款‘, Noneexcept Exception, e:return check_state, Noneelif retData.get(‘err_code‘) == ‘SUCCESS‘:return None, retDataelse:if retData.get(‘err_code‘) is not None:return wechat_error_codes[retData.get(‘err_code‘)], Noneelif retData.get(‘return_code‘) == ‘SUCCESS‘ and retData.get(‘result_code‘) == ‘SUCCESS‘:return None, retDataelse:return "fail", Noneelse:return "fail", None 省略一大推代码====def wechat_get_pro_order(self,cr,uid,params,context=None):# 获取当前访问urlbase_url = self.pool[‘ir.config_parameter‘].get_param(cr, SUPERUSER_ID, ‘web.base.url‘) # 生产新的parmas_data字典if params[‘trade_type‘] == ‘WeiXinScan‘:params_data = {‘body‘: params[‘body‘],‘out_trade_no‘: params[‘out_trade_no‘],‘total_fee‘: float(params[‘total_fee‘]),‘spbill_create_ip‘: params[‘spbill_create_ip‘],‘auth_code‘: params[‘auth_code‘],‘trade_type‘: params[‘trade_type‘],‘product_id‘: params[‘product_id‘],‘openid‘: params[‘openid‘],‘attach‘: params.get(‘attach‘),}return self.WechatScanOrder(cr, uid, params_data, context)else:params_data = {‘body‘: params[‘body‘],‘out_trade_no‘: params[‘out_trade_no‘],‘total_fee‘: float(params[‘total_fee‘]),‘spbill_create_ip‘: params[‘spbill_create_ip‘],‘product_id‘: params[‘product_id‘],‘trade_type‘: params[‘trade_type‘],‘openid‘: params[‘openid‘],‘attach‘: params.get(‘attach‘),‘notify_url‘: base_url + params[‘notify_url‘],}# 把params_data 传输到_wechat_unified_order进行处理并返回数据return self._wechat_unified_order(cr, uid, params_data, context)省略一大推代码====
util.py # 主要是做xml数据的格式处理,sign的生产等等,这个会在wechat.py里面调用到、
# -*- coding: utf-8 -*- try:import hashlib md5_constructor = hashlib.md5 md5_hmac = md5_constructor sha_constructor = hashlib.sha1 sha_hmac = sha_constructorexcept ImportError:import md5 md5_constructor = md5.new md5_hmac = md5import sha sha_constructor = sha.new sha_hmac = sha import sys reload(sys)sys.setdefaultencoding(‘utf8‘) md5 = md5_constructor def smart_str(s, encoding=‘utf-8‘, strings_only=False, errors=‘strict‘):""" Returns a bytestring version of ‘s‘, encoded as specified in ‘encoding‘. If strings_only is True, don‘t convert (some) non-string-like objects. """if strings_only and isinstance(s, (types.NoneType, int)):return sif not isinstance(s, basestring):try:return str(s)except UnicodeEncodeError:if isinstance(s, Exception):return ‘ ‘.join([smart_str(arg, encoding, strings_only, errors) for arg in s])return unicode(s).encode(encoding, errors)elif isinstance(s, unicode):return s.encode(encoding, errors)elif s and encoding != ‘utf-8‘:return s.decode(‘utf-8‘, errors).encode(encoding, errors)else:return s def params_filter(params):ks = params.keys()ks.sort()newparams = {}prestr = ‘‘for k in ks:v = params[k]k = smart_str(k, ‘utf-8‘)if k not in (‘sign‘,‘sign_type‘) and v != ‘‘:newparams[k] = smart_str(v, ‘utf-8‘)prestr += ‘%s=%s&‘ % (k, newparams[k])prestr = prestr[:-1]return newparams, prestr def build_mysign(prestr, key, sign_type = ‘MD5‘):if sign_type == ‘MD5‘:return md5(prestr + key).hexdigest()return ‘‘
test_url.py # 主要用来测试,直接模拟http发送
省略代码
剩下的就是测试环境,如果没有扫描枪,可以直接在‘auth_code‘:‘微信条形码上面的数字串‘,然后运行test_url.py就可以了。