From d25bc7689674513209ece9cb6980e90ea09cff9e Mon Sep 17 00:00:00 2001 From: kailip Date: Mon, 4 Sep 2023 09:03:17 +0200 Subject: [PATCH] chore: Updated dj-stripe (2.6.3 -> 2.8.1) and stripe-mock (0.110.0 -> 0.170.0) versions. (closes #191). (#384) --- docker-compose.yml | 2 +- .../commands/stripe_migrate_json_fields.py | 157 ++++++++++++++++++ .../backend/apps/finances/tests/fixtures.py | 25 +++ .../apps/finances/tests/test_webhooks.py | 7 +- packages/backend/config/settings.py | 1 - packages/backend/pdm.lock | 30 +--- packages/backend/pyproject.toml | 2 +- 7 files changed, 195 insertions(+), 29 deletions(-) create mode 100644 packages/backend/apps/finances/management/commands/stripe_migrate_json_fields.py diff --git a/docker-compose.yml b/docker-compose.yml index d73c0cf9a..89c6325d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: - db stripemock: - image: stripemock/stripe-mock:v0.103.0 + image: stripe/stripe-mock:v0.170.0 ports: - "12111:12111" - "12112:12112" diff --git a/packages/backend/apps/finances/management/commands/stripe_migrate_json_fields.py b/packages/backend/apps/finances/management/commands/stripe_migrate_json_fields.py new file mode 100644 index 000000000..bbac125ba --- /dev/null +++ b/packages/backend/apps/finances/management/commands/stripe_migrate_json_fields.py @@ -0,0 +1,157 @@ +# dj-stripe in version 2.8.0 dropped support for text-based JSON fields and hasn't provided migration to work it out. +# +# The migration below is based on the discussion by dj-stripe team available under those links: +# - https://github.com/dj-stripe/dj-stripe/issues/1566 +# - https://github.com/dj-stripe/dj-stripe/issues/517 + +from django.core.management.base import BaseCommand +from django.db import transaction, connection + +AFFECTED_FIELDS = [ + ("djstripe_account", "business_profile"), + ("djstripe_account", "company"), + ("djstripe_account", "individual"), + ("djstripe_account", "requirements"), + ("djstripe_account", "settings"), + ("djstripe_account", "tos_acceptance"), + ("djstripe_invoice", "customer_address"), + ("djstripe_invoice", "customer_shipping"), + ("djstripe_invoice", "discount"), + ("djstripe_invoice", "status_transitions"), + ("djstripe_invoice", "threshold_reason"), + ("djstripe_upcominginvoice", "customer_address"), + ("djstripe_upcominginvoice", "customer_shipping"), + ("djstripe_upcominginvoice", "discount"), + ("djstripe_upcominginvoice", "status_transitions"), + ("djstripe_upcominginvoice", "threshold_reason"), + ("djstripe_invoiceitem", "period"), + ("djstripe_plan", "tiers"), + ("djstripe_plan", "transform_usage"), + ("djstripe_subscription", "billing_thresholds"), + ("djstripe_subscription", "discount"), + ("djstripe_subscription", "pending_invoice_item_interval"), + ("djstripe_subscription", "pending_update"), + ("djstripe_subscriptionitem", "billing_thresholds"), + ("djstripe_subscriptionschedule", "current_phase"), + ("djstripe_subscriptionschedule", "default_settings"), + ("djstripe_subscriptionschedule", "phases"), + ("djstripe_taxid", "verification"), + ("djstripe_usagerecordsummary", "period"), + ("djstripe_session", "display_items"), + ("djstripe_session", "payment_method_types"), + ("djstripe_countryspec", "supported_bank_account_currencies"), + ("djstripe_countryspec", "supported_payment_currencies"), + ("djstripe_countryspec", "supported_payment_methods"), + ("djstripe_countryspec", "supported_transfer_countries"), + ("djstripe_countryspec", "verification_fields"), + ("djstripe_balancetransaction", "fee_details"), + ("djstripe_charge", "billing_details"), + ("djstripe_charge", "fraud_details"), + ("djstripe_charge", "outcome"), + ("djstripe_charge", "payment_method_details"), + ("djstripe_charge", "shipping"), + ("djstripe_charge", "transfer_data"), + ("djstripe_mandate", "customer_acceptance"), + ("djstripe_mandate", "payment_method_details"), + ("djstripe_mandate", "multi_use"), + ("djstripe_mandate", "single_use"), + ("djstripe_product", "attributes"), + ("djstripe_product", "deactivate_on"), + ("djstripe_product", "images"), + ("djstripe_product", "package_dimensions"), + ("djstripe_customer", "address"), + ("djstripe_customer", "invoice_settings"), + ("djstripe_customer", "preferred_locales"), + ("djstripe_customer", "shipping"), + ("djstripe_dispute", "balance_transactions"), + ("djstripe_dispute", "evidence"), + ("djstripe_dispute", "evidence_details"), + ("djstripe_event", "data"), + ("djstripe_paymentintent", "last_payment_error"), + ("djstripe_paymentintent", "next_action"), + ("djstripe_paymentintent", "payment_method_types"), + ("djstripe_paymentintent", "shipping"), + ("djstripe_paymentintent", "transfer_data"), + ("djstripe_setupintent", "last_setup_error"), + ("djstripe_setupintent", "next_action"), + ("djstripe_setupintent", "payment_method_types"), + ("djstripe_price", "recurring"), + ("djstripe_price", "tiers"), + ("djstripe_price", "transform_quantity"), + ("djstripe_source", "owner"), + ("djstripe_source", "code_verification"), + ("djstripe_source", "receiver"), + ("djstripe_source", "redirect"), + ("djstripe_source", "source_data"), + ("djstripe_paymentmethod", "billing_details"), + ("djstripe_paymentmethod", "acss_debit"), + ("djstripe_paymentmethod", "afterpay_clearpay"), + ("djstripe_paymentmethod", "alipay"), + ("djstripe_paymentmethod", "au_becs_debit"), + ("djstripe_paymentmethod", "bacs_debit"), + ("djstripe_paymentmethod", "bancontact"), + ("djstripe_paymentmethod", "boleto"), + ("djstripe_paymentmethod", "card"), + ("djstripe_paymentmethod", "card_present"), + ("djstripe_paymentmethod", "eps"), + ("djstripe_paymentmethod", "fpx"), + ("djstripe_paymentmethod", "giropay"), + ("djstripe_paymentmethod", "grabpay"), + ("djstripe_paymentmethod", "ideal"), + ("djstripe_paymentmethod", "interac_present"), + ("djstripe_paymentmethod", "oxxo"), + ("djstripe_paymentmethod", "p24"), + ("djstripe_paymentmethod", "sepa_debit"), + ("djstripe_paymentmethod", "sofort"), + ("djstripe_paymentmethod", "wechat_pay"), + ("djstripe_scheduledqueryrun", "error"), + ("djstripe_webhookendpoint", "enabled_events"), + ("djstripe_webhookeventtrigger", "headers"), + ("djstripe_account", "metadata"), + ("djstripe_coupon", "metadata"), + ("djstripe_invoice", "metadata"), + ("djstripe_upcominginvoice", "metadata"), + ("djstripe_invoiceitem", "metadata"), + ("djstripe_plan", "metadata"), + ("djstripe_subscription", "metadata"), + ("djstripe_subscriptionitem", "metadata"), + ("djstripe_subscriptionschedule", "metadata"), + ("djstripe_taxrate", "metadata"), + ("djstripe_session", "metadata"), + ("djstripe_applicationfee", "metadata"), + ("djstripe_applicationfeerefund", "metadata"), + ("djstripe_transfer", "metadata"), + ("djstripe_transferreversal", "metadata"), + ("djstripe_balancetransaction", "metadata"), + ("djstripe_charge", "metadata"), + ("djstripe_mandate", "metadata"), + ("djstripe_product", "metadata"), + ("djstripe_customer", "metadata"), + ("djstripe_dispute", "metadata"), + ("djstripe_event", "metadata"), + ("djstripe_file", "metadata"), + ("djstripe_filelink", "metadata"), + ("djstripe_paymentintent", "metadata"), + ("djstripe_setupintent", "metadata"), + ("djstripe_payout", "metadata"), + ("djstripe_price", "metadata"), + ("djstripe_refund", "metadata"), + ("djstripe_bankaccount", "metadata"), + ("djstripe_card", "metadata"), + ("djstripe_source", "metadata"), + ("djstripe_paymentmethod", "metadata"), + ("djstripe_scheduledqueryrun", "metadata"), + ("djstripe_webhookendpoint", "metadata"), +] + + +class Command(BaseCommand): + help = 'Migrate text-based JSON fields to json-based ones to support dj-stripe v2.8.0' + + @transaction.atomic + def handle(self, *args, **options): + with connection.cursor() as cursor: + for af in AFFECTED_FIELDS: + self.stdout.write(f'Migrating field "{af[1]}" of "{af[0]}" table..') + cursor.execute(f'ALTER TABLE {af[0]} ALTER COLUMN "{af[1]}" TYPE jsonb USING "{af[1]}"::text::jsonb;') + self.stdout.write(self.style.SUCCESS("Migration successful!")) diff --git a/packages/backend/apps/finances/tests/fixtures.py b/packages/backend/apps/finances/tests/fixtures.py index 9a54db2cc..70b0ae8f9 100644 --- a/packages/backend/apps/finances/tests/fixtures.py +++ b/packages/backend/apps/finances/tests/fixtures.py @@ -2,6 +2,7 @@ import pytest_factoryboy import stripe from django.contrib.auth import get_user_model +from djstripe.models import Account from . import factories from .. import constants @@ -88,6 +89,30 @@ def yearly_plan_price(price_factory, plan_factory): return price +@pytest.fixture(scope='function', autouse=True) +def stripe_mock_fixture_product_default_price(price_factory): + """ + stripe-mock v0.128.0 introduces "default_price" field in returned product fixture. In some cases the tests may fail, + if the Price object with this id does not exist in database. Adding it manually to avoid that issue. The hardcoded + ID may need to be changed when the Stripe-mock is updated to the newer version. + """ + return price_factory(id="price_1NapVeJr3d0nrouD2gX5mHOY") + + +@pytest.fixture(scope='function', autouse=True) +def stripe_find_owner_account_monkey_patch(): + """ + dj-stripe v2.7.0 restored checking for owner of an Account object. Stripe-mock returns different account ID on + every call, what leads to infinite loop of fetching accounts from Stripe-mock. This monkey patch restores old + _find_owner_account functionality of the Account model. + """ + + def _find_owner_account(cls, data, api_key): + return None + + Account._find_owner_account = classmethod(_find_owner_account) + + @pytest.fixture(scope='function', autouse=True) def stripe_proxy(): stripe.api_base = "http://stripemock:12111" diff --git a/packages/backend/apps/finances/tests/test_webhooks.py b/packages/backend/apps/finances/tests/test_webhooks.py index 16dc62458..d6436e9eb 100644 --- a/packages/backend/apps/finances/tests/test_webhooks.py +++ b/packages/backend/apps/finances/tests/test_webhooks.py @@ -1,11 +1,10 @@ import datetime -from djstripe.enums import RefundStatus, RefundFailureReason - import calleee import pytest - from djstripe import models as djstripe_models +from djstripe.enums import RefundStatus, RefundFailureReason + from .utils import stripe_encode from .. import notifications @@ -25,7 +24,7 @@ def test_previously_trialing_subscription_is_canceled( # The hardcoded ID is equal to the one returned from stripe-mock # If the test fails after stripe-mock update you most likely need to change this to match their # fixtures - 'id': 'si_IyZyeAN1KSAd6Z', + 'id': 'si_OQqOMhX37fUC5o', 'price': monthly_plan_price.id, } ], diff --git a/packages/backend/config/settings.py b/packages/backend/config/settings.py index ef98df999..72c4cb2d8 100644 --- a/packages/backend/config/settings.py +++ b/packages/backend/config/settings.py @@ -280,7 +280,6 @@ STRIPE_CHECKS_ENABLED = env.bool("STRIPE_CHECKS_ENABLED", default=True) if not STRIPE_CHECKS_ENABLED: SILENCED_SYSTEM_CHECKS.append("djstripe.C001") -DJSTRIPE_USE_NATIVE_JSONFIELD = False SUBSCRIPTION_TRIAL_PERIOD_DAYS = env("SUBSCRIPTION_TRIAL_PERIOD_DAYS", default=7) diff --git a/packages/backend/pdm.lock b/packages/backend/pdm.lock index dd177a982..0acb9bb61 100644 --- a/packages/backend/pdm.lock +++ b/packages/backend/pdm.lock @@ -261,13 +261,12 @@ dependencies = [ [[package]] name = "dj-stripe" -version = "2.6.3" -requires_python = ">=3.7.12,<4.0.0" +version = "2.8.1" +requires_python = ">=3.8.0,<4.0.0" summary = "Django + Stripe made easy" dependencies = [ - "django>=3.2.5", - "jsonfield>=3.0", - "stripe>=2.48.0", + "django>=3.2", + "stripe<5.0.0,>=2.48.0", ] [[package]] @@ -587,15 +586,6 @@ name = "jsondiff" version = "2.0.0" summary = "Diff JSON and JSON-like structures in Python" -[[package]] -name = "jsonfield" -version = "3.1.0" -requires_python = ">=3.6" -summary = "A reusable Django field that allows you to store validated JSON in your model." -dependencies = [ - "Django>=2.2", -] - [[package]] name = "jsonpatch" version = "1.32" @@ -1324,7 +1314,7 @@ dependencies = [ [metadata] lock_version = "4.2" groups = ["default", "dev"] -content_hash = "sha256:cf9ce847e6db798e36da7a5ca49c7ce6eb1c96cfb11d80bfab2394df54e828df" +content_hash = "sha256:e692dc0dad8e5a6b7c9aaeb8e45dc5af0280549eeb6e80e7ee3071115c7e79f1" [metadata.files] "aiohttp 3.8.4" = [ @@ -1678,9 +1668,9 @@ content_hash = "sha256:cf9ce847e6db798e36da7a5ca49c7ce6eb1c96cfb11d80bfab2394df5 {url = "https://files.pythonhosted.org/packages/51/6a/c3a0408646408f7283b7bc550c30a32cc791181ec4618592eec13e066ce3/Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, {url = "https://files.pythonhosted.org/packages/c8/d1/e412abc2a358a6b9334250629565fe12697ca1cdee4826239eddf944ddd0/Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, ] -"dj-stripe 2.6.3" = [ - {url = "https://files.pythonhosted.org/packages/27/72/5fe128b53d7839e6a96125040614b1007e7cd823f0f5fb46cc600550d33e/dj_stripe-2.6.3-py3-none-any.whl", hash = "sha256:113bf47075b1232f7e634147d4d91bab54969eb902b200ceb7b04ed13b380920"}, - {url = "https://files.pythonhosted.org/packages/f1/4f/43767e5501ef4a9e5b98cb31463abd29ba746d33fda5b8b74209b33ba568/dj_stripe-2.6.3.tar.gz", hash = "sha256:905f052a834b254329eef409f67a622a783806a8b64d9fb3debed0a69178c211"}, +"dj-stripe 2.8.1" = [ + {url = "https://files.pythonhosted.org/packages/e2/96/5e2f62611c1e535b03a0ae537d34b7d1cb74e357dd2d9472b2c2c3595c0f/dj_stripe-2.8.1-py3-none-any.whl", hash = "sha256:607635aacf0bb99b85a0de00ceb2f3bf1087f664477baeb87641b751a6c04724"}, + {url = "https://files.pythonhosted.org/packages/ff/aa/25769078c496c990d9a26a0ec1deb69e0adc38549c2596480d9baf190b17/dj_stripe-2.8.1.tar.gz", hash = "sha256:041f73eb9c5fbb0fd952dbd66e5fd2c8339ea34a159c27296aaf9253c8a88c77"}, ] "django 4.2" = [ {url = "https://files.pythonhosted.org/packages/9a/bb/48aa3e0850923096dff2766d21a6004d6e1a3317f0bd400ba81f586754e1/Django-4.2.tar.gz", hash = "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"}, @@ -2008,10 +1998,6 @@ content_hash = "sha256:cf9ce847e6db798e36da7a5ca49c7ce6eb1c96cfb11d80bfab2394df5 {url = "https://files.pythonhosted.org/packages/db/15/0d33d6e8114901a7b2a56d4190e3dc1803a195495ee4f9696c630e046c9e/jsondiff-2.0.0-py3-none-any.whl", hash = "sha256:689841d66273fc88fc79f7d33f4c074774f4f214b6466e3aff0e5adaf889d1e0"}, {url = "https://files.pythonhosted.org/packages/dd/13/2b691afe0a90fb930a32b8fc1b0fd6b5bdeaed459a32c5a58dc6654342da/jsondiff-2.0.0.tar.gz", hash = "sha256:2795844ef075ec8a2b8d385c4d59f5ea48b08e7180fce3cb2787be0db00b1fb4"}, ] -"jsonfield 3.1.0" = [ - {url = "https://files.pythonhosted.org/packages/10/3b/4542fd9465908380ba4321329d3cdfeb959769ec48d878dead978286b48d/jsonfield-3.1.0.tar.gz", hash = "sha256:7e4e84597de21eeaeeaaa7cc5da08c61c48a9b64d0c446b2d71255d01812887a"}, - {url = "https://files.pythonhosted.org/packages/7c/97/3a4805532a9c1982368fd9f37b58133419e83704744b733ccab9e9827176/jsonfield-3.1.0-py3-none-any.whl", hash = "sha256:df857811587f252b97bafba42e02805e70a398a7a47870bc6358a0308dd689ed"}, -] "jsonpatch 1.32" = [ {url = "https://files.pythonhosted.org/packages/21/67/83452af2a6db7c4596d1e2ecaa841b9a900980103013b867f2865e5e1cf0/jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, {url = "https://files.pythonhosted.org/packages/a3/55/f7c93bae36d869292aedfbcbae8b091386194874f16390d680136edd2b28/jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, diff --git a/packages/backend/pyproject.toml b/packages/backend/pyproject.toml index 2c6d4cf1f..c74a21542 100644 --- a/packages/backend/pyproject.toml +++ b/packages/backend/pyproject.toml @@ -95,7 +95,7 @@ dependencies = [ "django-hashid-field~=3.3", "django-hosts~=5.2", "drf-access-policy~=1.1", - "dj-stripe==2.6.3", + "dj-stripe==2.8.1", "psycopg2-binary~=2.9", "requests~=2.28", "sentry-sdk~=1.14",