diff --git a/HISTORY.rst b/HISTORY.rst index dee036d..f26fc5e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,15 @@ History ------- +1.3.0 (Unreleased) +++++++++++++++++++ + +* add support for `RecurringUserPlan.renewal_triggered_by` +* add `renewal_triggered_by` parameter to `Payment.set_renew_token` +* deprecate support for `RecurringUserPlan.renewal_triggered_by=None`; set an `AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY` instead +* deprecate `automatic_renewal` parameter of `Payment.set_renew_token`; migrate to `AbstractRecurringUserPlan.renewal_triggered_by` and use `renewal_triggered_by` parameter instead +* deprecate `None` value of `renewal_triggered_by` parameter of `Payment.set_renew_token`; set an `AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY` instead + 1.2.2 (2023-12-20) ++++++++++++++++++ diff --git a/plans_payments/models.py b/plans_payments/models.py index 5b18bb7..777487a 100644 --- a/plans_payments/models.py +++ b/plans_payments/models.py @@ -1,5 +1,6 @@ import json import logging +import warnings from decimal import Decimal from urllib.parse import urljoin @@ -12,6 +13,7 @@ from payments.core import get_base_url from payments.models import BasePayment from payments.signals import status_changed +from plans.base.models import AbstractRecurringUserPlan from plans.contrib import get_user_language, send_template_email from plans.models import Order from plans.signals import account_automatic_renewal @@ -125,13 +127,49 @@ def set_renew_token( card_expire_year=None, card_expire_month=None, card_masked_number=None, - automatic_renewal=True, + # TODO: automatic_renewal deprecated. Remove in the next major release. + automatic_renewal=None, + # TODO: renewal_triggered_by=None deprecated. Set to TASK in the next major release. + renewal_triggered_by=None, ): """ Store the recurring payments renew token for user of this payment The renew token is string defined by the provider Used by PayU provider for now """ + set_plan_renewal_kwargs = {} + + if automatic_renewal is None and renewal_triggered_by is None: + automatic_renewal = True + if automatic_renewal is not None: + warnings.warn( + "automatic_renewal is deprecated. " + "Migrate to AbstractRecurringUserPlan.renewal_triggered_by and use renewal_triggered_by instead.", + DeprecationWarning, + ) + set_plan_renewal_kwargs["has_automatic_renewal"] = automatic_renewal + + if renewal_triggered_by == "user": + set_plan_renewal_kwargs["renewal_triggered_by"] = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER + ) + elif renewal_triggered_by == "task": + set_plan_renewal_kwargs["renewal_triggered_by"] = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK + ) + elif renewal_triggered_by == "other": + set_plan_renewal_kwargs["renewal_triggered_by"] = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER + ) + elif renewal_triggered_by is None: + warnings.warn( + "renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + DeprecationWarning, + ) + else: + raise ValueError(f"Invalid renewal_triggered_by: {renewal_triggered_by}") + self.order.user.userplan.set_plan_renewal( order=self.order, token=token, @@ -139,7 +177,7 @@ def set_renew_token( card_expire_year=card_expire_year, card_expire_month=card_expire_month, card_masked_number=card_masked_number, - has_automatic_renewal=automatic_renewal, + **set_plan_renewal_kwargs, ) @@ -169,9 +207,23 @@ def change_payment_status(sender, *args, **kwargs): @receiver(account_automatic_renewal) def renew_accounts(sender, user, *args, **kwargs): userplan = user.userplan + userplan_recurring_renewal_triggered_by = userplan.recurring.renewal_triggered_by + # TODO: userplan.recurring.renewal_triggered_by=None deprecated. Remove in the next major release. + if userplan_recurring_renewal_triggered_by is None: + warnings.warn( + "Support for userplan.recurring.renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + DeprecationWarning, + ) + userplan_recurring_renewal_triggered_by = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK + if userplan.recurring.has_automatic_renewal + else AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER + ) if ( userplan.recurring.payment_provider in settings.PAYMENT_VARIANTS - and userplan.recurring.has_automatic_renewal + and userplan_recurring_renewal_triggered_by + == AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK ): order = userplan.recurring.create_renew_order() diff --git a/tests/test_models.py b/tests/test_models.py index 99c1af9..c97c9eb 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -8,6 +8,7 @@ Tests for `django-plans-payments` models module. """ import json +import warnings from datetime import datetime from decimal import Decimal @@ -16,7 +17,7 @@ from freezegun import freeze_time from model_bakery import baker from payments import PaymentStatus -from plans.models import Invoice, Order +from plans.models import Invoice, Order, RecurringUserPlan from plans_payments import models @@ -187,22 +188,119 @@ def test_get_renew_token_verified(self): ) self.assertEqual(p.get_renew_token(), "token") - def test_set_renew_token(self): + def test_set_renew_token_task(self): user = baker.make("User") p = models.Payment(order=baker.make("Order", user=user)) userplan = baker.make("UserPlan", user=user) - p.set_renew_token( - "token", - card_expire_year=2020, - card_expire_month=12, - card_masked_number="1234", - automatic_renewal=True, + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + p.set_renew_token( + "token", + card_expire_year=2020, + card_expire_month=12, + card_masked_number="1234", + renewal_triggered_by="task", + ) + self.assertEqual(userplan.recurring.token, "token") + self.assertEqual(userplan.recurring.card_expire_year, 2020) + self.assertEqual(userplan.recurring.card_expire_month, 12) + self.assertEqual(userplan.recurring.card_masked_number, "1234") + self.assertEqual( + userplan.recurring.renewal_triggered_by, + RecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, ) + self.assertEqual(userplan.recurring.has_automatic_renewal, True) + self.assertFalse(caught_warnings) + + def test_set_renew_token_other(self): + user = baker.make("User") + p = models.Payment(order=baker.make("Order", user=user)) + userplan = baker.make("UserPlan", user=user) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + p.set_renew_token( + "token", + card_expire_year=2020, + card_expire_month=12, + card_masked_number="1234", + renewal_triggered_by="other", + ) self.assertEqual(userplan.recurring.token, "token") self.assertEqual(userplan.recurring.card_expire_year, 2020) self.assertEqual(userplan.recurring.card_expire_month, 12) self.assertEqual(userplan.recurring.card_masked_number, "1234") + self.assertEqual( + userplan.recurring.renewal_triggered_by, + RecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER, + ) self.assertEqual(userplan.recurring.has_automatic_renewal, True) + self.assertFalse(caught_warnings) + + def test_set_renew_token_user(self): + user = baker.make("User") + p = models.Payment(order=baker.make("Order", user=user)) + userplan = baker.make("UserPlan", user=user) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + p.set_renew_token( + "token", + card_expire_year=2020, + card_expire_month=12, + card_masked_number="1234", + renewal_triggered_by="user", + ) + self.assertEqual(userplan.recurring.token, "token") + self.assertEqual(userplan.recurring.card_expire_year, 2020) + self.assertEqual(userplan.recurring.card_expire_month, 12) + self.assertEqual(userplan.recurring.card_masked_number, "1234") + self.assertEqual( + userplan.recurring.renewal_triggered_by, + RecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + ) + self.assertEqual(userplan.recurring.has_automatic_renewal, False) + self.assertFalse(caught_warnings) + + def test_set_renew_token_none(self): + user = baker.make("User") + p = models.Payment(order=baker.make("Order", user=user)) + userplan = baker.make("UserPlan", user=user) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + p.set_renew_token( + "token", + card_expire_year=2020, + card_expire_month=12, + card_masked_number="1234", + automatic_renewal=True, + ) + self.assertEqual(userplan.recurring.token, "token") + self.assertEqual(userplan.recurring.card_expire_year, 2020) + self.assertEqual(userplan.recurring.card_expire_month, 12) + self.assertEqual(userplan.recurring.card_masked_number, "1234") + self.assertIsNone(userplan.recurring.renewal_triggered_by) + self.assertEqual(userplan.recurring.has_automatic_renewal, True) + self.assertEqual(len(caught_warnings), 4) + self.assertTrue(issubclass(caught_warnings[0].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[0].message), + "automatic_renewal is deprecated. " + "Migrate to AbstractRecurringUserPlan.renewal_triggered_by and use renewal_triggered_by instead.", + ) + self.assertTrue(issubclass(caught_warnings[1].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[1].message), + "renewal_triggered_by=None is deprecated. Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + ) + self.assertTrue(issubclass(caught_warnings[2].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[2].message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + self.assertTrue(issubclass(caught_warnings[3].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[3].message), + "renewal_triggered_by=None is deprecated. Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + ) def test_change_payment_status(self): p = models.Payment(order=baker.make("Order", status=Order.STATUS.NEW)) @@ -268,15 +366,18 @@ def test_renew_accounts_no_variant(self): baker.make( "RecurringUserPlan", user_plan=userplan, - renewal_trigger=RecurringUserPlan.RENEWAL_TRIGGER.TASK, + renewal_triggered_by=RecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, has_automatic_renewal=True, ) - models.renew_accounts("sender", user, p) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + models.renew_accounts("sender", user, p) self.assertEqual(p.autorenewed_payment, False) self.assertFalse(Order.objects.exists()) self.assertFalse(models.Payment.objects.exclude(id=p.id).exists()) + self.assertFalse(caught_warnings) - def test_renew_accounts(self): + def test_renew_accounts_none(self): p = baker.make("Payment", variant="default", order__amount=12) user = baker.make("User") userplan = baker.make("UserPlan", user=user) @@ -286,11 +387,51 @@ def test_renew_accounts(self): "RecurringUserPlan", user_plan=userplan, payment_provider="default", + renewal_triggered_by=None, has_automatic_renewal=True, amount=14, pricing=plan_pricing.pricing, ) - models.renew_accounts("sender", user, p) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + models.renew_accounts("sender", user, p) + (order_renewed,) = Order.objects.exclude(id=p.order.id) + (payment_renewed,) = models.Payment.objects.exclude(id=p.id) + self.assertEqual(p.autorenewed_payment, False) + self.assertEqual(order_renewed.plan, plan_pricing.plan) + self.assertEqual(order_renewed.pricing, plan_pricing.pricing) + self.assertEqual(order_renewed.amount, Decimal(14)) + self.assertEqual(order_renewed.user, user) + self.assertEqual(payment_renewed.order, order_renewed) + self.assertEqual(payment_renewed.variant, "default") + self.assertTrue(payment_renewed.autorenewed_payment) + self.assertEqual(len(caught_warnings), 1) + self.assertTrue(issubclass(caught_warnings[0].category, DeprecationWarning)) + self.assertEqual( + str(caught_warnings[0].message), + "Support for userplan.recurring.renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + ) + + def test_renew_accounts(self): + p = baker.make("Payment", variant="default", order__amount=12) + user = baker.make("User") + userplan = baker.make("UserPlan", user=user) + plan_pricing = baker.make("PlanPricing", plan=userplan.plan, price=12) + baker.make("BillingInfo", user=user) + baker.make( + "RecurringUserPlan", + user_plan=userplan, + payment_provider="default", + renewal_triggered_by=RecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + # False to make sure that renewal_triggered_by takes precedence. + has_automatic_renewal=False, + amount=14, + pricing=plan_pricing.pricing, + ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + models.renew_accounts("sender", user, p) (order_renewed,) = Order.objects.exclude(id=p.order.id) (payment_renewed,) = models.Payment.objects.exclude(id=p.id) self.assertEqual(p.autorenewed_payment, False) @@ -301,6 +442,47 @@ def test_renew_accounts(self): self.assertEqual(payment_renewed.order, order_renewed) self.assertEqual(payment_renewed.variant, "default") self.assertTrue(payment_renewed.autorenewed_payment) + self.assertFalse(caught_warnings) + + def test_renew_accounts_user(self): + p = models.Payment(variant="default") + user = baker.make("User") + userplan = baker.make("UserPlan", user=user) + baker.make( + "RecurringUserPlan", + user_plan=userplan, + payment_provider="default", + renewal_triggered_by=RecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + # True to make sure that renewal_triggered_by takes precedence. + has_automatic_renewal=True, + ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + models.renew_accounts("sender", user, p) + self.assertEqual(p.autorenewed_payment, False) + self.assertFalse(Order.objects.exists()) + self.assertFalse(models.Payment.objects.exclude(id=p.id).exists()) + self.assertFalse(caught_warnings) + + def test_renew_accounts_other(self): + p = models.Payment(variant="default") + user = baker.make("User") + userplan = baker.make("UserPlan", user=user) + baker.make( + "RecurringUserPlan", + user_plan=userplan, + payment_provider="default", + renewal_triggered_by=RecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER, + # True to make sure that renewal_triggered_by takes precedence. + has_automatic_renewal=True, + ) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + models.renew_accounts("sender", user, p) + self.assertEqual(p.autorenewed_payment, False) + self.assertFalse(Order.objects.exists()) + self.assertFalse(models.Payment.objects.exclude(id=p.id).exists()) + self.assertFalse(caught_warnings) def test_change_payment_status_called(self): """test that change_payment_status receiver is executed when Payment.change_status is called