mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 17:52:00 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -4,6 +4,9 @@ from . import common
|
|||
from . import http_common
|
||||
from . import test_flows
|
||||
from . import test_multicompany_flows
|
||||
from . import test_payment_capture_wizard
|
||||
from . import test_payment_method
|
||||
from . import test_payment_provider
|
||||
from . import test_payment_token
|
||||
from . import test_payment_transaction
|
||||
from . import test_res_company
|
||||
|
|
|
|||
|
|
@ -1,48 +1,36 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from lxml import objectify
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.fields import Command, Domain
|
||||
from odoo.tools.misc import hmac as hmac_tool
|
||||
|
||||
from odoo.addons.base.tests.common import BaseCommon
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PaymentCommon(TransactionCase):
|
||||
class PaymentCommon(BaseCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.currency_euro = cls._prepare_currency('EUR')
|
||||
cls.currency_usd = cls._prepare_currency('USD')
|
||||
cls.currency_euro = cls._enable_currency('EUR')
|
||||
cls.currency_usd = cls._enable_currency('USD')
|
||||
|
||||
cls.country_belgium = cls.env.ref('base.be')
|
||||
cls.country_france = cls.env.ref('base.fr')
|
||||
cls.europe = cls.env.ref('base.europe')
|
||||
cls.country_belgium = cls.quick_ref('base.be')
|
||||
cls.country_france = cls.quick_ref('base.fr')
|
||||
cls.europe = cls.quick_ref('base.europe')
|
||||
|
||||
cls.group_user = cls.env.ref('base.group_user')
|
||||
cls.group_portal = cls.env.ref('base.group_portal')
|
||||
cls.group_public = cls.env.ref('base.group_public')
|
||||
|
||||
cls.admin_user = cls.env.ref('base.user_admin')
|
||||
cls.internal_user = cls.env['res.users'].create({
|
||||
'name': 'Internal User (Test)',
|
||||
'login': 'internal',
|
||||
'password': 'internal',
|
||||
'groups_id': [Command.link(cls.group_user.id)]
|
||||
})
|
||||
cls.portal_user = cls.env['res.users'].create({
|
||||
'name': 'Portal User (Test)',
|
||||
'login': 'payment_portal',
|
||||
'password': 'payment_portal',
|
||||
'groups_id': [Command.link(cls.group_portal.id)]
|
||||
})
|
||||
cls.public_user = cls.env.ref('base.public_user')
|
||||
cls.admin_user = cls.quick_ref('base.user_admin')
|
||||
cls.internal_user = cls._create_new_internal_user()
|
||||
cls.portal_user = cls._create_new_portal_user()
|
||||
cls.public_user = cls.quick_ref('base.public_user')
|
||||
|
||||
cls.admin_partner = cls.admin_user.partner_id
|
||||
cls.internal_partner = cls.internal_user.partner_id
|
||||
|
|
@ -72,16 +60,30 @@ class PaymentCommon(TransactionCase):
|
|||
'arch': arch,
|
||||
})
|
||||
|
||||
cls.pm_unknown = cls.quick_ref('payment.payment_method_unknown')
|
||||
cls.dummy_provider = cls.env['payment.provider'].create({
|
||||
'name': "Dummy Provider",
|
||||
'code': 'none',
|
||||
'state': 'test',
|
||||
'is_published': True,
|
||||
'payment_method_ids': [Command.set([cls.pm_unknown.id])],
|
||||
'allow_tokenization': True,
|
||||
'redirect_form_view_id': redirect_form.id,
|
||||
'available_currency_ids': [Command.set(
|
||||
(cls.currency_euro + cls.currency_usd + cls.env.company.currency_id).ids
|
||||
)],
|
||||
})
|
||||
# Activate pm
|
||||
cls.pm_unknown.write({
|
||||
'active': True,
|
||||
'support_tokenization': True,
|
||||
})
|
||||
|
||||
cls.provider = cls.dummy_provider
|
||||
cls.payment_methods = cls.provider.payment_method_ids
|
||||
cls.payment_method = cls.payment_methods[:1]
|
||||
cls.payment_method_id = cls.payment_method.id
|
||||
cls.payment_method_code = cls.payment_method.code
|
||||
cls.amount = 1111.11
|
||||
cls.company = cls.env.company
|
||||
cls.company_id = cls.company.id
|
||||
|
|
@ -91,65 +93,57 @@ class PaymentCommon(TransactionCase):
|
|||
|
||||
account_payment_module = cls.env['ir.module.module']._get('account_payment')
|
||||
cls.account_payment_installed = account_payment_module.state in ('installed', 'to upgrade')
|
||||
cls.enable_reconcile_after_done_patcher = True
|
||||
cls.enable_post_process_patcher = True
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
if self.account_payment_installed and self.enable_reconcile_after_done_patcher:
|
||||
if self.account_payment_installed and self.enable_post_process_patcher:
|
||||
# disable account payment generation if account_payment is installed
|
||||
# because the accounting setup of providers is not managed in this common
|
||||
self.reconcile_after_done_patcher = patch(
|
||||
'odoo.addons.account_payment.models.payment_transaction.PaymentTransaction._reconcile_after_done',
|
||||
self.post_process_patcher = patch(
|
||||
'odoo.addons.account_payment.models.payment_transaction.PaymentTransaction._post_process',
|
||||
)
|
||||
self.startPatcher(self.reconcile_after_done_patcher)
|
||||
self.startPatcher(self.post_process_patcher)
|
||||
|
||||
#=== Utils ===#
|
||||
|
||||
@classmethod
|
||||
def _prepare_currency(cls, currency_code):
|
||||
currency = cls.env['res.currency'].with_context(active_test=False).search(
|
||||
[('name', '=', currency_code.upper())]
|
||||
)
|
||||
currency.action_unarchive()
|
||||
return currency
|
||||
|
||||
@classmethod
|
||||
def _prepare_provider(cls, code='none', company=None, update_values=None):
|
||||
""" Prepare and return the first provider matching the given provider and company.
|
||||
|
||||
If no provider is found in the given company, we duplicate the one from the base company.
|
||||
def _prepare_provider(cls, code, company=None, update_values=None, **kwargs):
|
||||
""" Prepare and return the first active provider matching the given code and company.
|
||||
|
||||
All other providers belonging to the same company are disabled to avoid any interferences.
|
||||
|
||||
:param str code: The code of the provider to prepare
|
||||
:param recordset company: The company of the provider to prepare, as a `res.company` record
|
||||
:param dict update_values: The values used to update the provider
|
||||
:param dict kwargs: The keyword arguments passed as-is to the called function.
|
||||
:return: The provider to prepare, if found
|
||||
:rtype: recordset of `payment.provider`
|
||||
"""
|
||||
assert code != 'none', "Code 'none' should not be passed to _prepare_provider"
|
||||
|
||||
company = company or cls.env.company
|
||||
update_values = update_values or {}
|
||||
provider_domain = cls._get_provider_domain(code, **kwargs)
|
||||
|
||||
provider = cls.env['payment.provider'].sudo().search(
|
||||
[('code', '=', code), ('company_id', '=', company.id)], limit=1
|
||||
Domain.AND([provider_domain, [('company_id', '=', company.id)]]), limit=1
|
||||
)
|
||||
if not provider:
|
||||
base_provider = cls.env['payment.provider'].sudo().search(
|
||||
[('code', '=', code)], limit=1
|
||||
)
|
||||
if not base_provider:
|
||||
_logger.error("no payment.provider found for code %s", code)
|
||||
return cls.env['payment.provider']
|
||||
else:
|
||||
provider = base_provider.copy({'company_id': company.id})
|
||||
_logger.error("No payment.provider found for code %s in company %s", code, company.name)
|
||||
return cls.env['payment.provider']
|
||||
|
||||
update_values['state'] = 'test'
|
||||
provider.write(update_values)
|
||||
return provider
|
||||
|
||||
@classmethod
|
||||
def _get_provider_domain(cls, code, **kwargs):
|
||||
return [('code', '=', code)]
|
||||
|
||||
@classmethod
|
||||
def _prepare_user(cls, user, group_xmlid):
|
||||
user.groups_id = [Command.link(cls.env.ref(group_xmlid).id)]
|
||||
user.group_ids = [Command.link(cls.env.ref(group_xmlid).id)]
|
||||
# Flush and invalidate the cache to allow checking access rights.
|
||||
user.flush_recordset()
|
||||
user.invalidate_recordset()
|
||||
|
|
@ -157,6 +151,7 @@ class PaymentCommon(TransactionCase):
|
|||
|
||||
def _create_transaction(self, flow, sudo=True, **values):
|
||||
default_values = {
|
||||
'payment_method_id': self.payment_method_id,
|
||||
'amount': self.amount,
|
||||
'currency_id': self.currency.id,
|
||||
'provider_id': self.provider.id,
|
||||
|
|
@ -168,8 +163,9 @@ class PaymentCommon(TransactionCase):
|
|||
|
||||
def _create_token(self, sudo=True, **values):
|
||||
default_values = {
|
||||
'payment_details': "1234",
|
||||
'provider_id': self.provider.id,
|
||||
'payment_method_id': self.payment_method_id,
|
||||
'payment_details': "1234",
|
||||
'partner_id': self.partner.id,
|
||||
'provider_ref': "provider Ref (TEST)",
|
||||
'active': True,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
from uuid import uuid4
|
||||
from contextlib import contextmanager
|
||||
|
||||
from lxml import etree, objectify
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo.tests import HttpCase
|
||||
from odoo.tests import HttpCase, JsonRpcException
|
||||
from odoo.tools import urls
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
|
@ -22,7 +21,7 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
###########
|
||||
|
||||
def _build_url(self, route):
|
||||
return urls.url_join(self.base_url(), route)
|
||||
return urls.urljoin(self.base_url(), route)
|
||||
|
||||
def _make_http_get_request(self, url, params=None):
|
||||
""" Make an HTTP GET request to the provided URL.
|
||||
|
|
@ -33,7 +32,7 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
:rtype: :class:`requests.models.Response`
|
||||
"""
|
||||
formatted_params = self._format_http_request_payload(payload=params)
|
||||
return self.opener.get(url, params=formatted_params)
|
||||
return self.url_open(url, params=formatted_params)
|
||||
|
||||
def _make_http_post_request(self, url, data=None):
|
||||
""" Make an HTTP POST request to the provided URL.
|
||||
|
|
@ -44,7 +43,7 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
:rtype: :class:`requests.models.Response`
|
||||
"""
|
||||
formatted_data = self._format_http_request_payload(payload=data)
|
||||
return self.opener.post(url, data=formatted_data)
|
||||
return self.url_open(url, data=formatted_data, method='POST')
|
||||
|
||||
def _format_http_request_payload(self, payload=None):
|
||||
""" Format a request payload to replace float values by their string representation.
|
||||
|
|
@ -67,29 +66,19 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
:return: The response of the request
|
||||
:rtype: :class:`requests.models.Response`
|
||||
"""
|
||||
return self.opener.post(url, json=data)
|
||||
return self.url_open(url, json=data)
|
||||
|
||||
def _make_json_rpc_request(self, url, data=None):
|
||||
""" Make a JSON-RPC request to the provided URL.
|
||||
@contextmanager
|
||||
def _assertNotFound(self):
|
||||
with self.assertRaises(JsonRpcException) as cm:
|
||||
yield
|
||||
self.assertEqual(cm.exception.code, 404)
|
||||
|
||||
:param str url: The URL to make the request to
|
||||
:param dict data: The data to be send in the request body in JSON-RPC 2.0 format
|
||||
:return: The response of the request
|
||||
:rtype: :class:`requests.models.Response`
|
||||
"""
|
||||
return self.opener.post(url, json={
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'call',
|
||||
'id': str(uuid4()),
|
||||
'params': data,
|
||||
})
|
||||
|
||||
def _get_tx_context(self, response, form_name):
|
||||
"""Extracts txContext & other form info (provider & token ids)
|
||||
from a payment response (with manage/checkout html form)
|
||||
def _get_payment_context(self, response):
|
||||
"""Extracts the payment context & other form info (provider & token ids)
|
||||
from a payment response
|
||||
|
||||
:param response: http Response, with a payment form as text
|
||||
:param str form_name: o_payment_manage / o_payment_checkout
|
||||
:return: Transaction context (+ provider_ids & token_ids)
|
||||
:rtype: dict
|
||||
"""
|
||||
|
|
@ -100,9 +89,9 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
response.text,
|
||||
parser=etree.HTMLParser(),
|
||||
)
|
||||
checkout_form = html_tree.xpath(f"//form[@name='{form_name}']")[0]
|
||||
payment_form = html_tree.xpath('//form[@id="o_payment_form"]')[0]
|
||||
values = {}
|
||||
for key, val in checkout_form.items():
|
||||
for key, val in payment_form.items():
|
||||
if key.startswith("data-"):
|
||||
formatted_key = key[5:].replace('-', '_')
|
||||
if formatted_key.endswith('_id'):
|
||||
|
|
@ -114,21 +103,21 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
values[formatted_key] = formatted_val
|
||||
|
||||
payment_options_inputs = html_tree.xpath("//input[@name='o_payment_radio']")
|
||||
provider_ids = []
|
||||
token_ids = []
|
||||
payment_method_ids = []
|
||||
for p_o_input in payment_options_inputs:
|
||||
data = dict()
|
||||
for key, val in p_o_input.items():
|
||||
if key.startswith('data-'):
|
||||
data[key[5:]] = val
|
||||
if data['payment-option-type'] == 'provider':
|
||||
provider_ids.append(int(data['payment-option-id']))
|
||||
else:
|
||||
if data['payment-option-type'] == 'token':
|
||||
token_ids.append(int(data['payment-option-id']))
|
||||
else: # 'payment_method'
|
||||
payment_method_ids.append(int(data['payment-option-id']))
|
||||
|
||||
values.update({
|
||||
'provider_ids': provider_ids,
|
||||
'token_ids': token_ids,
|
||||
'payment_method_ids': payment_method_ids,
|
||||
})
|
||||
|
||||
return values
|
||||
|
|
@ -156,7 +145,7 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
}
|
||||
|
||||
def _portal_pay(self, **route_kwargs):
|
||||
"""/payment/pay txContext feedback
|
||||
"""/payment/pay payment context feedback
|
||||
|
||||
NOTE: must be authenticated before calling method.
|
||||
Or an access_token should be specified in route_kwargs
|
||||
|
|
@ -165,18 +154,18 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
url = self._build_url(uri)
|
||||
return self._make_http_get_request(url, route_kwargs)
|
||||
|
||||
def _get_tx_checkout_context(self, **route_kwargs):
|
||||
def _get_portal_pay_context(self, **route_kwargs):
|
||||
response = self._portal_pay(**route_kwargs)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
return self._get_tx_context(response, 'o_payment_checkout')
|
||||
return self._get_payment_context(response)
|
||||
|
||||
# /my/payment_method #
|
||||
######################
|
||||
|
||||
def _portal_payment_method(self):
|
||||
"""/my/payment_method txContext feedback
|
||||
"""/my/payment_method payment context feedback
|
||||
|
||||
NOTE: must be authenticated before calling method
|
||||
validation flow is restricted to logged users
|
||||
|
|
@ -185,56 +174,49 @@ class PaymentHttpCommon(PaymentCommon, HttpCase):
|
|||
url = self._build_url(uri)
|
||||
return self._make_http_get_request(url, {})
|
||||
|
||||
def _get_tx_manage_context(self):
|
||||
def _get_portal_payment_method_context(self):
|
||||
response = self._portal_payment_method()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
return self._get_tx_context(response, 'o_payment_manage')
|
||||
return self._get_payment_context(response)
|
||||
|
||||
# payment/transaction #
|
||||
#######################
|
||||
|
||||
def _prepare_transaction_values(self, payment_option_id, flow):
|
||||
def _prepare_transaction_values(self, payment_method_id, token_id, flow):
|
||||
""" Prepare the basic payment/transaction route values.
|
||||
|
||||
:param int payment_option_id: The payment option handling the transaction, as a
|
||||
`payment.provider` id or a `payment.token` id
|
||||
`payment.method` id or a `payment.token` id
|
||||
:param str flow: The payment flow
|
||||
:return: The route values
|
||||
:rtype: dict
|
||||
"""
|
||||
return {
|
||||
'provider_id': self.provider.id,
|
||||
'payment_method_id': payment_method_id,
|
||||
'token_id': token_id,
|
||||
'amount': self.amount,
|
||||
'currency_id': self.currency.id,
|
||||
'partner_id': self.partner.id,
|
||||
'access_token': self._generate_test_access_token(
|
||||
self.partner.id, self.amount, self.currency.id
|
||||
),
|
||||
'payment_option_id': payment_option_id,
|
||||
'reference_prefix': 'test',
|
||||
'tokenization_requested': True,
|
||||
'landing_route': 'Test',
|
||||
'reference_prefix': 'test',
|
||||
'is_validation': False,
|
||||
'flow': flow,
|
||||
}
|
||||
|
||||
def _portal_transaction(self, **route_kwargs):
|
||||
def _portal_transaction(self, tx_route='/payment/transaction', **route_kwargs):
|
||||
"""/payment/transaction feedback
|
||||
|
||||
:return: The response to the json request
|
||||
"""
|
||||
uri = '/payment/transaction'
|
||||
url = self._build_url(uri)
|
||||
response = self._make_json_rpc_request(url, route_kwargs)
|
||||
self.assertEqual(response.status_code, 200) # Check the request went through.
|
||||
|
||||
return response
|
||||
url = self._build_url(tx_route)
|
||||
return self.make_jsonrpc_request(url, route_kwargs)
|
||||
|
||||
def _get_processing_values(self, **route_kwargs):
|
||||
response = self._portal_transaction(**route_kwargs)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
resp_content = json.loads(response.content)
|
||||
return resp_content['result']
|
||||
return self._portal_transaction(**route_kwargs)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from unittest.mock import patch
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests import JsonRpcException, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.controllers.portal import PaymentPortal
|
||||
|
|
@ -26,34 +26,31 @@ class TestFlows(PaymentHttpCommon):
|
|||
route_values = self._prepare_pay_values()
|
||||
|
||||
# /payment/pay
|
||||
tx_context = self._get_tx_checkout_context(**route_values)
|
||||
for key, val in tx_context.items():
|
||||
payment_context = self._get_portal_pay_context(**route_values)
|
||||
for key, val in payment_context.items():
|
||||
if key in route_values:
|
||||
self.assertEqual(val, route_values[key])
|
||||
|
||||
self.assertIn(self.provider.id, tx_context['provider_ids'])
|
||||
|
||||
# Route values are taken from tx_context result of /pay route to correctly simulate the flow
|
||||
# Route values are taken from payment_context result of /pay route to correctly simulate the flow
|
||||
route_values = {
|
||||
k: tx_context[k]
|
||||
k: payment_context[k]
|
||||
for k in [
|
||||
'amount',
|
||||
'currency_id',
|
||||
'reference_prefix',
|
||||
'partner_id',
|
||||
'access_token',
|
||||
'landing_route',
|
||||
'reference_prefix',
|
||||
'access_token',
|
||||
]
|
||||
}
|
||||
route_values.update({
|
||||
'provider_id': self.provider.id,
|
||||
'payment_method_id': self.payment_method_id if flow != 'token' else None,
|
||||
'token_id': self._create_token().id if flow == 'token' else None,
|
||||
'flow': flow,
|
||||
'payment_option_id': self.provider.id,
|
||||
'tokenization_requested': False,
|
||||
})
|
||||
|
||||
if flow == 'token':
|
||||
route_values['payment_option_id'] = self._create_token().id
|
||||
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = self._get_processing_values(**route_values)
|
||||
tx_sudo = self._get_tx(processing_values['reference'])
|
||||
|
|
@ -71,6 +68,7 @@ class TestFlows(PaymentHttpCommon):
|
|||
self.assertEqual(processing_values['currency_id'], self.currency.id)
|
||||
self.assertEqual(processing_values['partner_id'], self.partner.id)
|
||||
self.assertEqual(processing_values['reference'], self.reference)
|
||||
self.assertFalse(processing_values['should_tokenize'])
|
||||
|
||||
# Verify computed values not provided, but added during the flow
|
||||
self.assertIn("tx_id=", tx_sudo.landing_route)
|
||||
|
|
@ -163,26 +161,28 @@ class TestFlows(PaymentHttpCommon):
|
|||
validation_amount = self.provider._get_validation_amount()
|
||||
validation_currency = self.provider._get_validation_currency()
|
||||
|
||||
tx_context = self._get_tx_manage_context()
|
||||
payment_context = self._get_portal_payment_method_context()
|
||||
expected_values = {
|
||||
'partner_id': self.partner.id,
|
||||
'access_token': self._generate_test_access_token(self.partner.id, None, None),
|
||||
'reference_prefix': expected_reference
|
||||
}
|
||||
for key, val in tx_context.items():
|
||||
for key, val in payment_context.items():
|
||||
if key in expected_values:
|
||||
self.assertEqual(val, expected_values[key])
|
||||
|
||||
transaction_values = {
|
||||
'provider_id': self.provider.id,
|
||||
'payment_method_id': self.payment_method_id,
|
||||
'token_id': None,
|
||||
'amount': None,
|
||||
'currency_id': None,
|
||||
'partner_id': tx_context['partner_id'],
|
||||
'access_token': tx_context['access_token'],
|
||||
'partner_id': payment_context['partner_id'],
|
||||
'access_token': payment_context['access_token'],
|
||||
'flow': flow,
|
||||
'payment_option_id': self.provider.id,
|
||||
'tokenization_requested': True,
|
||||
'reference_prefix': tx_context['reference_prefix'],
|
||||
'landing_route': tx_context['landing_route'],
|
||||
'landing_route': payment_context['landing_route'],
|
||||
'reference_prefix': payment_context['reference_prefix'],
|
||||
'is_validation': True,
|
||||
}
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
|
|
@ -196,7 +196,6 @@ class TestFlows(PaymentHttpCommon):
|
|||
self.assertEqual(tx_sudo.partner_id.id, self.partner.id)
|
||||
self.assertEqual(tx_sudo.reference, expected_reference)
|
||||
# processing_values == given values
|
||||
self.assertEqual(processing_values['provider_id'], self.provider.id)
|
||||
self.assertEqual(processing_values['amount'], validation_amount)
|
||||
self.assertEqual(processing_values['currency_id'], validation_currency.id)
|
||||
self.assertEqual(processing_values['partner_id'], self.partner.id)
|
||||
|
|
@ -237,7 +236,7 @@ class TestFlows(PaymentHttpCommon):
|
|||
|
||||
# Pay without a partner specified (but logged) --> pay with the partner of current user.
|
||||
self.authenticate(self.portal_user.login, self.portal_user.login)
|
||||
tx_context = self._get_tx_checkout_context(**route_values)
|
||||
tx_context = self._get_portal_pay_context(**route_values)
|
||||
self.assertEqual(tx_context['partner_id'], self.portal_partner.id)
|
||||
|
||||
def test_pay_no_token(self):
|
||||
|
|
@ -253,7 +252,7 @@ class TestFlows(PaymentHttpCommon):
|
|||
|
||||
# Pay without a partner specified (but logged) --> pay with the partner of current user.
|
||||
self.authenticate(self.portal_user.login, self.portal_user.login)
|
||||
tx_context = self._get_tx_checkout_context(**route_values)
|
||||
tx_context = self._get_portal_pay_context(**route_values)
|
||||
self.assertEqual(tx_context['partner_id'], self.portal_partner.id)
|
||||
|
||||
def test_pay_wrong_token(self):
|
||||
|
|
@ -279,30 +278,37 @@ class TestFlows(PaymentHttpCommon):
|
|||
|
||||
def test_transaction_wrong_flow(self):
|
||||
transaction_values = self._prepare_pay_values()
|
||||
transaction_values.pop('reference')
|
||||
transaction_values.update({
|
||||
'flow': 'this flow does not exist',
|
||||
'payment_option_id': self.provider.id,
|
||||
'tokenization_requested': False,
|
||||
'reference_prefix': 'whatever',
|
||||
'landing_route': 'whatever',
|
||||
'reference_prefix': 'whatever',
|
||||
})
|
||||
# Transaction step with a wrong flow --> UserError
|
||||
with mute_logger('odoo.http'):
|
||||
response = self._portal_transaction(**transaction_values)
|
||||
self.assertIn(
|
||||
"odoo.exceptions.UserError: The payment should either be direct, with redirection, or made by a token.",
|
||||
response.text)
|
||||
with mute_logger("odoo.http"), self.assertRaises(
|
||||
JsonRpcException,
|
||||
msg='odoo.exceptions.UserError: The payment should either be direct, with redirection, or made by a token.',
|
||||
):
|
||||
self._portal_transaction(**transaction_values)
|
||||
|
||||
@mute_logger('odoo.http')
|
||||
def test_transaction_route_rejects_unexpected_kwarg(self):
|
||||
route_kwargs = {
|
||||
**self._prepare_pay_values(),
|
||||
'custom_create_values': 'whatever', # This should be rejected.
|
||||
}
|
||||
with self.assertRaises(JsonRpcException, msg='odoo.exceptions.ValidationError'):
|
||||
self._portal_transaction(**route_kwargs)
|
||||
|
||||
def test_transaction_wrong_token(self):
|
||||
route_values = self._prepare_pay_values()
|
||||
route_values['access_token'] = "abcde"
|
||||
|
||||
# Transaction step with a wrong access token --> ValidationError
|
||||
with mute_logger('odoo.http'):
|
||||
response = self._portal_transaction(**route_values)
|
||||
self.assertIn(
|
||||
"odoo.exceptions.ValidationError: The access token is invalid.",
|
||||
response.text)
|
||||
with mute_logger('odoo.http'), self.assertRaises(JsonRpcException, msg='odoo.exceptions.ValidationError: The access token is invalid.'):
|
||||
self._portal_transaction(**route_values)
|
||||
|
||||
def test_access_disabled_providers_tokens(self):
|
||||
self.partner = self.portal_partner
|
||||
|
|
@ -311,31 +317,27 @@ class TestFlows(PaymentHttpCommon):
|
|||
self.authenticate(self.portal_user.login, self.portal_user.login)
|
||||
|
||||
token = self._create_token()
|
||||
provider_b = self.provider.copy()
|
||||
provider_b.state = 'test'
|
||||
provider_b = self.provider.copy({'is_published': True, 'state': 'test'})
|
||||
token_b = self._create_token(provider_id=provider_b.id)
|
||||
|
||||
# User must see both enabled providers and tokens
|
||||
manage_context = self._get_tx_manage_context()
|
||||
self.assertEqual(manage_context['partner_id'], self.partner.id)
|
||||
self.assertIn(self.provider.id, manage_context['provider_ids'])
|
||||
self.assertIn(provider_b.id, manage_context['provider_ids'])
|
||||
self.assertIn(token.id, manage_context['token_ids'])
|
||||
self.assertIn(token_b.id, manage_context['token_ids'])
|
||||
# User must see both tokens and compatible payment methods.
|
||||
payment_context = self._get_portal_payment_method_context()
|
||||
self.assertEqual(payment_context['partner_id'], self.partner.id)
|
||||
self.assertIn(token.id, payment_context['token_ids'])
|
||||
self.assertIn(token_b.id, payment_context['token_ids'])
|
||||
self.assertIn(self.payment_method_id, payment_context['payment_method_ids'])
|
||||
|
||||
# Token of disabled provider(s) & disabled providers should not be shown
|
||||
# Token of disabled provider(s) should not be shown.
|
||||
self.provider.state = 'disabled'
|
||||
manage_context = self._get_tx_manage_context()
|
||||
self.assertEqual(manage_context['partner_id'], self.partner.id)
|
||||
self.assertEqual(manage_context['provider_ids'], [provider_b.id])
|
||||
self.assertEqual(manage_context['token_ids'], [token_b.id])
|
||||
payment_context = self._get_portal_payment_method_context()
|
||||
self.assertEqual(payment_context['partner_id'], self.partner.id)
|
||||
self.assertEqual(payment_context['token_ids'], [token_b.id])
|
||||
|
||||
# Archived tokens must be hidden from the user
|
||||
token_b.active = False
|
||||
manage_context = self._get_tx_manage_context()
|
||||
self.assertEqual(manage_context['partner_id'], self.partner.id)
|
||||
self.assertEqual(manage_context['provider_ids'], [provider_b.id])
|
||||
self.assertEqual(manage_context['token_ids'], [])
|
||||
payment_context = self._get_portal_payment_method_context()
|
||||
self.assertEqual(payment_context['partner_id'], self.partner.id)
|
||||
self.assertEqual(payment_context['token_ids'], [])
|
||||
|
||||
@mute_logger('odoo.addons.payment.models.payment_transaction')
|
||||
def test_direct_payment_triggers_no_payment_request(self):
|
||||
|
|
@ -344,10 +346,10 @@ class TestFlows(PaymentHttpCommon):
|
|||
self.user = self.portal_user
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._send_payment_request'
|
||||
'._charge_with_token'
|
||||
) as patched:
|
||||
self._portal_transaction(
|
||||
**self._prepare_transaction_values(self.provider.id, 'direct')
|
||||
**self._prepare_transaction_values(self.payment_method_id, None, 'direct')
|
||||
)
|
||||
self.assertEqual(patched.call_count, 0)
|
||||
|
||||
|
|
@ -358,10 +360,10 @@ class TestFlows(PaymentHttpCommon):
|
|||
self.user = self.portal_user
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._send_payment_request'
|
||||
'._charge_with_token'
|
||||
) as patched:
|
||||
self._portal_transaction(
|
||||
**self._prepare_transaction_values(self.provider.id, 'redirect')
|
||||
**self._prepare_transaction_values(self.payment_method_id, None, 'redirect')
|
||||
)
|
||||
self.assertEqual(patched.call_count, 0)
|
||||
|
||||
|
|
@ -372,23 +374,30 @@ class TestFlows(PaymentHttpCommon):
|
|||
self.user = self.portal_user
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._send_payment_request'
|
||||
'._charge_with_token'
|
||||
) as patched:
|
||||
self._portal_transaction(
|
||||
**self._prepare_transaction_values(self._create_token().id, 'token')
|
||||
**self._prepare_transaction_values(None, self._create_token().id, 'token')
|
||||
)
|
||||
self.assertEqual(patched.call_count, 1)
|
||||
|
||||
def test_tokenization_input_is_show_to_logged_in_users(self):
|
||||
def test_tokenization_input_is_shown_to_logged_in_users(self):
|
||||
# Test both for portal and internal users
|
||||
self.user = self.portal_user
|
||||
self.provider.allow_tokenization = True
|
||||
show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(
|
||||
self.provider, logged_in=True
|
||||
)
|
||||
|
||||
show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(self.provider)
|
||||
self.assertDictEqual(show_tokenize_input, {self.provider.id: True})
|
||||
|
||||
def test_tokenization_input_is_hidden_for_logged_out_users(self):
|
||||
self.provider.allow_tokenization = False
|
||||
show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(
|
||||
self.provider, logged_in=True
|
||||
)
|
||||
self.assertDictEqual(show_tokenize_input, {self.provider.id: False})
|
||||
self.user = self.internal_user
|
||||
self.provider.allow_tokenization = True
|
||||
|
||||
show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(self.provider)
|
||||
self.assertDictEqual(show_tokenize_input, {self.provider.id: True})
|
||||
|
||||
def test_tokenization_input_is_shown_to_logged_out_users(self):
|
||||
self.user = self.public_user
|
||||
self.provider.allow_tokenization = True
|
||||
|
||||
show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(self.provider)
|
||||
self.assertDictEqual(show_tokenize_input, {self.provider.id: True})
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
'password': 'user_company_b',
|
||||
'company_id': cls.company_b.id,
|
||||
'company_ids': [Command.set(cls.company_b.ids)],
|
||||
'groups_id': [Command.link(cls.group_user.id)],
|
||||
'group_ids': [Command.link(cls.group_user.id)],
|
||||
})
|
||||
cls.user_multi_company = cls.env['res.users'].create({
|
||||
'name': "Multi Company User (TEST)",
|
||||
|
|
@ -32,10 +32,11 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
'password': 'user_multi_company',
|
||||
'company_id': cls.company_a.id,
|
||||
'company_ids': [Command.set([cls.company_a.id, cls.company_b.id])],
|
||||
'groups_id': [Command.link(cls.group_user.id)],
|
||||
'group_ids': [Command.link(cls.group_user.id)],
|
||||
})
|
||||
|
||||
cls.provider_company_b = cls._prepare_provider(company=cls.company_b)
|
||||
cls.provider = cls.dummy_provider.copy({'company_id': cls.company_b.id})
|
||||
cls.provider.state = 'test'
|
||||
|
||||
def test_pay_logged_in_another_company(self):
|
||||
"""User pays for an amount in another company."""
|
||||
|
|
@ -48,8 +49,8 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
# Pay in company B
|
||||
route_values['company_id'] = self.company_b.id
|
||||
|
||||
tx_context = self._get_tx_checkout_context(**route_values)
|
||||
for key, val in tx_context.items():
|
||||
payment_context = self._get_portal_pay_context(**route_values)
|
||||
for key, val in payment_context.items():
|
||||
if key in route_values:
|
||||
if key == 'access_token':
|
||||
continue # access_token was modified due to the change of partner.
|
||||
|
|
@ -59,24 +60,22 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
else:
|
||||
self.assertEqual(val, route_values[key])
|
||||
|
||||
available_providers = self.env['payment.provider'].sudo().browse(tx_context['provider_ids'])
|
||||
self.assertIn(self.provider_company_b, available_providers)
|
||||
self.assertEqual(available_providers.company_id, self.company_b)
|
||||
|
||||
validation_values = {
|
||||
k: tx_context[k]
|
||||
k: payment_context[k]
|
||||
for k in [
|
||||
'amount',
|
||||
'currency_id',
|
||||
'reference_prefix',
|
||||
'partner_id',
|
||||
'access_token',
|
||||
'landing_route',
|
||||
'reference_prefix',
|
||||
'access_token',
|
||||
]
|
||||
}
|
||||
validation_values.update({
|
||||
'provider_id': self.provider.id,
|
||||
'payment_method_id': self.provider.payment_method_ids[:1].id,
|
||||
'token_id': None,
|
||||
'flow': 'direct',
|
||||
'payment_option_id': self.provider_company_b.id,
|
||||
'tokenization_requested': False,
|
||||
})
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
|
|
@ -84,14 +83,14 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
tx_sudo = self._get_tx(processing_values['reference'])
|
||||
|
||||
# Tx values == given values
|
||||
self.assertEqual(tx_sudo.provider_id.id, self.provider_company_b.id)
|
||||
self.assertEqual(tx_sudo.provider_id.id, self.provider.id)
|
||||
self.assertEqual(tx_sudo.amount, self.amount)
|
||||
self.assertEqual(tx_sudo.currency_id.id, self.currency.id)
|
||||
self.assertEqual(tx_sudo.partner_id.id, self.user_company_a.partner_id.id)
|
||||
self.assertEqual(tx_sudo.reference, self.reference)
|
||||
self.assertEqual(tx_sudo.company_id, self.company_b)
|
||||
# processing_values == given values
|
||||
self.assertEqual(processing_values['provider_id'], self.provider_company_b.id)
|
||||
self.assertEqual(processing_values['provider_id'], self.provider.id)
|
||||
self.assertEqual(processing_values['amount'], self.amount)
|
||||
self.assertEqual(processing_values['currency_id'], self.currency.id)
|
||||
self.assertEqual(processing_values['partner_id'], self.user_company_a.partner_id.id)
|
||||
|
|
@ -104,15 +103,13 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
self.authenticate(self.portal_user.login, self.portal_user.login)
|
||||
|
||||
token = self._create_token()
|
||||
token_company_b = self._create_token(provider_id=self.provider_company_b.id)
|
||||
token_company_b = self._create_token(provider_id=self.provider.id)
|
||||
|
||||
# A partner should see all his tokens on the /my/payment_method route,
|
||||
# even if they are in other companies otherwise he won't ever see them.
|
||||
manage_context = self._get_tx_manage_context()
|
||||
self.assertEqual(manage_context['partner_id'], self.partner.id)
|
||||
self.assertEqual(manage_context['provider_ids'], self.provider.ids)
|
||||
self.assertIn(token.id, manage_context['token_ids'])
|
||||
self.assertIn(token_company_b.id, manage_context['token_ids'])
|
||||
payment_context = self._get_portal_payment_method_context()
|
||||
self.assertIn(token.id, payment_context['token_ids'])
|
||||
self.assertIn(token_company_b.id, payment_context['token_ids'])
|
||||
|
||||
def test_archive_token_logged_in_another_company(self):
|
||||
"""User archives his token from another company."""
|
||||
|
|
@ -128,6 +125,6 @@ class TestMultiCompanyFlows(PaymentHttpCommon):
|
|||
|
||||
# Archive token in company A
|
||||
url = self._build_url('/payment/archive_token')
|
||||
self._make_json_rpc_request(url, {'token_id': token.id})
|
||||
self.make_jsonrpc_request(url, {'token_id': token.id})
|
||||
|
||||
self.assertFalse(token.active)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPaymentCaptureWizard(PaymentCommon):
|
||||
|
||||
def test_partial_capture_wizard(self):
|
||||
self.provider.update({
|
||||
'capture_manually': True,
|
||||
'support_manual_capture': 'partial',
|
||||
})
|
||||
source_tx = self._create_transaction('direct', state='authorized')
|
||||
|
||||
wizard = self.env['payment.capture.wizard'].create({
|
||||
'transaction_ids': source_tx.ids,
|
||||
})
|
||||
wizard.amount_to_capture = 511.11
|
||||
wizard.action_capture()
|
||||
|
||||
child_tx_1 = source_tx.child_transaction_ids
|
||||
self.assertEqual(child_tx_1.state, 'draft')
|
||||
child_tx_1._set_done()
|
||||
|
||||
self.env['payment.capture.wizard'].create({
|
||||
'transaction_ids': source_tx.ids,
|
||||
}).action_capture()
|
||||
|
||||
child_tx_2 = (source_tx.child_transaction_ids - child_tx_1).ensure_one()
|
||||
child_tx_2._set_done()
|
||||
self.assertAlmostEqual(
|
||||
sum(source_tx.child_transaction_ids.mapped('amount')),
|
||||
source_tx.amount,
|
||||
)
|
||||
self.assertEqual(source_tx.state, 'done')
|
||||
|
||||
def test_support_partial_capture_computation_with_brands(self):
|
||||
self.provider.update({
|
||||
'capture_manually': True,
|
||||
'support_manual_capture': 'partial',
|
||||
})
|
||||
dummy_brand = self.env['payment.method'].create({
|
||||
'name': "Dummy Brand",
|
||||
'code': 'dumbrand',
|
||||
'primary_payment_method_id': self.payment_method.id,
|
||||
'provider_ids': self.provider.ids,
|
||||
})
|
||||
source_tx = self._create_transaction(
|
||||
'direct', state='authorized', payment_method_id=dummy_brand.id,
|
||||
)
|
||||
wizard = self.env['payment.capture.wizard'].create({
|
||||
'transaction_ids': source_tx.ids,
|
||||
})
|
||||
self.assertTrue(wizard.support_partial_capture)
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.payment.const import REPORT_REASONS_MAPPING
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestPaymentMethod(PaymentCommon):
|
||||
|
||||
def test_unlinking_payment_method_from_provider_state_archives_tokens(self):
|
||||
""" Test that the active tokens of a payment method created through a provider are archived
|
||||
when the method is unlinked from the provider. """
|
||||
token = self._create_token()
|
||||
self.payment_method.provider_ids = [Command.unlink(self.payment_method.provider_ids[:1].id)]
|
||||
self.assertFalse(token.active)
|
||||
|
||||
def test_payment_method_requires_provider_to_be_activated(self):
|
||||
""" Test that activating a payment method that is not linked to an enabled provider is
|
||||
forbidden. """
|
||||
self.provider.state = 'disabled'
|
||||
with self.assertRaises(UserError):
|
||||
self.payment_methods.active = True
|
||||
|
||||
def test_brand_compatible_with_manual_capture(self):
|
||||
""" Test that a "brand" can be enabled for providers which support manual capture. """
|
||||
self.provider.update({
|
||||
'capture_manually': True,
|
||||
'support_manual_capture': 'partial',
|
||||
})
|
||||
self.payment_method.support_manual_capture = 'partial'
|
||||
brand_payment_method = self.env['payment.method'].create({
|
||||
'name': "Dummy Brand",
|
||||
'code': 'dumbrand',
|
||||
'primary_payment_method_id': self.payment_method.id,
|
||||
'active': False,
|
||||
'provider_ids': self.provider.ids,
|
||||
})
|
||||
self._assert_does_not_raise(ValidationError, brand_payment_method.action_unarchive)
|
||||
self.assertTrue(brand_payment_method.active)
|
||||
|
||||
def test_payment_method_compatible_when_provider_is_enabled(self):
|
||||
""" Test that a payment method is available when it is supported by an enabled provider. """
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_not_compatible_when_provider_is_disabled(self):
|
||||
""" Test that a payment method is not available when there is no enabled provider that
|
||||
supports it. """
|
||||
self.provider.state = 'disabled'
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id
|
||||
)
|
||||
self.assertNotIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_non_primary_payment_method_not_compatible(self):
|
||||
""" Test that a "brand" (i.e., non-primary) payment method is never available. """
|
||||
brand_payment_method = self.payment_method.copy()
|
||||
brand_payment_method.primary_payment_method_id = self.payment_method_id # Make it a brand.
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id
|
||||
)
|
||||
self.assertNotIn(brand_payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_compatible_with_supported_countries(self):
|
||||
""" Test that the payment method is compatible with its supported countries. """
|
||||
belgium = self.env.ref('base.be')
|
||||
self.payment_method.supported_country_ids = [Command.set([belgium.id])]
|
||||
self.partner.country_id = belgium
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_not_compatible_with_unsupported_countries(self):
|
||||
""" Test that the payment method is not compatible with a country that is not supported. """
|
||||
belgium = self.env.ref('base.be')
|
||||
self.payment_method.supported_country_ids = [Command.set([belgium.id])]
|
||||
france = self.env.ref('base.fr')
|
||||
self.partner.country_id = france
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id
|
||||
)
|
||||
self.assertNotIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_compatible_when_no_supported_countries_set(self):
|
||||
""" Test that the payment method is always compatible when no supported countries are
|
||||
set. """
|
||||
self.payment_method.supported_country_ids = [Command.clear()]
|
||||
belgium = self.env.ref('base.be')
|
||||
self.partner.country_id = belgium
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_compatible_with_supported_currencies(self):
|
||||
""" Test that the payment method is compatible with its supported currencies. """
|
||||
self.payment_method.supported_currency_ids = [Command.set([self.currency_euro.id])]
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, currency_id=self.currency_euro.id
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_not_compatible_with_unsupported_currencies(self):
|
||||
""" Test that the payment method is not compatible with a currency that is not
|
||||
supported. """
|
||||
self.payment_method.supported_currency_ids = [Command.set([self.currency_euro.id])]
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, currency_id=self.currency_usd.id
|
||||
)
|
||||
self.assertNotIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_compatible_when_no_supported_currencies_set(self):
|
||||
""" Test that the payment method is always compatible when no supported currencies are
|
||||
set. """
|
||||
self.payment_method.supported_currency_ids = [Command.clear()]
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, currency_id=self.currency_euro.id
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_compatible_when_tokenization_forced(self):
|
||||
""" Test that the payment method is compatible when it supports tokenization while it is
|
||||
forced by the calling module. """
|
||||
self.payment_method.support_tokenization = True
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, force_tokenization=True
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_not_compatible_when_tokenization_forced(self):
|
||||
""" Test that the payment method is not compatible when it does not support tokenization
|
||||
while it is forced by the calling module. """
|
||||
self.payment_method.support_tokenization = False
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, force_tokenization=True
|
||||
)
|
||||
self.assertNotIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_compatible_with_express_checkout(self):
|
||||
""" Test that the payment method is compatible when it supports express checkout while it is
|
||||
an express checkout flow. """
|
||||
self.payment_method.support_express_checkout = True
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, is_express_checkout=True
|
||||
)
|
||||
self.assertIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_payment_method_not_compatible_with_express_checkout(self):
|
||||
""" Test that the payment method is not compatible when it does not support express checkout
|
||||
while it is an express checkout flow. """
|
||||
self.payment_method.support_express_checkout = False
|
||||
compatible_payment_methods = self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids, self.partner.id, is_express_checkout=True
|
||||
)
|
||||
self.assertNotIn(self.payment_method, compatible_payment_methods)
|
||||
|
||||
def test_availability_report_covers_all_reasons(self):
|
||||
""" Test that every possible unavailability reason is correctly reported. """
|
||||
# Disable all payment methods.
|
||||
pms = self.env['payment.method'].search([('is_primary', '=', True)])
|
||||
pms.active = False
|
||||
|
||||
# Prepare a base payment method.
|
||||
self.payment_method.write({
|
||||
'active': True,
|
||||
'support_express_checkout': True,
|
||||
'support_tokenization': True,
|
||||
})
|
||||
|
||||
# Prepare the report with a provider to allow checking provider availability.
|
||||
report = {}
|
||||
payment_utils.add_to_report(report, self.provider)
|
||||
|
||||
# Prepare a payment method with an unavailable provider.
|
||||
unavailable_provider = self.provider.copy()
|
||||
payment_utils.add_to_report(report, unavailable_provider, available=False, reason="test")
|
||||
no_provider_pm = self.payment_method.copy()
|
||||
no_provider_pm.provider_ids = [Command.set([unavailable_provider.id])]
|
||||
unavailable_provider.payment_method_ids = [Command.set([no_provider_pm.id])]
|
||||
|
||||
# Prepare a payment method with an incompatible country.
|
||||
invalid_country_pm = self.payment_method.copy()
|
||||
belgium = self.env.ref('base.be')
|
||||
invalid_country_pm.supported_country_ids = [Command.set([belgium.id])]
|
||||
france = self.env.ref('base.fr')
|
||||
self.partner.country_id = france
|
||||
|
||||
# Prepare a payment method with an incompatible currency.
|
||||
invalid_currency_pm = self.payment_method.copy()
|
||||
invalid_currency_pm.supported_currency_ids = [Command.set([self.currency_euro.id])]
|
||||
|
||||
# Prepare a payment method without support for tokenization.
|
||||
no_tokenization_pm = self.payment_method.copy()
|
||||
no_tokenization_pm.support_tokenization = False
|
||||
|
||||
# Prepare a payment method without support for express checkout.
|
||||
no_express_checkout_pm = self.payment_method.copy()
|
||||
no_express_checkout_pm.support_express_checkout = False
|
||||
|
||||
# Get compatible payment methods to generate their availability report.
|
||||
self.env['payment.method']._get_compatible_payment_methods(
|
||||
self.provider.ids,
|
||||
self.partner.id,
|
||||
currency_id=self.currency_usd.id,
|
||||
force_tokenization=True,
|
||||
is_express_checkout=True,
|
||||
report=report,
|
||||
)
|
||||
|
||||
# Compare the generated payment methods report with the expected one.
|
||||
expected_pms_report = {
|
||||
self.payment_method: {
|
||||
'available': True,
|
||||
'reason': '',
|
||||
'supported_providers': [(self.provider, True)],
|
||||
},
|
||||
no_provider_pm: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['provider_not_available'],
|
||||
'supported_providers': [(unavailable_provider, False)],
|
||||
},
|
||||
invalid_country_pm: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['incompatible_country'],
|
||||
'supported_providers': [(self.provider, True)],
|
||||
},
|
||||
invalid_currency_pm: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['incompatible_currency'],
|
||||
'supported_providers': [(self.provider, True)],
|
||||
},
|
||||
no_tokenization_pm: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['tokenization_not_supported'],
|
||||
'supported_providers': [(self.provider, True)],
|
||||
},
|
||||
no_express_checkout_pm: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['express_checkout_not_supported'],
|
||||
'supported_providers': [(self.provider, True)],
|
||||
},
|
||||
}
|
||||
self.maxDiff = None
|
||||
self.assertDictEqual(report['payment_methods'], expected_pms_report)
|
||||
|
|
@ -1,12 +1,100 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
from json.decoder import JSONDecodeError
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.const import REPORT_REASONS_MAPPING
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestPaymentprovider(PaymentCommon):
|
||||
class TestPaymentProvider(PaymentCommon):
|
||||
|
||||
def test_changing_provider_state_archives_tokens(self):
|
||||
""" Test that all active tokens of a provider are archived when its state is changed. """
|
||||
for old_state in ('enabled', 'test'): # No need to check when the provided was disabled.
|
||||
for new_state in ('enabled', 'test', 'disabled'):
|
||||
if old_state != new_state: # No need to check when the state is unchanged.
|
||||
self.provider.state = old_state
|
||||
token = self._create_token()
|
||||
self.provider.state = new_state
|
||||
self.assertFalse(token.active)
|
||||
|
||||
def test_enabling_provider_activates_default_payment_methods(self):
|
||||
""" Test that the default payment methods of a provider are activated when it is
|
||||
enabled. """
|
||||
self.payment_methods.active = False
|
||||
for new_state in ('enabled', 'test'):
|
||||
self.provider.state = 'disabled'
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_provider.PaymentProvider'
|
||||
'._get_default_payment_method_codes', return_value={self.payment_method_code},
|
||||
):
|
||||
self.provider.state = new_state
|
||||
self.assertTrue(self.payment_methods.active)
|
||||
|
||||
def test_enabling_manual_capture_provider_activates_compatible_default_pms(self):
|
||||
"""Test that only payment methods supporting manual capture are activated when a provider
|
||||
requiring manual capture is enabled."""
|
||||
payment_method_with_manual_capture = self.env['payment.method'].create({
|
||||
'name': 'Payment Method With Manual Capture',
|
||||
'code': 'pm_with_manual_capture',
|
||||
'support_manual_capture': 'full_only',
|
||||
})
|
||||
self.provider.state = 'disabled'
|
||||
self.provider.capture_manually = True
|
||||
self.provider.payment_method_ids = [Command.set([
|
||||
self.payment_method.id, payment_method_with_manual_capture.id
|
||||
])]
|
||||
self.payment_method.support_manual_capture = 'none'
|
||||
default_codes = {self.payment_method_code, payment_method_with_manual_capture.code}
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_provider.PaymentProvider'
|
||||
'._get_default_payment_method_codes', return_value=default_codes,
|
||||
):
|
||||
self.provider.state = 'test'
|
||||
self.assertFalse(self.payment_methods.active)
|
||||
self.assertTrue(payment_method_with_manual_capture.active)
|
||||
|
||||
def test_disabling_provider_deactivates_default_payment_methods(self):
|
||||
""" Test that the default payment methods of a provider are deactivated when it is
|
||||
disabled. """
|
||||
self.payment_methods.active = True
|
||||
for old_state in ('enabled', 'test'):
|
||||
self.provider.state = old_state
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_provider.PaymentProvider'
|
||||
'._get_default_payment_method_codes', return_value=self.payment_method_code,
|
||||
):
|
||||
self.provider.state = 'disabled'
|
||||
self.assertFalse(self.payment_methods.active)
|
||||
|
||||
def test_enabling_provider_activates_processing_cron(self):
|
||||
""" Test that the post-processing cron is activated when a provider is enabled. """
|
||||
self.env['payment.provider'].search([]).state = 'disabled' # Reset providers' state.
|
||||
post_processing_cron = self.env.ref('payment.cron_post_process_payment_tx')
|
||||
for enabled_state in ('enabled', 'test'):
|
||||
post_processing_cron.active = False # Reset the cron's active field.
|
||||
self.provider.state = 'disabled' # Prepare the dummy provider for enabling.
|
||||
self.provider.state = enabled_state
|
||||
self.assertTrue(post_processing_cron.active)
|
||||
|
||||
def test_disabling_provider_deactivates_processing_cron(self):
|
||||
""" Test that the post-processing cron is deactivated when a provider is disabled. """
|
||||
self.env['payment.provider'].search([]).state = 'disabled' # Reset providers' state.
|
||||
post_processing_cron = self.env.ref('payment.cron_post_process_payment_tx')
|
||||
for enabled_state in ('enabled', 'test'):
|
||||
post_processing_cron.active = True # Reset the cron's active field.
|
||||
self.provider.state = enabled_state # Prepare the dummy provider for disabling.
|
||||
self.provider.state = 'disabled'
|
||||
self.assertFalse(post_processing_cron.active)
|
||||
|
||||
def test_published_provider_compatible_with_all_users(self):
|
||||
""" Test that a published provider is always available to all users. """
|
||||
|
|
@ -38,13 +126,56 @@ class TestPaymentprovider(PaymentCommon):
|
|||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_with_branch_companies(self):
|
||||
""" Test that the provider is available to branch companies. """
|
||||
branch_company = self.env['res.company'].create({
|
||||
'name': "Provider Branch Company",
|
||||
'parent_id': self.provider.company_id.id,
|
||||
})
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
branch_company.id, self.partner.id, self.amount,
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_with_available_countries(self):
|
||||
""" Test that the provider is compatible with its available countries. """
|
||||
belgium = self.env.ref('base.be')
|
||||
self.provider.available_country_ids = [Command.set([belgium.id])]
|
||||
self.partner.country_id = belgium
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_not_compatible_with_unavailable_countries(self):
|
||||
""" Test that the provider is not compatible with a country that is not available. """
|
||||
belgium = self.env.ref('base.be')
|
||||
self.provider.available_country_ids = [Command.set([belgium.id])]
|
||||
france = self.env.ref('base.fr')
|
||||
self.partner.country_id = france
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_when_no_available_countries_set(self):
|
||||
""" Test that the provider is always compatible when no available countries are set. """
|
||||
self.provider.available_country_ids = [Command.clear()]
|
||||
belgium = self.env.ref('base.be')
|
||||
self.partner.country_id = belgium
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_when_maximum_amount_is_zero(self):
|
||||
""" Test that the maximum amount has no effect on the provider's compatibility when it is
|
||||
set to 0. """
|
||||
self.provider.maximum_amount = 0.
|
||||
currency = self.provider.main_currency_id.id
|
||||
|
||||
compatible_providers = self.env['payment.provider']._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, currency_id=self.env.company.currency_id.id,
|
||||
self.company.id, self.partner.id, self.amount, currency_id=currency
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
|
|
@ -52,9 +183,10 @@ class TestPaymentprovider(PaymentCommon):
|
|||
""" Test that a provider is compatible when the payment amount is less than the maximum
|
||||
amount. """
|
||||
self.provider.maximum_amount = self.amount + 10.0
|
||||
currency = self.provider.main_currency_id.id
|
||||
|
||||
compatible_providers = self.env['payment.provider']._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, currency_id=self.env.company.currency_id.id,
|
||||
self.company.id, self.partner.id, self.amount, currency_id=currency
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
|
|
@ -62,8 +194,240 @@ class TestPaymentprovider(PaymentCommon):
|
|||
""" Test that a provider is not compatible when the payment amount is more than the maximum
|
||||
amount. """
|
||||
self.provider.maximum_amount = self.amount - 10.0
|
||||
currency = self.provider.main_currency_id.id
|
||||
|
||||
compatible_providers = self.env['payment.provider']._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, currency_id=self.env.company.currency_id.id,
|
||||
self.company.id, self.partner.id, self.amount, currency_id=currency
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_with_available_currencies(self):
|
||||
""" Test that the provider is compatible with its available currencies. """
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, currency_id=self.currency_euro.id
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_not_compatible_with_unavailable_currencies(self):
|
||||
""" Test that the provider is not compatible with a currency that is not available. """
|
||||
# Make sure the list of available currencies is not empty.
|
||||
self.provider.available_currency_ids = [Command.unlink(self.currency_usd.id)]
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, currency_id=self.currency_usd.id
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_when_no_available_currencies_set(self):
|
||||
""" Test that the provider is always compatible when no available currency is set. """
|
||||
self.provider.available_currency_ids = [Command.clear()]
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, currency_id=self.currency_euro.id
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_when_tokenization_forced(self):
|
||||
""" Test that the provider is compatible when it allows tokenization while it is forced by
|
||||
the calling module. """
|
||||
self.provider.allow_tokenization = True
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, force_tokenization=True
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_not_compatible_when_tokenization_forced(self):
|
||||
""" Test that the provider is not compatible when it does not allow tokenization while it
|
||||
is forced by the calling module. """
|
||||
self.provider.allow_tokenization = False
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, force_tokenization=True
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_when_tokenization_required(self):
|
||||
""" Test that the provider is compatible when it allows tokenization while it is required by
|
||||
the payment context (e.g., when paying for a subscription). """
|
||||
self.provider.allow_tokenization = True
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_provider.PaymentProvider._is_tokenization_required',
|
||||
return_value=True,
|
||||
):
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_not_compatible_when_tokenization_required(self):
|
||||
""" Test that the provider is not compatible when it does not allow tokenization while it
|
||||
is required by the payment context (e.g., when paying for a subscription). """
|
||||
self.provider.allow_tokenization = False
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_provider.PaymentProvider._is_tokenization_required',
|
||||
return_value=True,
|
||||
):
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_compatible_with_express_checkout(self):
|
||||
""" Test that the provider is compatible when it allows express checkout while it is an
|
||||
express checkout flow. """
|
||||
self.provider.allow_express_checkout = True
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, is_express_checkout=True
|
||||
)
|
||||
self.assertIn(self.provider, compatible_providers)
|
||||
|
||||
def test_provider_not_compatible_with_express_checkout(self):
|
||||
""" Test that the provider is not compatible when it does not allow express checkout while
|
||||
it is an express checkout flow. """
|
||||
self.provider.allow_express_checkout = False
|
||||
compatible_providers = self.provider._get_compatible_providers(
|
||||
self.company.id, self.partner.id, self.amount, is_express_checkout=True
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_availability_report_covers_all_reasons(self):
|
||||
""" Test that every possible unavailability reason is correctly reported. """
|
||||
# Disable all providers.
|
||||
providers = self.env['payment.provider'].search([])
|
||||
providers.state = 'disabled'
|
||||
|
||||
# Prepare a base provider.
|
||||
self.provider.write({
|
||||
'state': 'test',
|
||||
'allow_express_checkout': True,
|
||||
'allow_tokenization': True,
|
||||
})
|
||||
|
||||
# Prepare a provider with an incompatible country.
|
||||
invalid_country_provider = self.provider.copy()
|
||||
belgium = self.env.ref('base.be')
|
||||
invalid_country_provider.write({
|
||||
'state': 'test',
|
||||
'available_country_ids': [Command.set([belgium.id])],
|
||||
})
|
||||
france = self.env.ref('base.fr')
|
||||
self.partner.country_id = france
|
||||
|
||||
# Prepare a provider with a maximum amount lower than the payment amount.
|
||||
exceeding_max_provider = self.provider.copy()
|
||||
exceeding_max_provider.write({
|
||||
'state': 'test',
|
||||
'maximum_amount': self.amount - 10.0,
|
||||
})
|
||||
|
||||
# Prepare a provider with an incompatible currency.
|
||||
invalid_currency_provider = self.provider.copy()
|
||||
invalid_currency_provider.write({
|
||||
'state': 'test',
|
||||
'available_currency_ids': [Command.unlink(self.currency_usd.id)],
|
||||
})
|
||||
|
||||
# Prepare a provider without tokenization support.
|
||||
no_tokenization_provider = self.provider.copy()
|
||||
no_tokenization_provider.write({
|
||||
'state': 'test',
|
||||
'allow_tokenization': False,
|
||||
})
|
||||
|
||||
# Prepare a provider without express checkout support.
|
||||
no_express_checkout_provider = self.provider.copy()
|
||||
no_express_checkout_provider.write({
|
||||
'state': 'test',
|
||||
'allow_express_checkout': False,
|
||||
})
|
||||
|
||||
# Get compatible providers to generate their availability report.
|
||||
report = {}
|
||||
self.env['payment.provider']._get_compatible_providers(
|
||||
self.company_id,
|
||||
self.partner.id,
|
||||
self.amount,
|
||||
currency_id=self.currency_usd.id,
|
||||
force_tokenization=True,
|
||||
is_express_checkout=True,
|
||||
report=report,
|
||||
)
|
||||
|
||||
# Compare the generated providers report with the expected one.
|
||||
expected_providers_report = {
|
||||
self.provider: {
|
||||
'available': True,
|
||||
'reason': '',
|
||||
},
|
||||
invalid_country_provider: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['incompatible_country'],
|
||||
},
|
||||
exceeding_max_provider: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['exceed_max_amount'],
|
||||
},
|
||||
invalid_currency_provider: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['incompatible_currency'],
|
||||
},
|
||||
no_tokenization_provider: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['tokenization_not_supported'],
|
||||
},
|
||||
no_express_checkout_provider: {
|
||||
'available': False,
|
||||
'reason': REPORT_REASONS_MAPPING['express_checkout_not_supported'],
|
||||
},
|
||||
}
|
||||
self.maxDiff = None
|
||||
self.assertDictEqual(report['providers'], expected_providers_report)
|
||||
|
||||
def test_validation_currency_is_supported(self):
|
||||
""" Test that only currencies supported by both the provider and the payment method can be
|
||||
used in validation operations. """
|
||||
self.provider.available_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
self.payment_method.supported_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertEqual(validation_currency, self.provider.company_id.currency_id)
|
||||
|
||||
self.provider.available_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
self.payment_method.supported_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertIn(validation_currency, self.provider.available_currency_ids)
|
||||
|
||||
self.provider.available_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
self.payment_method.supported_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertIn(validation_currency, self.payment_method.supported_currency_ids)
|
||||
|
||||
self.provider.available_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
self.payment_method.supported_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertIn(validation_currency, self.provider.available_currency_ids)
|
||||
self.assertIn(validation_currency, self.payment_method.supported_currency_ids)
|
||||
|
||||
@mute_logger('odoo.addons.payment.models.payment_provider')
|
||||
def test_parsing_non_json_response_falls_back_to_text_response(self):
|
||||
"""Test that a non-JSON response is smoothly parsed as a text response."""
|
||||
response = requests.Response()
|
||||
response.status_code = 502
|
||||
response._content = b"<html><body>Cloudflare Error</body></html>"
|
||||
with (
|
||||
patch('requests.request', return_value=response),
|
||||
patch(
|
||||
'odoo.addons.payment.models.payment_provider.PaymentProvider._parse_response_error',
|
||||
new=lambda _self, _response: _response.json(),
|
||||
),
|
||||
):
|
||||
try:
|
||||
self.provider._send_api_request('GET', '/dummy')
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.assertNotIsInstance(e, JSONDecodeError)
|
||||
self.assertIsInstance(e, ValidationError)
|
||||
self.assertIn("Cloudflare Error", e.args[0])
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ from datetime import date
|
|||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
|
@ -13,9 +14,31 @@ from odoo.addons.payment.tests.common import PaymentCommon
|
|||
@tagged('-at_install', 'post_install')
|
||||
class TestPaymentToken(PaymentCommon):
|
||||
|
||||
def test_token_cannot_be_unarchived(self):
|
||||
""" Test that unarchiving disabled tokens is forbidden. """
|
||||
@mute_logger('odoo.addons.base.models.ir_rule')
|
||||
def test_users_have_no_access_to_other_users_tokens(self):
|
||||
users = [self.public_user, self.portal_user, self.internal_user]
|
||||
token = self._create_token(partner_id=self.admin_partner.id)
|
||||
for user in users:
|
||||
with self.assertRaises(AccessError):
|
||||
token.with_user(user).read()
|
||||
|
||||
def test_cannot_assign_token_to_public_partner(self):
|
||||
""" Test that no token can be assigned to the public partner. """
|
||||
token = self._create_token()
|
||||
with self.assertRaises(ValidationError):
|
||||
token.partner_id = self.public_user.partner_id
|
||||
|
||||
def test_unarchiving_token_requires_active_provider(self):
|
||||
""" Test that unarchiving disabled tokens is forbidden if the provider is disabled. """
|
||||
token = self._create_token(active=False)
|
||||
token.provider_id.state = 'disabled'
|
||||
with self.assertRaises(UserError):
|
||||
token.active = True
|
||||
|
||||
def test_unarchiving_token_requires_active_payment_method(self):
|
||||
""" Test that unarchiving disabled tokens is forbidden if the method is disabled. """
|
||||
token = self._create_token(active=False)
|
||||
token.payment_method_id.active = False
|
||||
with self.assertRaises(UserError):
|
||||
token.active = True
|
||||
|
||||
|
|
@ -24,7 +47,7 @@ class TestPaymentToken(PaymentCommon):
|
|||
token = self._create_token()
|
||||
self.assertEqual(token._build_display_name(), '•••• 1234')
|
||||
|
||||
@freeze_time('2024-1-31 10:00:00')
|
||||
@freeze_time('2024-01-31 10:00:00')
|
||||
def test_display_name_for_empty_payment_details(self):
|
||||
""" Test that the display name is still built for token without payment details. """
|
||||
token = self._create_token(payment_details='')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
|
@ -9,28 +12,38 @@ from odoo.addons.payment.tests.common import PaymentCommon
|
|||
@tagged('-at_install', 'post_install')
|
||||
class TestPaymentTransaction(PaymentCommon):
|
||||
|
||||
def test_is_live_when_created_by_enabled_provider(self):
|
||||
self.provider.state = 'enabled'
|
||||
tx = self._create_transaction('redirect')
|
||||
self.assertTrue(tx.is_live)
|
||||
|
||||
def test_is_not_live_when_created_by_test_provider(self):
|
||||
self.provider.state = 'test' # Will work with anything other than 'enabled'
|
||||
tx = self._create_transaction('redirect')
|
||||
self.assertFalse(tx.is_live)
|
||||
|
||||
def test_capture_allowed_for_authorized_users(self):
|
||||
""" Test that users who have access to a transaction can capture it. """
|
||||
if 'account' not in self.env["ir.module.module"]._installed():
|
||||
self.skipTest("account module is not installed")
|
||||
self.provider.support_manual_capture = True
|
||||
if not self.env.ref('account.group_account_invoice', raise_if_not_found=False):
|
||||
self.skipTest("account needed for test")
|
||||
self.provider.support_manual_capture = 'full_only'
|
||||
tx = self._create_transaction('redirect', state='authorized')
|
||||
user = self._prepare_user(self.internal_user, 'account.group_account_invoice')
|
||||
self._assert_does_not_raise(AccessError, tx.with_user(user).action_capture)
|
||||
|
||||
def test_void_allowed_for_authorized_users(self):
|
||||
""" Test that users who have access to a transaction can void it. """
|
||||
if 'account' not in self.env["ir.module.module"]._installed():
|
||||
self.skipTest("account module is not installed")
|
||||
self.provider.support_manual_capture = True
|
||||
if not self.env.ref('account.group_account_invoice', raise_if_not_found=False):
|
||||
self.skipTest("account needed for test")
|
||||
self.provider.support_manual_capture = 'full_only'
|
||||
tx = self._create_transaction('redirect', state='authorized')
|
||||
user = self._prepare_user(self.internal_user, 'account.group_account_invoice')
|
||||
self._assert_does_not_raise(AccessError, tx.with_user(user).action_void)
|
||||
|
||||
def test_refund_allowed_for_authorized_users(self):
|
||||
""" Test that users who have access to a transaction can refund it. """
|
||||
if 'account' not in self.env["ir.module.module"]._installed():
|
||||
self.skipTest("account module is not installed")
|
||||
if not self.env.ref('account.group_account_invoice', raise_if_not_found=False):
|
||||
self.skipTest("account needed for test")
|
||||
self.provider.support_refund = 'full_only'
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
user = self._prepare_user(self.internal_user, 'account.group_account_invoice')
|
||||
|
|
@ -38,13 +51,13 @@ class TestPaymentTransaction(PaymentCommon):
|
|||
|
||||
def test_capture_blocked_for_unauthorized_user(self):
|
||||
""" Test that users who don't have access to a transaction cannot capture it. """
|
||||
self.provider.support_manual_capture = True
|
||||
self.provider.support_manual_capture = 'full_only'
|
||||
tx = self._create_transaction('redirect', state='authorized')
|
||||
self.assertRaises(AccessError, tx.with_user(self.internal_user).action_capture)
|
||||
|
||||
def test_void_blocked_for_unauthorized_user(self):
|
||||
""" Test that users who don't have access to a transaction cannot void it. """
|
||||
self.provider.support_manual_capture = True
|
||||
self.provider.support_manual_capture = 'full_only'
|
||||
tx = self._create_transaction('redirect', state='authorized')
|
||||
self.assertRaises(AccessError, tx.with_user(self.internal_user).action_void)
|
||||
|
||||
|
|
@ -66,7 +79,7 @@ class TestPaymentTransaction(PaymentCommon):
|
|||
state='done',
|
||||
operation=operation, # Override the computed flow
|
||||
source_transaction_id=tx.id,
|
||||
)._reconcile_after_done()
|
||||
)._post_process()
|
||||
|
||||
self.assertEqual(
|
||||
tx.refunds_count,
|
||||
|
|
@ -74,12 +87,30 @@ class TestPaymentTransaction(PaymentCommon):
|
|||
msg="The refunds count should only consider transactions with operation 'refund'."
|
||||
)
|
||||
|
||||
def test_capturing_tx_creates_child_tx(self):
|
||||
"""Test that capturing a transaction creates a child capture transaction."""
|
||||
self.provider.capture_manually = True
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
source_tx = self._create_transaction('direct', state='authorized')
|
||||
child_tx = source_tx._capture()
|
||||
self.assertTrue(child_tx)
|
||||
self.assertNotEqual(child_tx, source_tx)
|
||||
|
||||
def test_voiding_tx_creates_child_tx(self):
|
||||
"""Test that voiding a transaction creates a child void transaction."""
|
||||
self.provider.capture_manually = True
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
source_tx = self._create_transaction('direct', state='authorized')
|
||||
child_tx = source_tx._void()
|
||||
self.assertTrue(child_tx)
|
||||
self.assertNotEqual(child_tx, source_tx)
|
||||
|
||||
def test_refund_transaction_values(self):
|
||||
self.provider.support_refund = 'partial'
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
|
||||
# Test the default values of a full refund transaction
|
||||
refund_tx = tx._create_refund_transaction()
|
||||
refund_tx = tx._create_child_transaction(tx.amount, is_refund=True)
|
||||
self.assertEqual(
|
||||
refund_tx.reference,
|
||||
f'R-{tx.reference}',
|
||||
|
|
@ -99,7 +130,7 @@ class TestPaymentTransaction(PaymentCommon):
|
|||
self.assertEqual(
|
||||
refund_tx.currency_id,
|
||||
tx.currency_id,
|
||||
msg="The currency of the refund transaction should that of the source transaction."
|
||||
msg="The currency of the refund transaction should be that of the source transaction."
|
||||
)
|
||||
self.assertEqual(
|
||||
refund_tx.operation,
|
||||
|
|
@ -114,11 +145,11 @@ class TestPaymentTransaction(PaymentCommon):
|
|||
self.assertEqual(
|
||||
refund_tx.partner_id,
|
||||
tx.partner_id,
|
||||
msg="The partner of the refund transaction should that of the source transaction."
|
||||
msg="The partner of the refund transaction should be that of the source transaction."
|
||||
)
|
||||
|
||||
# Test the values of a partial refund transaction with custom refund amount
|
||||
partial_refund_tx = tx._create_refund_transaction(amount_to_refund=11.11)
|
||||
partial_refund_tx = tx._create_child_transaction(11.11, is_refund=True)
|
||||
self.assertAlmostEqual(
|
||||
partial_refund_tx.amount,
|
||||
-11.11,
|
||||
|
|
@ -126,3 +157,199 @@ class TestPaymentTransaction(PaymentCommon):
|
|||
msg="The amount of the refund transaction should be the negative value of the amount "
|
||||
"to refund."
|
||||
)
|
||||
|
||||
def test_partial_capture_transaction_values(self):
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
self.provider.capture_manually = True
|
||||
tx = self._create_transaction('redirect', state='authorized')
|
||||
|
||||
capture_tx = tx._create_child_transaction(11.11)
|
||||
self.assertEqual(
|
||||
capture_tx.reference,
|
||||
f'P-{tx.reference}',
|
||||
msg="The reference of a partial capture should be the prefixed reference of the source "
|
||||
"transaction.",
|
||||
)
|
||||
self.assertEqual(
|
||||
capture_tx.amount,
|
||||
11.11,
|
||||
msg="The amount of a partial capture should be the one passed as argument.",
|
||||
)
|
||||
self.assertEqual(
|
||||
capture_tx.currency_id,
|
||||
tx.currency_id,
|
||||
msg="The currency of the partial capture should be that of the source transaction.",
|
||||
)
|
||||
self.assertEqual(
|
||||
capture_tx.operation,
|
||||
tx.operation,
|
||||
msg="The operation of the partial capture should be the same as the source"
|
||||
" transaction.",
|
||||
)
|
||||
self.assertEqual(
|
||||
tx,
|
||||
capture_tx.source_transaction_id,
|
||||
msg="The partial capture transaction should be linked to the source transaction.",
|
||||
)
|
||||
self.assertEqual(
|
||||
capture_tx.partner_id,
|
||||
tx.partner_id,
|
||||
msg="The partner of the partial capture should be that of the source transaction.",
|
||||
)
|
||||
|
||||
def test_capturing_child_tx_triggers_source_tx_state_update(self):
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
self.provider.capture_manually = True
|
||||
source_tx = self._create_transaction(flow='direct', state='authorized')
|
||||
child_tx_1 = source_tx._create_child_transaction(100)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._update_source_transaction_state'
|
||||
) as patched:
|
||||
child_tx_1._set_done()
|
||||
patched.assert_called_once()
|
||||
|
||||
def test_voiding_child_tx_triggers_source_tx_state_update(self):
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
self.provider.capture_manually = True
|
||||
source_tx = self._create_transaction(flow='direct', state='authorized')
|
||||
child_tx_1 = source_tx._create_child_transaction(100)
|
||||
child_tx_1._set_done()
|
||||
child_tx_2 = source_tx._create_child_transaction(source_tx.amount-100)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._update_source_transaction_state'
|
||||
) as patched:
|
||||
child_tx_2._set_canceled()
|
||||
patched.assert_called_once()
|
||||
|
||||
def test_capturing_partial_amount_leaves_source_tx_authorized(self):
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
self.provider.capture_manually = True
|
||||
source_tx = self._create_transaction(flow='direct', state='authorized')
|
||||
child_tx_1 = source_tx._create_child_transaction(100)
|
||||
child_tx_1._set_done()
|
||||
self.assertEqual(
|
||||
source_tx.state,
|
||||
'authorized',
|
||||
msg="The whole amount of the source transaction has not been processed yet, it's state "
|
||||
"should still be 'authorized'.",
|
||||
)
|
||||
|
||||
def test_capturing_full_amount_confirms_source_tx(self):
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
self.provider.capture_manually = True
|
||||
source_tx = self._create_transaction(flow='direct', state='authorized')
|
||||
child_tx_1 = source_tx._create_child_transaction(100)
|
||||
child_tx_1._set_done()
|
||||
child_tx_2 = source_tx._create_child_transaction(source_tx.amount - 100)
|
||||
child_tx_2._set_canceled()
|
||||
self.assertEqual(
|
||||
source_tx.state,
|
||||
'done',
|
||||
msg="The whole amount of the source transaction has been processed, it's state is now "
|
||||
"'done'."
|
||||
)
|
||||
|
||||
def test_validate_amount_skips_validation_transactions(self):
|
||||
"""Test that the amount validation is skipped for validation transactions."""
|
||||
tx = self._create_transaction('redirect', operation='validation')
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._extract_amount_data', return_value={'amount': None, 'currency_code': None},
|
||||
):
|
||||
tx._validate_amount({})
|
||||
self.assertNotEqual(tx.state, 'error')
|
||||
|
||||
def test_processing_applies_updates_to_error_txs_with_valid_amount_data(self):
|
||||
tx = self._create_transaction('redirect', state='error')
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._validate_amount'
|
||||
), patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._apply_updates'
|
||||
) as apply_updates_mock:
|
||||
tx._process('test', {})
|
||||
self.assertEqual(apply_updates_mock.call_count, 1)
|
||||
|
||||
def test_processing_does_not_apply_updates_when_amount_data_is_invalid(self):
|
||||
tx = self._create_transaction('redirect', state='draft', amount=100)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._extract_amount_data', return_value={'amount': 10, 'currency_code': 'USD'}
|
||||
), patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._apply_updates'
|
||||
) as apply_updates_mock:
|
||||
tx._process('test', {})
|
||||
self.assertEqual(tx.state, 'error')
|
||||
self.assertEqual(apply_updates_mock.call_count, 0)
|
||||
|
||||
def test_processing_tokenizes_validated_transaction(self):
|
||||
"""Test that `_process` tokenizes 'authorized' and 'done' transactions when possible."""
|
||||
self.provider.support_manual_capture = 'partial'
|
||||
self.provider.capture_manually = True
|
||||
for state in ['authorized', 'done']:
|
||||
tx = self._create_transaction(
|
||||
'redirect', reference=f'Test {state}', state=state, tokenize=True
|
||||
)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._validate_amount', return_value=None
|
||||
), patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._extract_token_values', return_value={'provider_ref': 'test'}
|
||||
):
|
||||
tx._process('test', {})
|
||||
self.assertTrue(tx.token_id)
|
||||
|
||||
def test_processing_only_tokenizes_when_requested(self):
|
||||
"""Test that `_process` only triggers tokenization if the user requested it."""
|
||||
tx = self._create_transaction('redirect', state='done', tokenize=False)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._validate_amount', return_value=None
|
||||
), patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._tokenize'
|
||||
) as tokenize_mock:
|
||||
tx._process('test', {})
|
||||
self.assertEqual(tokenize_mock.call_count, 0)
|
||||
|
||||
@mute_logger('odoo.addons.payment.models.payment_transaction')
|
||||
def test_update_state_to_illegal_target_state(self):
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._update_state(['draft', 'pending', 'authorized'], 'cancel', None)
|
||||
self.assertEqual(tx.state, 'done')
|
||||
|
||||
def test_update_state_to_extra_allowed_state(self):
|
||||
tx = self._create_transaction('redirect', state='done')
|
||||
tx._update_state(
|
||||
['draft', 'pending', 'authorized', 'done'], 'cancel', None
|
||||
)
|
||||
self.assertEqual(tx.state, 'cancel')
|
||||
|
||||
def test_updating_state_resets_post_processing_status(self):
|
||||
if self.account_payment_installed:
|
||||
self.skipTest("This test should not be run after account_payment is installed.")
|
||||
|
||||
tx = self._create_transaction('redirect', state='draft')
|
||||
tx._set_pending()
|
||||
self.assertFalse(tx.is_post_processed)
|
||||
tx._post_process()
|
||||
self.assertTrue(tx.is_post_processed)
|
||||
|
||||
tx._set_done()
|
||||
self.assertFalse(tx.is_post_processed)
|
||||
|
||||
def test_validate_amount_uses_payment_minor_unit(self):
|
||||
self.currency_euro.rounding = 0.001
|
||||
tx = self._create_transaction('direct', amount=123.452, currency_id=self.currency_euro.id)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._extract_amount_data',
|
||||
return_value={'amount': 123.45, 'currency_code': self.currency_euro.name},
|
||||
):
|
||||
tx._validate_amount({})
|
||||
self.assertNotEqual(tx.state, 'error')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestResCompany(PaymentCommon):
|
||||
|
||||
def test_creating_company_duplicates_providers(self):
|
||||
"""Ensure that installed payment providers of an existing company are correctly duplicated
|
||||
when a new company is created."""
|
||||
main_company = self.env.company
|
||||
main_company_providers_count = self.env['payment.provider'].search_count([
|
||||
('company_id', '=', main_company.id),
|
||||
('module_state', '=', 'installed'),
|
||||
])
|
||||
|
||||
new_company = self.env['res.company'].create({'name': 'New Company'})
|
||||
new_company_providers_count = self.env['payment.provider'].search_count([
|
||||
('company_id', '=', new_company.id),
|
||||
('module_state', '=', 'installed'),
|
||||
])
|
||||
|
||||
self.assertEqual(new_company_providers_count, main_company_providers_count)
|
||||
Loading…
Add table
Add a link
Reference in a new issue