openerp要集成支付寶支付,我們項目一般都面對例如咖啡廳、農貿市場、超市等等收銀臺使用的屏幕上面,一般使用的屏幕都是安卓系統。這裏我們只考慮我們的erp的建立,當然一個這樣的項目包括很多,我們這裏只是涉及到支付寶支付方面,並且由於只是使用到掃描槍的情況,符合這種場景的支付方式就是支付寶的face2face當面付的方式。
支付的當面付在開始之前,必須要以商家的方式申請在支付寶的開放平臺的的當面付的簽證,具體查看支付寶的開放平臺的介紹(誰叫你使用人家的東西,一切按照官網的內容爲準)。這個時候你就會收到支付寶的當面付的appid,支付寶的私鑰、公鑰,這些都是要用上在開發過程中,這裏不得不吐槽一下支付寶,比起微信支付,傳輸的參數要多很多,並且安全級別要高,當面付現在只是支持RSA簽名編碼跟解碼,後面會說到底怎麼個麻煩法。
在開始之前我們要先準備一下相關內容跟參數:
1.商家的appid,具體怎麼查看,查詢支付寶官網,不行就找客服(支付寶的客服還是不錯的)
2.用openssel生產自己(開發者的)私鑰以及公鑰,具體還是看官網,裏面有詳細的介紹
3.上傳你生產的公鑰到支付寶用於解碼我們生產的sign簽名,並且複製支付寶的公鑰到本地用於解碼支付寶返回來的sign簽名
開發流程:
1.新建一個openerp模塊:payment_alipay(具體不同版本可能有些不相同,我用的openerp7)
2.主要處理代碼:
3.開發本質上面就是開發一個RESTful過程,就是訪問url+參數,我程序接收到post過來的數據然後進行處理
相關代碼內容:
mail.py(主要是做route過程)
@http.route('/payment/alipay/scancode', type='json', auth='none', methods=['POST']) def alipay_scancode(self, **post): logger.info('Beginning Alipay notify with post data %s', pprint.pformat(post)) # debug return_pay_msg = {'return_code': 'FAIL', 'return_msg': ''} cr, uid, context = request.cr, SUPERUSER_ID, request.context try: money = float(post['money']) except ValueError: return_pay_msg['return_msg'] = u'傳輸的金錢格式有問,只能爲數字!' return return_pay_msg if post['orderID'] and isinstance(money, float) and post['auth_code'] and post['subject']: pay_record = request.registry['payment.record'] # 註冊addons裏面的payment_record.models.payment_record.py # 裏面的class PaymentRecord record = dict() record['ref_id'] = str(post['orderID']) record['type'] = u'支付寶' record['sub_type'] = u'支付寶掃條形碼支付' record['fee'] = str(post['money']) record['subject'] = post['subject'] record_id = pay_record.create_alipay_record(cr, uid, record) # return record['record_id'] post_data = dict() post_data['out_trade_no'] = record_id # out_trade_no= reference of Payment Transaction post_data['money'] = str(post['money']) post_data['auth_code'] = post['auth_code'] post_data['subject'] = post['subject'] res = request.registry['payment.acquirer'].make_data_and_post(cr, uid, post_data, context) # 處理數據並post到阿里 if res: res_db = request.registry['payment.transaction'].pay_record_to_db(cr, uid, res, context) if res_db: return_pay_msg['return_code'] = 'SUCCESS' return_pay_msg['return_msg'] = u'支付成功並寫入數據庫' return return_pay_msg else: return_pay_msg['return_msg'] = u'支付處理出現問題!查詢數據處理模塊狀態.' return return_pay_msg else: return_pay_msg['return_msg'] = u'請檢查post的參數,一定要包括orderID、money、auth_code、subject!' print return_pay_msg['return_msg'] print return_pay_msg return return_pay_msg
alipay.py(跟支付寶交互的處理)
def encode_dict(self, params): # 處理編碼問題,都是設置成utf-8 return {k: six.u(v).encode('utf-8') if isinstance(v, str) else v.encode('utf-8') if isinstance(v, six.string_types) else v for k, v in six.iteritems(params)} def make_data_and_post(self, cr, uid, post_data, context=None): ids = self.search(cr, uid, [('provider', '=', 'alipay')], context=context) tz = timezone(country_timezones('cn')[0]) # 獲取當前時區 now_cst = datetime.datetime.now(tz) # 根據當前時區生產時間 biz_content = "{\"out_trade_no\":\""+post_data['out_trade_no']+"\"," biz_content += "\"scene\":\"bar_code\"," biz_content += "\"auth_code\":\""+post_data['auth_code']+"\"," biz_content += "\"total_amount\":\"" + post_data['money'] +"\",\"discountable_amount\":\"0.00\"," biz_content += "\"subject\":\""+post_data['subject']+"\",\"body\":\"test\"," biz_content += "\"goods_detail\":[{\"goods_id\":\"apple-01\",\"goods_name\":\"ipad\",\"goods_category\":\"7788230\",\"price\":\"88.00\",\"quantity\":\"1\"},{\"goods_id\":\"apple-02\",\"goods_name\":\"iphone\",\"goods_category\":\"7788231\",\"price\":\"88.00\",\"quantity\":\"1\"}]," biz_content += "\"operator_id\":\"op001\",\"store_id\":\"pudong001\",\"terminal_id\":\"t_001\"," biz_content += "\"timeout_express\":\"5m\"}" del post_data['out_trade_no'] # 公共請求參數不需要 del post_data['auth_code'] # 公共請求參數不需要 del post_data['money'] # 公共請求參數不需要 del post_data['subject'] if ids: acquirer = self.browse(cr, uid, ids[0], context=None) post_data.update(app_id='2015052600089077') post_data.update(charset='UTF-8') post_data.update(method='alipay.trade.pay') post_data.update(timestamp=now_cst.strftime("%Y-%m-%d %H:%M:%S")) post_data.update(version='1.0') post_data.update(sign_type='RSA') post_data.update(biz_content=biz_content) cpd = util.params_filter(post_data) if cpd: sign = util.build_mysign(cpd, sign_type='RSA') post_data.update(sign=sign) try: back_info = self.post_alipay(cr, uid, post_data, acquirer.environment, context=None) return back_info except Exception, e: return e else: return u'組合字符串有問題!' else: return u'沒有創建支付寶支付模式' def post_alipay(self, cr, uid, post_data, environment, context=None): alipay_url = self.get_alipay_urls(cr, uid, environment, context=None) pay_state_info = self.post_to_alipay_url(alipay_url, post_data) return pay_state_info def post_to_alipay_url(self, url, data): headers = {'Content-type': 'application/json;charset=utf-8'} data = self.encode_dict(data) # url_values = urllib.urlencode(self.encode_dict(data)) # url_values = util.params_filter1(data) # print "==========url_values==========" # print url_values # res = Request(url, url_values, headers) # try: # rsp = urlopen(res) # redirect_url = rsp.geturl() # print '===========redirectUrl=========' # print redirect_url # the_back_info = rsp.read() # print '===========the_back_info========' # print the_back_info # rsp.close() # return the_back_info # except URLError, e: # if hasattr(e, 'reason'): # print u'不能訪問到達改服務器' # print u'原因是:', e.reason # return e.reason # except HTTPError, e: # print u'錯誤代碼:', e.code # return e.code data = urllib.urlencode(data) # rsp = requests.post(url, data=json.dumps(data), headers=headers) rsp = requests.get(url, params=data, headers=headers) if rsp.status_code != 200: return False if rsp.text: return rsp.text
util.py(生產傳輸參數,以及sign的生產)
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' and v != '': newparams[k] = v # newparams[k] = smart_str(v, 'utf-8') prestr += '%s=%s&' % (k, newparams[k]) prestr = prestr[:-1] return prestr def build_mysign(prestr, sign_type='MD5'): if sign_type == 'MD5': # 這裏的md5估計用不了,後面有需求再修改 return md5_constructor(prestr).hexdigest() elif sign_type == 'RSA': # 打開私匙文件並轉換成python rsa的private key格式 with open(r'D:\alipaykey\rsa_private_key.pem') as priv_key: privkey = priv_key.read() privatekey = rsa.PrivateKey.load_pkcs1(privkey) signature = rsa.sign(prestr, privatekey, 'SHA-1') sign = base64.b64encode(signature) return sign
不得不說這裏的難點,就是python的rsa,在生產支付寶的sign過程中嘗試了很多,首先了解一下python的rsa,附上rsa官網:https://stuvel.eu/python-rsa-doc/index.html
一般情況就是:
>>> (pubkey,privkey)=rsa.newkeys(512) # 使用python rsa生產的公鑰和私鑰>>> message='Go left at the blue tree'>>> signature=rsa.sign(message,privkey,'SHA-1') # 根據私鑰生成的SHA-1的簽名
解碼sign:
>>> message='Go left at the blue tree'>>> rsa.verify(message,signature,pubkey)True
但是現在我們的情況是我們已經用openssl生產了自己的私鑰和公鑰,這也是支付寶要求用openssl生產的,我們還是用openssl生產吧,但是問題來了,python的rsa跟openssl的私鑰、公鑰的格式不一樣的,大家可以嘗試打印一下rsa生產的key,它是一大串的數字串,但是openssl生產的是有數字、符號、字母等等的字符串!我在這裏卡了很久,後面還是在同事幫忙下找到了解決方法:rsa.PrivateKey.load_pkcs1()
以及rsa.PublicKey.load_pkcs1()
方法,專門是用來跟openssl生產的key交互的,其實rsa是可以生產openssl格式的key,這裏具體不去做嘗試了,具體詳細看官網!
with open(r'D:\alipaykey\rsa_private_key.pem') as priv_key: privkey = priv_key.read() privatekey = rsa.PrivateKey.load_pkcs1(privkey) signature = rsa.sign(prestr, privatekey, 'SHA-1')
這樣生產signture是符合支付寶的sign簽名要求的,以上還是一個開始,因爲後面有
等等要做開發的。