提供应用内购买服务¶
In-App Purchase (IAP) 允许通过 Odoo 应用提供持续服务的提供者获得补偿,而不是仅仅进行一次初始购买。
在这种情况下,Odoo 主要充当客户和 Odoo 应用程序开发人员之间的 代理人 :
用户从Odoo购买服务令牌。
当服务被请求时,服务提供商从用户的Odoo账户中提取令牌。
注意
本文档旨在为 服务提供商 提供帮助,介绍了如何通过直接使用JSON-RPC2_或使用Odoo提供的便利工具来实现。
概览¶
服务提供者(可能是您,读者),您将以按使用付费的形式向客户提供服务价值。
客户端安装了您的Odoo应用程序,从那里请求服务。
Odoo 代理记账,客户向其账户添加信用额度,您可以从中提取信用额度以提供服务。
外部服务是一个可选的角色: 您 可以直接提供服务,也可以委托实际服务作为Odoo系统和实际服务之间的桥梁/翻译器。
注解
从 2018年10月 开始,积分从整数变为浮点数。整数值仍然受支持。
每个通过IAP平台提供的服务都可以由客户使用令牌或 积分 。积分是一个浮点单位,其货币价值取决于服务,并由提供者决定。这可能是:
对于短信服务:1个积分=1条短信;
对于广告服务:1个积分=1个广告;或者
对于邮寄服务:1积分=1邮票。
信用额度也可以与固定金额关联,以弥补价格的变化(例如,短信和邮票的价格可能会因国家而异)。
客户可以在 https://iap.odoo.com 上购买预付费信用包来确定信用的价值(参见 Packs)。
注解
在接下来的解释中,我们将忽略外部服务,它们只是您提供的服务的一个细节。
如果一切顺利,正常的流程如下:
客户端请求某种服务。
服务提供商向Odoo询问客户账户中是否有足够的积分用于该服务,并创建一个等于该金额的交易。
服务提供商提供服务(可以自己提供或调用外部服务)。
服务提供商返回Odoo,以捕获(如果可以提供服务)或取消(如果无法提供服务)在步骤2中创建的交易。
最后,服务提供商通知客户端服务已经提供,可能会在客户端系统中显示或存储其结果(取决于服务)。
然而,如果客户的账户没有足够的服务信用,流程将如下所示:
客户以前请求服务。
服务提供商向Odoo询问客户账户上是否有足够的信用额度,但得到了否定的回复。
这将被传回给客户端。
被重定向到其Odoo账户以充值并重试的用户。
构建您的服务¶
在这个例子中,我们提供的服务是为了获得一次积分而烧掉10秒的CPU。对于你自己的服务,你可以,例如:
提供自己的在线服务(例如,将报价转换为传真,为日本的企业服务);
提供自己的 离线 服务(例如提供会计服务);或者
充当其他服务提供商的中介(例如,桥接到MMS网关)。
在Odoo上注册服务¶
首先,您需要在IAP端点(生产环境和/或测试环境)上注册您的服务,然后才能实际查询用户账户。要创建一个服务,请访问IAP端点上的 Portal Account (生产环境请访问https://iap.odoo.com,测试环境请访问https://iap-sandbox.odoo.com,这些端点是 独立的 , 不会同步 )。或者,您可以访问Odoo上的门户网站(https://iap.odoo.com/my/home)并选择 In-App Services 。
注解
在生产环境中,使用真实交易前需要进行手动验证。在沙盒环境中,此步骤会自动通过以便于测试。
登录后,转到
,点击创建并提供您的服务的信息。该服务有 七个 重要字段:
name
-ServiceName
: 这是您在从Odoo请求交易时需要在客户端的 app 中提供的字符串。 (例如self.env['iap.account].get(name)
). 作为良好的实践,这应该与您的应用程序的技术名称匹配。label
-Label
: 在购物门户上显示给客户的名称。
警告
无论是 ServiceName
还是 Label
都是唯一的。作为良好的实践, ServiceName
通常应与您的 Odoo 客户端应用程序的名称相匹配。
icon
-Icon
: 一个通用的图标,将作为您的 packs 的默认图标。key
-ServiceKey
: 开发者密钥,用于在IAP中标识您(参见:your service),并允许从客户账户中提取积分。它只会在服务创建时显示一次,并且可以随时重新生成。
危险
您的 ServiceKey
是一个秘密,泄露您的服务密钥将允许其他应用程序开发人员使用您购买的服务积分。
试用积分
-浮点数
: 这对应于您准备在用户首次使用时提供给您的应用程序用户的积分。请注意,此类服务仅适用于具有有效企业合同的客户。隐私政策
-PrivacyPolicy
: 这是您服务的隐私政策的URL。这应明确提到您收集的**信息**,以及您如何**使用它,它对使您的服务工作的**相关性**,并告知客户如何**访问、更新或删除他们的个人信息**。
您可以创建 积分包 ,客户可以购买这些积分包以使用您的服务。
礼包¶
信用包实质上是具有五个特征的产品:
名称:套餐名称,
图标:包的特定图标(如果未提供,则会回退到服务图标)
描述:在商店页面和发票上显示的包的详细信息。
数量:客户购买包时有权获得的积分数量,
价格:以欧元计价(目前计划支持美元)。
注解
Odoo对所有套餐销售收取25%的佣金。请相应调整您的销售价格。
注解
根据策略,每个积分包的价格可能会有所不同。
Odoo 应用程序¶
第二步是开发一个 Odoo App
_,客户可以在他们的Odoo实例中安装该应用程序,通过该应用程序他们可以 请求 您提供的服务。我们的应用程序只会在合作伙伴表单中添加一个按钮,让用户请求在服务器上消耗一些CPU时间。
首先,我们将创建一个 odoo模块 ,依赖于 iap
。IAP是一个标准的V11模块,依赖关系确保了本地账户的正确设置,并且我们将能够访问一些必要的视图和有用的辅助工具。
{
'name': "Coal Roller",
'category': 'Tools',
'depends': ['iap'],
}
第二,整合的“本地”方面。在这里,我们只会向合作伙伴视图添加一个操作按钮,但是您当然可以通过您的应用程序提供显著的本地价值,并通过远程服务提供其他部分。
{
'name': "Coal Roller",
'category': 'Tools',
'depends': ['iap'],
'data': [
'views/res_partner_views.xml',
],
}
<odoo>
<record model="ir.ui.view" id="partner_form_coalroll">
<field name="name">partner.form.coalroll</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']">
<button type="object" name="action_partner_coalroll"
class="oe_stat_button" icon="fa-gears">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Roll Coal</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>
我们现在可以实现动作方法/回调。这将 调用我们自己的服务器。
在服务器或应用与我们的服务器之间的通信协议方面没有任何要求,但是 iap
提供了一个 iap_jsonrpc()
助手函数,用于调用另一个 Odoo 实例上的 JSON-RPC2 端点,并透明地重新引发相关的 Odoo 异常 (InsufficientCreditError
、odoo.exceptions.AccessError
和 odoo.exceptions.UserError
)。
在该调用中,我们需要提供:
任何相关的客户端参数(这里没有),
当前客户端的
token
由iap.account
模型的account_token
字段提供。您可以通过调用env['iap.account'].get(service_name)
来检索您的服务的帐户,其中service_name
是在IAP端点上注册的服务的名称。
from odoo import api, models
from odoo.addons.iap import jsonrpc, InsufficientCreditError
# whichever URL you deploy the service at, here we will run the remote
# service in a local Odoo bound to the port 8070
DEFAULT_ENDPOINT = 'http://localhost:8070'
class Partner(models.Model):
_inherit = 'res.partner'
def action_partner_coalroll(self):
# fetch the user's token for our service
user_token = self.env['iap.account'].get('coalroller')
params = {
# we don't have any parameter to provide
'account_token': user_token.account_token
}
# ir.config_parameter allows locally overriding the endpoint
# for testing & al
endpoint = self.env['ir.config_parameter'].sudo().get_param('coalroller.endpoint', DEFAULT_ENDPOINT)
jsonrpc(endpoint + '/roll', params=params)
return True
注解
iap
automatically handles
InsufficientCreditError
coming from the action
and prompts the user to add credits to their account.
iap_jsonrpc()
负责重新引发 InsufficientCreditError
错误给你。
危险
如果您没有使用 iap_jsonrpc()
您 必须 小心地重新引发 InsufficientCreditError
在您的 处理程序中,否则用户将不会被提示充值他们的账户,下一次调用也会失败。
服务¶
虽然这不是 必需的 ,因为 iap
提供了JSON-RPC2_调用的客户端助手( iap_jsonrpc()
)和交易的服务助手( iap_charge
),我们还将实现服务端作为一个Odoo模块:
{
'name': "Coal Roller Service",
'category': 'Tools',
'depends': ['iap'],
}
由于客户端的查询是以 JSON-RPC2 的形式发送的,我们需要相应的控制器,该控制器可以调用 iap_charge
并在其中执行服务:
from passlib import pwd, hash
from odoo import http
from odoo.addons.iap.tools.iap_tools import iap_charge
class CoalBurnerController(http.Controller):
@http.route('/roll', type='json', auth='none', csrf='false')
def roll(self, account_token):
# the service key *is a secret*, it should not be committed in
# the source
service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
# we charge 1 credit for 10 seconds of CPU
cost = 1
# we set the transaction to expire after 1 hour
ttl = 1
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
with iap_charge(http.request.env, service_key, account_token, cost, ttl=ttl):
# 10 seconds of CPU per credit
end = time.time() (10 * cost)
while time.time() < end:
# we will use CPU doing useful things: generating and
# hashing passphrases
p = pwd.genphrase()
h = hash.pbkdf2_sha512.hash(p)
# here we don't have anything useful to the client, an error
# will be raised & transmitted in case of issue, if no error
# is raised we did the job
The iap_charge
helper will:
使用指定数量的积分授权(创建)交易,如果账户没有足够的积分,则会引发相关错误
执行
with
语句的主体部分如果
with
语句体成功执行,则根据需要更新交易价格确认(捕获)交易
否则,如果在
with
语句体中引发错误,则取消事务(并释放对积分的保留)
危险
默认情况下, iap_charge
会连接到 生产 IAP 终点,https://iap.odoo.com。在开发和测试服务时,您可能希望将其指向 开发 IAP 终点 https://iap-sandbox.odoo.com。
要这样做,请在您的服务Odoo中设置 iap.endpoint
配置参数:在调试/开发者模式下, ,如果还没有,请为键 iap.endpoint
定义一个条目。
The iap_charge
helper has two additional optional
parameters we can use to make things clearer to the end-user.
description
是一条将与交易相关联并显示在用户仪表板中的消息,它有助于提醒用户为什么存在此项费用。
credit_template
是一个 QWeb模板 模板的名称,如果用户的账户可用余额少于服务提供商请求的金额,将会渲染并显示给用户,其目的是告诉您的用户为什么他们应该对您的 IAP 提供感兴趣。
{
'name': "Coal Roller Service",
'category': 'Tools',
'depends': ['iap'],
'data': [
'views/no-credit.xml',
],
}
@http.route('/roll', type='json', auth='none', csrf='false')
def roll(self, account_token):
# the service key *is a secret*, it should not be committed in
# the source
service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
# we charge 1 credit for 10 seconds of CPU
cost = 1
# we set the transaction to expire after 1 hour
ttl = 1
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
with charge(http.request.env, service_key, account_token, cost, ttl=ttl
description="We're just obeying orders",
credit_template='coalroller_service.no_credit'):
# 10 seconds of CPU per credit
end = time.time() (10 * cost)
while time.time() < end:
# we will use CPU doing useful things: generating and
# hashing passphrases
p = pwd.genphrase()
h = hash.pbkdf2_sha512.hash(p)
<odoo>
<template id="no_credit" name="No credit warning">
<div>
<div class="container-fluid">
<div class="row">
<div class="col-md-7 offset-lg-1 mt32 mb32">
<h2>Consume electricity doing nothing useful!</h2>
<ul>
<li>Heat our state of the art data center for no reason</li>
<li>Use multiple watts for only 0.1€</li>
<li>Roll coal without going outside</li>
</ul>
</div>
</div>
</div>
</div>
</template>
</odoo>
JSON-RPC2 交易 API¶
IAP 交易 API 在实现服务器网关时不需要使用 Odoo,调用是标准的 JSON-RPC2。
Calls use different endpoints but the same method on all endpoints (
call
).当出现异常时,将返回 JSON-RPC2 错误,正式的异常名称可在
data.name
中进行编程操作。
另请参阅
有关更多信息,请参阅 iap.odoo.com文档
_。
捕获¶
- /iap/1/capture
确认指定的交易,将用户账户中保留的积分转移到服务提供商的账户中。
捕获调用是幂等的:对已经捕获的交易执行捕获调用不会产生进一步的影响。
- 参数
token (TransactionToken) –
key (ServiceKey) –
credit_to_capture (float) – 可选参数,用于捕获比授权的信用额度更小的金额
- 引发
r2 = requests.post(ODOO + '/iap/1/capture', json={
'jsonrpc': '2.0',
'id': None,
'method': 'call',
'params': {
'token': tx,
'key': SERVICE_KEY,
'credit_to_capture': credit or False,
}
}).json()
if 'error' in r:
# handle capture error
# otherwise transaction is captured
取消¶
- /iap/1/cancel
取消指定的交易,释放用户信用额度的保留。
Cancel调用是幂等的:对已经取消的交易执行捕获调用不会产生任何进一步的影响。
- 参数
token (TransactionToken) –
key (ServiceKey) –
- 引发
r2 = requests.post(ODOO + '/iap/1/cancel', json={
'jsonrpc': '2.0',
'id': None,
'method': 'call',
'params': {
'token': tx,
'key': SERVICE_KEY,
}
}).json()
if 'error' in r:
# handle cancel error
# otherwise transaction is cancelled
类型¶
除非有例外,否则这些都是用于清晰度的 抽象类型 ,您不需要关心它们是如何实现的。
- class ServiceName¶
在 https://iap.odoo.com(生产环境)上标识您的服务的字符串,以及与客户数据库中您的服务相关联的帐户。
- class ServiceKey¶
为提供者服务生成的标识符。每个键(和服务)都与由服务提供者生成的固定值的令牌匹配。
多种类型的令牌对应多种服务。例如,短信和彩信可以是同一种服务(其中一个彩信相当于多个短信),也可以是不同价格点上的独立服务。
危险
您的服务密钥 是一个秘密,泄露您的服务密钥将允许其他应用程序开发人员使用您购买的服务的积分。
- class UserToken¶
用户账户的标识符。
- class TransactionToken¶
交易标识符,由授权过程返回并由捕获或取消交易消耗。
- exception odoo.addons.iap.tools.iap_tools.InsufficientCreditError¶
如果账户上当前没有足够的积分或者有太多未完成的交易/持有,交易授权期间会引发此异常。
- exception odoo.exceptions.AccessError
提出者:
如果服务令牌无效,则需要服务令牌的任何操作;或者
任何在服务器间调用中的失败。(通常在
iap_jsonrpc()
中)。
- exception odoo.exceptions.UserError
由应用程序开发者(你)自行决定,对于任何意外行为都会引发此异常。
测试 API¶
为了测试开发的应用程序,我们提供了一个沙盒平台,可以让您:
测试整个流程,从客户端的角度来看 - 可以咨询的实际服务和交易。(再次需要更改端点,请参阅 Service 中的危险提示)。
测试 API。
后者包括仅在 IAP-Sandbox 上有效的特定令牌。
Token
000000
:表示一个不存在的账户。在授权尝试时返回InsufficientCreditError
。Token
000111
:表示一个没有足够信用来执行任何服务的账户。在授权尝试时返回InsufficientCreditError
。Token
111111
:表示一个具有足够信用来执行任何服务的帐户。授权尝试将返回一个虚拟交易令牌,该令牌由捕获和取消路由处理。
注解
这些令牌仅在IAP-Sanbox服务器上有效。
在此流程中,服务密钥将被完全忽略。如果您想对您的服务进行全面测试,您应该忽略这些令牌。
Odoo 助手¶
如果您正在使用Odoo实现您的服务,为了方便起见, iap
模块提供了一些帮助程序,使IAP流程变得更加简单。
充电中¶
- class odoo.addons.iap.tools.iap_tools.iap_charge(env, key, account_token, credit[, dbuuid, description, credit_template, ttl])¶
一个 上下文管理器 ,用于授权并自动捕获或取消后端/代理中使用的交易。
工作方式类似于光标上下文管理器:
立即使用指定的参数授权交易;
执行
with
语句体;如果请求体完整执行且没有错误,则捕获交易;
否则取消它。
- 参数
env (odoo.api.Environment) – 用于检索
iap.endpoint
配置键key (ServiceKey) –
token (UserToken) –
credit (float) –
description (str) –
credit_template (Qweb template) –
ttl (int) –
@route('/deathstar/superlaser', type='json')
def superlaser(self, user_account,
coordinates, target,
factor=1.0):
"""
:param factor: superlaser power factor,
0.0 is none, 1.0 is full power
"""
credits = int(MAXIMUM_POWER * factor)
description = "We will demonstrate the power of this station on your home planet of Alderaan."
with iap_charge(request.env, SERVICE_KEY, user_account, credits, description) as transaction:
# TODO: allow other targets
transaction.credit = max(credits, 2)
# Sales ongoing one the energy price,
# a maximum of 2 credits will be charged/captured.
self.env['systems.planets'].search([
('grid', '=', 'M-10'),
('name', '=', 'Alderaan'),
]).unlink()
授权¶
- class odoo.addons.iap.tools.iap_tools.iap_authorize(env, key, account_token, credit[, dbuuid, description, credit_template, ttl])¶
将授权所有内容。
- 参数
env (odoo.api.Environment) – 用于检索
iap.endpoint
配置键key (ServiceKey) –
token (UserToken) –
credit (float) –
description (str) –
credit_template (Qweb template) –
ttl (int) –
@route('/deathstar/superlaser', type='json')
def superlaser(self, user_account,
coordinates, target,
factor=1.0):
"""
:param factor: superlaser power factor,
0.0 is none, 1.0 is full power
"""
credits = int(MAXIMUM_POWER * factor)
description = "We will demonstrate the power of this station on your home planet of Alderaan."
#actual IAP stuff
transaction_token = authorize(request.env, SERVICE_KEY, user_account, credits, description=description)
try:
# Beware the power of this laser
self.put_galactical_princess_in_sorrow()
except Exception as e:
# Nevermind ...
r = cancel(env,transaction_token, key)
raise e
else:
# We shall rule over the galaxy!
capture(env,transaction_token, key, min(credits, 2))
取消¶
- class odoo.addons.iap.tools.iap_tools.iap_cancel(env, transaction_token, key)¶
将取消已授权的交易。
- 参数
env (odoo.api.Environment) – 用于检索
iap.endpoint
配置键transaction_token (str) –
key (ServiceKey) –
@route('/deathstar/superlaser', type='json')
def superlaser(self, user_account,
coordinates, target,
factor=1.0):
"""
:param factor: superlaser power factor,
0.0 is none, 1.0 is full power
"""
credits = int(MAXIMUM_POWER * factor)
description = "We will demonstrate the power of this station on your home planet of Alderaan."
#actual IAP stuff
transaction_token = authorize(request.env, SERVICE_KEY, user_account, credits, description=description)
try:
# Beware the power of this laser
self.put_galactical_princess_in_sorrow()
except Exception as e:
# Nevermind ...
r = cancel(env,transaction_token, key)
raise e
else:
# We shall rule over the galaxy!
capture(env,transaction_token, key, min(credits, 2))
捕获¶
- class odoo.addons.iap.tools.iap_tools.iap_capture(env, transaction_token, key, credit)¶
将在给定交易中捕获金额为
credit
的款项。- 参数
env (odoo.api.Environment) – 用于检索
iap.endpoint
配置键transaction_token (str) –
key (ServiceKey) –
credit –
@route('/deathstar/superlaser', type='json')
def superlaser(self, user_account,
coordinates, target,
factor=1.0):
"""
:param factor: superlaser power factor,
0.0 is none, 1.0 is full power
"""
credits = int(MAXIMUM_POWER * factor)
description = "We will demonstrate the power of this station on your home planet of Alderaan."
#actual IAP stuff
transaction_token = authorize(request.env, SERVICE_KEY, user_account, credits, description=description)
try:
# Beware the power of this laser
self.put_galactical_princess_in_sorrow()
except Exception as e:
# Nevermind ...
r = cancel(env,transaction_token, key)
raise e
else:
# We shall rule over the galaxy!
capture(env,transaction_token, key, min(credits, 2))