diff --git a/HISTORY.rst b/HISTORY.rst index 27862e8..ac69404 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,9 +6,11 @@ History Unreleased ********** * fix backward compatibility by making PayuProvider's get_refund_description argument optional +* add a renewal_triggered_by_use parameter * 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 +* deprecate False value of the renewal_triggered_by_use parameter; migrate to AbstractRecurringUserPlan.renewal_triggered_by and set renewal_triggered_by_use=True instead 1.3.1 (2024-03-19) ****************** diff --git a/README.rst b/README.rst index c76f5a6..ef4320b 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ Quickstart Install `django-payments `_ and set up PayU payment provider backend according to `django-payments documentation `_: -.. class:: payments_payu.provider.PayuProvider(client_secret, second_key, pos_id, get_refund_description, [sandbox=False, endpoint="https://secure.payu.com/", recurring_payments=False, express_payments=False, widget_branding=False, get_refund_ext_id=_DEFAULT_GET_REFUND_EXT_ID]) +.. class:: payments_payu.provider.PayuProvider(client_secret, second_key, pos_id, get_refund_description, [sandbox=False, endpoint="https://secure.payu.com/", recurring_payments=False, renewal_triggered_by_use=False, express_payments=False, widget_branding=False, get_refund_ext_id=_DEFAULT_GET_REFUND_EXT_ID]) This backend implements payments using `PayU.com `_. @@ -42,24 +42,26 @@ Example:: 'client_secret': 'peopleiseedead', 'sandbox': True, 'capture': False, + 'renewal_triggered_by_use': True, 'get_refund_description': lambda payment, amount: 'My refund', 'get_refund_ext_id': lambda payment, amount: str(uuid.uuid4()), }), } Here are valid parameters for the provider: - :client_secret: PayU OAuth protocol client secret - :pos_id: PayU POS ID - :second_key: PayU second key (MD5) - :shop_name: Name of the shop send to the API - :sandbox: if ``True``, set the endpoint to sandbox - :endpoint: endpoint URL, if not set, the will be automatically set based on `sandbox` settings - :recurring_payments: enable recurring payments, only valid with ``express_payments=True``, see bellow for additional setup, that is needed - :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: 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. + :client_secret: PayU OAuth protocol client secret + :pos_id: PayU POS ID + :second_key: PayU second key (MD5) + :shop_name: Name of the shop send to the API + :sandbox: if ``True``, set the endpoint to sandbox + :endpoint: endpoint URL, if not set, the will be automatically set based on `sandbox` settings + :recurring_payments: enable recurring payments, only valid with ``express_payments=True``, see bellow for additional setup, that is needed + :renewal_triggered_by_use: (default: False) Pass the ``renewal_triggered_by`` argument instead of the ``automatic_renewal`` argument to ``Payment.set_renew_token()`` + :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: 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. NOTE: notifications about the payment status from PayU are requested to be sent to `django-payments` `process_payment` url. The request from PayU can fail for several reasons (i.e. it can be blocked by proxy). Use "Show reports" page in PayU administration to get more information about the requests. diff --git a/payments_payu/provider.py b/payments_payu/provider.py index ed7db84..ff9ae2a 100644 --- a/payments_payu/provider.py +++ b/payments_payu/provider.py @@ -186,6 +186,14 @@ 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.__renewal_triggered_by_use = kwargs.pop("renewal_triggered_by_use", False) + # TODO: renewal_triggered_by_use=False deprecated. Remove in the next major release. + if not self.__renewal_triggered_by_use: + warnings.warn( + "renewal_triggered_by_use=False is deprecated. Migrate to " + "AbstractRecurringUserPlan.renewal_triggered_by and set renewal_triggered_by_use=True instead.", + DeprecationWarning, + ) self.get_refund_description = kwargs.pop( "get_refund_description", # TODO: The default is deprecated. Remove in the next major release. @@ -430,6 +438,15 @@ def create_order(self, payment, payment_processor, auto_renew=False): payment.transaction_id = response_dict["orderId"] if "payMethods" in response_dict: + set_renew_token_kwargs = {} + if self.__renewal_triggered_by_use: + set_renew_token_kwargs["renewal_triggered_by"] = ( + "task" if self.recurring_payments else "user" + ) + else: + set_renew_token_kwargs["automatic_renewal"] = ( + self.recurring_payments + ) payment.set_renew_token( response_dict["payMethods"]["payMethod"]["value"], card_expire_year=response_dict["payMethods"]["payMethod"]["card"][ @@ -441,7 +458,7 @@ def create_order(self, payment, payment_processor, auto_renew=False): card_masked_number=response_dict["payMethods"]["payMethod"]["card"][ "number" ], - automatic_renewal=self.recurring_payments, + **set_renew_token_kwargs, ) add_extra_data(payment, {"card_response": response_dict}) diff --git a/tests/test_payu.py b/tests/test_payu.py index 73d02f5..76d4c5c 100644 --- a/tests/test_payu.py +++ b/tests/test_payu.py @@ -34,6 +34,8 @@ def __eq__(self, other): class Payment(Mock): + UNSET = object() + id = 1 description = "payment" currency = "USD" @@ -98,13 +100,15 @@ def set_renew_token( card_expire_year=None, card_expire_month=None, card_masked_number=None, - automatic_renewal=None, + automatic_renewal=UNSET, + renewal_triggered_by=UNSET, ): self.token = token self.card_expire_year = card_expire_year self.card_expire_month = card_expire_month self.card_masked_number = card_masked_number self.automatic_renewal = automatic_renewal + self.renewal_triggered_by = renewal_triggered_by class TestPayuProvider(TestCase): @@ -113,7 +117,9 @@ class TestPayuProvider(TestCase): def setUp(self): self.payment = Payment() - def set_up_provider(self, recurring, express, **kwargs): + def set_up_provider( + self, recurring, express, renewal_triggered_by_use=False, **kwargs + ): with patch("requests.post") as mocked_post: data = MagicMock() data = '{"access_token": "test_access_token"}' @@ -128,6 +134,7 @@ def set_up_provider(self, recurring, express, **kwargs): pos_id=POS_ID, base_payu_url="http://mock.url/", recurring_payments=recurring, + renewal_triggered_by_use=renewal_triggered_by_use, express_payments=express, **kwargs, ) @@ -135,7 +142,10 @@ def set_up_provider(self, recurring, express, **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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) form = self.provider.get_form(payment=self.payment) self.assertEqual(form.__class__.__name__, "RenewPaymentForm") @@ -145,7 +155,10 @@ def test_redirect_to_recurring_payment(self): def test_redirect_payu(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -157,9 +170,52 @@ 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, get_refund_description=lambda payment, amount: "test" - ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.set_up_provider( + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", + ) + with patch("requests.post") as mocked_post: + post = MagicMock() + post.text = json.dumps( + { + "redirectUri": "test_redirect_uri", + "status": {"statusCode": "SUCCESS"}, + "orderId": 123, + "payMethods": { + "payMethod": { + "value": 1211, + "card": { + "expirationYear": 2021, + "expirationMonth": 1, + "number": "1234xxx", + }, + } + }, + } + ) + post.status_code = 200 + mocked_post.return_value = post + with self.assertRaises(RedirectNeeded) as context: + self.provider.get_form(payment=self.payment) + self.assertEqual(context.exception.args[0], "test_redirect_uri") + self.assertEqual(self.payment.token, 1211) + self.assertEqual(self.payment.card_expire_year, 2021) + self.assertEqual(self.payment.card_expire_month, 1) + self.assertEqual(self.payment.card_masked_number, "1234xxx") + self.assertEqual(self.payment.automatic_renewal, Payment.UNSET) + self.assertEqual(self.payment.renewal_triggered_by, "task") + self.assertFalse(caught_warnings) + + def test_redirect_payu_store_token_deprecated(self): + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + 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( @@ -189,10 +245,21 @@ def test_redirect_payu_store_token(self): self.assertEqual(self.payment.card_expire_month, 1) self.assertEqual(self.payment.card_masked_number, "1234xxx") self.assertEqual(self.payment.automatic_renewal, True) + self.assertEqual(self.payment.renewal_triggered_by, Payment.UNSET) + self.assertEqual(len(caught_warnings), 1) + self.assertTrue(issubclass(caught_warnings[0].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[0].message), + "renewal_triggered_by_use=False is deprecated. Migrate to " + "AbstractRecurringUserPlan.renewal_triggered_by and set renewal_triggered_by_use=True instead.", + ) def test_redirect_payu_unknown_status(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -247,7 +314,10 @@ def test_redirect_payu_unknown_status(self): def test_redirect_payu_bussiness_error(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -266,7 +336,10 @@ def test_redirect_payu_bussiness_error(self): def test_redirect_payu_duplicate_order(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.status = PaymentStatus.CONFIRMED self.payment.save() @@ -289,7 +362,10 @@ def test_redirect_payu_duplicate_order(self): def test_redirect_payu_no_status_code(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -343,7 +419,10 @@ def test_redirect_payu_no_status_code(self): def test_redirect_payu_unauthorized_status(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -376,7 +455,10 @@ def test_redirect_payu_unauthorized_status(self): def test_get_access_token_trusted_merchant(self): self.set_up_provider( - True, False, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -409,7 +491,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -470,7 +555,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=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: @@ -500,7 +588,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + False, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -556,7 +647,10 @@ def test_redirect_3ds_form(self): def test_payu_renew_form(self): """Test showing PayU card form""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) transaction_id = "1234" data = MagicMock() @@ -578,7 +672,10 @@ def test_payu_renew_form(self): def test_payu_widget_form(self): """Test showing PayU card widget""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.token = None transaction_id = "1234" @@ -605,7 +702,10 @@ def test_payu_widget_form(self): def test_process_notification(self): """Test processing PayU notification""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = json.dumps({"order": {"status": "COMPLETED"}}).encode( @@ -628,7 +728,10 @@ def test_process_notification(self): def test_process_notification_cancelled(self): """Test processing PayU cancelled notification""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.transaction_id = "123" self.payment.save() @@ -666,7 +769,10 @@ def test_process_notification_refund(self): self.payment.save() self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = json.dumps( @@ -704,7 +810,10 @@ def test_process_notification_partial_refund(self): self.payment.refresh_from_db() self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = json.dumps( @@ -737,7 +846,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = json.dumps( @@ -762,7 +874,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = json.dumps( @@ -791,7 +906,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = b"{}" @@ -811,7 +929,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) mocked_request = MagicMock() mocked_request.body = b"{}" @@ -823,7 +944,10 @@ def test_process_notification_error_malformed_post(self): def test_process_first_renew(self): """Test processing first renew""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.token = None with patch("requests.post") as mocked_post: @@ -884,7 +1008,10 @@ def test_process_first_renew(self): def test_process_renew(self): """Test processing renew""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -948,7 +1075,10 @@ def test_process_renew(self): def test_process_renew_card_on_file(self): """Test processing renew""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.provider.card_on_file = True with patch("requests.post") as mocked_post: @@ -1013,7 +1143,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -1028,7 +1161,10 @@ 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, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) with patch("requests.post") as mocked_post: post = MagicMock() @@ -1049,7 +1185,10 @@ def test_auto_complete_recurring_cvv2(self): def test_delete_card_token(self): """Test delete_card_token()""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.transaction_id = "1234" with patch("requests.delete") as mocked_post: @@ -1070,7 +1209,10 @@ def test_delete_card_token(self): def test_get_paymethod_tokens(self): """Test delete_card_token()""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.transaction_id = "1234" with patch("requests.get") as mocked_post: @@ -1093,7 +1235,10 @@ def test_get_paymethod_tokens(self): def test_reject_order(self): """Test processing renew""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.transaction_id = "1234" with patch("requests.delete") as mocked_post: @@ -1115,7 +1260,10 @@ def test_reject_order(self): def test_reject_order_error(self): """Test processing renew""" self.set_up_provider( - True, True, get_refund_description=lambda payment, amount: "test" + True, + True, + renewal_triggered_by_use=True, + get_refund_description=lambda payment, amount: "test", ) self.payment.transaction_id = "1234" with patch("requests.delete") as mocked_post: @@ -1140,6 +1288,7 @@ def test_refund(self): self.set_up_provider( True, True, + renewal_triggered_by_use=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}", ) @@ -1218,6 +1367,7 @@ def test_refund_no_amount(self): self.set_up_provider( True, True, + renewal_triggered_by_use=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}", ) @@ -1275,6 +1425,7 @@ def test_refund_no_get_refund_ext_id(self): self.set_up_provider( True, True, + renewal_triggered_by_use=True, get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", ) self.payment.transaction_id = "1234" @@ -1334,6 +1485,7 @@ def test_refund_no_ext_id(self): self.set_up_provider( True, True, + renewal_triggered_by_use=True, get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", get_refund_ext_id=lambda payment, amount: None, ) @@ -1390,6 +1542,7 @@ def test_refund_no_ext_id_twice(self): self.set_up_provider( True, True, + renewal_triggered_by_use=True, get_refund_description=lambda payment, amount: f"desc {payment.transaction_id} {amount}", get_refund_ext_id=lambda payment, amount: None, ) @@ -1449,6 +1602,7 @@ def test_refund_finalized(self): self.set_up_provider( True, True, + renewal_triggered_by_use=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}", ) @@ -1506,6 +1660,7 @@ def test_refund_canceled(self): self.set_up_provider( True, True, + renewal_triggered_by_use=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}", ) @@ -1565,6 +1720,7 @@ def test_refund_error(self): self.set_up_provider( True, True, + renewal_triggered_by_use=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}", ) @@ -1620,6 +1776,7 @@ def test_refund_no_get_refund_description(self): self.set_up_provider( True, True, + renewal_triggered_by_use=True, get_refund_ext_id=lambda payment, amount: f"ext {payment.transaction_id} {amount}", ) self.payment.transaction_id = "1234"