[MERGE] Forward-port of branch 13.0 to 14.0

This commit is contained in:
Antoine Vandevenne (anv)
2021-07-07 15:39:35 +02:00
220 changed files with 1841 additions and 2574 deletions
+890
View File
@@ -0,0 +1,890 @@
.. _webservices/iap:
===============
In-App Purchase
===============
In-App Purchase (IAP) allows providers of ongoing services through Odoo apps to
be compensated for ongoing service use rather than — and possibly instead of
— a sole initial purchase.
In that context, Odoo acts mostly as a *broker* between a client and an Odoo
App Developer:
* Users purchase service tokens from Odoo.
* Service providers draw tokens from the user's Odoo account when service
is requested.
.. attention::
This document is intended for *service providers* and presents the latter,
which can be done either via direct JSON-RPC2_ or if you are using Odoo
using the convenience helpers it provides.
Overview
========
.. figure:: images/players.png
:align: center
The Players
* The Service Provider is (probably) you the reader, you will be providing
value to the client in the form of a service paid per-use.
* The Client installed your Odoo App, and from there will request services.
* Odoo brokers crediting, the Client adds credit to their account, and you
can draw credits from there to provide services.
* The External Service is an optional player: *you* can either provide a
service directly, or you can delegate the actual service acting as a
bridge/translator between an Odoo system and the actual service.
.. figure:: images/credits.jpg
:align: center
The Credits
.. note:: The credits went from integer to float value starting **October 2018**.
Integer values are still supported.
Every service provided through the IAP platform can be used by the
clients with tokens or *credits*. The credits are an float unit and
their monetary value depends on the service and is decided by the
provider. This could be:
* for an sms service: 1 credit = 1 sms;
* for an ad service: 1 credit = 1 ad; or
* for a postage service: 1 credit = 1 post stamp.
A credit can also simply be associated with a fixed amount of money
to palliate the variations of price (e.g. the prices of sms and stamps
may vary following the countries).
The value of the credits is fixed with the help of prepaid credit packs
that the clients can buy on https://iap.odoo.com (see :ref:`Packs <iap-packages>`).
.. note:: In the following explanations we will ignore the External Service,
they are just a detail of the service you provide.
.. figure:: images/normal.png
:align: center
'Normal' service flow
If everything goes well, the normal flow is the following:
1. The Client requests a service of some sort.
2. The Service Provider asks Odoo if there are enough credits for the
service in the Client's account, and creates a transaction over that
amount.
3. The Service Provider provides the service (either on their own or
calling to External Services).
4. The Service Provider goes back to Odoo to capture (if the service could
be provided) or cancel (if the service could not be provided) the
transaction created at step 2.
5. Finally, the Service Provider notifies the Client that the service has
been rendered, possibly (depending on the service) displaying or
storing its results in the client's system.
.. figure:: images/no-credit.png
:align: center
Insufficient credits
However, if the Client's account lacks credits for the service, the flow will be as follows:
1. The Client requests a service as previously.
2. The Service Provider asks Odoo if there are enough credits on the
Client's account and gets a negative reply.
3. This is signaled back to the Client.
4. Who is redirected to their Odoo account to credit it and re-try.
Building your service
=====================
For this example, the service we will provide is ~~mining dogecoins~~ burning
10 seconds of CPU for a credit. For your own services, you could, for example:
* provide an online service yourself (e.g. convert quotations to faxes for
business in Japan);
* provide an *offline* service yourself (e.g. provide accountancy service); or
* act as intermediary to an other service provider (e.g. bridge to an MMS
gateway).
.. _register-service:
Register the service on Odoo
----------------------------
.. todo:: complete this part with screenshots
The first step is to register your service on the IAP endpoint (production
and/or test) before you can actually query user accounts. To create a service,
go to your *Portal Account* on the IAP endpoint (https://iap.odoo.com for
production, https://iap-sandbox.odoo.com for testing, the endpoints are
*independent* and *not synchronized*). Alternatively, you can go to your portal
on Odoo (https://iap.odoo.com/my/home) and select *In-App Services*.
.. note::
On production, there is a manual validation step before the service
can be used to manage real transactions. This step is automatically passed when
on sandbox to ease the tests.
Log in then go to :menuselection:`My Account --> Your In-App Services`, click
Create and provide the information of your service.
The service has *seven* important fields:
* :samp:`name` - :class:`ServiceName`: This is the string you will need to provide inside
the client's :ref:`app <iap-odoo-app>` when requesting a transaction from Odoo. (e.g.
:class:`self.env['iap.account].get(name)`). As good practice, this should match the
technical name of your app.
* :samp:`label` - :class:`Label`: The name displayed on the shopping portal for the
client.
.. warning::
Both the :class:`ServiceName` and :class:`Label` are unique. As good practice, the
:class:`ServiceName` should usually match the name of your Odoo Client App.
* :samp:`icon` - :class:`Icon`: A generic icon that will serve as default for your
:ref:`packs <iap-packages>`.
* :samp:`key` - :class:`ServiceKey`: The developer key that identifies you in
IAP (see :ref:`your service <iap-service>`) and allows to draw credits from
the client's account. It will be shown only once upon creation of the service
and can be regenerated at will.
.. danger::
Your :class:`ServiceKey` *is a secret*, leaking your service key
allows other application developers to draw credits bought for
your service(s).
* :samp:`trial credits` - :class:`Float`: This corresponds to the credits you are ready to offer
upon first use to your app users. Note that such service will only be available to clients that
have an active enterprise contract.
* :samp:`privacy policy` - :class:`PrivacyPolicy`: This is an url to the privacy
policy of your service. This should explicitly mention the **information you collect**,
how you **use it, its relevance** to make your service work and inform the
client on how they can **access, update or delete their personal information**.
.. image:: images/menu.png
:align: center
.. image:: images/service_list.png
:align: center
.. image:: images/creating_service.png
:align: center
.. image:: images/service_created.png
:align: center
You can then create *credit packs* which clients can purchase in order to
use your service.
.. _iap-packages:
Packs
-----
A credit pack is essentially a product with five characteristics:
* Name: name of the pack,
* Icon: specific icon for the pack (if not provided, it will fallback on the service icon),
* Description: details on the pack that will appear on the shop page as
well as the invoice,
* Amount: amount of credits the client is entitled to when buying the pack,
* Price: price in EUR (for the time being, USD support is planned).
.. note::
Odoo takes a 25% commission on all pack sales. Adjust your selling price accordingly.
.. note::
Depending on the strategy, the price per credit may vary from one
pack to another.
.. image:: images/package.png
:align: center
.. _iap-odoo-app:
Odoo App
--------
.. todo:: does this actually require apps?
The second step is to develop an `Odoo App`_ which clients can install in their
Odoo instance and through which they can *request* the services you provide.
Our app will just add a button to the Partners form which lets a user request
burning some CPU time on the server.
First, we will create an *odoo module* depending on ``iap``. IAP is a standard
V11 module and the dependency ensures a local account is properly set up and
we will have access to some necessary views and useful helpers.
.. code-block:: python
:emphasize-lines: 1-5
:caption: `coalroller/__manifest__.py`
{
'name': "Coal Roller",
'category': 'Tools',
'depends': ['iap'],
}
Second, the "local" side of the integration. Here we will only be adding an
action button to the partners view, but you can of course provide significant
local value via your application and additional parts via a remote service.
.. code-block:: python
:emphasize-lines: 5-7
:caption: `coalroller/__manifest__.py`
{
'name': "Coal Roller",
'category': 'Tools',
'depends': ['iap'],
'data': [
'views/res_partner_views.xml',
],
}
.. code-block:: xml
:emphasize-lines: 1-17
:caption: `coalroller/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>
.. image:: images/button.png
:align: center
We can now implement the action method/callback. This will *call our own
server*.
There are no requirements when it comes to the server or the communication
protocol between the app and our server, but ``iap`` provides a
:func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc` helper to call a JSON-RPC2_ endpoint on an
other Odoo instance and transparently re-raise relevant Odoo exceptions
(:class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError`,
:class:`odoo.exceptions.AccessError` and :class:`odoo.exceptions.UserError`).
In that call, we will need to provide:
* any relevant client parameter (none here),
* the :class:`token <UserToken>` of the current client that is provided by
the ``iap.account`` model's ``account_token`` field. You can retrieve the
account for your service by calling :samp:`env['iap.account'].get({service_name})`
where :class:`service_name <ServiceName>` is the name of the service registered
on IAP endpoint.
.. code-block:: python
:emphasize-lines: 1-21
:caption: `coalroller/models/res_partner.py`
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
.. note::
``iap`` automatically handles
:class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` coming from the action
and prompts the user to add credits to their account.
:func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc` takes care of re-raising
:class:`~odoo.addons.iap.models.iap.InsufficientCreditError` for you.
.. danger::
If you are not using :func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc` you *must* be
careful to re-raise
:class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` in your handler
otherwise the user will not be prompted to credit their account, and the
next call will fail the same way.
.. _iap-service:
Service
-------
Though that is not *required*, since ``iap`` provides both a client helper
for JSON-RPC2_ calls (:func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc`) and a service helper
for transactions (:class:`~odoo.addons.iap.tools.iap_tools.iap_charge`) we will also be
implementing the service side as an Odoo module:
.. code-block:: python
:emphasize-lines: 1-5
:caption: `coalroller_service/__manifest__.py`
{
'name': "Coal Roller Service",
'category': 'Tools',
'depends': ['iap'],
}
Since the query from the client comes as JSON-RPC2_ we will need the
corresponding controller which can call :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` and
perform the service within:
.. code-block:: python
:emphasize-lines: 1-27
:caption: `coalroller_service/controllers/main.py`
from passlib import pwd, hash
from odoo import http
from odoo.addons.iap import 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
# 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):
# 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
.. todo:: for the actual IAP will the "portal" page be on odoo.com or iap.odoo.com?
.. todo:: "My Account" > "Your InApp Services"?
The :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` helper will:
1. authorize (create) a transaction with the specified number of credits,
if the account does not have enough credits it will raise the relevant
error
2. execute the body of the ``with`` statement
3. if the body of the ``with`` executes successfully, update the price
of the transaction if needed
4. capture (confirm) the transaction
5. otherwise, if an error is raised from the body of the ``with``, cancel the
transaction (and release the hold on the credits)
.. danger::
By default, :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` contacts the *production*
IAP endpoint, https://iap.odoo.com. While developing and testing your
service you may want to point it towards the *development* IAP endpoint
https://iap-sandbox.odoo.com.
To do so, set the ``iap.endpoint`` config parameter in your service
Odoo: in debug/developer mode, :menuselection:`Setting --> Technical -->
Parameters --> System Parameters`, just define an entry for the key
``iap.endpoint`` if none already exists).
The :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` helper has two additional optional
parameters we can use to make things clearer to the end-user.
``description``
is a message which will be associated with the transaction and will be
displayed in the user's dashboard, it is useful to remind the user why
the charge exists.
``credit_template``
is the name of a :ref:`reference/qweb` template which will be rendered
and shown to the user if their account has less credit available than the
service provider is requesting, its purpose is to tell your users why
they should be interested in your IAP offers.
.. code-block:: python
:emphasize-lines: 5-7
:caption: `coalroller_service/__manifest__.py`
{
'name': "Coal Roller Service",
'category': 'Tools',
'depends': ['iap'],
'data': [
'views/no-credit.xml',
],
}
.. code-block:: python
:emphasize-lines: 10-12
:caption: `coalroller_service/controllers/main.py`
@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
# 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,
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)
.. code-block:: xml
:emphasize-lines: 1-18
:caption: `coalroller_service/views/no-credit.xml`
<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>
.. TODO:: how do you test your service?
JSON-RPC2_ Transaction API
==========================
.. image:: images/flow.png
:align: center
* The IAP transaction API does not require using Odoo when implementing your
server gateway, calls are standard JSON-RPC2_.
* Calls use different *endpoints* but the same *method* on all endpoints
(``call``).
* Exceptions are returned as JSON-RPC2_ errors, the formal exception name is
available on ``data.name`` for programmatic manipulation.
.. seealso:: `iap.odoo.com documentation`_ for additional information.
Authorize
---------
.. function:: /iap/1/authorize
Verifies that the user's account has at least as ``credit`` available
*and creates a hold (pending transaction) on that amount*.
Any amount currently on hold by a pending transaction is considered
unavailable to further authorize calls.
Returns a :class:`TransactionToken` identifying the pending transaction
which can be used to capture (confirm) or cancel said transaction (`iap.odoo.com documentation`_).
:param ServiceKey key:
:param UserToken account_token:
:param float credit:
:param str description: optional, helps users identify the reason for
charges on their account
:param str dbuuid: optional, allows the user to benefit from trial
credits if his database is eligible (see :ref:`Service registration <register-service>`)
:returns: :class:`TransactionToken` if the authorization succeeded
:raises: :class:`~odoo.exceptions.AccessError` if the service token is invalid
:raises: :class:`~odoo.addons.iap.models.iap.InsufficientCreditError` if the account does not have enough credits
:raises: ``TypeError`` if the ``credit`` value is not an integer or a float
.. code-block:: python
r = requests.post(ODOO + '/iap/1/authorize', json={
'jsonrpc': '2.0',
'id': None,
'method': 'call',
'params': {
'account_token': user_account,
'key': SERVICE_KEY,
'credit': 25,
'description': "Why this is being charged",
}
}).json()
if 'error' in r:
# handle authorize error
tx = r['result']
# provide your service here
Capture
-------
.. function:: /iap/1/capture
Confirms the specified transaction, transferring the reserved credits from
the user's account to the service provider's.
Capture calls are idempotent: performing capture calls on an already
captured transaction has no further effect.
:param TransactionToken token:
:param ServiceKey key:
:param float credit_to_capture: optional parameter to capture a smaller amount of credits than authorized
:raises: :class:`~odoo.exceptions.AccessError`
.. code-block:: python
:emphasize-lines: 8
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
Cancel
------
.. function:: /iap/1/cancel
Cancels the specified transaction, releasing the hold on the user's
credits.
Cancel calls are idempotent: performing capture calls on an already
cancelled transaction has no further effect.
:param TransactionToken token:
:param ServiceKey key:
:raises: :class:`~odoo.exceptions.AccessError`
.. code-block:: python
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
Types
-----
Exceptions aside, these are *abstract types* used for clarity, you should not
care how they are implemented.
.. class:: ServiceName
String identifying your service on https://iap.odoo.com (production) as well
as the account related to your service in the client's database.
.. class:: ServiceKey
Identifier generated for the provider's service. Each key (and service)
matches a token of a fixed value, as generated by the service provide.
Multiple types of tokens correspond to multiple services. As an exampe, SMS and MMS
could either be the same service (with an MMS being 'worth' multiple SMS)
or could be separate services at separate price points.
.. danger:: Your service key *is a secret*, leaking your service key
allows other application developers to draw credits bought for
your service(s).
.. class:: UserToken
Identifier for a user account.
.. class:: TransactionToken
Transaction identifier, returned by the authorization process and consumed
by either capturing or cancelling the transaction.
.. exception:: odoo.addons.iap.tools.iap_tools.InsufficientCreditError
Raised during transaction authorization if the credits requested are not
currently available on the account (either not enough credits or too many
pending transactions/existing holds).
.. exception:: odoo.exceptions.AccessError
:noindex:
Raised by:
* any operation to which a service token is required, if the service token is invalid; or
* any failure in an inter-server call. (typically, in :func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc`).
.. exception:: odoo.exceptions.UserError
:noindex:
Raised by any unexpected behaviour at the discretion of the App developer (*you*).
Test the API
------------
In order to test the developed app, we propose a sandbox platform that allows you to:
1. Test the whole flow from the client's point of view - Actual services and transactions
that can be consulted. (again this requires to change the endpoint, see the danger note
in :ref:`Service <iap-service>`).
2. Test the API.
The latter consists in specific tokens that will work on **IAP-Sandbox only**.
* Token ``000000``: Represents a non-existing account. Returns
an :class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` on authorize attempt.
* Token ``000111``: Represents an account without sufficient credits to perform any service.
Returns an :class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` on authorize attempt.
* Token ``111111``: Represents an account with enough credits to perform any service.
An authorize attempt will return a dummy transaction token that is processed by the capture
and cancel routes.
.. note::
* Those tokens are only active on the IAP-Sanbox server.
* The service key is completely ignored with this flow, If you want to run a robust test
of your service, you should ignore these tokens.
Odoo Helpers
============
For convenience, if you are implementing your service using Odoo the ``iap``
module provides a few helpers to make IAP flow even simpler.
.. _iap-charging:
Charging
--------
.. class:: odoo.addons.iap.tools.iap_tools.iap_charge(env, key, account_token, credit[, dbuuid, description, credit_template])
A *context manager* for authorizing and automatically capturing or
cancelling transactions for use in the backend/proxy.
Works much like e.g. a cursor context manager:
* immediately authorizes a transaction with the specified parameters;
* executes the ``with`` body;
* if the body executes in full without error, captures the transaction;
* otherwise cancels it.
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
configuration key
:param ServiceKey key:
:param UserToken token:
:param float credit:
:param str description:
:param Qweb template credit_template:
.. code-block:: python
:emphasize-lines: 11,13,14,15
@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()
Authorize
---------
.. class:: odoo.addons.iap.tools.iap_tools.iap_authorize(env, key, account_token, credit[, dbuuid, description, credit_template])
Will authorize everything.
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
configuration key
:param ServiceKey key:
:param UserToken token:
:param float credit:
:param str description:
:param Qweb template credit_template:
.. code-block:: python
:emphasize-lines: 12
@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))
Cancel
------
.. class:: odoo.addons.iap.tools.iap_tools.iap_cancel(env, transaction_token, key)
Will cancel an authorized transaction.
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
configuration key
:param str transaction_token:
:param ServiceKey key:
.. code-block:: python
:emphasize-lines: 16,17,18,19
@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))
Capture
-------
.. class:: odoo.addons.iap.tools.iap_tools.iap_capture(env, transaction_token, key, credit)
Will capture the amount ``credit`` on the given transaction.
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
configuration key
:param str transaction_token:
:param ServiceKey key:
:param credit:
.. code-block:: python
:emphasize-lines: 20,21,22
@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))
.. _JSON-RPC2: https://www.jsonrpc.org/specification
.. _Odoo App: https://www.odoo.com/apps
.. _iap.odoo.com documentation: https://iap.odoo.com/iap/1/documentation
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because it is too large Load Diff
+655
View File
@@ -0,0 +1,655 @@
:code-column:
.. _reference/upgrade-api:
================
Database Upgrade
================
Introduction
~~~~~~~~~~~~
This document describes the API used to upgrade an Odoo database to a
higher version.
It allows a database to be upgraded without ressorting to the html form at
https://upgrade.odoo.com
Although the database will follow the same process described on that form.
The required steps are:
* :ref:`creating a request <upgrade-api-create-method>`
* :ref:`uploading a database dump <upgrade-api-upload-method>`
* :ref:`running the upgrade process <upgrade-api-process-method>`
* :ref:`obtaining the status of the database request <upgrade-api-status-method>`
* :ref:`downloading the upgraded database dump <upgrade-api-download-method>`
The methods
~~~~~~~~~~~
.. _upgrade-api-create-method:
Creating a database upgrade request
===================================
This action creates a database request with the following information:
* your contract reference
* your email address
* the target version (the Odoo version you want to upgrade to)
* the purpose of your request (test or production)
* the database dump name (required but purely informative)
* optionally the server timezone (for Odoo source version < 6.1)
The ``create`` method
---------------------
.. py:function:: https://upgrade.odoo.com/database/v1/create
Creates a database upgrade request
:param str contract: (required) your enterprise contract reference
:param str email: (required) your email address
:param str target: (required) the Odoo version you want to upgrade to. Valid choices: 11.0, 12.0, 13.0
:param str aim: (required) the purpose of your upgrade database request. Valid choices: test, production.
:param str filename: (required) a purely informative name for you database dump file
:param str timezone: (optional) the timezone used by your server. Only for Odoo source version < 6.1
:return: request result
:rtype: JSON dictionary
The *create* method returns a JSON dictionary containing the following keys:
.. _upgrade-api-json-failure:
``failures``
''''''''''''
The list of errors.
A list of dictionaries, each dictionary giving information about one particular
error. Each dictionary can contain various keys depending of the type of error
but you will always get the ``reason`` and the ``message`` keys:
* ``reason``: the error type
* ``message``: a human friendly message
Some possible keys:
* ``code``: a faulty value
* ``value``: a faulty value
* ``expected``: a list of valid values
See a sample output aside.
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: json
{
"failures": [
{
"expected": [
"11.0",
"12.0",
"13.0",
],
"message": "Invalid value \"5.0\"",
"reason": "TARGET:INVALID",
"value": "5.0"
},
{
"code": "M123456-abcxyz",
"message": "Can not find contract M123456-abcxyz",
"reason": "CONTRACT:NOT_FOUND"
}
]
}
``request``
'''''''''''
If the *create* method is successful, the value associated to the *request* key
will be a dictionary containing various information about the created request:
The most important keys are:
* ``id``: the request id
* ``key``: your private key for this request
These 2 values will be requested by the other methods (upload, process and status)
The other keys will be explained in the section describing the :ref:`status method <upgrade-api-status-method>`.
Sample script
'''''''''''''
Here are 2 examples of database upgrade request creation using:
* one in the python programming language using the requests library
* one in the bash programming language using `curl <https://curl.haxx.se>`_ (tool
for transferring data using http) and `jq <https://stedolan.github.io/jq>`_ (JSON processor):
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: python
import requests
CREATE_URL = "https://upgrade.odoo.com/database/v1/create"
CONTRACT = "M123456-abcdef"
AIM = "test"
TARGET = "12.0"
EMAIL = "john.doe@example.com"
FILENAME = "db_name.dump"
fields = dict([
('aim', AIM),
('email', EMAIL),
('filename', DB_SOURCE),
('contract', CONTRACT),
('target', TARGET),
])
r = requests.get(CREATE_URL, data=fields)
print(r.text)
.. code-block:: bash
CONTRACT=M123456-abcdef
AIM=test
TARGET=12.0
EMAIL=john.doe@example.com
FILENAME=db_name.dump
CREATE_URL="https://upgrade.odoo.com/database/v1/create"
URL_PARAMS="contract=${CONTRACT}&aim=${AIM}&target=${TARGET}&email=${EMAIL}&filename=${FILENAME}"
curl -sS "${CREATE_URL}?${URL_PARAMS}" > create_result.json
# check for failures
failures=$(cat create_result.json | jq -r '.failures[]')
if [ "$failures" != "" ]; then
echo $failures | jq -r '.'
exit 1
fi
.. _upgrade-api-upload-method:
Uploading your database dump
============================
There are 2 methods to upload your database dump:
* the ``upload`` method using the HTTPS protocol
* the ``request_sftp_access`` method using the SFTP protocol
The ``upload`` method
---------------------
It's the most simple and most straightforward way of uploading your database dump.
It uses the HTTPS protocol.
.. py:function:: https://upgrade.odoo.com/database/v1/upload
Uploads a database dump
:param str key: (required) your private key
:param str request: (required) your request id
:return: request result
:rtype: JSON dictionary
The request id and the private key are obtained using the :ref:`create method
<upgrade-api-create-method>`
The result is a JSON dictionary containing the list of ``failures``, which
should be empty if everything went fine.
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: python
import requests
UPLOAD_URL = "https://upgrade.odoo.com/database/v1/upload"
DUMPFILE = "/tmp/dump.sql"
fields = dict([
('request', '10534'),
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
])
headers = {"Content-Type": "application/octet-stream"}
with open(DUMPFILE, 'rb') as f:
requests.post(UPLOAD_URL, data=f, params=fields, headers=headers)
.. code-block:: bash
UPLOAD_URL="https://upgrade.odoo.com/database/v1/upload"
DUMPFILE="openchs.70.cdump"
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
REQUEST_ID="10534"
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
HEADER="Content-Type: application/octet-stream"
curl -H $HEADER --data-binary "@${DUMPFILE}" "${UPLOAD_URL}?${URL_PARAMS}"
.. _upgrade-api-request-sftp-access-method:
The ``request_sftp_access`` method
----------------------------------
This method is recommended for big database dumps.
It uses the SFTP protocol and supports resuming.
It will create a temporary SFTP server where you can connect to and allow you
to upload your database dump using an SFTP client.
.. py:function:: https://upgrade.odoo.com/database/v1/request_sftp_access
Creates an SFTP server
:param str key: (required) your private key
:param str request: (required) your request id
:param str ssh_keys: (required) the path to a file listing the ssh public keys you'd like to use
:return: request result
:rtype: JSON dictionary
The request id and the private key are obtained using the :ref:`create method
<upgrade-api-create-method>`
The file listing your ssh public keys should be roughly similar to a standard ``authorized_keys`` file.
This file should only contains public keys, blank lines or comments (lines starting with the ``#`` character)
Your database upgrade request should be in the ``draft`` state.
The ``request_sftp_access`` method returns a JSON dictionary containing the following keys:
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: python
import requests
UPLOAD_URL = "https://upgrade.odoo.com/database/v1/request_sftp_access"
SSH_KEY = "$HOME/.ssh/id_rsa.pub"
SSH_KEY_CONTENT = open(SSH_KEY,'r').read()
fields = dict([
('request', '10534'),
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
('ssh_keys', SSH_KEY_CONTENT)
])
r = requests.post(UPLOAD_URL, params=fields)
print(r.text)
.. code-block:: bash
REQUEST_SFTP_ACCESS_URL="https://upgrade.odoo.com/database/v1/request_sftp_access"
SSH_KEYS=/path/to/your/authorized_keys
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
REQUEST_ID="10534"
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
curl -sS "${REQUEST_SFTP_ACCESS_URL}?${URL_PARAMS}" -F ssh_keys=@${SSH_KEYS} > request_sftp_result.json
# check for failures
failures=$(cat request_sftp_result.json | jq -r '.failures[]')
if [ "$failures" != "" ]; then
echo $failures | jq -r '.'
exit 1
fi
``failures``
''''''''''''
The list of errors. See :ref:`failures <upgrade-api-json-failure>` for an
explanation about the JSON dictionary returned in case of failure.
``request``
'''''''''''
If the call is successful, the value associated to the *request* key
will be a dictionary containing your SFTP connection parameters:
* ``hostname``: the host address to connect to
* ``sftp_port``: the port to connect to
* ``sftp_user``: the SFTP user to use for connecting
* ``shared_file``: the filename you need to use (identical to the ``filename`` value you have used when creating the request in the :ref:`create method <upgrade-api-create-method>`.)
* ``request_id``: the related upgrade request id (only informative, ,not required for the connection)
* ``sample_command``: a sample command using the 'sftp' client
You should normally be able to connect using the sample command as is.
You will only have access to the ``shared_file``. No other files will be
accessible and you will not be able to create new files in your shared
environment on the SFTP server.
Using the 'sftp' client
+++++++++++++++++++++++
Once you have successfully connected using your SFTP client, you can upload
your database dump. Here is a sample session using the 'sftp' client:
::
$ sftp -P 2200 user_10534@upgrade.odoo.com
Connected to upgrade.odoo.com.
sftp> put /path/to/openchs.70.cdump openchs.70.cdump
Uploading /path/to/openchs.70.cdump to /openchs.70.cdump
sftp> ls -l openchs.70.cdump
-rw-rw-rw- 0 0 0 849920 Aug 30 15:58 openchs.70.cdump
If your connection is interrupted, you can continue your file transfer using
the ``-a`` command line switch:
.. code-block:: text
sftp> put -a /path/to/openchs.70.cdump openchs.70.cdump
Resuming upload of /path/to/openchs.70.cdump to /openchs.70.cdump
If you don't want to manually type the command and need to automate your
database upgrade using a script, you can use a batch file or pipe your commands to 'sftp':
::
echo "put /path/to/openchs.70.cdump openchs.70.cdump" | sftp -b - -P 2200 user_10534@upgrade.odoo.com
The ``-b`` parameter takes a filename. If the filename is ``-``, it reads the commands from standard input.
.. _upgrade-api-process-method:
Asking to process your request
==============================
This action ask the Upgrade Platform to process your database dump.
The ``process`` method
----------------------
.. py:function:: https://upgrade.odoo.com/database/v1/process
Process a database dump
:param str key: (required) your private key
:param str request: (required) your request id
:return: request result
:rtype: JSON dictionary
The request id and the private key are obtained using the :ref:`create method
<upgrade-api-create-method>`
The result is a JSON dictionary containing the list of ``failures``, which
should be empty if everything went fine.
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: python
import requests
PROCESS_URL = "https://upgrade.odoo.com/database/v1/process"
fields = dict([
('request', '10534'),
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
])
r = requests.get(PROCESS_URL, data=fields)
print(r.text)
.. code-block:: bash
PROCESS_URL="https://upgrade.odoo.com/database/v1/process"
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
REQUEST_ID="10534"
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
curl -sS "${PROCESS_URL}?${URL_PARAMS}"
.. _upgrade-api-skip-tests:
Asking to skip the tests
=========================
This action asks the Upgrade Platform to skip the tests for your request.
If you don't want Odoo to test and validate the migration, you can bypass the testing stage and directly get the migrated dump.
The ``skip_test`` method
------------------------
.. py:function:: https://upgrade.odoo.com/database/v1/skip_test
Skip the tests, deliver the upgraded dump, and set the state to 'delivered'
:param str key: (required) your private key
:param str request: (required) your request id
:return: request result
:rtype: JSON dictionary
The request id and the private key are obtained using the :ref:`create method
<upgrade-api-create-method>`
The result is a JSON dictionary containing the list of ``failures``, which
should be empty if everything went fine.
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: python
import requests
PROCESS_URL = "https://upgrade.odoo.com/database/v1/skip_test"
fields = dict([
('request', '10534'),
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
])
r = requests.get(PROCESS_URL, data=fields)
print(r.text)
.. code-block:: bash
PROCESS_URL="https://upgrade.odoo.com/database/v1/skip_test"
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
REQUEST_ID="10534"
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
curl -sS "${PROCESS_URL}?${URL_PARAMS}"
.. _upgrade-api-status-method:
Obtaining your request status
=============================
This action ask the status of your database upgrade request.
The ``status`` method
---------------------
.. py:function:: https://upgrade.odoo.com/database/v1/status
Ask the status of a database upgrade request
:param str key: (required) your private key
:param str request: (required) your request id
:return: request result
:rtype: JSON dictionary
The request id and the private key are obtained using the :ref:`create method
<upgrade-api-create-method>`
The result is a JSON dictionary containing various information about your
database upgrade request.
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: python
import requests
PROCESS_URL = "https://upgrade.odoo.com/database/v1/status"
fields = dict([
('request', '10534'),
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
])
r = requests.get(PROCESS_URL, data=fields)
print(r.text)
.. code-block:: bash
STATUS_URL="https://upgrade.odoo.com/database/v1/status"
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
REQUEST_ID="10534"
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
curl -sS "${STATUS_URL}?${URL_PARAMS}"
Sample output
-------------
The ``request`` key contains various useful information about your request:
``id``
the request id
``key``
your private key
``email``
the email address you supplied when creating the request
``target``
the target Odoo version you supplied when creating the request
``aim``
the purpose (test, production) of your database upgrade request you supplied when creating the request
``filename``
the filename you supplied when creating the request
``timezone``
the timezone you supplied when creating the request
``state``
the state of your request
``issue_stage``
the stage of the issue we have create on Odoo main server
``issue``
the id of the issue we have create on Odoo main server
``status_url``
the URL to access your database upgrade request html page
``notes_url``
the URL to get the notes about your database upgrade
``original_sql_url``
the URL used to get your uploaded (not upgraded) database as an SQL stream
``original_dump_url``
the URL used to get your uploaded (not upgraded) database as an archive file
``upgraded_sql_url``
the URL used to get your upgraded database as an SQL stream
``upgraded_dump_url``
the URL used to get your upgraded database as an archive file
``modules_url``
the URL used to get your custom modules
``filesize``
the size of your uploaded database file
``database_uuid``
the Unique ID of your database
``created_at``
the date when you created the request
``estimated_time``
an estimation of the time it takes to upgrade your database
``processed_at``
time when your database upgrade was started
``elapsed``
the time it takes to upgrade your database
``filestore``
your attachments were converted to the filestore
``customer_message``
an important message related to your request
``database_version``
the guessed Odoo version of your uploaded (not upgraded) database
``postgresql``
the guessed Postgresql version of your uploaded (not upgraded) database
``compressions``
the compression methods used by your uploaded database
.. rst-class:: setup doc-aside
.. switcher::
.. code-block:: json
{
"failures": [],
"request": {
"id": 10534,
"key": "Aw7pItGVKFuZ_FOR3U8VFQ==",
"email": "john.doe@example.com",
"target": "12.0",
"aim": "test",
"filename": "db_name.dump",
"timezone": null,
"state": "draft",
"issue_stage": "new",
"issue": 648398,
"status_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/status",
"notes_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/upgraded/notes",
"original_sql_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/original/sql",
"original_dump_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/original/archive",
"upgraded_sql_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/upgraded/sql",
"upgraded_dump_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/upgraded/archive",
"modules_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/modules/archive",
"filesize": "912.99 Kb",
"database_uuid": null,
"created_at": "2018-09-09 07:13:49",
"estimated_time": null,
"processed_at": null,
"elapsed": "00:00",
"filestore": false,
"customer_message": null,
"database_version": null,
"postgresql": "9.4",
"compressions": [
"pgdmp_custom",
"sql"
]
}
}
.. _upgrade-api-download-method:
Downloading your database dump
==============================
Beside downloading your migrated database using the URL provided by the
:ref:`status method <upgrade-api-status-method>`, you can also use the SFTP
protocol as described in the :ref:`request_sftp_access method
<upgrade-api-request-sftp-access-method>`
The difference is that you'll only be able to download the migrated database. No
uploading will be possible.
Your database upgrade request should be in the ``done`` state.
Once you have successfully connected using your SFTP client, you can download
your database dump. Here is a sample session using the 'sftp' client:
::
$ sftp -P 2200 user_10534@upgrade.odoo.com
Connected to upgrade.odoo.com.
sftp> get upgraded_openchs.70.cdump /path/to/upgraded_openchs.70.cdump
Downloading /upgraded_openchs.70.cdump to /path/to/upgraded_openchs.70.cdump