odoo支持多种支付方式,国内来说最常见的莫非支付宝和微信支付了,今天我们就来手动实现一个支付宝对接模块。
沙箱环境
由于支付宝对接需要各种申请权限,好在官方提供了一个沙箱环境,我们就在沙箱环境下进行我们的开发。首先,我们用支付宝去申请一个沙箱账号:
AppID是支付宝应用必须的一个应用ID,申请应用时会自动分配一个。
然后我们需要设置应用的密钥,支付宝支持的加密方式有RSA和RSA2两种,推荐的长度是2048,我们可以使用官方的密钥生成工具来帮助我们生成两种密钥。
因为我们是Python应用,所以选择PKCS1格式。
将生成完的密钥上传到支付宝后台,密钥这步就算完成了。
另外,我们可以再下载一个安卓版的测试钱包,使用官方提供的沙箱账号登录测试。
Python SDK
支付宝的验签和请求流程可谓相当麻烦,我们这里使用自己编写的SDK包来帮助我们简化我们的开发任务。(没有使用官方SDK的原因,一方面是因为过于臃肿,另一方面官方的SDK代码一种浓浓的java味道)
Payment Alipay
各种支付方式在odoo中都是一个payment.acquirer的对象,因此我们需要继承这个对象。
provider用来指明支付提供商,这里当然就是支付宝了,因为我们是继承,所以需要再父类的基础上添加一个provider,命名为alipay。
剩下的字段就是支付宝集成需要的字段,主要包括:
- seller_id:卖家ID 用来验证收款方ID
- alipay_appid: 前面提到的appid
- alipay_secret: 前面提到的密钥
- alipay_public_key: 支付宝公钥,区别于应用公钥。应用于支付宝异步消息验签。
- alipay_sign_type: 验签方式,RSA和RSA2
1 | class AcquirerAlipay(models.Model): |
跳转支付宝付款
在Shop中有一步是选择支付方式,然后跳转支付提供方的页面进行付款。这一步在payment中是通过_get_form_action_url方法来实现的,父类会根据不同支付方式的名称不同,调用不同的子类方法,例如我们这里的支付名称是alipay,因此我们的方法名称就要命名为:alipay_get_form_action_url。
1 |
|
我们这里只跳转到了一个中间URL,是因为有些参数只有controller中才能获取。
1 |
|
我们在web页面中购买了某项产品之后,然后点击支付按跳转到支付宝页面进行支付,支付完成后,我们需要告诉支付宝回跳到我们的网站。这个参数是通过return_url来实现的。
我们下单支付,并会跳页面,使用的是支付宝的统一收单下单并支付页面接口接口。在我们的sdk中对应的接口是trade_page_pay。
因此我们可以看到_get_alipay_url方法调用的就是trade_page_pay:
1 |
|
支付结果验证
支付完成后,支付宝会通过同步和异步两种方式来告诉我们支付结果。同步就是通过回跳页面中的参数,异步是在将支付结果Post到我们调用接口时传入的notify_url参数的URL。
为了保证支付结果,我们要对这两种支付结果都进行验证。
odoo中的支付过程是payment.transaction对象,我们根据支付宝返回的结果来控制payment.transaction的状态。
同步验证
同步验证就是把支付宝回传的订单号,去支付宝服务器进行查询验证,确保支付成功,如果没有支付成功,则挂起这次支付。如果异步结果在前,那么直接返回异步的结果。
1 |
|
异步验证
异步验证,需要验证支付宝推送的消息是否跟自身的匹配,校验订单信息等,如果都一致,则通过。否则挂起本次支付。
1 | def _verify_pay(self, data): |
odoo 中文翻译的一个坑
当我接入完成,付款完成之后,页面在处理支付宝返回的信息时报了一个错误:
1 | not all arguments converted during string formatting |
经过排查,原因出在中文翻译文件中…
有一句原文是:
1 | The transaction %s with %s for %s has been confirmed. The related payment is posted: %s |
居然给翻译成了:
1 | %s的%s交易%s已确认。等待获取发送付款状态...... |
很明显,最后一个占位符被这条翻译者给吃了…
完整示例
这里是一个完整的示例:
- 设置支付参数:
- 选择商品
- 确认订单
- 填写收货信息
- 选择支付方式
- 支付宝支付
- 支付完成
模块开源,下载地址