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签名要求的,以上还是一个开始,因为后面有
等等要做开发的。