Before this commit, it was not possible to refund a payment from Odoo. Users had to go through the payment acquirer's backend and update the payment accordingly in Odoo. With this commit, refunds are made available in Odoo directly from the payment form, for acquirers that support them. Acquirer can either only support full refunds or also support partial refunds. As of now, the only acquirer allowing refunds is Adyen, with partial refund support. task-2527891 closes odoo/odoo#70881 Related: odoo/upgrade#2689 Related: odoo/enterprise#19829 Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com>
118 lines
5.0 KiB
Python
118 lines
5.0 KiB
Python
# 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('-at_install', 'post_install')
|
|
class TestPayments(PaymentCommon):
|
|
|
|
def test_no_amount_available_for_refund_when_not_supported(self):
|
|
self.acquirer.support_refund = False
|
|
tx = self.create_transaction('redirect', state='done')
|
|
tx._reconcile_after_done() # Create the payment
|
|
self.assertEqual(
|
|
tx.payment_id.amount_available_for_refund,
|
|
0,
|
|
msg="The value of `amount_available_for_refund` should be 0 when the acquirer doesn't "
|
|
"support refunds."
|
|
)
|
|
|
|
def test_full_amount_available_for_refund_when_not_yet_refunded(self):
|
|
self.acquirer.support_refund = 'full_only' # Should simply not be False
|
|
tx = self.create_transaction('redirect', state='done')
|
|
tx._reconcile_after_done() # Create the payment
|
|
self.assertAlmostEqual(
|
|
tx.payment_id.amount_available_for_refund,
|
|
tx.amount,
|
|
places=2,
|
|
msg="The value of `amount_available_for_refund` should be that of `total` when there "
|
|
"are no linked refunds."
|
|
)
|
|
|
|
def test_full_amount_available_for_refund_when_refunds_are_pending(self):
|
|
self.acquirer.write({
|
|
'support_refund': 'full_only', # Should simply not be False
|
|
'support_authorization': True, # To create transaction in the 'authorized' state
|
|
})
|
|
tx = self.create_transaction('redirect', state='done')
|
|
tx._reconcile_after_done() # Create the payment
|
|
for reference_index, state in enumerate(('draft', 'pending', 'authorized')):
|
|
self.create_transaction(
|
|
'dummy',
|
|
amount=-tx.amount,
|
|
reference=f'R-{tx.reference}-{reference_index + 1}',
|
|
state=state,
|
|
operation='refund', # Override the computed flow
|
|
source_transaction_id=tx.id,
|
|
)
|
|
self.assertAlmostEqual(
|
|
tx.payment_id.amount_available_for_refund,
|
|
tx.payment_id.amount,
|
|
places=2,
|
|
msg="The value of `amount_available_for_refund` should be that of `total` when all the "
|
|
"linked refunds are pending (not in the state 'done')."
|
|
)
|
|
|
|
def test_no_amount_available_for_refund_when_fully_refunded(self):
|
|
self.acquirer.support_refund = 'full_only' # Should simply not be False
|
|
tx = self.create_transaction('redirect', state='done')
|
|
tx._reconcile_after_done() # Create the payment
|
|
self.create_transaction(
|
|
'dummy',
|
|
amount=-tx.amount,
|
|
reference=f'R-{tx.reference}',
|
|
state='done',
|
|
operation='refund', # Override the computed flow
|
|
source_transaction_id=tx.id,
|
|
)._reconcile_after_done()
|
|
self.assertEqual(
|
|
tx.payment_id.amount_available_for_refund,
|
|
0,
|
|
msg="The value of `amount_available_for_refund` should be 0 when there is a linked "
|
|
"refund of the full amount that is confirmed (state 'done')."
|
|
)
|
|
|
|
def test_no_full_amount_available_for_refund_when_partially_refunded(self):
|
|
self.acquirer.support_refund = 'partial'
|
|
tx = self.create_transaction('redirect', state='done')
|
|
tx._reconcile_after_done() # Create the payment
|
|
self.create_transaction(
|
|
'dummy',
|
|
amount=-(tx.amount / 10),
|
|
reference=f'R-{tx.reference}',
|
|
state='done',
|
|
operation='refund', # Override the computed flow
|
|
source_transaction_id=tx.id,
|
|
)._reconcile_after_done()
|
|
self.assertAlmostEqual(
|
|
tx.payment_id.amount_available_for_refund,
|
|
tx.payment_id.amount - (tx.amount / 10),
|
|
places=2,
|
|
msg="The value of `amount_available_for_refund` should be equal to the total amount "
|
|
"minus the sum of the absolute amount of the refunds that are confirmed (state "
|
|
"'done')."
|
|
)
|
|
|
|
def test_refunds_count(self):
|
|
self.acquirer.support_refund = 'full_only' # Should simply not be False
|
|
tx = self.create_transaction('redirect', state='done')
|
|
tx._reconcile_after_done() # Create the payment
|
|
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,
|
|
)._reconcile_after_done()
|
|
|
|
self.assertEqual(
|
|
tx.payment_id.refunds_count,
|
|
1,
|
|
msg="The refunds count should only consider transactions with operation 'refund'."
|
|
)
|