openerp7微信支付開發

這裏最要介紹一下微信的刷卡支付開發,至於微信的網頁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方法處理,並返回errres
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數據(舉例):

    <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_acquirername字段等於 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就可以了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章