本博客包含多个文档和书籍的翻译,但有能力者推荐阅读英文原版

Odoo 12开发者指南第十七章 Odoo的应用内购买

Odoo Alan 7个月前 (05-19) 1276次浏览 0个评论

全书完整目录请见: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服务流每个步骤的讲解:

  1. 客户向服务提供服务商请求提供服务。通过这个请求,客户将传递账号令牌,服务提供商使用它来识别用户。(注意客户需要在服务中安装你的模块)。
  2. 在接收来自客户的请求之后,服务提供者将询问Odoo IAP客户的账户里是否有足够的余额。如果客户有足够的余额,那么它就会创建交易来在提供服务前保留余额。
  3. 在保留余额之后,服务提供者会执行服务。在某些情况下,服务提供者会调用外部服务来执行所请求的服务。
  4. 在执行由客户所请求的服务之后,服务提供者回到Odoo API来在第2步中获取所预留的余额。如果所请求的服务由于错误未能正常提供,服务提供者会要求Odoo来释放所预留的余额。
  5. 最后,服务提供者会回到客户,通知他们所请求的服务已成功提供。一些服务可能会返回结果信息,这时你就会得到服务的结果。这一结果信息由客户根据他们的规格(取决于服务)来使用。

扩展知识…

如果客户没有足够的余额,服务流如下:

  1. 客户请求服务(如前面的流程一样)。
  2. 服务提供者获取到请求并询问Odoo用户是否有足够的余额。假定客户没有足够的余额。
  3. 服务提供者返回到客户并告知他们在账户中没有足够的余额,显示用户可以购买服务的信息(一个Odoo服务包链接)。
  4. 客户重定向到Odoo并进行服务的充值。

在Odoo中注册一个IAP服务

为能从客户账户中提供余额,服务提供者需要在Odoo上注册服务。你还要为服务定义方案。用户将通过这一注册服务来购买方案。本节中,我们将在Odoo上注册我们的服务并为服务定义方案。

准备工作…

通过IAP平台销售服务,服务提供者需要向Odoo注册服务和方案。我们将在https://iap-sandbox.odoo.com/上注册服务。这个IAP端点用于进行测试。你可以免费购买一个服务包。对于生产环境,你需要在https://iap.odoo.com注册服务。本节中我们将使IAP沙盒端点。

如何实现…

按照如下步骤来在Odoo上创建IAP服务:

  1. 打开https://iap-sandbox.odoo.com/并登录(如没有账户请注册)。
  2. 在首页中点击Manage my service按钮。
  3. 点击Create按钮来新建服务。
  4. 这会打开如下图的表单。此处,填写包含服务Logo、技术名称(必须唯一)、单位名称、隐私政策等信息。
    TODO
  5. 保存服务会显示服务密钥,如下图所示。注意这里的服务密钥不会再次显示:
    TODO
  6. 通过在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号的图书信息请求。这个服务模块会从客户账户收取余额并返回书名、作者及封面图等信息。

要便于理解,我们将分成两节来开发一个服务模块。本节中,我们将创建一个创建图书信息表的基本模块。在客户进行请求时,服务提供者会通过在这个表中搜索来返回图书信息。下一节中,我们将添加服务模块的第二部分,在那个模块中, 我们将添加代码来获取余额。

如何实现…

按照如下步骤来生成基本服务模块:

  1. 创建一个新的iap_isbn_service模块并添加_init__.py:
  2. 添加__manifest__.py及如下内容:
  3. 在models/book_info.py添加添加book.info模块及获取图书数据的方法:
  4. 在controller/main.py文件中添加一个http控制器(别忘记添加controllers/__init__.py文件):
  5. 在security/ir.model.access.csv中添加访问规则:
  6. 在views/book_info_views.xml中添加视图、菜单和动作:
  7. 在data/books_data.xml中添加一些示例图书数据(不要忘记在给定目录中添加封面图):

在安装模块后,你会看到一个带有图书数据的新菜单,如下:

TODO

运行原理…

现在我们创建了iap_isbn_service模块并创建了新的book.info表。把这张表看作主表,这里存储所有图书的数据。在客户请求图书数据时,我们将在这张表中进行搜索。如果查找 到了所请求的数据,我们将收取金额来交换图书数据。

ℹ️如果你是出于商业目的创建这个服务的话,你就需要拥有这个世界上所有图书的信息。在现实世界中,你会需要一个外部服务来作为图书的信息来源。我们练习假定在book.info表中有所有图书的信息,并且我们仅提供表中所有的图书数据。

在模型中,我们还创建了一个_books_data_by_isbn()方法。该方法会通过ISBN号查找图书并生成相应的数据来发送回给客户。结果中的status键将用于表明是否找到了这本书的数据。它将用于在未查到图书数据时释放所预留的余额。

我们还有路由added /get_book_data。IAP客户会对这个URL发送请求来获取图书详情。我们还需要添加代码来为这个服务获取IAP余额,在下一节中将会进行这一操作。但是,出于测试目的,我们可以像下面这样进行测试请求:

它会返回下面这样的信息: {“jsonrpc”: “2.0”, “id”: null, “result”: {“test”: “data”}}。

本节中剩下的步骤来自前一小节,无需进行详细的讲解。在下一节中,我们将更新这个模块来获取客户的余额并向他们返回图书数据。

授权并收取IAP余额

本节中我们将完成IAP服务模块。我们将使用IAP平台来授权并从客户账户中获取余额。我们还将添加可选配置来保存本章的在Odoo中注册一个IAP服务一节中所生成的服务密钥。

准备工作…

本节中我们将使用iap_isbn_service模块。

因为我们使用的是IAP沙盒服务,需要在系统参数中设置一个IAP端点。按照如下步骤来设置IAP沙盒端点:

  1. 启用开发者模式。
  2. 打开菜单Technical > Parameters > System Parameters
  3. 新建记录并添加一个键和值,如下:
    TODO

如何实现…

为完成这个服务模块,我们将添加一个配置选项来存储服务密钥。按照如下步骤添加新字段来在通过设置中设置isbn_service_key:

  1. 在res.config.settings中添加isbn_service_key字段:
  2. 在通用设置视图中添加isbn_service_key字段:

这会在通用设置中添加一个字段来存储服务密钥,如下图中所示。如果你还记得,我们在本章的在Odoo中注册一个IAP服务一节中生成了服务密钥。在这个字段中添加服务密钥。参见下图获取更多信息:

TODO

现在,我们将更新/get_book_data控制器来获取客户的余额。更新main.py文件如下:

更新该模块来应用这些修改。

运行原理…

为从客户账户提取余额,我们将需要通过IAP平台生成服务密钥。在本章的在Odoo中注册一个IAP服务一节中,我们生成了一个服务密钥。(如果你丢了那个密钥也没有问题,可以通过服务页面重新生成)。我们在通用设置中添加了isbn_service_key字段,这样我们可以在Odoo中存储服务密钥。你可能注意到我们在文件定义中使用了config_parameter属性。

在字段中使用这个属性会在ir.config_parameter模型中存储值,也称之为系统参数。在保存之后,你可以在开发者模式的Technical > Parameters > System Parameters菜单中查看它的值。在获取IAP余额时,我们将从系统参数中获取服务密钥。要从系统参数中获取这些值,可以使用get_param()。例如,我们可以像这样获取服务密钥:

这里,第一个参数是带有我们想要访问的值的参数的键,第二参数是默认值。如果在数据库中不存在所请求的键,那么会返回默认值。

接下来,我们更新了/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本书的数据。在这种情况下,可以在运行过程中修改实现的金额并获取部分余额。参见以下更进一步的讲解:

在以上代码块中,我们更新了金额来即时地根据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服务单独运行一个服务。如果你想要在同一台机器上进行测试,可以使用不同的端口和不同数据库来运行服务实例,如下:

这会在8070端口上运行Odoo服务。确保在这个数据库中安装了服务模块并且已添加IAP服务密钥。注意本节假定你的IAP服务运行在http://localhost:8070这一链接上。

如何实现…

我们将新建一个iap_isbn_client模块。这个模块将继承my_library模块并在图书的表单视图中添加按钮。点击按钮将向运行于8090端口的IAP服务发送一个请求。IAP服务将获取余额并返回所请求图书的信息。我们将在书本的记录中写入这一信息。按照如下步骤来完成IAP客户端模块:

  1. 新建一个iap_isbn_client模块并添加__init__.py::
  2. 将给定的内容添加到__manifest__.py中:
  3. 添加models/library_book.py并通过继承library.book模型来添加一些字段:
  4. 在相同的模型中添加fetch_book_data()方法。它会在点击按钮时进行调用:
  5. 添加process_result()方法来处理IAP服务的响应:
  6. 添加views/library_books_views.xml,并通过继承图书的表单视图来添加按钮和字段:

安装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服务提供者模块提供,因此它可以任何时间无需更新客户端模块而进行修改。

如何实现…

按照如下步骤来添加自定义收款模板:

  1. 在views/templates.xml中使用服务信息添加模板:
  2. 在__manifest__.py中添加一个模板:
  3. 添加一个iap.charge at controllers/main.py的模板引用:

更新模块来应用修改。在更新之后,会在客户的所有余额都消费完时看到付费弹窗:

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 “/>

喜欢 (2)
[]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址