mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 21:12:06 +02:00
355 lines
16 KiB
Python
355 lines
16 KiB
Python
# 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
|
|
|
|
|
|
@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 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 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 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')
|
|
self._assert_does_not_raise(AccessError, tx.with_user(user).action_refund)
|
|
|
|
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 = '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 = 'full_only'
|
|
tx = self._create_transaction('redirect', state='authorized')
|
|
self.assertRaises(AccessError, tx.with_user(self.internal_user).action_void)
|
|
|
|
def test_refund_blocked_for_unauthorized_user(self):
|
|
""" Test that users who don't have access to a transaction cannot refund it. """
|
|
self.provider.support_refund = 'full_only'
|
|
tx = self._create_transaction('redirect', state='done')
|
|
self.assertRaises(AccessError, tx.with_user(self.internal_user).action_refund)
|
|
|
|
def test_refunds_count(self):
|
|
self.provider.support_refund = 'full_only' # Should simply not be False
|
|
tx = self._create_transaction('redirect', state='done')
|
|
for reference_index, operation in enumerate(
|
|
('online_redirect', 'online_direct', 'online_token', 'validation', 'refund')
|
|
):
|
|
self._create_transaction(
|
|
'dummy',
|
|
reference=f'R-{tx.reference}-{reference_index + 1}',
|
|
state='done',
|
|
operation=operation, # Override the computed flow
|
|
source_transaction_id=tx.id,
|
|
)._post_process()
|
|
|
|
self.assertEqual(
|
|
tx.refunds_count,
|
|
1,
|
|
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_child_transaction(tx.amount, is_refund=True)
|
|
self.assertEqual(
|
|
refund_tx.reference,
|
|
f'R-{tx.reference}',
|
|
msg="The reference of the refund transaction should be the prefixed reference of the "
|
|
"source transaction."
|
|
)
|
|
self.assertLess(
|
|
refund_tx.amount, 0, msg="The amount of a refund transaction should always be negative."
|
|
)
|
|
self.assertAlmostEqual(
|
|
refund_tx.amount,
|
|
-tx.amount,
|
|
places=2,
|
|
msg="The amount of the refund transaction should be taken from the amount of the "
|
|
"source transaction."
|
|
)
|
|
self.assertEqual(
|
|
refund_tx.currency_id,
|
|
tx.currency_id,
|
|
msg="The currency of the refund transaction should be that of the source transaction."
|
|
)
|
|
self.assertEqual(
|
|
refund_tx.operation,
|
|
'refund',
|
|
msg="The operation of the refund transaction should be 'refund'."
|
|
)
|
|
self.assertEqual(
|
|
tx,
|
|
refund_tx.source_transaction_id,
|
|
msg="The refund transaction should be linked to the source transaction."
|
|
)
|
|
self.assertEqual(
|
|
refund_tx.partner_id,
|
|
tx.partner_id,
|
|
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_child_transaction(11.11, is_refund=True)
|
|
self.assertAlmostEqual(
|
|
partial_refund_tx.amount,
|
|
-11.11,
|
|
places=2,
|
|
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')
|