这里最要介绍一下微信的刷卡支付开发,至于微信的网页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 # 当前用户id
context = request.context
pay_record = request.registry['payment.record'] # 注册addons里面的payment_record.models.payment_record.py
# 里面的class PaymentRecord
record = {}
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,res
form_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 = None
request.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_data
return ret_data
def wechat_validate_data(self, **kwargs):
res = False
post = 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数据(举例):
刷卡支付测试
2.如果是需要密码支付的话,就要查询微信服务器
查询接口地址:https://api.mch.weixin.qq.com/pay/orderquery
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的记录list
if 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 = 0
while 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 += 10
if check_state == 'SUCCESS':
return None, check_data
else:
# 超时撤单代码
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 + ',并且撤单成功,请重新生产支付单!', None
elif cancel_state == 'FAIL':
return check_state + ',并且撤单失败!请求技术人员支持.', None
else:
try:
return wechat_error_codes[check_state]+'但是已经支付超时,如果有发生扣款情况,请返回扣款', None
except Exception, e:
return check_state, None
elif retData.get('err_code') == 'SUCCESS':
return None, retData
else:
if retData.get('err_code') is not None:
return wechat_error_codes[retData.get('err_code')], None
elif retData.get('return_code') == 'SUCCESS' and retData.get('result_code') == 'SUCCESS':
return None, retData
else:
return "fail", None
else:
return "fail", None
省略一大推代码====
def wechat_get_pro_order(self,cr,uid,params,context=None):
# 获取当前访问url
base_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_constructor
except ImportError:
import md5
md5_constructor = md5.new
md5_hmac = md5
import 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 s
if 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就可以了。