Changeset View
Changeset View
Standalone View
Standalone View
looper/tests/test_subscriptions.py
| from typing import Dict | |||||
| from unittest import mock | |||||
| import datetime | import datetime | ||||
| import logging | import logging | ||||
| from unittest import mock | |||||
| from django.dispatch import receiver | from django.dispatch import receiver | ||||
| import django.utils.timezone | import django.utils.timezone | ||||
| from . import AbstractLooperTestCase | from . import AbstractLooperTestCase | ||||
| from ..money import Money | from ..money import Money | ||||
| from ..models import Subscription | from ..models import Subscription | ||||
| from ..exceptions import IncorrectStatusError | from ..exceptions import IncorrectStatusError | ||||
| from looper.taxes import TaxType, ProductType | |||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||
| class SubscriptionsTest(AbstractLooperTestCase): | class SubscriptionsTest(AbstractLooperTestCase): | ||||
| def test_subscription_activation_signal(self): | def test_subscription_activation_signal(self): | ||||
| from ..signals import subscription_activated | from ..signals import subscription_activated | ||||
| ▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Lines | def test_create_managed_subscription(self): | ||||
| interval_unit=self.planvar.interval_unit, | interval_unit=self.planvar.interval_unit, | ||||
| interval_length=self.planvar.interval_length, | interval_length=self.planvar.interval_length, | ||||
| ) | ) | ||||
| self.assertEqual('on-hold', subscription.status) | self.assertEqual('on-hold', subscription.status) | ||||
| self.assertEqual(0, subscription.order_set.count()) | self.assertEqual(0, subscription.order_set.count()) | ||||
| self.assertIsNone(subscription.latest_order()) | self.assertIsNone(subscription.latest_order()) | ||||
| class SubscriptionTaxTest(AbstractLooperTestCase): | |||||
| def _create_subscription_with_product_and_billing_details( | |||||
| self, | |||||
| product_type=ProductType.DONATION.value, | |||||
| vat_number: str = '', | |||||
| **billing_address_data: Dict[str, str], | |||||
| ): | |||||
| self.plan.product.type = product_type | |||||
| self.plan.product.save(update_fields={'type'}) | |||||
| self.user.customer.vat_number = vat_number | |||||
| self.user.customer.save(update_fields={'vat_number'}) | |||||
| billing_address = self.user.customer.billing_address | |||||
| for k, v in billing_address_data.items(): | |||||
| setattr(billing_address, k, v) | |||||
| billing_address.save() | |||||
| return self.create_subscription() | |||||
| def test_update_tax_plan_has_default_donation_product_type_no_billing_address(self): | |||||
| subs = self.create_subscription() | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, '') | |||||
| self.assertEqual(subs.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(subs.tax_rate, 0) | |||||
| # No tax charged, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, '') | |||||
| self.assertEqual(order.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(order.tax_rate, 0) | |||||
| def test_update_tax_plan_has_detault_donation_product_type_no_vat_number_nl(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| vat_number='', | |||||
| country='NL' | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, 'NL') | |||||
| self.assertEqual(subs.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(subs.tax_rate, 0) | |||||
| # No tax charged, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, 'NL') | |||||
| self.assertEqual(order.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(order.tax_rate, 0) | |||||
| def test_update_tax_plan_has_electronic_services_type_no_billing_address(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, '') | |||||
| self.assertEqual(subs.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(subs.tax_rate, 0) | |||||
| # No tax charged, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, '') | |||||
| self.assertEqual(order.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(order.tax_rate, 0) | |||||
| def test_update_tax_plan_has_electronic_services_type_no_vat_number_nl(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='', | |||||
| country='NL', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 525)) | |||||
| self.assertEqual(subs.tax_country, 'NL') | |||||
| self.assertEqual(subs.tax_rate, 21) | |||||
| self.assertEqual(subs.tax_type, TaxType.VAT_CHARGE.value) | |||||
| # VAT is included into the price, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 525)) | |||||
| self.assertEqual(order.tax_country, 'NL') | |||||
| self.assertEqual(order.tax_rate, 21) | |||||
| self.assertEqual(order.tax_type, TaxType.VAT_CHARGE.value) | |||||
| def test_update_tax_plan_has_electronic_services_type_vat_number_nl(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='NL818152011B01', | |||||
| country='NL', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 525)) | |||||
| self.assertEqual(subs.tax_country, 'NL') | |||||
| self.assertEqual(subs.tax_rate, 21) | |||||
| # No reverse-charge for NL customers | |||||
| self.assertEqual(subs.tax_type, TaxType.VAT_CHARGE.value) | |||||
| # VAT is included into the price, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 525)) | |||||
| self.assertEqual(order.tax_country, 'NL') | |||||
| self.assertEqual(order.tax_rate, 21) | |||||
| self.assertEqual(order.tax_type, TaxType.VAT_CHARGE.value) | |||||
| def test_update_tax_plan_has_electronic_services_type_no_vat_number_de(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='', | |||||
| country='DE', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 475)) | |||||
| self.assertEqual(subs.tax_country, 'DE') | |||||
| self.assertEqual(subs.tax_rate, 19) | |||||
| self.assertEqual(subs.tax_type, TaxType.VAT_CHARGE.value) | |||||
| # VAT is included into the price, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 475)) | |||||
| self.assertEqual(order.tax_country, 'DE') | |||||
| self.assertEqual(order.tax_rate, 19) | |||||
| self.assertEqual(order.tax_type, TaxType.VAT_CHARGE.value) | |||||
| def test_update_tax_plan_has_electronic_services_type_vat_number_de(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='DE260543043', | |||||
| country='DE', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, 'DE') | |||||
| self.assertEqual(subs.tax_rate, 19) | |||||
| self.assertEqual(subs.tax_type, TaxType.VAT_REVERSE_CHARGE.value) | |||||
| # Reverse-charged VAT is subtracted from the subscription price | |||||
| self.assertEqual(order.price, Money('EUR', 2025)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, 'DE') | |||||
| self.assertEqual(order.tax_rate, 19) | |||||
| self.assertEqual(order.tax_type, TaxType.VAT_REVERSE_CHARGE.value) | |||||
| def test_update_tax_plan_has_electronic_services_type_no_vat_number_se(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='', | |||||
| country='SE', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 625)) | |||||
| self.assertEqual(subs.tax_country, 'SE') | |||||
| self.assertEqual(subs.tax_rate, 25) | |||||
| self.assertEqual(subs.tax_type, TaxType.VAT_CHARGE.value) | |||||
| # VAT is included into the price, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 625)) | |||||
| self.assertEqual(order.tax_country, 'SE') | |||||
| self.assertEqual(order.tax_rate, 25) | |||||
| self.assertEqual(order.tax_type, TaxType.VAT_CHARGE.value) | |||||
| def test_update_tax_plan_has_electronic_services_type_vat_number_se(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='SE 556070171501', | |||||
| country='SE', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, 'SE') | |||||
| self.assertEqual(subs.tax_rate, 25) | |||||
| self.assertEqual(subs.tax_type, TaxType.VAT_REVERSE_CHARGE.value) | |||||
| # Reverse-charged VAT is subtracted from the subscription price | |||||
| self.assertEqual(order.price, Money('EUR', 1875)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, 'SE') | |||||
| self.assertEqual(order.tax_rate, 25) | |||||
| self.assertEqual(order.tax_type, TaxType.VAT_REVERSE_CHARGE.value) | |||||
| def test_update_tax_plan_has_electronic_services_type_no_vat_number_us(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='', | |||||
| country='US', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, 'US') | |||||
| self.assertEqual(subs.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(subs.tax_rate, 0) | |||||
| # No tax charged, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, 'US') | |||||
| self.assertEqual(order.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(order.tax_rate, 0) | |||||
| def test_update_tax_plan_has_electronic_services_type_bogus_vat_number_us(self): | |||||
| subs = self._create_subscription_with_product_and_billing_details( | |||||
| product_type=ProductType.ELECTRONIC_SERVICE.value, | |||||
| vat_number='BOGUSVATNUMBER', | |||||
| country='US', | |||||
| ) | |||||
| subs.update_tax() | |||||
| order = subs.generate_order() | |||||
| self.assertEqual(subs.price, Money('EUR', 2500)) | |||||
| self.assertEqual(subs.tax, Money('EUR', 0)) | |||||
| self.assertEqual(subs.tax_country, 'US') | |||||
| self.assertEqual(subs.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(subs.tax_rate, 0) | |||||
| # No tax charged, hence no difference between subscription and order prices | |||||
| self.assertEqual(order.price, Money('EUR', 2500)) | |||||
| self.assertEqual(order.tax, Money('EUR', 0)) | |||||
| self.assertEqual(order.tax_country, 'US') | |||||
| self.assertEqual(order.tax_type, TaxType.NO_CHARGE.value) | |||||
| self.assertEqual(order.tax_rate, 0) | |||||
| class SubscriptionCancellationTest(AbstractLooperTestCase): | class SubscriptionCancellationTest(AbstractLooperTestCase): | ||||
| log = log.getChild('SubscriptionCancellationTest') | log = log.getChild('SubscriptionCancellationTest') | ||||
| def cancel(self, subscription: Subscription) -> datetime.datetime: | def cancel(self, subscription: Subscription) -> datetime.datetime: | ||||
| log.debug('Cancelling subscription pk=%d', subscription.pk) | log.debug('Cancelling subscription pk=%d', subscription.pk) | ||||
| now = django.utils.timezone.now() | now = django.utils.timezone.now() | ||||
| with mock.patch('django.utils.timezone.now') as mock_now: | with mock.patch('django.utils.timezone.now') as mock_now: | ||||
| mock_now.side_effect = [now] | mock_now.side_effect = [now] | ||||
| ▲ Show 20 Lines • Show All 124 Lines • Show Last 20 Lines | |||||