From 838e06cf48bb3cecdee689a051cc846816265e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Hol=C3=BD?= Date: Sun, 7 Apr 2024 13:30:14 +0200 Subject: [PATCH 1/2] fix backward compatibility by making PayuProvider's get_refund_description argument optional --- AUTHORS.rst | 2 +- HISTORY.rst | 8 ++ README.rst | 2 +- payments_payu/provider.py | 15 +- tests/test_payu.py | 282 ++++++++++++++++++++++++++------------ 5 files changed, 220 insertions(+), 89 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 661c530..123b311 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,4 +10,4 @@ Development Lead Contributors ------------ -* Radek HolĂ˝ +* BlenderKit diff --git a/HISTORY.rst b/HISTORY.rst index 7504961..ab57d08 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,15 +3,23 @@ History ------- +Unreleased +********** +* fix backward compatibility by making PayuProvider's get_refund_description argument optional +* make PayuProvider.refund fail if get_refund_description is not provided +* deprecate the default value of get_refund_description; set it to a callable instead + 1.3.1 (2024-03-19) ****************** * Fix description on PyPI 1.3.0 (2024-03-19) ****************** +* add get_refund_description and get_refund_ext_id arguments to PayuProvider * add PayuProvider.refund * update payment.captured_amount only when order is completed * subtract refunds from payment.captured_amount rather than from payment.total +* rename PayuProvider.payu_api_order_url to payu_api_orders_url * tests for Django 2.2-5.0 Python 3.7-3.12 1.2.4 (2022-03-17) diff --git a/README.rst b/README.rst index 2033abb..c76f5a6 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,7 @@ Here are valid parameters for the provider: :express_payments: use PayU express form :widget_branding: tell express form to show PayU branding :store_card: (default: False) whether PayU should store the card - :get_refund_description: A mandatory callable that is called with two keyword arguments `payment` and `amount` in order to get the string description of the particular refund whenever ``provider.refund(payment, amount)`` is called. + :get_refund_description: An optional callable that is called with two keyword arguments `payment` and `amount` in order to get the string description of the particular refund whenever ``provider.refund(payment, amount)`` is called. The callable is optional because of backwards compatibility. However, if it is not set, an attempt to refund raises an exception. A default value of `get_refund_description` is deprecated. :get_refund_ext_id: An optional callable that is called with two keyword arguments `payment` and `amount` in order to get the External string refund ID of the particular refund whenever ``provider.refund(payment, amount)`` is called. If ``None`` is returned, no External refund ID is set. An External refund ID is not necessary if partial refunds won't be performed more than once per second. Otherwise, a unique ID is recommended since `PayuProvider.refund` is idempotent and if exactly same data will be provided, it will return the result of the already previously performed refund instead of performing a new refund. Defaults to a random UUID version 4 in the standard form. diff --git a/payments_payu/provider.py b/payments_payu/provider.py index da465df..e5b19ee 100644 --- a/payments_payu/provider.py +++ b/payments_payu/provider.py @@ -2,6 +2,7 @@ import json import logging import uuid +import warnings from decimal import ROUND_HALF_UP, Decimal from urllib.parse import urljoin @@ -185,7 +186,16 @@ def __init__(self, *args, **kwargs): self.payu_shop_name = kwargs.pop("shop_name", "") self.grant_type = kwargs.pop("grant_type", "client_credentials") self.recurring_payments = kwargs.pop("recurring_payments", False) - self.get_refund_description = kwargs.pop("get_refund_description") + self.get_refund_description = kwargs.pop( + "get_refund_description", + # TODO: The default is deprecated. Remove in the next major release. + None, + ) + if self.get_refund_description is None: + warnings.warn( + "A default value of get_refund_description is deprecated. Set it to a callable instead.", + DeprecationWarning, + ) self.get_refund_ext_id = kwargs.pop( "get_refund_ext_id", lambda payment, amount: str(uuid.uuid4()) ) @@ -604,6 +614,9 @@ def process_data(self, payment, request, *args, **kwargs): ) def refund(self, payment, amount=None): + if self.get_refund_description is None: + raise ValueError("get_refund_description not set") + request_url = self._get_payu_api_order_url(payment.transaction_id) + "/refunds" request_data = { diff --git a/tests/test_payu.py b/tests/test_payu.py index d4d520c..73d02f5 100644 --- a/tests/test_payu.py +++ b/tests/test_payu.py @@ -2,6 +2,7 @@ import contextlib import json +import warnings from decimal import Decimal from unittest import TestCase @@ -112,13 +113,7 @@ class TestPayuProvider(TestCase): def setUp(self): self.payment = Payment() - def set_up_provider( - self, - recurring, - express, - get_refund_description=lambda payment, amount: "test", - **kwargs, - ): + def set_up_provider(self, recurring, express, **kwargs): with patch("requests.post") as mocked_post: data = MagicMock() data = '{"access_token": "test_access_token"}' @@ -134,13 +129,14 @@ def set_up_provider( base_payu_url="http://mock.url/", recurring_payments=recurring, express_payments=express, - get_refund_description=get_refund_description, **kwargs, ) def test_redirect_to_recurring_payment(self): """Test that if the payment recurrence is set, the user is redirected to renew payment form""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) form = self.provider.get_form(payment=self.payment) self.assertEqual(form.__class__.__name__, "RenewPaymentForm") self.assertEqual(form.action, "https://example.com/process_url/token") @@ -148,7 +144,9 @@ def test_redirect_to_recurring_payment(self): self.assertEqual(self.payment.captured_amount, Decimal("0")) def test_redirect_payu(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = '{"redirectUri": "test_redirect_uri", "status": {"statusCode": "SUCCESS"}, "orderId": 123}' @@ -159,7 +157,9 @@ def test_redirect_payu(self): self.assertEqual(context.exception.args[0], "test_redirect_uri") def test_redirect_payu_store_token(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -191,7 +191,9 @@ def test_redirect_payu_store_token(self): self.assertEqual(self.payment.automatic_renewal, True) def test_redirect_payu_unknown_status(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post_text = { @@ -244,7 +246,9 @@ def test_redirect_payu_unknown_status(self): ) def test_redirect_payu_bussiness_error(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post_text = { @@ -261,7 +265,9 @@ def test_redirect_payu_bussiness_error(self): self.assertEqual(self.payment.fraud_status, FraudStatus.REJECT) def test_redirect_payu_duplicate_order(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) self.payment.status = PaymentStatus.CONFIRMED self.payment.save() with patch("requests.post") as mocked_post: @@ -282,7 +288,9 @@ def test_redirect_payu_duplicate_order(self): self.assertEqual(context.exception.args[0], "") def test_redirect_payu_no_status_code(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post_text = { @@ -334,7 +342,9 @@ def test_redirect_payu_no_status_code(self): ) def test_redirect_payu_unauthorized_status(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -365,7 +375,9 @@ def test_redirect_payu_unauthorized_status(self): ) def test_get_access_token_trusted_merchant(self): - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -396,7 +408,9 @@ def test_get_access_token_trusted_merchant(self): def test_redirect_cvv_form(self): """Test redirection to CVV form if requested by PayU""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -455,7 +469,9 @@ def test_redirect_cvv_form(self): def test_showing_cvv_form(self): """Test redirection to CVV form if requested by PayU""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.extra_data = json.dumps({"cvv_url": "foo_url"}) with patch("requests.post") as mocked_post: post = MagicMock() @@ -483,7 +499,9 @@ def test_showing_cvv_form(self): def test_redirect_3ds_form(self): """Test redirection to 3DS page if requested by PayU""" - self.set_up_provider(True, False) + self.set_up_provider( + True, False, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -537,7 +555,9 @@ def test_redirect_3ds_form(self): def test_payu_renew_form(self): """Test showing PayU card form""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) transaction_id = "1234" data = MagicMock() data.return_value = { @@ -557,7 +577,9 @@ def test_payu_renew_form(self): def test_payu_widget_form(self): """Test showing PayU card widget""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.token = None transaction_id = "1234" data = MagicMock() @@ -582,7 +604,9 @@ def test_payu_widget_form(self): def test_process_notification(self): """Test processing PayU notification""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = json.dumps({"order": {"status": "COMPLETED"}}).encode( "utf8" @@ -603,7 +627,9 @@ def test_process_notification(self): def test_process_notification_cancelled(self): """Test processing PayU cancelled notification""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.transaction_id = "123" self.payment.save() mocked_request = MagicMock() @@ -639,7 +665,9 @@ def test_process_notification_refund(self): self.payment.change_status(PaymentStatus.CONFIRMED) self.payment.save() - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = json.dumps( { @@ -675,7 +703,9 @@ def test_process_notification_partial_refund(self): self.payment.save() self.payment.refresh_from_db() - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = json.dumps( { @@ -706,7 +736,9 @@ def test_process_notification_partial_refund(self): def test_process_notification_refund_not_finalized(self): """Test processing PayU partial refund notification""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = json.dumps( { @@ -729,7 +761,9 @@ def test_process_notification_refund_not_finalized(self): def test_process_notification_total_amount(self): """Test processing PayU notification if it captures correct amount""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = json.dumps( { @@ -756,7 +790,9 @@ def test_process_notification_total_amount(self): def test_process_notification_error(self): """Test processing PayU notification with wrong signature""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = b"{}" mocked_request.META = { @@ -774,7 +810,9 @@ def test_process_notification_error(self): def test_process_notification_error_malformed_post(self): """Test processing PayU notification with malformed POST""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) mocked_request = MagicMock() mocked_request.body = b"{}" mocked_request.META = {"CONTENT_TYPE": "application/json"} @@ -784,7 +822,9 @@ def test_process_notification_error_malformed_post(self): def test_process_first_renew(self): """Test processing first renew""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.token = None with patch("requests.post") as mocked_post: post = MagicMock() @@ -843,7 +883,9 @@ def test_process_first_renew(self): def test_process_renew(self): """Test processing renew""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -905,7 +947,9 @@ def test_process_renew(self): def test_process_renew_card_on_file(self): """Test processing renew""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.provider.card_on_file = True with patch("requests.post") as mocked_post: post = MagicMock() @@ -968,7 +1012,9 @@ def test_process_renew_card_on_file(self): def test_auto_complete_recurring(self): """Test processing renew. The function should return 'success' string, if nothing is required from user.""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = '{"status": {"statusCode": "SUCCESS"}, "orderId": 123}' @@ -981,7 +1027,9 @@ def test_auto_complete_recurring(self): def test_auto_complete_recurring_cvv2(self): """Test processing renew when cvv2 form is required - it should return the payment processing URL""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) with patch("requests.post") as mocked_post: post = MagicMock() post.text = json.dumps( @@ -1000,7 +1048,9 @@ def test_auto_complete_recurring_cvv2(self): def test_delete_card_token(self): """Test delete_card_token()""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.transaction_id = "1234" with patch("requests.delete") as mocked_post: post = MagicMock() @@ -1019,7 +1069,9 @@ def test_delete_card_token(self): def test_get_paymethod_tokens(self): """Test delete_card_token()""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.transaction_id = "1234" with patch("requests.get") as mocked_post: post = MagicMock() @@ -1040,7 +1092,9 @@ def test_get_paymethod_tokens(self): def test_reject_order(self): """Test processing renew""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.transaction_id = "1234" with patch("requests.delete") as mocked_post: post = MagicMock() @@ -1060,7 +1114,9 @@ def test_reject_order(self): def test_reject_order_error(self): """Test processing renew""" - self.set_up_provider(True, True) + self.set_up_provider( + True, True, get_refund_description=lambda payment, amount: "test" + ) self.payment.transaction_id = "1234" with patch("requests.delete") as mocked_post: post = MagicMock() @@ -1079,12 +1135,14 @@ def test_reject_order_error(self): self.assertEqual(self.payment.status, PaymentStatus.WAITING) def test_refund(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", + ) payment_extra_data_refund_response_previous = { "orderId": "1234", "refund": { @@ -1152,14 +1210,17 @@ def test_refund(self): payment_extra_data_refund_responses, [payment_extra_data_refund_response_previous, refund_request_response_body], ) + self.assertFalse(caught_warnings) def test_refund_no_amount(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1206,13 +1267,16 @@ def test_refund_no_amount(self): self.assertEqual( payment_extra_data_refund_responses, [refund_request_response_body] ) + self.assertFalse(caught_warnings) def test_refund_no_get_refund_ext_id(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1262,14 +1326,17 @@ def test_refund_no_get_refund_ext_id(self): self.assertEqual( payment_extra_data_refund_responses, [refund_request_response_body] ) + self.assertFalse(caught_warnings) def test_refund_no_ext_id(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: None, - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: None, + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1315,14 +1382,17 @@ def test_refund_no_ext_id(self): self.assertEqual( payment_extra_data_refund_responses, [refund_request_response_body] ) + self.assertFalse(caught_warnings) def test_refund_no_ext_id_twice(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: None, - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: None, + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1371,14 +1441,17 @@ def test_refund_no_ext_id_twice(self): payment_extra_data_refund_responses, [refund_request_response_body, refund_request_response_body], ) + self.assertFalse(caught_warnings) def test_refund_finalized(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1425,14 +1498,17 @@ def test_refund_finalized(self): self.assertEqual( payment_extra_data_refund_responses, [refund_request_response_body] ) + self.assertFalse(caught_warnings) def test_refund_canceled(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1481,14 +1557,17 @@ def test_refund_canceled(self): self.assertEqual( payment_extra_data_refund_responses, [refund_request_response_body] ) + self.assertFalse(caught_warnings) def test_refund_error(self): - self.set_up_provider( - True, - True, - get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", - get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", + get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", + ) self.payment.transaction_id = "1234" self.payment.captured_amount = self.payment.total self.payment.change_status(PaymentStatus.CONFIRMED) @@ -1533,6 +1612,37 @@ def test_refund_error(self): self.assertEqual( payment_extra_data_refund_responses, [refund_request_response_body] ) + self.assertFalse(caught_warnings) + + def test_refund_no_get_refund_description(self): + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + True, + get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", + ) + self.payment.transaction_id = "1234" + self.payment.captured_amount = self.payment.total + self.payment.change_status(PaymentStatus.CONFIRMED) + self.payment.save() + + with self.assertRaisesRegex(ValueError, r"^get_refund_description not set"): + self.provider.refund(self.payment, Decimal(110)) + + payment_extra_data_refund_responses = json.loads(self.payment.extra_data).get( + "refund_responses", [] + ) + self.assertEqual(self.payment.total, Decimal(220)) + self.assertEqual(self.payment.captured_amount, Decimal(220)) + self.assertEqual(self.payment.status, PaymentStatus.CONFIRMED) + self.assertFalse(payment_extra_data_refund_responses) + self.assertEqual(len(caught_warnings), 1) + self.assertTrue(issubclass(caught_warnings[0].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[0].message), + "A default value of get_refund_description is deprecated. Set it to a callable instead.", + ) @contextlib.contextmanager def _patch_refund( From d2f4e902b92b07245a976dc071c6eb14a6bb4aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Hol=C3=BD?= Date: Sun, 7 Apr 2024 13:33:47 +0200 Subject: [PATCH 2/2] make PayuProvider.refund raise PayuApiError if an unexpected response is received --- HISTORY.rst | 1 + payments_payu/provider.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index ab57d08..27862e8 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,6 +7,7 @@ Unreleased ********** * fix backward compatibility by making PayuProvider's get_refund_description argument optional * make PayuProvider.refund fail if get_refund_description is not provided +* make PayuProvider.refund raise PayuApiError if an unexpected response is received * deprecate the default value of get_refund_description; set it to a callable instead 1.3.1 (2024-03-19) diff --git a/payments_payu/provider.py b/payments_payu/provider.py index e5b19ee..ed7db84 100644 --- a/payments_payu/provider.py +++ b/payments_payu/provider.py @@ -657,7 +657,7 @@ def refund(self, payment, amount=None): response_status = dict(response["status"]) response_status_code = response_status["statusCode"] except Exception: - raise ValueError( + raise PayuApiError( f"invalid response to refund {refund_id or '???'} of payment {payment.id}: {response}" ) if response_status_code != "SUCCESS": @@ -669,7 +669,7 @@ def refund(self, payment, amount=None): f"statusDesc={response_status.get('statusDesc', '???')}" ) if refund_id is None: - raise ValueError( + raise PayuApiError( f"invalid response to refund of payment {payment.id}: {response}" ) @@ -679,7 +679,7 @@ def refund(self, payment, amount=None): refund_currency = refund["currencyCode"] refund_amount = dequantize_price(refund["amount"], refund_currency) except Exception: - raise ValueError( + raise PayuApiError( f"invalid response to refund {refund_id} of payment {payment.id}: {response}" ) if refund_order_id != payment.transaction_id: @@ -690,7 +690,7 @@ def refund(self, payment, amount=None): if refund_status == "CANCELED": raise ValueError(f"refund {refund_id} of payment {payment.id} canceled") elif refund_status not in {"PENDING", "FINALIZED"}: - raise ValueError( + raise PayuApiError( f"invalid status of refund {refund_id} of payment {payment.id}" ) if refund_currency != payment.currency: