全书完整目录请见:Odoo 12开发者指南(Cookbook)第三版
本章中,我们将讲解如下内容:
- 应用内购买(IAP)的概念
- 在Odoo中注册一个IAP服务
- 创建一个IAP服务模块
- 授权并收取IAP余额
- 创建一个IAP客户端模块
- 在账户缺少余额时显示报价
引言
Odoo在版本11中引入了IAP。IAP用于提供循环收费服务而又无需进行复杂配置。通常,从应用商店中购买的应用只需要由客户支付一次费用,因为它们是常规模块、无需花费开发者的成本。对比这种,IAP应用用于向用户提供服务,因此,它们在提供持续性服务时会存丰运营成本。在这种情况下,不可能只进行一次初始购买就提供服务的。服务提供者需要以循环的方式按照使用来向用户收费。Odoo IAP解决了这一问题并提供一种根据使用来收费的方式。本章中,我们将创建一个小服务来根据ISBN数提供完整的图书信息。
技术准备
本章的技术要求包含在线Odoo平台。
本章中使用的所有代码可通过GitHub仓库进行下载:https://github.com/PacktPublishing/Odoo-12-Development-Cookbook-Third-Edition/tree/master/Chapter17。
观看如下视频来查看实时代码操作:http://bit.ly/2ULQAc0
应用内购买(IAP)的概念
本节中,我们将探讨IAP过程各部分的不同实体。我们还将查看每个实体的角色以及它们如何合并为完成IAP的流程周期。
运行原理…
在IAP流程中有3种主要实体:客户、服务提供者和Odoo自身。描述如下:
- 客户是想要使用服务的终端用户。为使用服务,客户需要安装由服务提供者所提供的应用。然后客户需要根据他们的使用要求购买一个服务方案。通过这样,客户就可以开始直接使用服务了。这防止了给客户带来的麻烦,因为无需执行复杂的配置。反而他们只要购买服务并开始使用即可。
- 服务提供者是想要销售服务的开发人员(可能就是你)。客户会向提供者要求服务,这时服务提供者会检查客户账户中是否还有足够的余额。如果客户拥有足够的余额,服务提供者会扣除余额并向客户提供服务。
- 这里Odoo是某种中间商。它提供处理付款、余额、销售方案等的媒介。客户从Odoo充值服务余额,服务提供者在提供服务时提取余额。然后Odoo填补客户和服务提供者之间的空白,这样客户无需进行复杂的配置,而服务提供者无需设置支付网关、客户账号管理等。作为回报,Odoo从销售中提取佣金。在写本书时,Odoo从购买包中拿走25%作为佣金。
在这个流程中还有一个可选实体,即外部服务。在一些情况下,服务提供商使用一些外部服务。但我们在这里将忽略外部服务,因为他们是二级服务提供者。其中的一个示例可能是SMS短信服务。如果你向Odoo用户提供SMS IAP服务,那么你(服务提供者)将在内部使用SMS服务。
IAP 服务流
现在,我们将查看所有的IAP实体如何一起协助来提供服务。下图中描绘了IAP的流程:
TODO
以下是IAP服务流每个步骤的讲解:
- 客户向服务提供服务商请求提供服务。通过这个请求,客户将传递账号令牌,服务提供商使用它来识别用户。(注意客户需要在服务中安装你的模块)。
- 在接收来自客户的请求之后,服务提供者将询问Odoo IAP客户的账户里是否有足够的余额。如果客户有足够的余额,那么它就会创建交易来在提供服务前保留余额。
- 在保留余额之后,服务提供者会执行服务。在某些情况下,服务提供者会调用外部服务来执行所请求的服务。
- 在执行由客户所请求的服务之后,服务提供者回到Odoo API来在第2步中获取所预留的余额。如果所请求的服务由于错误未能正常提供,服务提供者会要求Odoo来释放所预留的余额。
- 最后,服务提供者会回到客户,通知他们所请求的服务已成功提供。一些服务可能会返回结果信息,这时你就会得到服务的结果。这一结果信息由客户根据他们的规格(取决于服务)来使用。
扩展知识…
如果客户没有足够的余额,服务流如下:
- 客户请求服务(如前面的流程一样)。
- 服务提供者获取到请求并询问Odoo用户是否有足够的余额。假定客户没有足够的余额。
- 服务提供者返回到客户并告知他们在账户中没有足够的余额,显示用户可以购买服务的信息(一个Odoo服务包链接)。
- 客户重定向到Odoo并进行服务的充值。
在Odoo中注册一个IAP服务
为能从客户账户中提供余额,服务提供者需要在Odoo上注册服务。你还要为服务定义方案。用户将通过这一注册服务来购买方案。本节中,我们将在Odoo上注册我们的服务并为服务定义方案。
准备工作…
通过IAP平台销售服务,服务提供者需要向Odoo注册服务和方案。我们将在https://iap-sandbox.odoo.com/上注册服务。这个IAP端点用于进行测试。你可以免费购买一个服务包。对于生产环境,你需要在https://iap.odoo.com注册服务。本节中我们将使IAP沙盒端点。
如何实现…
按照如下步骤来在Odoo上创建IAP服务:
- 打开https://iap-sandbox.odoo.com/并登录(如没有账户请注册)。
- 在首页中点击Manage my service按钮。
- 点击Create按钮来新建服务。
- 这会打开如下图的表单。此处,填写包含服务Logo、技术名称(必须唯一)、单位名称、隐私政策等信息。
TODO - 保存服务会显示服务密钥,如下图所示。注意这里的服务密钥不会再次显示:
TODO - 通过在Packs怎中点击Create new按钮创建一些服务包(方案)。例如,Get 50 books info in 10 Euro.。下图显示了创建新服务包页面的截图:
TODO
在配置结束时,你的服务页面会是这样:
TODO
运行原理…
我们在https://iap-sandbox.odoo.com/上创建了一个IAP服务,因为我们想要迁移到生产环境前测试下IAP服务。让我们来了解下创建服务时所填写的字段的用途吧:
- 技术名称(Technical name )用于识别服务,它必须是唯一名称。我们在这里添加了book_isbn。(此后无法修改这一名称)
- 标签、描述和服务Logo用于提供信息。这一信息会在用户购买服务的网页中显示。
- 单位名是服务销售的单位。例如,在SMS服务中,你的单位名将是SMS(例如100条短信$5)。本例中,我们使用了Books info作为单位名。
- Trial Credit是提供给客户用于测试的免费余额。它对每个客户仅提供一次。
- 隐私政策是你的服务的隐私政策URL。
在提交这些详情后,你的服务将被创建并且它会显示服务密钥。参见本节第5步中的截图来获取更多信息。安全存放这一密钥,因为它不会再次显示,但是可以在同一个页面中生成新的密钥。服务密钥用于在服务请求过程中获取客户的余额。
我们还需要为我们的服务创建方案。你需要提供方案名、描述、logo、数量和价格。数量字段用于该方案的服务单位的数量。价格字段用于定义用䚮获取这个方案所要支付的金额。在本节的第6步中,我们创建了一个50 Books Info in 10 Euro的方案。这里Book Info是我们在创建服务时所提交的单位类型。这表示如果用户购买了这一方案,他将能够获取到50本书的信息。
小贴士:Odoo从这一报价中抽取25%的佣金,因此请相应地定义你的服务方案。
现在,我们将在接下来的小节中创建一个IAP服务以及IAP客户端模块。
创建一个IAP服务模块
本节中,我们将创建一个服务模块来由服务提供者使用。这个模块将接收来自客户的IAP请求并在响应中返回服务结果。
准备工作…
我们将创建iap_isbn_service模块。这个服务模块将处理客户的IAP请求。客户将发送带有ISBN号的图书信息请求。这个服务模块会从客户账户收取余额并返回书名、作者及封面图等信息。
要便于理解,我们将分成两节来开发一个服务模块。本节中,我们将创建一个创建图书信息表的基本模块。在客户进行请求时,服务提供者会通过在这个表中搜索来返回图书信息。下一节中,我们将添加服务模块的第二部分,在那个模块中, 我们将添加代码来获取余额。
如何实现…
按照如下步骤来生成基本服务模块:
- 创建一个新的iap_isbn_service模块并添加_init__.py:
12from . import modelsfrom . import controllers - 添加__manifest__.py及如下内容:
12345678910111213{'name': "IAP ISBN service",'summary': "Get books information by ISBN number",'website': "http://www.example.com",'category': 'Uncategorized','version': '12.0.1','depends': ['iap', 'web', 'base_setup'],'data': ['security/ir.model.access.csv','views/book_info_views.xml','data/books_data.xml',]} - 在models/book_info.py添加添加book.info模块及获取图书数据的方法:
12345678910111213141516171819202122232425262728from odoo import models, fields, apiclass BookInfo(models.Model):_name = 'book.info'name = fields.Char('Books Name', required=True)isbn = fields.Char('ISBN', required=True)date_release = fields.Date('Release Date')cover_image = fields.Binary('BooksCover')author_ids = fields.Many2many('res.partner', string='Authors')@api.modeldef _books_data_by_isbn(self, isbn):book = self.search([('isbn', '=', isbn)], limit=1)if book:return {'status': 'found','data': {'name': book.name,'isbn': book.isbn,'date_release': book.date_release,'cover_image': book.cover_image,'authors': [a.name for a in book.author_ids]}}else:return {'status': 'not found',} - 在controller/main.py文件中添加一个http控制器(别忘记添加controllers/__init__.py文件):
12345678910from odoo import httpfrom odoo.http import requestclass Main(http.Controller):@http.route('/get_book_data', type='json', auth="public")def get_book_data(self):# 我们会在这里收取金额return {'test': 'data'} - 在security/ir.model.access.csv中添加访问规则:
12id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlinkacl_book_backend_user,book_info,model_book_info,base.group_user,1,1,1,1 - 在views/book_info_views.xml中添加视图、菜单和动作:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253<?xml version="1.0" encoding="utf-8"?><odoo><!-- Form View --><record id="book_info_view_form" model="ir.ui.view"><field name="name">Book Info Form</field><field name="model">book.info</field><field name="arch" type="xml"><form><sheet><field name="cover_image" widget='image' class="oe_avatar"/><div class="oe_title"><label for="name" class="oe_edit_only"/><h1><field name="name" class="oe_inline"/></h1></div><group><group><field name="isbn"/><field name="author_ids" widget="many2many_tags"/></group><group><field name="date_release"/></group></group></sheet></form></field></record><!-- Tree(list) View --><record id="books_info_view_tree" model="ir.ui.view"><field name="name">Book Info List</field><field name="model">book.info</field><field name="arch" type="xml"><tree><field name="name"/><field name="date_release"/></tree></field></record><!-- action and menus --><record id='book_info_action' model='ir.actions.act_window'><field name="name">Book info</field><field name="res_model">book.info</field><field name="view_type">form</field><field name="view_mode">tree,form</field></record><menuitem name="Books Data" id="books_info_base_menu" /><menuitem name="Books" id="book_info_menu"parent="books_info_base_menu" action="book_info_action"/></odoo> - 在data/books_data.xml中添加一些示例图书数据(不要忘记在给定目录中添加封面图):
123456789101112131415161718192021<?xml version="1.0" encoding="utf-8"?><odoo noupdate="1"><record id="book_info_data_1" model="book.info"><field name="name">Learning PostgreSQL 10</field><field name="isbn">1788392019</field><field name="author_ids" eval="[(0, 0, {'name':'Salahaldin Juba'}), (0, 0, {'name': 'Andrey Volkov'})]"/><field name="date_release">2017/12/01</field><field name="cover_image" type="base64"file="iap_isbn_service/static/img/postgres.jpg"/></record><record id="book_info_data_2" model="book.info"><field name="name">Odoo 10 Development Essentials</field><field name="isbn">9781785884887</field><field name="author_ids" eval="[(0, 0, {'name': 'Daniel Reis'})]"/><field name="date_release">2016/09/25</field><field name="cover_image" type="base64"file="iap_isbn_service/static/img/odoo.jpg"/></record></odoo>
在安装模块后,你会看到一个带有图书数据的新菜单,如下:
TODO
运行原理…
现在我们创建了iap_isbn_service模块并创建了新的book.info表。把这张表看作主表,这里存储所有图书的数据。在客户请求图书数据时,我们将在这张表中进行搜索。如果查找 到了所请求的数据,我们将收取金额来交换图书数据。
ℹ️如果你是出于商业目的创建这个服务的话,你就需要拥有这个世界上所有图书的信息。在现实世界中,你会需要一个外部服务来作为图书的信息来源。我们练习假定在book.info表中有所有图书的信息,并且我们仅提供表中所有的图书数据。
在模型中,我们还创建了一个_books_data_by_isbn()方法。该方法会通过ISBN号查找图书并生成相应的数据来发送回给客户。结果中的status键将用于表明是否找到了这本书的数据。它将用于在未查到图书数据时释放所预留的余额。
我们还有路由added /get_book_data。IAP客户会对这个URL发送请求来获取图书详情。我们还需要添加代码来为这个服务获取IAP余额,在下一节中将会进行这一操作。但是,出于测试目的,我们可以像下面这样进行测试请求:
1 2 3 4 |
curl --header "Content-Type: application/json" \ --request POST \ --data "{}" \ http://localhost:8069/get_book_data |
它会返回下面这样的信息: {“jsonrpc”: “2.0”, “id”: null, “result”: {“test”: “data”}}。
本节中剩下的步骤来自前一小节,无需进行详细的讲解。在下一节中,我们将更新这个模块来获取客户的余额并向他们返回图书数据。
授权并收取IAP余额
本节中我们将完成IAP服务模块。我们将使用IAP平台来授权并从客户账户中获取余额。我们还将添加可选配置来保存本章的在Odoo中注册一个IAP服务一节中所生成的服务密钥。
准备工作…
本节中我们将使用iap_isbn_service模块。
因为我们使用的是IAP沙盒服务,需要在系统参数中设置一个IAP端点。按照如下步骤来设置IAP沙盒端点:
- 启用开发者模式。
- 打开菜单Technical > Parameters > System Parameters
- 新建记录并添加一个键和值,如下:
TODO
如何实现…
为完成这个服务模块,我们将添加一个配置选项来存储服务密钥。按照如下步骤添加新字段来在通过设置中设置isbn_service_key:
- 在res.config.settings中添加isbn_service_key字段:
1234567from odoo import models, fieldsclass ConfigSettings(models.TransientModel):_inherit = 'res.config.settings'isbn_service_key = fields.Char("ISBN service key",config_parameter='iap.isbn_service_key') - 在通用设置视图中添加isbn_service_key字段:
1234567891011121314151617181920212223242526272829<?xml version="1.0" encoding="utf-8"?><odoo><record id="view_general_config_isbn_service" model="ir.ui.view"><field name="name">Configuration: IAP service key</field><field name="model">res.config.settings</field><field name="inherit_id" ref="base_setup.res_config_settings_view_form" /><field name="arch" type="xml"><div id="business_documents" position="before"><h2>IAP Books ISBN service</h2><div class="row mt16 o_settings_container"><div class="col-12 col-lg-6 o_setting_box"><div class="o_setting_right_pane"><span class="o_form_label">IAP service key</span><div class="text-muted">Generate service in odoo IAP and add service key here</div><div class="content-group"><div class="mt16 row"><label for="isbn_service_key" class="col-3 collg-3 o_light_label"/><field name="isbn_service_key" class="oe_inline" required="1"/></div></div></div></div></div></div></field></record></odoo>
这会在通用设置中添加一个字段来存储服务密钥,如下图中所示。如果你还记得,我们在本章的在Odoo中注册一个IAP服务一节中生成了服务密钥。在这个字段中添加服务密钥。参见下图获取更多信息:
TODO
现在,我们将更新/get_book_data控制器来获取客户的余额。更新main.py文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from odoo import http from odoo.http import request from odoo.addons.iap.models import iap class Main(http.Controller): @http.route('/get_book_data', type='json', auth="public") def get_book_data(self, account_token, isbn_number): service_key = request.env['ir.config_parameter'].sudo().get_param('iap.isbn_service_key', False) if not service_key: return { 'status': 'service is not active' } credits_to_reserve = 1 data = {} with iap.charge(request.env, service_key, account_token, credits_to_reserve): data = request.env['book.info'].sudo()._books_data_by_isbn(isbn_number) if data['status'] == 'not found': raise Exception('Book not found') return data |
更新该模块来应用这些修改。
运行原理…
为从客户账户提取余额,我们将需要通过IAP平台生成服务密钥。在本章的在Odoo中注册一个IAP服务一节中,我们生成了一个服务密钥。(如果你丢了那个密钥也没有问题,可以通过服务页面重新生成)。我们在通用设置中添加了isbn_service_key字段,这样我们可以在Odoo中存储服务密钥。你可能注意到我们在文件定义中使用了config_parameter属性。
在字段中使用这个属性会在ir.config_parameter模型中存储值,也称之为系统参数。在保存之后,你可以在开发者模式的Technical > Parameters > System Parameters菜单中查看它的值。在获取IAP余额时,我们将从系统参数中获取服务密钥。要从系统参数中获取这些值,可以使用get_param()。例如,我们可以像这样获取服务密钥:
1 |
self.env['ir.config_parameter'].sudo().get_param('iap.isbn_service_key', False) |
这里,第一个参数是带有我们想要访问的值的参数的键,第二参数是默认值。如果在数据库中不存在所请求的键,那么会返回默认值。
接下来,我们更新了/get_book_data路由。此时,它接收两个参数:
- account_token,用于标识用户的客户令牌。客户为服务所购买的余额将会在IAP平台中与这个account_token进行关联。服务提供者将在获取余额时发送这一令牌。
- isbn_number是客户想要通过余额换取信息的图书的ISBN号。
小贴士:此处的这些参数并不固定。我们的示例服务需要一个isbn_number,因此进行了该值的传递。但是,你可以传递任意数量所需的参数。只是要确保你传递了account_token,因为没有它你就无法从客户的账户中获取余额。
IAP提供iap.charge()帮助方法,它处理从客户账户中获取余额的流程。charge() 方法接收4个参数:环境、服务密钥、客户账户令牌和想要获取的金额。charge()方法管理如下内容:
- 创建交易对象并保留指定量的余额。如果客户账号没有足够的余额,那么会抛出InsufficientCreditError。
- 如果在客户账户中有足够的余额,它会在with代码块中运行代码。
- 如果with代码块成功运行,它会获取所接收到的余额。
- 如果在with代码块中的代码产生异常,它会翻译所预留的余额,因为无法完成服务请求。
在上例中,我们使用了相同的iap.charge()方法来为图书请求获取余额。我使用了我们的服务密码和客户账号令牌来为图书信息保留1份余额。然后,在with代码块中,我们使用了_books_data_by_isbn()方法来根据ISBN号获取图书数据。如果查找到了图书数据,那么它会执行这个with代码块,不产生报错并且一份余额将会从客户账户中扣除。然后,我们将向客户返回这一数据。如果未查找到图书数据,那么我们抛出异常来释放所预留的余额。
扩展知识…
在示例中,我们仅处理了对一本书的数据的请求,而获取单份余额还很简单,但在获取多份余额时就会变得更复杂。一个复杂的价格结构会导致一些极端状况。我们来通过下面的盒子进行了解这一问题。假定我们想要处理多本书的请求。在用例中,客户请求了10本书的数据,但仅得到了5本书的数据。此处,如果我们完成了这一个区块并且没有遇到任何问题的话,charge()会获取到10份余额,这是错误的,因为我们仅有一定数据的数据。此外,如果我们抛出异常,那么它会释放所有这10份余额并对客户显示未找到图书信息。解决这一问题 ,Odoo在with区块中提交了交易的对象。在某些用例中,无法提供完整服务。举个例子,用户请求了10本书的数据,但我们仅有5本书的数据。在这种情况下,可以在运行过程中修改实现的金额并获取部分余额。参见以下更进一步的讲解:
1 2 3 4 5 6 7 8 9 10 11 12 |
... isbn_list = [<assume list of 10 isbn number>] credits_to_reserve = len(isbn_list) data_found = [] with iap.charge(request.env, service_key, account_token, credits_to_reserve) as transection: for isbn in isbn_list: data = request.env['books.info']._books_data_by_isbn(isbn) if data['status'] == 'found': data_found.appned(data) transection.credit = len(data_found) return data_found |
在以上代码块中,我们更新了金额来即时地根据transection.credit获取余额,这种方式我们为查找到的图书数据收取金额。
其它内容
- IAP不仅限于Odoo框架。你可以开在其它平台或框架中开发一个服务提供者模块。仅需确保它能够处理JSON-RPC2请求。
- 如果想要在其它平台开发一个服务提供者,你还需要手动使用IAP端点来管理交易。你将需要通过请求IAP端点来授权并获取余额。可以参见https://www.odoo.com/documentation/12.0/webservices/iap.html#json-rpc2-transaction-api获取更多有关端点的知识。
创建一个IAP客户端模块
在前一节中,我们创建了IAP服务模块。下面,我们将创建一个IAP客户端模块来完成IAP服务流。
准备工作…
我们需要使用第四章 创建Odoo插件模块中的my_library模块。我们将在图书的表单视图中添加一个按钮,点击该按钮将创建一个对IAP服务的请求并获取图书数据。
对于IAP服务流,客户向服务提供者发出请求。此处要发出一个客户请求,我们需要针对IAP服务单独运行一个服务。如果你想要在同一台机器上进行测试,可以使用不同的端口和不同数据库来运行服务实例,如下:
1 |
./odoo-bin -c server-config -d service_db --db-filter=^service_db$ --http-port=8070 |
这会在8070端口上运行Odoo服务。确保在这个数据库中安装了服务模块并且已添加IAP服务密钥。注意本节假定你的IAP服务运行在http://localhost:8070这一链接上。
如何实现…
我们将新建一个iap_isbn_client模块。这个模块将继承my_library模块并在图书的表单视图中添加按钮。点击按钮将向运行于8090端口的IAP服务发送一个请求。IAP服务将获取余额并返回所请求图书的信息。我们将在书本的记录中写入这一信息。按照如下步骤来完成IAP客户端模块:
- 新建一个iap_isbn_client模块并添加__init__.py::
1from . import models - 将给定的内容添加到__manifest__.py中:
1234567891011{'name': "Books ISBN",'summary': "Get Books Data based on ISBN",'website': "http://www.example.com",'category': 'Uncategorized','version': '12.0.1','depends': ['iap', 'my_library'],'data': ['views/library_books_views.xml',]} - 添加models/library_book.py并通过继承library.book模型来添加一些字段:
12345678910from odoo import models, fields, apifrom odoo.exceptions import UserErrorfrom odoo.addons.iap import jsonrpcclass LibraryBook(models.Model):_inherit = 'library.book'cover_image = fields.Binary('Books Cover')isbn = fields.Char('ISBN') - 在相同的模型中添加fetch_book_data()方法。它会在点击按钮时进行调用:
123456789101112131415def fetch_book_data(self):self.ensure_one()if not self.isbn:raise UserError("Please add ISBN number")user_token = self.env['iap.account'].get('book_isbn')params = {'account_token': user_token.account_token,'isbn_number': self.isbn}service_endpoint = 'http://localhost:8070'result = jsonrpc(service_endpoint + '/get_book_data', params=params)if result.get('status') == 'found':self.write(self.process_result(result['data']))return True - 添加process_result()方法来处理IAP服务的响应:
12345678910111213141516171819@api.modeldef process_result(self, result):authors = []existing_author_ids = []for author_name in result['authors']:author = self.env['res.partner'].search([('name','=',author_name)], limit=1)if author:existing_author_ids.append(author.id)else:authors.append((0, 0, {'name': author_name}))if existing_author_ids:authors.append((6, 0, existing_author_ids))return {'author_ids': authors,'name': result.get('name'),'isbn': result.get('isbn'),'cover_image': result.get('cover_image'),'date_release': result.get('date_release'),} - 添加views/library_books_views.xml,并通过继承图书的表单视图来添加按钮和字段:
1234567891011121314151617181920212223<?xml version="1.0" encoding="utf-8"?><odoo><record id="library_book_view_form_inh" model="ir.ui.view"><field name="name">Library Book Form</field><field name="model">library.book</field><field name="inherit_id"ref="my_library.library_book_view_form"/><field name="arch" type="xml"><xpath expr="//group" position="before"><header><button name="fetch_book_data"string="Fetch Book Data" type="object"/></header></xpath><field name="date_release" position="after"><field name="isbn"/><field name="cover_image" widget="image" class="oe_avatar"/></field></field></record></odoo>
安装iap_isbn_client模块。这会在图书表单中添加Fetch Book Data按钮。然后添加一个有效ISBN号(如1788392019)并点击按钮。这会发送请求并从服务获取数据。如果你是首次进行IAP服务调用,那么你的Odoo实例不会有关联账号的信息,因此Odoo会弹出购买余额的页面如下:
TODO
在点击Buy credits at Odoo按钮时,会重定向到IAP服务页面,这里你会看到有关可选购买包的信息。就本节而言,你会看到本章在Odoo中注册一个IAP服务一节中在注册我们的服务时定义的包。参见如下截图,这是一个购买包的列表:
TODO
因我们在使用沙盒端点,你可以无需支付就购买包。然后,你可以从图书的表单视图请求图书信息。
运行原理…
在服务模块中我们创建了一个路由/get_book_data。这个路由用于处理客户的IAP请求。因此通过这个客户端模块,我们将对该路由做一个JSON-RPC请求。IAP请求将获取余额并取得图书数据。所幸IAP为jsonrpc请求提供一个封装,我们将进行使用。
my_library模块的library.book模型没有ISBN和cover_image字段,因此我们通过继承在library.book模型中拥有了一些额外字段。参见第五章 应用模型中的使用继承向模型添加功能一节。我们通过继承添加了字段,因为不希望在iap_isbn_client未安装时使用这些字段。
为进行请求,我们通过继承向图书表单视图添加了一个按钮。点击按钮会触发fetch_book_data()方法,在该方法中,我们向服务端点做出了jsonrpc请求。通过这个请求,我们传递了两个参数:客户账号令牌和图书数据的ISBN号。
你可以通过iap.account模型的get() 方法获取客户账号。令牌的生成是自动的。你只需通过服务的名称调用get()方法。在本例中,服务名为book_isbn。这将返回客户IAP账号的记录集,并且你可以获取到客户令牌account_token字段。
我们做出了jsonrpc请求来获取图书信息。如果客户没有足够的余额服务模块会生成InsufficientCreditError。jsonrpc会自动处理这个异常,它会向客户显示一个弹窗来购买余额。弹窗中有一个客户可以购买服务方案的页面链接。因为我们使用的是沙盒,你可以无需支付而获取任意包。但是,在生产环境中,客户需要支付来获取服务。
点击按钮时,如果一些顺利,客户有足够的余额而我们的数据库也有所请求ISBN的数据,会从客户账户中扣除金额并且jsonrpc会返回图书数据。然后我们只需将结果传递给process_result()方法并向图书记录写入数据。
扩展知识…
如果你想了解服务的剩余金额,可以通过仪表盘所提供的链接进行查看:
TODO
在账户缺少余额时显示报价
如果在消费完所有的余额后进行IAP服务请求,那么服务模块会产生InsufficientCreditError,客户端模块将自动处理这个错误并显示弹窗。无论何时你的IAP账户余额消耗完,Odoo都会显示像下面这个购买更多余额的弹窗:
TODO
默认弹窗太过简单并且没有提供足够的信息。在本节中,我们将学习如何使用美观的模板替换弹窗的内容。
准备工作…
本节我们将使用iap_isbn_service模块。付款模板由IAP服务提供者模块提供,因此它可以任何时间无需更新客户端模块而进行修改。
如何实现…
按照如下步骤来添加自定义收款模板:
- 在views/templates.xml中使用服务信息添加模板:
1234567891011121314151617181920212223242526272829303132333435363738394041424344<odoo><template id="no_credit_info" name="No credit info"><section class="jumbotron text-center bg-primary"><div class="container pb32 pt32"><h1 class="jumbotron-heading">Library ISBN</h1><p class="lead text-muted">Get full book information with cover image just by the ISBN number.</p><span class="badge badge-warning" style="fontsize: 30px;">20% Off</span></div></section><div class="container"><div class="row"><div class="col"><div class="card mb-3"><div class="card-header"><i class="fa fa-database"/> Large books database</div><div class="card-body"><p class="card-text">We have largest book databse. It contains more than 2500000+ books.</p></div></div></div><div class="col"><div class="card mb-3"><div class="card-header"><i class="fa fa-image"/>With cover image</div><div class="card-body"><p class="card-text">More then 95% of our books having high quality book cover images.</p></div></div></div></div></div></template></odoo> - 在__manifest__.py中添加一个模板:
123456789...'data': ['security/ir.model.access.csv','views/book_info_views.xml','data/books_data.xml','views/res_config_settings.xml','views/templates.xml']... - 添加一个iap.charge at controllers/main.py的模板引用:
1234567...with iap.charge(request.env, service_key, account_token,credits_to_reserve,credit_template='iap_isbn_service.no_credit_info'):data = request.env['book.info'].sudo()._books_data_by_isbn(isbn_number)if data['status'] == 'not found':raise Exception('Book not found')
更新模块来应用修改。在更新之后,会在客户的所有余额都消费完时看到付费弹窗:
TODO
运行原理…
为在客户端显示更好看的弹窗,我们需要创建一个QWeb模板。在第1步中,我们创建了QWeb模板no_credit_info。这通过简单的bootstrap内容来获取。注意它仅包含静态HTML内容。在下一步中,我们向应用声明添加了模板文件。
在设计模板之后,你需要向iap.charge()方法传递XML模板引用。这可通过可选的credit_template参数进行传递。在第3步中,我们传递一个对charge方法的模板引用。在传递了模板之后,如果抛出了InsufficientCreditError,那么模板会带着错误消息传递给客户。在客户端,如果错误页面通过模板体接收,那么这个自定义模板会在一个代替默认弹窗的弹窗中显示。
扩展知识…
在模板中没有图片,但如果我想要在模板中使用图片,需要格外小心。原因是在这里不能像平常那样使用图片的绝对路径URL。因为服务模块运行在单独的服务器上,弹窗不会显示图片。要解决这一问题,你需要传递带有域名的完整图片URL,因为这个模板会在客户端屏幕上显示。例如,如果服务域名为http://localhost:8070,那么你需要使用下面这样的图片写法:
<image src=”http://localhost:8070/module_name/static/img/image.png “/>