From 2ea81d4127d95134921d8206002c9d62c2973088 Mon Sep 17 00:00:00 2001 From: Fabrizio Messina Date: Tue, 29 Aug 2023 17:55:37 +0200 Subject: [PATCH 1/3] first version of the model --- subscriptions/models.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/subscriptions/models.py b/subscriptions/models.py index f475d79..a122783 100644 --- a/subscriptions/models.py +++ b/subscriptions/models.py @@ -559,4 +559,34 @@ def __str__(self) -> str: return f'{self.id} {self.amount}' +class SubscriptionNotificationEvent(models.Model): + """ + This model represent notifications that have happened + or are happening. + """ + class Status(models.IntegerChoices): + PENDING = 0 + ONGOING = 1 + COMPLETED = 2 + CANCELLED = 3 + ERROR = 4 + + subscription = models.ForeignKey( + Subscription, + on_delete=models.CASCADE, + related_name='notification_events' + ) + name = models.CharField( + max_length=255 + ) + status = models.IntegerField(choices=Status.choices, default=Status.PENDING) + + class Meta: + unique_together = ( + ('subscription', 'name'), + ) + + + + from .signals import create_default_subscription_for_new_user # noqa From 64c8e297582aaa9f4eb4ad61539ca8c9ce51f9ae Mon Sep 17 00:00:00 2001 From: Fabrizio Messina Date: Tue, 29 Aug 2023 19:21:02 +0200 Subject: [PATCH 2/3] some changes --- .../0033_creste_subscriptions_event_model.py | 53 +++++++++++++++ subscriptions/models.py | 11 +--- subscriptions/tasks.py | 66 ++++++++++++++++++- 3 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 subscriptions/migrations/0033_creste_subscriptions_event_model.py diff --git a/subscriptions/migrations/0033_creste_subscriptions_event_model.py b/subscriptions/migrations/0033_creste_subscriptions_event_model.py new file mode 100644 index 0000000..9e360b8 --- /dev/null +++ b/subscriptions/migrations/0033_creste_subscriptions_event_model.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.3 on 2023-08-29 17:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import djmoney.models.fields + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("subscriptions", "0032_subscription_initial_charge_offset_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="SubscriptionNotificationEvent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ( + "status", + models.IntegerField( + choices=[ + (0, "Pending"), + (1, "Ongoing"), + (2, "Completed"), + (3, "Cancelled"), + (4, "Error"), + ], + default=0, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "subscription", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="subscription_notification_events", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/subscriptions/models.py b/subscriptions/models.py index a122783..120ca0e 100644 --- a/subscriptions/models.py +++ b/subscriptions/models.py @@ -572,20 +572,15 @@ class Status(models.IntegerChoices): ERROR = 4 subscription = models.ForeignKey( - Subscription, + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, - related_name='notification_events' + related_name='subscription_notification_events' ) name = models.CharField( max_length=255 ) status = models.IntegerField(choices=Status.choices, default=Status.PENDING) - - class Meta: - unique_together = ( - ('subscription', 'name'), - ) - + created = models.DateTimeField(auto_now_add=True) diff --git a/subscriptions/tasks.py b/subscriptions/tasks.py index 1dd34dd..8f8c74c 100644 --- a/subscriptions/tasks.py +++ b/subscriptions/tasks.py @@ -1,11 +1,13 @@ +import dataclasses from concurrent.futures import ThreadPoolExecutor, wait from datetime import datetime, timedelta -from functools import partial +from functools import partial, wraps from logging import getLogger from operator import or_ -from typing import Iterable, Optional +from typing import Iterable, Callable, Optional, Union, TYPE_CHECKING from django.conf import settings +from django.contrib.auth import get_user_model from django.db import transaction from django.db.models import Q, QuerySet from django.utils.timezone import now @@ -13,11 +15,18 @@ from .defaults import DEFAULT_SUBSCRIPTIONS_OFFLINE_CHARGE_ATTEMPTS_SCHEDULE from .exceptions import PaymentError, ProlongationImpossible -from .models import Subscription, SubscriptionPayment +from .models import Subscription, SubscriptionPayment, SubscriptionNotificationEvent from .providers import get_provider +if TYPE_CHECKING: + from typing import ParamSpec, TypeVar + P = ParamSpec('P') + R = TypeVar('R') +F = Callable[['P'], 'R'] + log = getLogger(__name__) + DEFAULT_CHARGE_ATTEMPTS_SCHEDULE = getattr( settings, 'SUBSCRIPTIONS_OFFLINE_CHARGE_ATTEMPTS_SCHEDULE', @@ -192,3 +201,54 @@ def check_unfinished_payments(within: timedelta = timedelta(hours=12)): # TODO: check for concurrency issues, probably add transactions + + +@dataclasses.dataclass +class _Notification: + name: str + function: F + queryset: Optional[QuerySet] = dataclasses.field(default_factory=get_user_model().objects.all) + parameters: Optional[dict[str, Union[str, int]]] = dataclasses.field(default_factory=dict) + + +class _NotificaticationManager: + """ + Protected class, only one instance should be necessary. + """ + + def __init__(self): + self.__notifications: dict[str, _Notification] = {} + + def __call__( + self, name: str, + forget_after: int = None, + queryset: QuerySet = None, + **parameters + ) -> Callable[[F], F]: + if name in self.__notifications: + notification = self.__notifications[name] + raise ValueError(f'Notification {name} already registered for {notification.function}') + + def decorator(function: F) -> F: + self.__notifications[name] = _Notification(name, function, queryset, parameters) + return function + return decorator + + def fetch(self, name: str) -> QuerySet: + """ Fetch all the missing notifications. """ + notification = self.__notifications[name] + filters = {} + for parameter, value in notification.parameters.keys(): + if 'days_since_' in parameter: + parameter = parameter[len('days_since_'):] + since = now() - timedelta(days=value) + filters.update({ + f'{parameter}__gte': since, + f'{parameter}__lte': since + timedelta(1) + }) + else: + filters[parameter] = value + return notification.queryset.filter(**filters) + + + From d3923f339c019e05614aeeab2c196401e2a9c9b4 Mon Sep 17 00:00:00 2001 From: Fabrizio Messina Date: Mon, 4 Sep 2023 20:49:34 +0200 Subject: [PATCH 3/3] most of the changes have been made some changes are still needed to be done --- README.md | 6 +- demo/demo/tests/test_notifications.py | 224 +++ subscriptions/logic/__init__.py | 0 subscriptions/logic/notifications.py | 171 +++ .../0033_creste_subscriptions_event_model.py | 53 - ...er_plan_charge_amount_currency_and_more.py | 1364 +++++++++++++++++ subscriptions/models.py | 3 +- subscriptions/tasks.py | 69 +- 8 files changed, 1775 insertions(+), 115 deletions(-) create mode 100644 demo/demo/tests/test_notifications.py create mode 100644 subscriptions/logic/__init__.py create mode 100644 subscriptions/logic/notifications.py delete mode 100644 subscriptions/migrations/0033_creste_subscriptions_event_model.py create mode 100644 subscriptions/migrations/0037_alter_plan_charge_amount_currency_and_more.py diff --git a/README.md b/README.md index 5c0c220..717a040 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,10 @@ docker-compose -f demo/docker-compose.yml up Run tests with ```bash + nox -s test ``` - +If you want to use ipdb for debugging, you can set it up as following: +```bash +export PYTHONBREAKPOINT=ipdb.set_trace +``` diff --git a/demo/demo/tests/test_notifications.py b/demo/demo/tests/test_notifications.py new file mode 100644 index 0000000..408952b --- /dev/null +++ b/demo/demo/tests/test_notifications.py @@ -0,0 +1,224 @@ +""" +Test the notifications. + +These are mainly used for win back emails. +""" +from unittest import mock +import pytest + +from subscriptions.models import Subscription +from subscriptions.logic.notifications import NotificationManager, get_default_notification_manager +from subscriptions import tasks +from datetime import timedelta +from django.utils.timezone import now + + +@pytest.fixture() +def notifications(monkeypatch) -> NotificationManager: + """ + The notifications use a single instance whose state changes, + to avoid conflicts. + """ + _notifications = NotificationManager._notifications + NotificationManager._notifications = {} + yield get_default_notification_manager() + NotificationManager._notifications = _notifications + + +@pytest.fixture() +def ended_subscription(user, plan) -> Subscription: + """ + A subscription ended a few days ago + """ + return Subscription.objects.create( + user=user, + plan=plan, + end=now() - timedelta(5, hours=-3), + start=now() - timedelta(15) + ) + + +@pytest.fixture() +def default_plan_subscription(user, default_plan) -> Subscription: + """ + Default plan with end in the future. + """ + return Subscription.objects.create( + user=user, + plan=default_plan, + end=now() + timedelta(90), + start=now() + ) + + +@pytest.mark.django_db(transaction=True, databases=['actual_db']) +def test_user_notification_simple_case( + notifications: NotificationManager, + ended_subscription: Subscription +): + """ + Scenario: + The user has an expired subscription 5 days ago. + No default subscription. + + The notification function is called. + """ + mocked = mock.Mock() + # will remove a few hours to be sure. + notifications.register( + 'test', + days_since_subscriptions_end=5 + )(mocked) + notifications.execute('test') + mocked.assert_called_once_with(ended_subscription.user) + + +@pytest.mark.django_db(transaction=True, databases=['actual_db']) +def test_user_notification_prevented_existing_subscription( + notifications: NotificationManager, + ended_subscription: Subscription, + subscription: Subscription +): + """ + Scenario: + The user has an expired subscription 5 days ago, + but they have a subscription that whose end is more than 5 days ago, + possibly but not necessarily in some future date + + The notification function is never called. + """ + mocked = mock.Mock() + notifications.register( + f'test', + days_since_subscriptions_end=5 + )(mocked) + notifications.execute('test') + mocked.assert_not_called() + + +@pytest.mark.django_db(transaction=True, databases=['actual_db']) +def test_user_notification_only_once( + notifications: NotificationManager, + ended_subscription: Subscription +): + """ + Scenario: + The scheduler runs multiple times a day. + + The execute method must call the user at most one time. + """ + mocked = mock.Mock() + notifications.register( + 'test', + days_since_subscriptions_end=5 + )(mocked) + notifications.execute('test') + notifications.execute('test') + mocked.assert_called_once_with(ended_subscription.user) + + +@pytest.mark.this +class TestDefaultPlanScenario: + """ + Scenarios related to the default plan + """ + @pytest.mark.django_db(transaction=True, databases=['actual_db']) + def test_default_plan_is_ignored( + self, + ended_subscription: Subscription, + default_plan_subscription: Subscription, + notifications: NotificationManager, + + ): + """ + Scenario: + The user paid_subscription expired, he still has a default subscription + + The user will be notified. + """ + mocked = mock.Mock() + notifications.register( + 'test_default_plan_is_ignored', + days_since_subscriptions_end=5 + )(mocked) + notifications.execute('test_default_plan_is_ignored') + mocked.assert_called_once_with(ended_subscription.user) + + +@pytest.mark.django_db(transaction=True, databases=['actual_db']) +def test_clear_notification( + notifications: NotificationManager, + ended_subscription: Subscription +): + """ + Scenario: + The scheduler runs multiple times a day. + + The execute method must call the user at most one time. + """ + mocked = mock.Mock() + notifications.register( + 'test', + days_since_subscriptions_end=5, + forget_after=0 + )(mocked) + notifications.execute('test') + notifications.execute('test') + mocked.assert_has_calls([ + mock.call(ended_subscription.user), + mock.call(ended_subscription.user), + ]) + + +class TestDispatchNotificationTask: + @pytest.mark.django_db(transaction=True, databases=['actual_db']) + def test_dispatch_all_notification( + self, + notifications: NotificationManager, + ended_subscription: Subscription + ): + """ + Scenario: + The scheduler runs multiple times a day. + + The execute method must call the user at most one time. + """ + mocked_1 = mock.Mock() + mocked_2 = mock.Mock() + notifications.register( + 'test', + days_since_subscriptions_end=5 + )(mocked_1) + notifications.register( + 'test_2', + days_since_subscriptions_end=5 + )(mocked_2) + tasks.dispatch_notifications() + mocked_1.assert_called_once_with(ended_subscription.user) + mocked_2.assert_called_once_with(ended_subscription.user) + + @pytest.mark.django_db(transaction=True, databases=['actual_db']) + def test_dispatch_single_notification( + self, + notifications: NotificationManager, + ended_subscription: Subscription + ): + """ + Scenario: + The scheduler runs multiple times a day. + + The execute method must call the user at most one time. + """ + mocked_1 = mock.Mock() + mocked_2 = mock.Mock() + notifications.register( + 'test_1', + days_since_subscriptions_end=5 + )(mocked_1) + notifications.register( + 'test_2', + days_since_subscriptions_end=5 + )(mocked_2) + tasks.dispatch_notifications('test_1') + mocked_1.assert_called_once_with(ended_subscription.user) + mocked_2.assert_not_called() diff --git a/subscriptions/logic/__init__.py b/subscriptions/logic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subscriptions/logic/notifications.py b/subscriptions/logic/notifications.py new file mode 100644 index 0000000..e89b958 --- /dev/null +++ b/subscriptions/logic/notifications.py @@ -0,0 +1,171 @@ +from __future__ import annotations +import dataclasses + +from contextlib import contextmanager, suppress +from datetime import timedelta +from typing import Callable, Optional, Union, TYPE_CHECKING, ClassVar + +from django.contrib.auth import get_user_model +from django.db.models import QuerySet, Max, Q +from django.utils.timezone import now + +from subscriptions.models import SubscriptionNotificationEvent +from subscriptions.utils import HardDBLock + + +if TYPE_CHECKING: + from typing import ParamSpec, TypeVar + P = ParamSpec('P') + R = TypeVar('R') +F = Callable[['P'], 'R'] + + +@dataclasses.dataclass +class _Notification: + name: str + function: F + queryset: Optional[QuerySet] + parameters: Optional[dict[str, Union[str, int]]] + forget_after: int = None + ignore_default_plan: bool = True + + +class NotificationManager: + """ + Protected class, only one instance should be necessary. + """ + _notifications: ClassVar[dict[str, _Notification]] = {} + default_queryset: QuerySet + + def __init__(self, default_queryset: QuerySet = None, subscriptions_plan_to_exclude: list[int] = None): + if default_queryset: + self.default_queryset = default_queryset + else: + self.default_queryset = get_user_model().objects.all() + self.subscriptions_plan_to_exclude = subscriptions_plan_to_exclude if subscriptions_plan_to_exclude else [] + + @classmethod + def execute_all(cls): + """ Execute all notifications. """ + for notification in cls._notifications.keys(): + cls.execute(notification) + + def register( + self, + name: str, + queryset: QuerySet = None, + forget_after: int = None, + **parameters + ) -> Callable[[F], F]: + + if not queryset: + queryset = self.default_queryset + + def decorator(function: F) -> F: + if name in self._notifications: + notification = self._notifications[name] + raise ValueError(f'Notification {name} already registered for {notification.function}') + self._notifications[name] = _Notification(name, function, queryset, parameters, forget_after=forget_after) + return function + return decorator + + @staticmethod + def _get_queryset(notification: _Notification) -> QuerySet: + filters = {} + + queryset = notification.queryset.exclude( + subscription_notification_events__name=notification.name, + ) + for parameter, value in notification.parameters.items(): + # in the case of days since we do an aggregation. + # we do want to be sure it is the last item + if 'days_since_' in parameter: + parameter = parameter[len('days_since_'):] + since = now() - timedelta(days=value) + filters.update({ + f'{parameter}__gte': since, + f'{parameter}__lte': since + timedelta(1) + }) + else: + filters[parameter] = value + return queryset.filter(**filters) + + @classmethod + def execute(cls, name: str) -> None: + """ Fetch all the missing notifications. """ + + # to ensure the same entry has been generated only once + with HardDBLock(cls.__name__, name): + notification = cls._notifications[name] + # eventually some notification might be designed to be sent after enough time passed. + # for example, see this scenario + # --- unsubscribe -> coupon -> subscribe -> unsubscribe -> coupon ? --- + # THe coupon might be sent if enough time has passed since last time. + if notification.forget_after is not None: + # TODO: There is a reason why not to delete these? + SubscriptionNotificationEvent.objects.filter( + name=notification.name, + created__lte=now() - timedelta(notification.forget_after) + ).delete() + queryset = cls._get_queryset(notification) + events = [ + SubscriptionNotificationEvent(user=user, name=name) + for user in queryset + ] + SubscriptionNotificationEvent.objects.bulk_create( + events + ) + for event in events: + cls._execute_event(event, notification) + + @classmethod + def _execute_event(cls, event: SubscriptionNotificationEvent, notification: _Notification): + with cls._event_handler(event): + notification.function(event.user) + + @classmethod + @contextmanager + def _event_handler(cls, event: SubscriptionNotificationEvent): + + event.status = event.Status.ONGOING + event.save() + try: + yield + event.status = event.Status.COMPLETED + event.save() + except Exception: + event.status = event.Status.ERROR + event.save() + raise + + +def get_default_notification_manager() -> NotificationManager: + """ + This notification manager is aware of the most common use case. + Which is filter by subscription end, ignore default subscription + + Available custom filter fields: + - subscriptions_end: the max end date of the user paid subscriptions. + """ + plan_id = _get_default_plan() + queryset = get_user_model().objects.all() + + # if we have a plan id ignore it. + if plan_id: + queryset = queryset.annotate( + **{f'subscriptions_end': Max('subscriptions__end', filter=~Q( + subscriptions__plan_id__in=[plan_id] + ))} + ) + else: + queryset = queryset.annotate( + **{f'subscription_end': Max('subscription__end')} + ) + return NotificationManager(default_queryset=queryset) + + +def _get_default_plan() -> Optional[int]: + with suppress(ImportError): + from constance import config + return config.SUBSCRIPTIONS_DEFAULT_PLAN_ID + return None diff --git a/subscriptions/migrations/0033_creste_subscriptions_event_model.py b/subscriptions/migrations/0033_creste_subscriptions_event_model.py deleted file mode 100644 index 9e360b8..0000000 --- a/subscriptions/migrations/0033_creste_subscriptions_event_model.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 4.2.3 on 2023-08-29 17:08 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import djmoney.models.fields - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("subscriptions", "0032_subscription_initial_charge_offset_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="SubscriptionNotificationEvent", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=255)), - ( - "status", - models.IntegerField( - choices=[ - (0, "Pending"), - (1, "Ongoing"), - (2, "Completed"), - (3, "Cancelled"), - (4, "Error"), - ], - default=0, - ), - ), - ("created", models.DateTimeField(auto_now_add=True)), - ( - "subscription", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="subscription_notification_events", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/subscriptions/migrations/0037_alter_plan_charge_amount_currency_and_more.py b/subscriptions/migrations/0037_alter_plan_charge_amount_currency_and_more.py new file mode 100644 index 0000000..1fdb231 --- /dev/null +++ b/subscriptions/migrations/0037_alter_plan_charge_amount_currency_and_more.py @@ -0,0 +1,1364 @@ +# Generated by Django 4.2.3 on 2023-09-03 12:17 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import djmoney.models.fields +import subscriptions.utils + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("subscriptions", "0036_auto_20230711_0614"), + ] + + operations = [ + migrations.AlterField( + model_name="plan", + name="charge_amount_currency", + field=djmoney.models.fields.CurrencyField( + choices=[ + ("XUA", "ADB Unit of Account"), + ("AFN", "Afghan Afghani"), + ("AFA", "Afghan Afghani (1927–2002)"), + ("ALL", "Albanian Lek"), + ("ALK", "Albanian Lek (1946–1965)"), + ("DZD", "Algerian Dinar"), + ("ADP", "Andorran Peseta"), + ("AOA", "Angolan Kwanza"), + ("AOK", "Angolan Kwanza (1977–1991)"), + ("AON", "Angolan New Kwanza (1990–2000)"), + ("AOR", "Angolan Readjusted Kwanza (1995–1999)"), + ("ARA", "Argentine Austral"), + ("ARS", "Argentine Peso"), + ("ARM", "Argentine Peso (1881–1970)"), + ("ARP", "Argentine Peso (1983–1985)"), + ("ARL", "Argentine Peso Ley (1970–1983)"), + ("AMD", "Armenian Dram"), + ("AWG", "Aruban Florin"), + ("AUD", "Australian Dollar"), + ("ATS", "Austrian Schilling"), + ("AZN", "Azerbaijani Manat"), + ("AZM", "Azerbaijani Manat (1993–2006)"), + ("BSD", "Bahamian Dollar"), + ("BHD", "Bahraini Dinar"), + ("BDT", "Bangladeshi Taka"), + ("BBD", "Barbadian Dollar"), + ("BYN", "Belarusian Ruble"), + ("BYB", "Belarusian Ruble (1994–1999)"), + ("BYR", "Belarusian Ruble (2000–2016)"), + ("BEF", "Belgian Franc"), + ("BEC", "Belgian Franc (convertible)"), + ("BEL", "Belgian Franc (financial)"), + ("BZD", "Belize Dollar"), + ("BMD", "Bermudan Dollar"), + ("BTN", "Bhutanese Ngultrum"), + ("BOB", "Bolivian Boliviano"), + ("BOL", "Bolivian Boliviano (1863–1963)"), + ("BOV", "Bolivian Mvdol"), + ("BOP", "Bolivian Peso"), + ("BAM", "Bosnia-Herzegovina Convertible Mark"), + ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"), + ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"), + ("BWP", "Botswanan Pula"), + ("BRC", "Brazilian Cruzado (1986–1989)"), + ("BRZ", "Brazilian Cruzeiro (1942–1967)"), + ("BRE", "Brazilian Cruzeiro (1990–1993)"), + ("BRR", "Brazilian Cruzeiro (1993–1994)"), + ("BRN", "Brazilian New Cruzado (1989–1990)"), + ("BRB", "Brazilian New Cruzeiro (1967–1986)"), + ("BRL", "Brazilian Real"), + ("GBP", "British Pound"), + ("BND", "Brunei Dollar"), + ("BGL", "Bulgarian Hard Lev"), + ("BGN", "Bulgarian Lev"), + ("BGO", "Bulgarian Lev (1879–1952)"), + ("BGM", "Bulgarian Socialist Lev"), + ("BUK", "Burmese Kyat"), + ("BIF", "Burundian Franc"), + ("XPF", "CFP Franc"), + ("KHR", "Cambodian Riel"), + ("CAD", "Canadian Dollar"), + ("CVE", "Cape Verdean Escudo"), + ("KYD", "Cayman Islands Dollar"), + ("XAF", "Central African CFA Franc"), + ("CLE", "Chilean Escudo"), + ("CLP", "Chilean Peso"), + ("CLF", "Chilean Unit of Account (UF)"), + ("CNX", "Chinese People’s Bank Dollar"), + ("CNY", "Chinese Yuan"), + ("CNH", "Chinese Yuan (offshore)"), + ("COP", "Colombian Peso"), + ("COU", "Colombian Real Value Unit"), + ("KMF", "Comorian Franc"), + ("CDF", "Congolese Franc"), + ("CRC", "Costa Rican Colón"), + ("HRD", "Croatian Dinar"), + ("HRK", "Croatian Kuna"), + ("CUC", "Cuban Convertible Peso"), + ("CUP", "Cuban Peso"), + ("CYP", "Cypriot Pound"), + ("CZK", "Czech Koruna"), + ("CSK", "Czechoslovak Hard Koruna"), + ("DKK", "Danish Krone"), + ("DJF", "Djiboutian Franc"), + ("DOP", "Dominican Peso"), + ("NLG", "Dutch Guilder"), + ("XCD", "East Caribbean Dollar"), + ("DDM", "East German Mark"), + ("ECS", "Ecuadorian Sucre"), + ("ECV", "Ecuadorian Unit of Constant Value"), + ("EGP", "Egyptian Pound"), + ("GQE", "Equatorial Guinean Ekwele"), + ("ERN", "Eritrean Nakfa"), + ("EEK", "Estonian Kroon"), + ("ETB", "Ethiopian Birr"), + ("EUR", "Euro"), + ("XBA", "European Composite Unit"), + ("XEU", "European Currency Unit"), + ("XBB", "European Monetary Unit"), + ("XBC", "European Unit of Account (XBC)"), + ("XBD", "European Unit of Account (XBD)"), + ("FKP", "Falkland Islands Pound"), + ("FJD", "Fijian Dollar"), + ("FIM", "Finnish Markka"), + ("FRF", "French Franc"), + ("XFO", "French Gold Franc"), + ("XFU", "French UIC-Franc"), + ("GMD", "Gambian Dalasi"), + ("GEK", "Georgian Kupon Larit"), + ("GEL", "Georgian Lari"), + ("DEM", "German Mark"), + ("GHS", "Ghanaian Cedi"), + ("GHC", "Ghanaian Cedi (1979–2007)"), + ("GIP", "Gibraltar Pound"), + ("XAU", "Gold"), + ("GRD", "Greek Drachma"), + ("GTQ", "Guatemalan Quetzal"), + ("GWP", "Guinea-Bissau Peso"), + ("GNF", "Guinean Franc"), + ("GNS", "Guinean Syli"), + ("GYD", "Guyanaese Dollar"), + ("HTG", "Haitian Gourde"), + ("HNL", "Honduran Lempira"), + ("HKD", "Hong Kong Dollar"), + ("HUF", "Hungarian Forint"), + ("IMP", "IMP"), + ("ISK", "Icelandic Króna"), + ("ISJ", "Icelandic Króna (1918–1981)"), + ("INR", "Indian Rupee"), + ("IDR", "Indonesian Rupiah"), + ("IRR", "Iranian Rial"), + ("IQD", "Iraqi Dinar"), + ("IEP", "Irish Pound"), + ("ILS", "Israeli New Shekel"), + ("ILP", "Israeli Pound"), + ("ILR", "Israeli Shekel (1980–1985)"), + ("ITL", "Italian Lira"), + ("JMD", "Jamaican Dollar"), + ("JPY", "Japanese Yen"), + ("JOD", "Jordanian Dinar"), + ("KZT", "Kazakhstani Tenge"), + ("KES", "Kenyan Shilling"), + ("KWD", "Kuwaiti Dinar"), + ("KGS", "Kyrgystani Som"), + ("LAK", "Laotian Kip"), + ("LVL", "Latvian Lats"), + ("LVR", "Latvian Ruble"), + ("LBP", "Lebanese Pound"), + ("LSL", "Lesotho Loti"), + ("LRD", "Liberian Dollar"), + ("LYD", "Libyan Dinar"), + ("LTL", "Lithuanian Litas"), + ("LTT", "Lithuanian Talonas"), + ("LUL", "Luxembourg Financial Franc"), + ("LUC", "Luxembourgian Convertible Franc"), + ("LUF", "Luxembourgian Franc"), + ("MOP", "Macanese Pataca"), + ("MKD", "Macedonian Denar"), + ("MKN", "Macedonian Denar (1992–1993)"), + ("MGA", "Malagasy Ariary"), + ("MGF", "Malagasy Franc"), + ("MWK", "Malawian Kwacha"), + ("MYR", "Malaysian Ringgit"), + ("MVR", "Maldivian Rufiyaa"), + ("MVP", "Maldivian Rupee (1947–1981)"), + ("MLF", "Malian Franc"), + ("MTL", "Maltese Lira"), + ("MTP", "Maltese Pound"), + ("MRU", "Mauritanian Ouguiya"), + ("MRO", "Mauritanian Ouguiya (1973–2017)"), + ("MUR", "Mauritian Rupee"), + ("MXV", "Mexican Investment Unit"), + ("MXN", "Mexican Peso"), + ("MXP", "Mexican Silver Peso (1861–1992)"), + ("MDC", "Moldovan Cupon"), + ("MDL", "Moldovan Leu"), + ("MCF", "Monegasque Franc"), + ("MNT", "Mongolian Tugrik"), + ("MAD", "Moroccan Dirham"), + ("MAF", "Moroccan Franc"), + ("MZE", "Mozambican Escudo"), + ("MZN", "Mozambican Metical"), + ("MZM", "Mozambican Metical (1980–2006)"), + ("MMK", "Myanmar Kyat"), + ("NAD", "Namibian Dollar"), + ("NPR", "Nepalese Rupee"), + ("ANG", "Netherlands Antillean Guilder"), + ("TWD", "New Taiwan Dollar"), + ("NZD", "New Zealand Dollar"), + ("NIO", "Nicaraguan Córdoba"), + ("NIC", "Nicaraguan Córdoba (1988–1991)"), + ("NGN", "Nigerian Naira"), + ("KPW", "North Korean Won"), + ("NOK", "Norwegian Krone"), + ("OMR", "Omani Rial"), + ("PKR", "Pakistani Rupee"), + ("XPD", "Palladium"), + ("PAB", "Panamanian Balboa"), + ("PGK", "Papua New Guinean Kina"), + ("PYG", "Paraguayan Guarani"), + ("PEI", "Peruvian Inti"), + ("PEN", "Peruvian Sol"), + ("PES", "Peruvian Sol (1863–1965)"), + ("PHP", "Philippine Peso"), + ("XPT", "Platinum"), + ("PLN", "Polish Zloty"), + ("PLZ", "Polish Zloty (1950–1995)"), + ("PTE", "Portuguese Escudo"), + ("GWE", "Portuguese Guinea Escudo"), + ("QAR", "Qatari Riyal"), + ("XRE", "RINET Funds"), + ("RHD", "Rhodesian Dollar"), + ("RON", "Romanian Leu"), + ("ROL", "Romanian Leu (1952–2006)"), + ("RUB", "Russian Ruble"), + ("RUR", "Russian Ruble (1991–1998)"), + ("RWF", "Rwandan Franc"), + ("SVC", "Salvadoran Colón"), + ("WST", "Samoan Tala"), + ("SAR", "Saudi Riyal"), + ("RSD", "Serbian Dinar"), + ("CSD", "Serbian Dinar (2002–2006)"), + ("SCR", "Seychellois Rupee"), + ("SLL", "Sierra Leonean Leone (1964—2022)"), + ("XAG", "Silver"), + ("SGD", "Singapore Dollar"), + ("SKK", "Slovak Koruna"), + ("SIT", "Slovenian Tolar"), + ("SBD", "Solomon Islands Dollar"), + ("SOS", "Somali Shilling"), + ("ZAR", "South African Rand"), + ("ZAL", "South African Rand (financial)"), + ("KRH", "South Korean Hwan (1953–1962)"), + ("KRW", "South Korean Won"), + ("KRO", "South Korean Won (1945–1953)"), + ("SSP", "South Sudanese Pound"), + ("SUR", "Soviet Rouble"), + ("ESP", "Spanish Peseta"), + ("ESA", "Spanish Peseta (A account)"), + ("ESB", "Spanish Peseta (convertible account)"), + ("XDR", "Special Drawing Rights"), + ("LKR", "Sri Lankan Rupee"), + ("SHP", "St. Helena Pound"), + ("XSU", "Sucre"), + ("SDD", "Sudanese Dinar (1992–2007)"), + ("SDG", "Sudanese Pound"), + ("SDP", "Sudanese Pound (1957–1998)"), + ("SRD", "Surinamese Dollar"), + ("SRG", "Surinamese Guilder"), + ("SZL", "Swazi Lilangeni"), + ("SEK", "Swedish Krona"), + ("CHF", "Swiss Franc"), + ("SYP", "Syrian Pound"), + ("STN", "São Tomé & Príncipe Dobra"), + ("STD", "São Tomé & Príncipe Dobra (1977–2017)"), + ("TVD", "TVD"), + ("TJR", "Tajikistani Ruble"), + ("TJS", "Tajikistani Somoni"), + ("TZS", "Tanzanian Shilling"), + ("XTS", "Testing Currency Code"), + ("THB", "Thai Baht"), + ( + "XXX", + "The codes assigned for transactions where no currency is involved", + ), + ("TPE", "Timorese Escudo"), + ("TOP", "Tongan Paʻanga"), + ("TTD", "Trinidad & Tobago Dollar"), + ("TND", "Tunisian Dinar"), + ("TRY", "Turkish Lira"), + ("TRL", "Turkish Lira (1922–2005)"), + ("TMT", "Turkmenistani Manat"), + ("TMM", "Turkmenistani Manat (1993–2009)"), + ("USD", "US Dollar"), + ("USN", "US Dollar (Next day)"), + ("USS", "US Dollar (Same day)"), + ("UGX", "Ugandan Shilling"), + ("UGS", "Ugandan Shilling (1966–1987)"), + ("UAH", "Ukrainian Hryvnia"), + ("UAK", "Ukrainian Karbovanets"), + ("AED", "United Arab Emirates Dirham"), + ("UYW", "Uruguayan Nominal Wage Index Unit"), + ("UYU", "Uruguayan Peso"), + ("UYP", "Uruguayan Peso (1975–1993)"), + ("UYI", "Uruguayan Peso (Indexed Units)"), + ("UZS", "Uzbekistani Som"), + ("VUV", "Vanuatu Vatu"), + ("VES", "Venezuelan Bolívar"), + ("VEB", "Venezuelan Bolívar (1871–2008)"), + ("VEF", "Venezuelan Bolívar (2008–2018)"), + ("VND", "Vietnamese Dong"), + ("VNN", "Vietnamese Dong (1978–1985)"), + ("CHE", "WIR Euro"), + ("CHW", "WIR Franc"), + ("XOF", "West African CFA Franc"), + ("YDD", "Yemeni Dinar"), + ("YER", "Yemeni Rial"), + ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"), + ("YUD", "Yugoslavian Hard Dinar (1966–1990)"), + ("YUM", "Yugoslavian New Dinar (1994–2002)"), + ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"), + ("ZWN", "ZWN"), + ("ZRN", "Zairean New Zaire (1993–1998)"), + ("ZRZ", "Zairean Zaire (1971–1993)"), + ("ZMW", "Zambian Kwacha"), + ("ZMK", "Zambian Kwacha (1968–2012)"), + ("ZWD", "Zimbabwean Dollar (1980–2008)"), + ("ZWR", "Zimbabwean Dollar (2008)"), + ("ZWL", "Zimbabwean Dollar (2009)"), + ], + default="USD", + editable=False, + max_length=3, + null=True, + ), + ), + migrations.AlterField( + model_name="plan", + name="metadata", + field=models.JSONField( + blank=True, + default=dict, + encoder=subscriptions.utils.AdvancedJSONEncoder, + ), + ), + migrations.AlterField( + model_name="subscriptionpayment", + name="amount_currency", + field=djmoney.models.fields.CurrencyField( + choices=[ + ("XUA", "ADB Unit of Account"), + ("AFN", "Afghan Afghani"), + ("AFA", "Afghan Afghani (1927–2002)"), + ("ALL", "Albanian Lek"), + ("ALK", "Albanian Lek (1946–1965)"), + ("DZD", "Algerian Dinar"), + ("ADP", "Andorran Peseta"), + ("AOA", "Angolan Kwanza"), + ("AOK", "Angolan Kwanza (1977–1991)"), + ("AON", "Angolan New Kwanza (1990–2000)"), + ("AOR", "Angolan Readjusted Kwanza (1995–1999)"), + ("ARA", "Argentine Austral"), + ("ARS", "Argentine Peso"), + ("ARM", "Argentine Peso (1881–1970)"), + ("ARP", "Argentine Peso (1983–1985)"), + ("ARL", "Argentine Peso Ley (1970–1983)"), + ("AMD", "Armenian Dram"), + ("AWG", "Aruban Florin"), + ("AUD", "Australian Dollar"), + ("ATS", "Austrian Schilling"), + ("AZN", "Azerbaijani Manat"), + ("AZM", "Azerbaijani Manat (1993–2006)"), + ("BSD", "Bahamian Dollar"), + ("BHD", "Bahraini Dinar"), + ("BDT", "Bangladeshi Taka"), + ("BBD", "Barbadian Dollar"), + ("BYN", "Belarusian Ruble"), + ("BYB", "Belarusian Ruble (1994–1999)"), + ("BYR", "Belarusian Ruble (2000–2016)"), + ("BEF", "Belgian Franc"), + ("BEC", "Belgian Franc (convertible)"), + ("BEL", "Belgian Franc (financial)"), + ("BZD", "Belize Dollar"), + ("BMD", "Bermudan Dollar"), + ("BTN", "Bhutanese Ngultrum"), + ("BOB", "Bolivian Boliviano"), + ("BOL", "Bolivian Boliviano (1863–1963)"), + ("BOV", "Bolivian Mvdol"), + ("BOP", "Bolivian Peso"), + ("BAM", "Bosnia-Herzegovina Convertible Mark"), + ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"), + ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"), + ("BWP", "Botswanan Pula"), + ("BRC", "Brazilian Cruzado (1986–1989)"), + ("BRZ", "Brazilian Cruzeiro (1942–1967)"), + ("BRE", "Brazilian Cruzeiro (1990–1993)"), + ("BRR", "Brazilian Cruzeiro (1993–1994)"), + ("BRN", "Brazilian New Cruzado (1989–1990)"), + ("BRB", "Brazilian New Cruzeiro (1967–1986)"), + ("BRL", "Brazilian Real"), + ("GBP", "British Pound"), + ("BND", "Brunei Dollar"), + ("BGL", "Bulgarian Hard Lev"), + ("BGN", "Bulgarian Lev"), + ("BGO", "Bulgarian Lev (1879–1952)"), + ("BGM", "Bulgarian Socialist Lev"), + ("BUK", "Burmese Kyat"), + ("BIF", "Burundian Franc"), + ("XPF", "CFP Franc"), + ("KHR", "Cambodian Riel"), + ("CAD", "Canadian Dollar"), + ("CVE", "Cape Verdean Escudo"), + ("KYD", "Cayman Islands Dollar"), + ("XAF", "Central African CFA Franc"), + ("CLE", "Chilean Escudo"), + ("CLP", "Chilean Peso"), + ("CLF", "Chilean Unit of Account (UF)"), + ("CNX", "Chinese People’s Bank Dollar"), + ("CNY", "Chinese Yuan"), + ("CNH", "Chinese Yuan (offshore)"), + ("COP", "Colombian Peso"), + ("COU", "Colombian Real Value Unit"), + ("KMF", "Comorian Franc"), + ("CDF", "Congolese Franc"), + ("CRC", "Costa Rican Colón"), + ("HRD", "Croatian Dinar"), + ("HRK", "Croatian Kuna"), + ("CUC", "Cuban Convertible Peso"), + ("CUP", "Cuban Peso"), + ("CYP", "Cypriot Pound"), + ("CZK", "Czech Koruna"), + ("CSK", "Czechoslovak Hard Koruna"), + ("DKK", "Danish Krone"), + ("DJF", "Djiboutian Franc"), + ("DOP", "Dominican Peso"), + ("NLG", "Dutch Guilder"), + ("XCD", "East Caribbean Dollar"), + ("DDM", "East German Mark"), + ("ECS", "Ecuadorian Sucre"), + ("ECV", "Ecuadorian Unit of Constant Value"), + ("EGP", "Egyptian Pound"), + ("GQE", "Equatorial Guinean Ekwele"), + ("ERN", "Eritrean Nakfa"), + ("EEK", "Estonian Kroon"), + ("ETB", "Ethiopian Birr"), + ("EUR", "Euro"), + ("XBA", "European Composite Unit"), + ("XEU", "European Currency Unit"), + ("XBB", "European Monetary Unit"), + ("XBC", "European Unit of Account (XBC)"), + ("XBD", "European Unit of Account (XBD)"), + ("FKP", "Falkland Islands Pound"), + ("FJD", "Fijian Dollar"), + ("FIM", "Finnish Markka"), + ("FRF", "French Franc"), + ("XFO", "French Gold Franc"), + ("XFU", "French UIC-Franc"), + ("GMD", "Gambian Dalasi"), + ("GEK", "Georgian Kupon Larit"), + ("GEL", "Georgian Lari"), + ("DEM", "German Mark"), + ("GHS", "Ghanaian Cedi"), + ("GHC", "Ghanaian Cedi (1979–2007)"), + ("GIP", "Gibraltar Pound"), + ("XAU", "Gold"), + ("GRD", "Greek Drachma"), + ("GTQ", "Guatemalan Quetzal"), + ("GWP", "Guinea-Bissau Peso"), + ("GNF", "Guinean Franc"), + ("GNS", "Guinean Syli"), + ("GYD", "Guyanaese Dollar"), + ("HTG", "Haitian Gourde"), + ("HNL", "Honduran Lempira"), + ("HKD", "Hong Kong Dollar"), + ("HUF", "Hungarian Forint"), + ("IMP", "IMP"), + ("ISK", "Icelandic Króna"), + ("ISJ", "Icelandic Króna (1918–1981)"), + ("INR", "Indian Rupee"), + ("IDR", "Indonesian Rupiah"), + ("IRR", "Iranian Rial"), + ("IQD", "Iraqi Dinar"), + ("IEP", "Irish Pound"), + ("ILS", "Israeli New Shekel"), + ("ILP", "Israeli Pound"), + ("ILR", "Israeli Shekel (1980–1985)"), + ("ITL", "Italian Lira"), + ("JMD", "Jamaican Dollar"), + ("JPY", "Japanese Yen"), + ("JOD", "Jordanian Dinar"), + ("KZT", "Kazakhstani Tenge"), + ("KES", "Kenyan Shilling"), + ("KWD", "Kuwaiti Dinar"), + ("KGS", "Kyrgystani Som"), + ("LAK", "Laotian Kip"), + ("LVL", "Latvian Lats"), + ("LVR", "Latvian Ruble"), + ("LBP", "Lebanese Pound"), + ("LSL", "Lesotho Loti"), + ("LRD", "Liberian Dollar"), + ("LYD", "Libyan Dinar"), + ("LTL", "Lithuanian Litas"), + ("LTT", "Lithuanian Talonas"), + ("LUL", "Luxembourg Financial Franc"), + ("LUC", "Luxembourgian Convertible Franc"), + ("LUF", "Luxembourgian Franc"), + ("MOP", "Macanese Pataca"), + ("MKD", "Macedonian Denar"), + ("MKN", "Macedonian Denar (1992–1993)"), + ("MGA", "Malagasy Ariary"), + ("MGF", "Malagasy Franc"), + ("MWK", "Malawian Kwacha"), + ("MYR", "Malaysian Ringgit"), + ("MVR", "Maldivian Rufiyaa"), + ("MVP", "Maldivian Rupee (1947–1981)"), + ("MLF", "Malian Franc"), + ("MTL", "Maltese Lira"), + ("MTP", "Maltese Pound"), + ("MRU", "Mauritanian Ouguiya"), + ("MRO", "Mauritanian Ouguiya (1973–2017)"), + ("MUR", "Mauritian Rupee"), + ("MXV", "Mexican Investment Unit"), + ("MXN", "Mexican Peso"), + ("MXP", "Mexican Silver Peso (1861–1992)"), + ("MDC", "Moldovan Cupon"), + ("MDL", "Moldovan Leu"), + ("MCF", "Monegasque Franc"), + ("MNT", "Mongolian Tugrik"), + ("MAD", "Moroccan Dirham"), + ("MAF", "Moroccan Franc"), + ("MZE", "Mozambican Escudo"), + ("MZN", "Mozambican Metical"), + ("MZM", "Mozambican Metical (1980–2006)"), + ("MMK", "Myanmar Kyat"), + ("NAD", "Namibian Dollar"), + ("NPR", "Nepalese Rupee"), + ("ANG", "Netherlands Antillean Guilder"), + ("TWD", "New Taiwan Dollar"), + ("NZD", "New Zealand Dollar"), + ("NIO", "Nicaraguan Córdoba"), + ("NIC", "Nicaraguan Córdoba (1988–1991)"), + ("NGN", "Nigerian Naira"), + ("KPW", "North Korean Won"), + ("NOK", "Norwegian Krone"), + ("OMR", "Omani Rial"), + ("PKR", "Pakistani Rupee"), + ("XPD", "Palladium"), + ("PAB", "Panamanian Balboa"), + ("PGK", "Papua New Guinean Kina"), + ("PYG", "Paraguayan Guarani"), + ("PEI", "Peruvian Inti"), + ("PEN", "Peruvian Sol"), + ("PES", "Peruvian Sol (1863–1965)"), + ("PHP", "Philippine Peso"), + ("XPT", "Platinum"), + ("PLN", "Polish Zloty"), + ("PLZ", "Polish Zloty (1950–1995)"), + ("PTE", "Portuguese Escudo"), + ("GWE", "Portuguese Guinea Escudo"), + ("QAR", "Qatari Riyal"), + ("XRE", "RINET Funds"), + ("RHD", "Rhodesian Dollar"), + ("RON", "Romanian Leu"), + ("ROL", "Romanian Leu (1952–2006)"), + ("RUB", "Russian Ruble"), + ("RUR", "Russian Ruble (1991–1998)"), + ("RWF", "Rwandan Franc"), + ("SVC", "Salvadoran Colón"), + ("WST", "Samoan Tala"), + ("SAR", "Saudi Riyal"), + ("RSD", "Serbian Dinar"), + ("CSD", "Serbian Dinar (2002–2006)"), + ("SCR", "Seychellois Rupee"), + ("SLL", "Sierra Leonean Leone (1964—2022)"), + ("XAG", "Silver"), + ("SGD", "Singapore Dollar"), + ("SKK", "Slovak Koruna"), + ("SIT", "Slovenian Tolar"), + ("SBD", "Solomon Islands Dollar"), + ("SOS", "Somali Shilling"), + ("ZAR", "South African Rand"), + ("ZAL", "South African Rand (financial)"), + ("KRH", "South Korean Hwan (1953–1962)"), + ("KRW", "South Korean Won"), + ("KRO", "South Korean Won (1945–1953)"), + ("SSP", "South Sudanese Pound"), + ("SUR", "Soviet Rouble"), + ("ESP", "Spanish Peseta"), + ("ESA", "Spanish Peseta (A account)"), + ("ESB", "Spanish Peseta (convertible account)"), + ("XDR", "Special Drawing Rights"), + ("LKR", "Sri Lankan Rupee"), + ("SHP", "St. Helena Pound"), + ("XSU", "Sucre"), + ("SDD", "Sudanese Dinar (1992–2007)"), + ("SDG", "Sudanese Pound"), + ("SDP", "Sudanese Pound (1957–1998)"), + ("SRD", "Surinamese Dollar"), + ("SRG", "Surinamese Guilder"), + ("SZL", "Swazi Lilangeni"), + ("SEK", "Swedish Krona"), + ("CHF", "Swiss Franc"), + ("SYP", "Syrian Pound"), + ("STN", "São Tomé & Príncipe Dobra"), + ("STD", "São Tomé & Príncipe Dobra (1977–2017)"), + ("TVD", "TVD"), + ("TJR", "Tajikistani Ruble"), + ("TJS", "Tajikistani Somoni"), + ("TZS", "Tanzanian Shilling"), + ("XTS", "Testing Currency Code"), + ("THB", "Thai Baht"), + ( + "XXX", + "The codes assigned for transactions where no currency is involved", + ), + ("TPE", "Timorese Escudo"), + ("TOP", "Tongan Paʻanga"), + ("TTD", "Trinidad & Tobago Dollar"), + ("TND", "Tunisian Dinar"), + ("TRY", "Turkish Lira"), + ("TRL", "Turkish Lira (1922–2005)"), + ("TMT", "Turkmenistani Manat"), + ("TMM", "Turkmenistani Manat (1993–2009)"), + ("USD", "US Dollar"), + ("USN", "US Dollar (Next day)"), + ("USS", "US Dollar (Same day)"), + ("UGX", "Ugandan Shilling"), + ("UGS", "Ugandan Shilling (1966–1987)"), + ("UAH", "Ukrainian Hryvnia"), + ("UAK", "Ukrainian Karbovanets"), + ("AED", "United Arab Emirates Dirham"), + ("UYW", "Uruguayan Nominal Wage Index Unit"), + ("UYU", "Uruguayan Peso"), + ("UYP", "Uruguayan Peso (1975–1993)"), + ("UYI", "Uruguayan Peso (Indexed Units)"), + ("UZS", "Uzbekistani Som"), + ("VUV", "Vanuatu Vatu"), + ("VES", "Venezuelan Bolívar"), + ("VEB", "Venezuelan Bolívar (1871–2008)"), + ("VEF", "Venezuelan Bolívar (2008–2018)"), + ("VND", "Vietnamese Dong"), + ("VNN", "Vietnamese Dong (1978–1985)"), + ("CHE", "WIR Euro"), + ("CHW", "WIR Franc"), + ("XOF", "West African CFA Franc"), + ("YDD", "Yemeni Dinar"), + ("YER", "Yemeni Rial"), + ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"), + ("YUD", "Yugoslavian Hard Dinar (1966–1990)"), + ("YUM", "Yugoslavian New Dinar (1994–2002)"), + ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"), + ("ZWN", "ZWN"), + ("ZRN", "Zairean New Zaire (1993–1998)"), + ("ZRZ", "Zairean Zaire (1971–1993)"), + ("ZMW", "Zambian Kwacha"), + ("ZMK", "Zambian Kwacha (1968–2012)"), + ("ZWD", "Zimbabwean Dollar (1980–2008)"), + ("ZWR", "Zimbabwean Dollar (2008)"), + ("ZWL", "Zimbabwean Dollar (2009)"), + ], + default="USD", + editable=False, + max_length=3, + null=True, + ), + ), + migrations.AlterField( + model_name="subscriptionpayment", + name="metadata", + field=models.JSONField( + blank=True, + default=dict, + encoder=subscriptions.utils.AdvancedJSONEncoder, + ), + ), + migrations.AlterField( + model_name="subscriptionpaymentrefund", + name="amount_currency", + field=djmoney.models.fields.CurrencyField( + choices=[ + ("XUA", "ADB Unit of Account"), + ("AFN", "Afghan Afghani"), + ("AFA", "Afghan Afghani (1927–2002)"), + ("ALL", "Albanian Lek"), + ("ALK", "Albanian Lek (1946–1965)"), + ("DZD", "Algerian Dinar"), + ("ADP", "Andorran Peseta"), + ("AOA", "Angolan Kwanza"), + ("AOK", "Angolan Kwanza (1977–1991)"), + ("AON", "Angolan New Kwanza (1990–2000)"), + ("AOR", "Angolan Readjusted Kwanza (1995–1999)"), + ("ARA", "Argentine Austral"), + ("ARS", "Argentine Peso"), + ("ARM", "Argentine Peso (1881–1970)"), + ("ARP", "Argentine Peso (1983–1985)"), + ("ARL", "Argentine Peso Ley (1970–1983)"), + ("AMD", "Armenian Dram"), + ("AWG", "Aruban Florin"), + ("AUD", "Australian Dollar"), + ("ATS", "Austrian Schilling"), + ("AZN", "Azerbaijani Manat"), + ("AZM", "Azerbaijani Manat (1993–2006)"), + ("BSD", "Bahamian Dollar"), + ("BHD", "Bahraini Dinar"), + ("BDT", "Bangladeshi Taka"), + ("BBD", "Barbadian Dollar"), + ("BYN", "Belarusian Ruble"), + ("BYB", "Belarusian Ruble (1994–1999)"), + ("BYR", "Belarusian Ruble (2000–2016)"), + ("BEF", "Belgian Franc"), + ("BEC", "Belgian Franc (convertible)"), + ("BEL", "Belgian Franc (financial)"), + ("BZD", "Belize Dollar"), + ("BMD", "Bermudan Dollar"), + ("BTN", "Bhutanese Ngultrum"), + ("BOB", "Bolivian Boliviano"), + ("BOL", "Bolivian Boliviano (1863–1963)"), + ("BOV", "Bolivian Mvdol"), + ("BOP", "Bolivian Peso"), + ("BAM", "Bosnia-Herzegovina Convertible Mark"), + ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"), + ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"), + ("BWP", "Botswanan Pula"), + ("BRC", "Brazilian Cruzado (1986–1989)"), + ("BRZ", "Brazilian Cruzeiro (1942–1967)"), + ("BRE", "Brazilian Cruzeiro (1990–1993)"), + ("BRR", "Brazilian Cruzeiro (1993–1994)"), + ("BRN", "Brazilian New Cruzado (1989–1990)"), + ("BRB", "Brazilian New Cruzeiro (1967–1986)"), + ("BRL", "Brazilian Real"), + ("GBP", "British Pound"), + ("BND", "Brunei Dollar"), + ("BGL", "Bulgarian Hard Lev"), + ("BGN", "Bulgarian Lev"), + ("BGO", "Bulgarian Lev (1879–1952)"), + ("BGM", "Bulgarian Socialist Lev"), + ("BUK", "Burmese Kyat"), + ("BIF", "Burundian Franc"), + ("XPF", "CFP Franc"), + ("KHR", "Cambodian Riel"), + ("CAD", "Canadian Dollar"), + ("CVE", "Cape Verdean Escudo"), + ("KYD", "Cayman Islands Dollar"), + ("XAF", "Central African CFA Franc"), + ("CLE", "Chilean Escudo"), + ("CLP", "Chilean Peso"), + ("CLF", "Chilean Unit of Account (UF)"), + ("CNX", "Chinese People’s Bank Dollar"), + ("CNY", "Chinese Yuan"), + ("CNH", "Chinese Yuan (offshore)"), + ("COP", "Colombian Peso"), + ("COU", "Colombian Real Value Unit"), + ("KMF", "Comorian Franc"), + ("CDF", "Congolese Franc"), + ("CRC", "Costa Rican Colón"), + ("HRD", "Croatian Dinar"), + ("HRK", "Croatian Kuna"), + ("CUC", "Cuban Convertible Peso"), + ("CUP", "Cuban Peso"), + ("CYP", "Cypriot Pound"), + ("CZK", "Czech Koruna"), + ("CSK", "Czechoslovak Hard Koruna"), + ("DKK", "Danish Krone"), + ("DJF", "Djiboutian Franc"), + ("DOP", "Dominican Peso"), + ("NLG", "Dutch Guilder"), + ("XCD", "East Caribbean Dollar"), + ("DDM", "East German Mark"), + ("ECS", "Ecuadorian Sucre"), + ("ECV", "Ecuadorian Unit of Constant Value"), + ("EGP", "Egyptian Pound"), + ("GQE", "Equatorial Guinean Ekwele"), + ("ERN", "Eritrean Nakfa"), + ("EEK", "Estonian Kroon"), + ("ETB", "Ethiopian Birr"), + ("EUR", "Euro"), + ("XBA", "European Composite Unit"), + ("XEU", "European Currency Unit"), + ("XBB", "European Monetary Unit"), + ("XBC", "European Unit of Account (XBC)"), + ("XBD", "European Unit of Account (XBD)"), + ("FKP", "Falkland Islands Pound"), + ("FJD", "Fijian Dollar"), + ("FIM", "Finnish Markka"), + ("FRF", "French Franc"), + ("XFO", "French Gold Franc"), + ("XFU", "French UIC-Franc"), + ("GMD", "Gambian Dalasi"), + ("GEK", "Georgian Kupon Larit"), + ("GEL", "Georgian Lari"), + ("DEM", "German Mark"), + ("GHS", "Ghanaian Cedi"), + ("GHC", "Ghanaian Cedi (1979–2007)"), + ("GIP", "Gibraltar Pound"), + ("XAU", "Gold"), + ("GRD", "Greek Drachma"), + ("GTQ", "Guatemalan Quetzal"), + ("GWP", "Guinea-Bissau Peso"), + ("GNF", "Guinean Franc"), + ("GNS", "Guinean Syli"), + ("GYD", "Guyanaese Dollar"), + ("HTG", "Haitian Gourde"), + ("HNL", "Honduran Lempira"), + ("HKD", "Hong Kong Dollar"), + ("HUF", "Hungarian Forint"), + ("IMP", "IMP"), + ("ISK", "Icelandic Króna"), + ("ISJ", "Icelandic Króna (1918–1981)"), + ("INR", "Indian Rupee"), + ("IDR", "Indonesian Rupiah"), + ("IRR", "Iranian Rial"), + ("IQD", "Iraqi Dinar"), + ("IEP", "Irish Pound"), + ("ILS", "Israeli New Shekel"), + ("ILP", "Israeli Pound"), + ("ILR", "Israeli Shekel (1980–1985)"), + ("ITL", "Italian Lira"), + ("JMD", "Jamaican Dollar"), + ("JPY", "Japanese Yen"), + ("JOD", "Jordanian Dinar"), + ("KZT", "Kazakhstani Tenge"), + ("KES", "Kenyan Shilling"), + ("KWD", "Kuwaiti Dinar"), + ("KGS", "Kyrgystani Som"), + ("LAK", "Laotian Kip"), + ("LVL", "Latvian Lats"), + ("LVR", "Latvian Ruble"), + ("LBP", "Lebanese Pound"), + ("LSL", "Lesotho Loti"), + ("LRD", "Liberian Dollar"), + ("LYD", "Libyan Dinar"), + ("LTL", "Lithuanian Litas"), + ("LTT", "Lithuanian Talonas"), + ("LUL", "Luxembourg Financial Franc"), + ("LUC", "Luxembourgian Convertible Franc"), + ("LUF", "Luxembourgian Franc"), + ("MOP", "Macanese Pataca"), + ("MKD", "Macedonian Denar"), + ("MKN", "Macedonian Denar (1992–1993)"), + ("MGA", "Malagasy Ariary"), + ("MGF", "Malagasy Franc"), + ("MWK", "Malawian Kwacha"), + ("MYR", "Malaysian Ringgit"), + ("MVR", "Maldivian Rufiyaa"), + ("MVP", "Maldivian Rupee (1947–1981)"), + ("MLF", "Malian Franc"), + ("MTL", "Maltese Lira"), + ("MTP", "Maltese Pound"), + ("MRU", "Mauritanian Ouguiya"), + ("MRO", "Mauritanian Ouguiya (1973–2017)"), + ("MUR", "Mauritian Rupee"), + ("MXV", "Mexican Investment Unit"), + ("MXN", "Mexican Peso"), + ("MXP", "Mexican Silver Peso (1861–1992)"), + ("MDC", "Moldovan Cupon"), + ("MDL", "Moldovan Leu"), + ("MCF", "Monegasque Franc"), + ("MNT", "Mongolian Tugrik"), + ("MAD", "Moroccan Dirham"), + ("MAF", "Moroccan Franc"), + ("MZE", "Mozambican Escudo"), + ("MZN", "Mozambican Metical"), + ("MZM", "Mozambican Metical (1980–2006)"), + ("MMK", "Myanmar Kyat"), + ("NAD", "Namibian Dollar"), + ("NPR", "Nepalese Rupee"), + ("ANG", "Netherlands Antillean Guilder"), + ("TWD", "New Taiwan Dollar"), + ("NZD", "New Zealand Dollar"), + ("NIO", "Nicaraguan Córdoba"), + ("NIC", "Nicaraguan Córdoba (1988–1991)"), + ("NGN", "Nigerian Naira"), + ("KPW", "North Korean Won"), + ("NOK", "Norwegian Krone"), + ("OMR", "Omani Rial"), + ("PKR", "Pakistani Rupee"), + ("XPD", "Palladium"), + ("PAB", "Panamanian Balboa"), + ("PGK", "Papua New Guinean Kina"), + ("PYG", "Paraguayan Guarani"), + ("PEI", "Peruvian Inti"), + ("PEN", "Peruvian Sol"), + ("PES", "Peruvian Sol (1863–1965)"), + ("PHP", "Philippine Peso"), + ("XPT", "Platinum"), + ("PLN", "Polish Zloty"), + ("PLZ", "Polish Zloty (1950–1995)"), + ("PTE", "Portuguese Escudo"), + ("GWE", "Portuguese Guinea Escudo"), + ("QAR", "Qatari Riyal"), + ("XRE", "RINET Funds"), + ("RHD", "Rhodesian Dollar"), + ("RON", "Romanian Leu"), + ("ROL", "Romanian Leu (1952–2006)"), + ("RUB", "Russian Ruble"), + ("RUR", "Russian Ruble (1991–1998)"), + ("RWF", "Rwandan Franc"), + ("SVC", "Salvadoran Colón"), + ("WST", "Samoan Tala"), + ("SAR", "Saudi Riyal"), + ("RSD", "Serbian Dinar"), + ("CSD", "Serbian Dinar (2002–2006)"), + ("SCR", "Seychellois Rupee"), + ("SLL", "Sierra Leonean Leone (1964—2022)"), + ("XAG", "Silver"), + ("SGD", "Singapore Dollar"), + ("SKK", "Slovak Koruna"), + ("SIT", "Slovenian Tolar"), + ("SBD", "Solomon Islands Dollar"), + ("SOS", "Somali Shilling"), + ("ZAR", "South African Rand"), + ("ZAL", "South African Rand (financial)"), + ("KRH", "South Korean Hwan (1953–1962)"), + ("KRW", "South Korean Won"), + ("KRO", "South Korean Won (1945–1953)"), + ("SSP", "South Sudanese Pound"), + ("SUR", "Soviet Rouble"), + ("ESP", "Spanish Peseta"), + ("ESA", "Spanish Peseta (A account)"), + ("ESB", "Spanish Peseta (convertible account)"), + ("XDR", "Special Drawing Rights"), + ("LKR", "Sri Lankan Rupee"), + ("SHP", "St. Helena Pound"), + ("XSU", "Sucre"), + ("SDD", "Sudanese Dinar (1992–2007)"), + ("SDG", "Sudanese Pound"), + ("SDP", "Sudanese Pound (1957–1998)"), + ("SRD", "Surinamese Dollar"), + ("SRG", "Surinamese Guilder"), + ("SZL", "Swazi Lilangeni"), + ("SEK", "Swedish Krona"), + ("CHF", "Swiss Franc"), + ("SYP", "Syrian Pound"), + ("STN", "São Tomé & Príncipe Dobra"), + ("STD", "São Tomé & Príncipe Dobra (1977–2017)"), + ("TVD", "TVD"), + ("TJR", "Tajikistani Ruble"), + ("TJS", "Tajikistani Somoni"), + ("TZS", "Tanzanian Shilling"), + ("XTS", "Testing Currency Code"), + ("THB", "Thai Baht"), + ( + "XXX", + "The codes assigned for transactions where no currency is involved", + ), + ("TPE", "Timorese Escudo"), + ("TOP", "Tongan Paʻanga"), + ("TTD", "Trinidad & Tobago Dollar"), + ("TND", "Tunisian Dinar"), + ("TRY", "Turkish Lira"), + ("TRL", "Turkish Lira (1922–2005)"), + ("TMT", "Turkmenistani Manat"), + ("TMM", "Turkmenistani Manat (1993–2009)"), + ("USD", "US Dollar"), + ("USN", "US Dollar (Next day)"), + ("USS", "US Dollar (Same day)"), + ("UGX", "Ugandan Shilling"), + ("UGS", "Ugandan Shilling (1966–1987)"), + ("UAH", "Ukrainian Hryvnia"), + ("UAK", "Ukrainian Karbovanets"), + ("AED", "United Arab Emirates Dirham"), + ("UYW", "Uruguayan Nominal Wage Index Unit"), + ("UYU", "Uruguayan Peso"), + ("UYP", "Uruguayan Peso (1975–1993)"), + ("UYI", "Uruguayan Peso (Indexed Units)"), + ("UZS", "Uzbekistani Som"), + ("VUV", "Vanuatu Vatu"), + ("VES", "Venezuelan Bolívar"), + ("VEB", "Venezuelan Bolívar (1871–2008)"), + ("VEF", "Venezuelan Bolívar (2008–2018)"), + ("VND", "Vietnamese Dong"), + ("VNN", "Vietnamese Dong (1978–1985)"), + ("CHE", "WIR Euro"), + ("CHW", "WIR Franc"), + ("XOF", "West African CFA Franc"), + ("YDD", "Yemeni Dinar"), + ("YER", "Yemeni Rial"), + ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"), + ("YUD", "Yugoslavian Hard Dinar (1966–1990)"), + ("YUM", "Yugoslavian New Dinar (1994–2002)"), + ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"), + ("ZWN", "ZWN"), + ("ZRN", "Zairean New Zaire (1993–1998)"), + ("ZRZ", "Zairean Zaire (1971–1993)"), + ("ZMW", "Zambian Kwacha"), + ("ZMK", "Zambian Kwacha (1968–2012)"), + ("ZWD", "Zimbabwean Dollar (1980–2008)"), + ("ZWR", "Zimbabwean Dollar (2008)"), + ("ZWL", "Zimbabwean Dollar (2009)"), + ], + default="USD", + editable=False, + max_length=3, + null=True, + ), + ), + migrations.AlterField( + model_name="subscriptionpaymentrefund", + name="metadata", + field=models.JSONField( + blank=True, + default=dict, + encoder=subscriptions.utils.AdvancedJSONEncoder, + ), + ), + migrations.AlterField( + model_name="tax", + name="amount_currency", + field=djmoney.models.fields.CurrencyField( + choices=[ + ("XUA", "ADB Unit of Account"), + ("AFN", "Afghan Afghani"), + ("AFA", "Afghan Afghani (1927–2002)"), + ("ALL", "Albanian Lek"), + ("ALK", "Albanian Lek (1946–1965)"), + ("DZD", "Algerian Dinar"), + ("ADP", "Andorran Peseta"), + ("AOA", "Angolan Kwanza"), + ("AOK", "Angolan Kwanza (1977–1991)"), + ("AON", "Angolan New Kwanza (1990–2000)"), + ("AOR", "Angolan Readjusted Kwanza (1995–1999)"), + ("ARA", "Argentine Austral"), + ("ARS", "Argentine Peso"), + ("ARM", "Argentine Peso (1881–1970)"), + ("ARP", "Argentine Peso (1983–1985)"), + ("ARL", "Argentine Peso Ley (1970–1983)"), + ("AMD", "Armenian Dram"), + ("AWG", "Aruban Florin"), + ("AUD", "Australian Dollar"), + ("ATS", "Austrian Schilling"), + ("AZN", "Azerbaijani Manat"), + ("AZM", "Azerbaijani Manat (1993–2006)"), + ("BSD", "Bahamian Dollar"), + ("BHD", "Bahraini Dinar"), + ("BDT", "Bangladeshi Taka"), + ("BBD", "Barbadian Dollar"), + ("BYN", "Belarusian Ruble"), + ("BYB", "Belarusian Ruble (1994–1999)"), + ("BYR", "Belarusian Ruble (2000–2016)"), + ("BEF", "Belgian Franc"), + ("BEC", "Belgian Franc (convertible)"), + ("BEL", "Belgian Franc (financial)"), + ("BZD", "Belize Dollar"), + ("BMD", "Bermudan Dollar"), + ("BTN", "Bhutanese Ngultrum"), + ("BOB", "Bolivian Boliviano"), + ("BOL", "Bolivian Boliviano (1863–1963)"), + ("BOV", "Bolivian Mvdol"), + ("BOP", "Bolivian Peso"), + ("BAM", "Bosnia-Herzegovina Convertible Mark"), + ("BAD", "Bosnia-Herzegovina Dinar (1992–1994)"), + ("BAN", "Bosnia-Herzegovina New Dinar (1994–1997)"), + ("BWP", "Botswanan Pula"), + ("BRC", "Brazilian Cruzado (1986–1989)"), + ("BRZ", "Brazilian Cruzeiro (1942–1967)"), + ("BRE", "Brazilian Cruzeiro (1990–1993)"), + ("BRR", "Brazilian Cruzeiro (1993–1994)"), + ("BRN", "Brazilian New Cruzado (1989–1990)"), + ("BRB", "Brazilian New Cruzeiro (1967–1986)"), + ("BRL", "Brazilian Real"), + ("GBP", "British Pound"), + ("BND", "Brunei Dollar"), + ("BGL", "Bulgarian Hard Lev"), + ("BGN", "Bulgarian Lev"), + ("BGO", "Bulgarian Lev (1879–1952)"), + ("BGM", "Bulgarian Socialist Lev"), + ("BUK", "Burmese Kyat"), + ("BIF", "Burundian Franc"), + ("XPF", "CFP Franc"), + ("KHR", "Cambodian Riel"), + ("CAD", "Canadian Dollar"), + ("CVE", "Cape Verdean Escudo"), + ("KYD", "Cayman Islands Dollar"), + ("XAF", "Central African CFA Franc"), + ("CLE", "Chilean Escudo"), + ("CLP", "Chilean Peso"), + ("CLF", "Chilean Unit of Account (UF)"), + ("CNX", "Chinese People’s Bank Dollar"), + ("CNY", "Chinese Yuan"), + ("CNH", "Chinese Yuan (offshore)"), + ("COP", "Colombian Peso"), + ("COU", "Colombian Real Value Unit"), + ("KMF", "Comorian Franc"), + ("CDF", "Congolese Franc"), + ("CRC", "Costa Rican Colón"), + ("HRD", "Croatian Dinar"), + ("HRK", "Croatian Kuna"), + ("CUC", "Cuban Convertible Peso"), + ("CUP", "Cuban Peso"), + ("CYP", "Cypriot Pound"), + ("CZK", "Czech Koruna"), + ("CSK", "Czechoslovak Hard Koruna"), + ("DKK", "Danish Krone"), + ("DJF", "Djiboutian Franc"), + ("DOP", "Dominican Peso"), + ("NLG", "Dutch Guilder"), + ("XCD", "East Caribbean Dollar"), + ("DDM", "East German Mark"), + ("ECS", "Ecuadorian Sucre"), + ("ECV", "Ecuadorian Unit of Constant Value"), + ("EGP", "Egyptian Pound"), + ("GQE", "Equatorial Guinean Ekwele"), + ("ERN", "Eritrean Nakfa"), + ("EEK", "Estonian Kroon"), + ("ETB", "Ethiopian Birr"), + ("EUR", "Euro"), + ("XBA", "European Composite Unit"), + ("XEU", "European Currency Unit"), + ("XBB", "European Monetary Unit"), + ("XBC", "European Unit of Account (XBC)"), + ("XBD", "European Unit of Account (XBD)"), + ("FKP", "Falkland Islands Pound"), + ("FJD", "Fijian Dollar"), + ("FIM", "Finnish Markka"), + ("FRF", "French Franc"), + ("XFO", "French Gold Franc"), + ("XFU", "French UIC-Franc"), + ("GMD", "Gambian Dalasi"), + ("GEK", "Georgian Kupon Larit"), + ("GEL", "Georgian Lari"), + ("DEM", "German Mark"), + ("GHS", "Ghanaian Cedi"), + ("GHC", "Ghanaian Cedi (1979–2007)"), + ("GIP", "Gibraltar Pound"), + ("XAU", "Gold"), + ("GRD", "Greek Drachma"), + ("GTQ", "Guatemalan Quetzal"), + ("GWP", "Guinea-Bissau Peso"), + ("GNF", "Guinean Franc"), + ("GNS", "Guinean Syli"), + ("GYD", "Guyanaese Dollar"), + ("HTG", "Haitian Gourde"), + ("HNL", "Honduran Lempira"), + ("HKD", "Hong Kong Dollar"), + ("HUF", "Hungarian Forint"), + ("IMP", "IMP"), + ("ISK", "Icelandic Króna"), + ("ISJ", "Icelandic Króna (1918–1981)"), + ("INR", "Indian Rupee"), + ("IDR", "Indonesian Rupiah"), + ("IRR", "Iranian Rial"), + ("IQD", "Iraqi Dinar"), + ("IEP", "Irish Pound"), + ("ILS", "Israeli New Shekel"), + ("ILP", "Israeli Pound"), + ("ILR", "Israeli Shekel (1980–1985)"), + ("ITL", "Italian Lira"), + ("JMD", "Jamaican Dollar"), + ("JPY", "Japanese Yen"), + ("JOD", "Jordanian Dinar"), + ("KZT", "Kazakhstani Tenge"), + ("KES", "Kenyan Shilling"), + ("KWD", "Kuwaiti Dinar"), + ("KGS", "Kyrgystani Som"), + ("LAK", "Laotian Kip"), + ("LVL", "Latvian Lats"), + ("LVR", "Latvian Ruble"), + ("LBP", "Lebanese Pound"), + ("LSL", "Lesotho Loti"), + ("LRD", "Liberian Dollar"), + ("LYD", "Libyan Dinar"), + ("LTL", "Lithuanian Litas"), + ("LTT", "Lithuanian Talonas"), + ("LUL", "Luxembourg Financial Franc"), + ("LUC", "Luxembourgian Convertible Franc"), + ("LUF", "Luxembourgian Franc"), + ("MOP", "Macanese Pataca"), + ("MKD", "Macedonian Denar"), + ("MKN", "Macedonian Denar (1992–1993)"), + ("MGA", "Malagasy Ariary"), + ("MGF", "Malagasy Franc"), + ("MWK", "Malawian Kwacha"), + ("MYR", "Malaysian Ringgit"), + ("MVR", "Maldivian Rufiyaa"), + ("MVP", "Maldivian Rupee (1947–1981)"), + ("MLF", "Malian Franc"), + ("MTL", "Maltese Lira"), + ("MTP", "Maltese Pound"), + ("MRU", "Mauritanian Ouguiya"), + ("MRO", "Mauritanian Ouguiya (1973–2017)"), + ("MUR", "Mauritian Rupee"), + ("MXV", "Mexican Investment Unit"), + ("MXN", "Mexican Peso"), + ("MXP", "Mexican Silver Peso (1861–1992)"), + ("MDC", "Moldovan Cupon"), + ("MDL", "Moldovan Leu"), + ("MCF", "Monegasque Franc"), + ("MNT", "Mongolian Tugrik"), + ("MAD", "Moroccan Dirham"), + ("MAF", "Moroccan Franc"), + ("MZE", "Mozambican Escudo"), + ("MZN", "Mozambican Metical"), + ("MZM", "Mozambican Metical (1980–2006)"), + ("MMK", "Myanmar Kyat"), + ("NAD", "Namibian Dollar"), + ("NPR", "Nepalese Rupee"), + ("ANG", "Netherlands Antillean Guilder"), + ("TWD", "New Taiwan Dollar"), + ("NZD", "New Zealand Dollar"), + ("NIO", "Nicaraguan Córdoba"), + ("NIC", "Nicaraguan Córdoba (1988–1991)"), + ("NGN", "Nigerian Naira"), + ("KPW", "North Korean Won"), + ("NOK", "Norwegian Krone"), + ("OMR", "Omani Rial"), + ("PKR", "Pakistani Rupee"), + ("XPD", "Palladium"), + ("PAB", "Panamanian Balboa"), + ("PGK", "Papua New Guinean Kina"), + ("PYG", "Paraguayan Guarani"), + ("PEI", "Peruvian Inti"), + ("PEN", "Peruvian Sol"), + ("PES", "Peruvian Sol (1863–1965)"), + ("PHP", "Philippine Peso"), + ("XPT", "Platinum"), + ("PLN", "Polish Zloty"), + ("PLZ", "Polish Zloty (1950–1995)"), + ("PTE", "Portuguese Escudo"), + ("GWE", "Portuguese Guinea Escudo"), + ("QAR", "Qatari Riyal"), + ("XRE", "RINET Funds"), + ("RHD", "Rhodesian Dollar"), + ("RON", "Romanian Leu"), + ("ROL", "Romanian Leu (1952–2006)"), + ("RUB", "Russian Ruble"), + ("RUR", "Russian Ruble (1991–1998)"), + ("RWF", "Rwandan Franc"), + ("SVC", "Salvadoran Colón"), + ("WST", "Samoan Tala"), + ("SAR", "Saudi Riyal"), + ("RSD", "Serbian Dinar"), + ("CSD", "Serbian Dinar (2002–2006)"), + ("SCR", "Seychellois Rupee"), + ("SLL", "Sierra Leonean Leone (1964—2022)"), + ("XAG", "Silver"), + ("SGD", "Singapore Dollar"), + ("SKK", "Slovak Koruna"), + ("SIT", "Slovenian Tolar"), + ("SBD", "Solomon Islands Dollar"), + ("SOS", "Somali Shilling"), + ("ZAR", "South African Rand"), + ("ZAL", "South African Rand (financial)"), + ("KRH", "South Korean Hwan (1953–1962)"), + ("KRW", "South Korean Won"), + ("KRO", "South Korean Won (1945–1953)"), + ("SSP", "South Sudanese Pound"), + ("SUR", "Soviet Rouble"), + ("ESP", "Spanish Peseta"), + ("ESA", "Spanish Peseta (A account)"), + ("ESB", "Spanish Peseta (convertible account)"), + ("XDR", "Special Drawing Rights"), + ("LKR", "Sri Lankan Rupee"), + ("SHP", "St. Helena Pound"), + ("XSU", "Sucre"), + ("SDD", "Sudanese Dinar (1992–2007)"), + ("SDG", "Sudanese Pound"), + ("SDP", "Sudanese Pound (1957–1998)"), + ("SRD", "Surinamese Dollar"), + ("SRG", "Surinamese Guilder"), + ("SZL", "Swazi Lilangeni"), + ("SEK", "Swedish Krona"), + ("CHF", "Swiss Franc"), + ("SYP", "Syrian Pound"), + ("STN", "São Tomé & Príncipe Dobra"), + ("STD", "São Tomé & Príncipe Dobra (1977–2017)"), + ("TVD", "TVD"), + ("TJR", "Tajikistani Ruble"), + ("TJS", "Tajikistani Somoni"), + ("TZS", "Tanzanian Shilling"), + ("XTS", "Testing Currency Code"), + ("THB", "Thai Baht"), + ( + "XXX", + "The codes assigned for transactions where no currency is involved", + ), + ("TPE", "Timorese Escudo"), + ("TOP", "Tongan Paʻanga"), + ("TTD", "Trinidad & Tobago Dollar"), + ("TND", "Tunisian Dinar"), + ("TRY", "Turkish Lira"), + ("TRL", "Turkish Lira (1922–2005)"), + ("TMT", "Turkmenistani Manat"), + ("TMM", "Turkmenistani Manat (1993–2009)"), + ("USD", "US Dollar"), + ("USN", "US Dollar (Next day)"), + ("USS", "US Dollar (Same day)"), + ("UGX", "Ugandan Shilling"), + ("UGS", "Ugandan Shilling (1966–1987)"), + ("UAH", "Ukrainian Hryvnia"), + ("UAK", "Ukrainian Karbovanets"), + ("AED", "United Arab Emirates Dirham"), + ("UYW", "Uruguayan Nominal Wage Index Unit"), + ("UYU", "Uruguayan Peso"), + ("UYP", "Uruguayan Peso (1975–1993)"), + ("UYI", "Uruguayan Peso (Indexed Units)"), + ("UZS", "Uzbekistani Som"), + ("VUV", "Vanuatu Vatu"), + ("VES", "Venezuelan Bolívar"), + ("VEB", "Venezuelan Bolívar (1871–2008)"), + ("VEF", "Venezuelan Bolívar (2008–2018)"), + ("VND", "Vietnamese Dong"), + ("VNN", "Vietnamese Dong (1978–1985)"), + ("CHE", "WIR Euro"), + ("CHW", "WIR Franc"), + ("XOF", "West African CFA Franc"), + ("YDD", "Yemeni Dinar"), + ("YER", "Yemeni Rial"), + ("YUN", "Yugoslavian Convertible Dinar (1990–1992)"), + ("YUD", "Yugoslavian Hard Dinar (1966–1990)"), + ("YUM", "Yugoslavian New Dinar (1994–2002)"), + ("YUR", "Yugoslavian Reformed Dinar (1992–1993)"), + ("ZWN", "ZWN"), + ("ZRN", "Zairean New Zaire (1993–1998)"), + ("ZRZ", "Zairean Zaire (1971–1993)"), + ("ZMW", "Zambian Kwacha"), + ("ZMK", "Zambian Kwacha (1968–2012)"), + ("ZWD", "Zimbabwean Dollar (1980–2008)"), + ("ZWR", "Zimbabwean Dollar (2008)"), + ("ZWL", "Zimbabwean Dollar (2009)"), + ], + default="USD", + editable=False, + max_length=3, + ), + ), + migrations.CreateModel( + name="SubscriptionNotificationEvent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ( + "status", + models.IntegerField( + choices=[ + (0, "Pending"), + (1, "Ongoing"), + (2, "Completed"), + (3, "Cancelled"), + (4, "Error"), + ], + default=0, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="subscription_notification_events", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/subscriptions/models.py b/subscriptions/models.py index a0053fa..0bd8e27 100644 --- a/subscriptions/models.py +++ b/subscriptions/models.py @@ -616,7 +616,7 @@ class Status(models.IntegerChoices): CANCELLED = 3 ERROR = 4 - subscription = models.ForeignKey( + user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='subscription_notification_events' @@ -628,5 +628,4 @@ class Status(models.IntegerChoices): created = models.DateTimeField(auto_now_add=True) - from .signals import create_default_subscription_for_new_user # noqa diff --git a/subscriptions/tasks.py b/subscriptions/tasks.py index a9a7ab9..4229855 100644 --- a/subscriptions/tasks.py +++ b/subscriptions/tasks.py @@ -1,17 +1,13 @@ -import dataclasses from __future__ import annotations from collections import defaultdict from concurrent.futures import ThreadPoolExecutor, wait from datetime import datetime, timedelta -from functools import partial, wraps +from functools import partial from logging import getLogger from typing import Iterable -from operator import or_ -from typing import Iterable, Callable, Optional, Union, TYPE_CHECKING from django.conf import settings -from django.contrib.auth import get_user_model from django.db import transaction from django.db.models import Q, QuerySet from django.utils.timezone import now @@ -22,14 +18,10 @@ DEFAULT_SUBSCRIPTIONS_OFFLINE_CHARGE_ATTEMPTS_SCHEDULE, ) from .exceptions import PaymentError, ProlongationImpossible -from .models import Subscription, SubscriptionPayment, SubscriptionNotificationEvent +from .models import Subscription, SubscriptionPayment from .providers import get_provider +from .logic.notifications import NotificationManager -if TYPE_CHECKING: - from typing import ParamSpec, TypeVar - P = ParamSpec('P') - R = TypeVar('R') -F = Callable[['P'], 'R'] log = getLogger(__name__) @@ -251,55 +243,14 @@ def check_duplicated_payments() -> dict[tuple[str, str], list[SubscriptionPaymen return result -# TODO: check for concurrency issues, probably add transactions - - -@dataclasses.dataclass -class _Notification: - name: str - function: F - queryset: Optional[QuerySet] = dataclasses.field(default_factory=get_user_model().objects.all) - parameters: Optional[dict[str, Union[str, int]]] = dataclasses.field(default_factory=dict) +def dispatch_notifications(name: str = None) -> None: + """ Dispatch the notifications. """ -class _NotificaticationManager: - """ - Protected class, only one instance should be necessary. - """ - - def __init__(self): - self.__notifications: dict[str, _Notification] = {} - - def __call__( - self, name: str, - forget_after: int = None, - queryset: QuerySet = None, - **parameters - ) -> Callable[[F], F]: - if name in self.__notifications: - notification = self.__notifications[name] - raise ValueError(f'Notification {name} already registered for {notification.function}') - - def decorator(function: F) -> F: - self.__notifications[name] = _Notification(name, function, queryset, parameters) - return function - return decorator - - def fetch(self, name: str) -> QuerySet: - """ Fetch all the missing notifications. """ - notification = self.__notifications[name] - filters = {} - for parameter, value in notification.parameters.keys(): - if 'days_since_' in parameter: - parameter = parameter[len('days_since_'):] - since = now() - timedelta(days=value) - filters.update({ - f'{parameter}__gte': since, - f'{parameter}__lte': since + timedelta(1) - }) - else: - filters[parameter] = value - return notification.queryset.filter(**filters) - + if name: + NotificationManager.execute(name) + else: + NotificationManager.execute_all() +# TODO: check for concurrency issues, probably add transactions