From 26e8e3dcd90efe25c95d877416863ea95c2f3651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Hol=C3=BD?= Date: Wed, 20 Mar 2024 09:03:18 +0100 Subject: [PATCH] add optional returning orders when payments are refunded --- HISTORY.rst | 5 +++++ README.rst | 2 +- docs/usage.rst | 6 +++++ plans_payments/models.py | 14 +++++++++++- requirements.txt | 2 +- setup.py | 2 +- tests/test_models.py | 47 +++++++++++++++++++++++++++++++++++++++- 7 files changed, 73 insertions(+), 5 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index dee036d..8779b5e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ History ------- +1.3.0 (Unreleased) +++++++++++++++++++ + +* add optional returning orders when payments are refunded + 1.2.2 (2023-12-20) ++++++++++++++++++ diff --git a/README.rst b/README.rst index e99b4ce..0ff9b5c 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ Django plans payments :target: https://codecov.io/gh/PetrDlouhy/django-plans-payments Almost automatic integration between `django-plans `_ and `django-payments `_. -This will add payment buttons to the order page and automatically confirm the `Order` after the payment. +This will add payment buttons to the order page and automatically confirm the `Order` after the payment. Optionally, it can return the corresponding order when a payment is refunded. Documentation ------------- diff --git a/docs/usage.rst b/docs/usage.rst index 7cf15cc..9cf461f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -24,3 +24,9 @@ Add Django plans payments's URL patterns: url(r'^', include(plans_payments_urls)), ... ] + +To enable returning orders when payments are refunded, set `PLANS_PAYMENTS_RETURN_ORDER_WHEN_PAYMENT_REFUNDED` to `True` in your settings. + +.. code-block:: python + + PLANS_PAYMENTS_RETURN_ORDER_WHEN_PAYMENT_REFUNDED = True diff --git a/plans_payments/models.py b/plans_payments/models.py index 5b18bb7..23c323a 100644 --- a/plans_payments/models.py +++ b/plans_payments/models.py @@ -152,7 +152,19 @@ def change_payment_status(sender, *args, **kwargs): order.user.userplan.recurring.token_verified = True order.user.userplan.recurring.save() order.complete_order() - if order.status != Order.STATUS.COMPLETED and payment.status not in ( + if ( + getattr(settings, "PLANS_PAYMENTS_RETURN_ORDER_WHEN_PAYMENT_REFUNDED", False) + and payment.status == PaymentStatus.REFUNDED + ): + order._change_reason = ( + f"Django-plans-payments: Payment status changed to {payment.status}" + ) + try: + order.return_order() + except ValueError: + logger.exception("Failed to return the order: %s", order.pk) + return + elif order.status != Order.STATUS.COMPLETED and payment.status not in ( PaymentStatus.CONFIRMED, PaymentStatus.WAITING, PaymentStatus.INPUT, diff --git a/requirements.txt b/requirements.txt index 583c9e3..f648527 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Additional requirements go here -django-plans +django-plans>=1.0.7 django-payments django-related-admin diff --git a/setup.py b/setup.py index b782319..f3a0b8f 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ def get_version(*file_paths): ], include_package_data=True, install_requires=[ - "django-plans", + "django-plans>=1.0.7", "django-payments", ], license="MIT", diff --git a/tests/test_models.py b/tests/test_models.py index 2c6ef56..6e54735 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -12,7 +12,7 @@ from decimal import Decimal import pytz -from django.test import TestCase +from django.test import TestCase, override_settings from freezegun import freeze_time from model_bakery import baker from payments import PaymentStatus @@ -275,6 +275,17 @@ def test_change_payment_status_refunded(self): self.assertEqual(p.status, "refunded") self.assertEqual(p.order.status, Order.STATUS.COMPLETED) + @override_settings(PLANS_PAYMENTS_RETURN_ORDER_WHEN_PAYMENT_REFUNDED=True) + def test_change_payment_status_refunded_return_enabled(self): + p = models.Payment( + order=baker.make("Order", status=Order.STATUS.COMPLETED), + status=PaymentStatus.REFUNDED, + ) + baker.make("UserPlan", user=p.order.user) + models.change_payment_status("sender", instance=p) + self.assertEqual(p.status, "refunded") + self.assertEqual(p.order.status, Order.STATUS.RETURNED) + def test_change_payment_status_refunded_order_not_valid(self): p = models.Payment( order=baker.make("Order", status=Order.STATUS.NOT_VALID), @@ -285,6 +296,40 @@ def test_change_payment_status_refunded_order_not_valid(self): self.assertEqual(p.status, "refunded") self.assertEqual(p.order.status, Order.STATUS.CANCELED) + @override_settings(PLANS_PAYMENTS_RETURN_ORDER_WHEN_PAYMENT_REFUNDED=True) + def test_change_payment_status_refunded_order_not_valid_return_enabled(self): + p = models.Payment( + order=baker.make("Order", status=Order.STATUS.NOT_VALID), + status=PaymentStatus.REFUNDED, + ) + baker.make("UserPlan", user=p.order.user) + models.change_payment_status("sender", instance=p) + self.assertEqual(p.status, "refunded") + self.assertEqual(p.order.status, Order.STATUS.RETURNED) + + # This should not happen practically but with Django Admin, anything can happen... + def test_change_payment_status_refunded_order_canceled(self): + p = models.Payment( + order=baker.make("Order", status=Order.STATUS.CANCELED), + status=PaymentStatus.REFUNDED, + ) + baker.make("UserPlan", user=p.order.user) + models.change_payment_status("sender", instance=p) + self.assertEqual(p.status, "refunded") + self.assertEqual(p.order.status, Order.STATUS.CANCELED) + + # This should not happen practically but with Django Admin, anything can happen... + @override_settings(PLANS_PAYMENTS_RETURN_ORDER_WHEN_PAYMENT_REFUNDED=True) + def test_change_payment_status_refunded_order_canceled_return_enabled(self): + p = models.Payment( + order=baker.make("Order", status=Order.STATUS.CANCELED), + status=PaymentStatus.REFUNDED, + ) + baker.make("UserPlan", user=p.order.user) + models.change_payment_status("sender", instance=p) + self.assertEqual(p.status, "refunded") + self.assertEqual(p.order.status, Order.STATUS.CANCELED) + def test_change_payment_status_confirmed_no_recurring(self): p = models.Payment( order=baker.make("Order", status=Order.STATUS.NEW),