summaryrefslogtreecommitdiff
path: root/addons/iap/tools
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/iap/tools
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/iap/tools')
-rw-r--r--addons/iap/tools/__init__.py4
-rw-r--r--addons/iap/tools/iap_tools.py170
2 files changed, 174 insertions, 0 deletions
diff --git a/addons/iap/tools/__init__.py b/addons/iap/tools/__init__.py
new file mode 100644
index 00000000..d2ad0468
--- /dev/null
+++ b/addons/iap/tools/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import iap_tools
diff --git a/addons/iap/tools/iap_tools.py b/addons/iap/tools/iap_tools.py
new file mode 100644
index 00000000..86c42d6d
--- /dev/null
+++ b/addons/iap/tools/iap_tools.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import contextlib
+import logging
+import json
+import requests
+import uuid
+from unittest.mock import patch
+
+from odoo import exceptions, _
+from odoo.tests.common import BaseCase
+from odoo.tools import pycompat
+
+_logger = logging.getLogger(__name__)
+
+DEFAULT_ENDPOINT = 'https://iap.odoo.com'
+
+
+# We need to mock iap_jsonrpc during tests as we don't want to perform real calls to RPC endpoints
+def iap_jsonrpc_mocked(*args, **kwargs):
+ raise exceptions.AccessError("Unavailable during tests.")
+
+
+iap_patch = patch('odoo.addons.iap.tools.iap_tools.iap_jsonrpc', iap_jsonrpc_mocked)
+
+
+def setUp(self):
+ old_setup_func(self)
+ iap_patch.start()
+ self.addCleanup(iap_patch.stop)
+
+
+old_setup_func = BaseCase.setUp
+BaseCase.setUp = setUp
+
+#----------------------------------------------------------
+# Helpers for both clients and proxy
+#----------------------------------------------------------
+
+def iap_get_endpoint(env):
+ url = env['ir.config_parameter'].sudo().get_param('iap.endpoint', DEFAULT_ENDPOINT)
+ return url
+
+#----------------------------------------------------------
+# Helpers for clients
+#----------------------------------------------------------
+
+class InsufficientCreditError(Exception):
+ pass
+
+
+def iap_jsonrpc(url, method='call', params=None, timeout=15):
+ """
+ Calls the provided JSON-RPC endpoint, unwraps the result and
+ returns JSON-RPC errors as exceptions.
+ """
+ payload = {
+ 'jsonrpc': '2.0',
+ 'method': method,
+ 'params': params,
+ 'id': uuid.uuid4().hex,
+ }
+
+ _logger.info('iap jsonrpc %s', url)
+ try:
+ req = requests.post(url, json=payload, timeout=timeout)
+ req.raise_for_status()
+ response = req.json()
+ if 'error' in response:
+ name = response['error']['data'].get('name').rpartition('.')[-1]
+ message = response['error']['data'].get('message')
+ if name == 'InsufficientCreditError':
+ e_class = InsufficientCreditError
+ elif name == 'AccessError':
+ e_class = exceptions.AccessError
+ elif name == 'UserError':
+ e_class = exceptions.UserError
+ else:
+ raise requests.exceptions.ConnectionError()
+ e = e_class(message)
+ e.data = response['error']['data']
+ raise e
+ return response.get('result')
+ except (ValueError, requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, requests.exceptions.Timeout, requests.exceptions.HTTPError) as e:
+ raise exceptions.AccessError(
+ _('The url that this service requested returned an error. Please contact the author of the app. The url it tried to contact was %s', url)
+ )
+
+#----------------------------------------------------------
+# Helpers for proxy
+#----------------------------------------------------------
+
+class IapTransaction(object):
+
+ def __init__(self):
+ self.credit = None
+
+
+def iap_authorize(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None):
+ endpoint = iap_get_endpoint(env)
+ params = {
+ 'account_token': account_token,
+ 'credit': credit,
+ 'key': key,
+ 'description': description,
+ }
+ if dbuuid:
+ params.update({'dbuuid': dbuuid})
+ try:
+ transaction_token = iap_jsonrpc(endpoint + '/iap/1/authorize', params=params)
+ except InsufficientCreditError as e:
+ if credit_template:
+ arguments = json.loads(e.args[0])
+ arguments['body'] = pycompat.to_text(env['ir.qweb']._render(credit_template))
+ e.args = (json.dumps(arguments),)
+ raise e
+ return transaction_token
+
+
+def iap_cancel(env, transaction_token, key):
+ endpoint = iap_get_endpoint(env)
+ params = {
+ 'token': transaction_token,
+ 'key': key,
+ }
+ r = iap_jsonrpc(endpoint + '/iap/1/cancel', params=params)
+ return r
+
+
+def iap_capture(env, transaction_token, key, credit):
+ endpoint = iap_get_endpoint(env)
+ params = {
+ 'token': transaction_token,
+ 'key': key,
+ 'credit_to_capture': credit,
+ }
+ r = iap_jsonrpc(endpoint + '/iap/1/capture', params=params)
+ return r
+
+
+@contextlib.contextmanager
+def iap_charge(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None):
+ """
+ Account charge context manager: takes a hold for ``credit``
+ amount before executing the body, then captures it if there
+ is no error, or cancels it if the body generates an exception.
+
+ :param str key: service identifier
+ :param str account_token: user identifier
+ :param int credit: cost of the body's operation
+ :param description: a description of the purpose of the charge,
+ the user will be able to see it in their
+ dashboard
+ :type description: str
+ :param credit_template: a QWeb template to render and show to the
+ user if their account does not have enough
+ credits for the requested operation
+ :type credit_template: str
+ """
+ transaction_token = iap_authorize(env, key, account_token, credit, dbuuid, description, credit_template)
+ try:
+ transaction = IapTransaction()
+ transaction.credit = credit
+ yield transaction
+ except Exception as e:
+ r = iap_cancel(env,transaction_token, key)
+ raise e
+ else:
+ r = iap_capture(env,transaction_token, key, transaction.credit)