From cd7738b7e9950c23d8057f61f96cd0a2ab5800cb Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Thu, 23 Nov 2023 20:32:00 +0000 Subject: [PATCH 1/6] Create webhook event types --- .../chargebee/webhook_event_types.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 api/organisations/chargebee/webhook_event_types.py diff --git a/api/organisations/chargebee/webhook_event_types.py b/api/organisations/chargebee/webhook_event_types.py new file mode 100644 index 000000000000..da509c94d446 --- /dev/null +++ b/api/organisations/chargebee/webhook_event_types.py @@ -0,0 +1,17 @@ +PAYMENT_FAILED = "payment_failed" +PAYMENT_SUCCEEDED = "payment_succeeded" +PLAN_CREATED = "plan_created" +PLAN_UPDATED = "plan_updated" +PLAN_DELETED = "plan_deleted" +ADDON_CREATED = "addon_created" +ADDON_UPDATED = "addon_updated" +ADDON_DELETED = "addon_deleted" + +CACHE_REBUILD_TYPES = { + PLAN_CREATED, + PLAN_UPDATED, + PLAN_DELETED, + ADDON_CREATED, + ADDON_UPDATED, + ADDON_DELETED, +} From 97e1df84492808e59102567801c7fced00bec8bf Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Thu, 23 Nov 2023 20:33:31 +0000 Subject: [PATCH 2/6] Create test for cache rebuildable chargebee webhooks --- .../test_unit_organisation_views.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/api/tests/unit/organisations/test_unit_organisation_views.py b/api/tests/unit/organisations/test_unit_organisation_views.py index 8f7aa977d82e..d47463ac3c6f 100644 --- a/api/tests/unit/organisations/test_unit_organisation_views.py +++ b/api/tests/unit/organisations/test_unit_organisation_views.py @@ -1497,9 +1497,49 @@ def test_payment_failed_chargebee_webhook_no_subscription_id( assert subscription.billing_status == SUBSCRIPTION_BILLING_STATUS_ACTIVE +def test_cache_rebuild_event_chargebee_webhook( + staff_client: FFAdminUser, + mocker: MockerFixture, +) -> None: + # Given + data = { + "id": "ev_XpbG6hnQqGFm2n3O", + "occurred_at": 1341085213, + "source": "api", + "user": "full_access_key_v1", + "object": "event", + "api_version": "v2", + "content": { + "addon": { + "id": "support", + "name": "Support", + "description": "This is addon added when support is needed", + "type": "quantity", + "charge_type": "recurring", + "price": 1000, + "period": 1, + "period_unit": "month", + "status": "deleted", + } + }, + "event_type": "addon_deleted", + } + + url = reverse("api-v1:chargebee-webhook") + task = mocker.patch("organisations.chargebee.tasks.ChargebeeCache") + + # When + response = staff_client.post( + url, data=json.dumps(data), content_type="application/json" + ) + # Then + assert response.status_code == 200 + task.assert_called_once() + + def test_payment_succeeded_chargebee_webhook( staff_client: FFAdminUser, subscription: Subscription -): +) -> None: # Given subscription.billing_status = SUBSCRIPTION_BILLING_STATUS_DUNNING subscription.subscription_id = "best_id" From f2dd98c7e8769ca23b7cd811d266bb70e5aaec01 Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Thu, 23 Nov 2023 20:34:17 +0000 Subject: [PATCH 3/6] Add webhook handler for cache rebuildable events --- api/organisations/chargebee/webhook_handlers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/organisations/chargebee/webhook_handlers.py b/api/organisations/chargebee/webhook_handlers.py index f0770666332e..ce64a4a960fe 100644 --- a/api/organisations/chargebee/webhook_handlers.py +++ b/api/organisations/chargebee/webhook_handlers.py @@ -5,6 +5,7 @@ from rest_framework.request import Request from rest_framework.response import Response +from organisations.chargebee.tasks import update_chargebee_cache from organisations.models import Subscription from organisations.subscriptions.constants import ( SUBSCRIPTION_BILLING_STATUS_ACTIVE, @@ -16,6 +17,12 @@ logger = logging.getLogger(__name__) +def cache_rebuild_event(request: Request) -> Response: + logger.info("Chargebee plan or addon webhook fired, rebuilding cache.") + update_chargebee_cache.delay() + return Response(status=status.HTTP_200_OK) + + def payment_failed(request: Request) -> Response: serializer = PaymentFailedSerializer(data=request.data) From bfc343c45eefae12e405e6c54f516853ddf4ae60 Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Thu, 23 Nov 2023 20:34:49 +0000 Subject: [PATCH 4/6] Add cache rebuilding events and event types to webhook handler --- api/organisations/views.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/organisations/views.py b/api/organisations/views.py index 7d23e63fe552..17399fa39f36 100644 --- a/api/organisations/views.py +++ b/api/organisations/views.py @@ -20,7 +20,7 @@ from rest_framework.response import Response from rest_framework.throttling import ScopedRateThrottle -from organisations.chargebee import webhook_handlers +from organisations.chargebee import webhook_event_types, webhook_handlers from organisations.exceptions import OrganisationHasNoPaidSubscription from organisations.models import ( Organisation, @@ -274,10 +274,12 @@ def chargebee_webhook(request: Request) -> Response: """ event_type = request.data.get("event_type") - if event_type == "payment_failed": + if event_type == webhook_event_types.PAYMENT_FAILED: return webhook_handlers.payment_failed(request) - if event_type == "payment_succeeded": + if event_type == webhook_event_types.PAYMENT_SUCCEEDED: return webhook_handlers.payment_succeeded(request) + if event_type in webhook_event_types.CACHE_REBUILD_TYPES: + return webhook_handlers.cache_rebuild_event(request) if request.data.get("content") and "subscription" in request.data.get("content"): subscription_data: dict = request.data["content"]["subscription"] From bb7c89cfabd70bc464ba07bb33a11d3d9a90a419 Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Mon, 27 Nov 2023 16:27:23 +0000 Subject: [PATCH 5/6] Switch to task mock --- .../unit/organisations/test_unit_organisation_views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/tests/unit/organisations/test_unit_organisation_views.py b/api/tests/unit/organisations/test_unit_organisation_views.py index d47463ac3c6f..ea7c1c7a8385 100644 --- a/api/tests/unit/organisations/test_unit_organisation_views.py +++ b/api/tests/unit/organisations/test_unit_organisation_views.py @@ -1526,15 +1526,16 @@ def test_cache_rebuild_event_chargebee_webhook( } url = reverse("api-v1:chargebee-webhook") - task = mocker.patch("organisations.chargebee.tasks.ChargebeeCache") - + task = mocker.patch( + "organisations.chargebee.webhook_handlers.update_chargebee_cache" + ) # When response = staff_client.post( url, data=json.dumps(data), content_type="application/json" ) # Then assert response.status_code == 200 - task.assert_called_once() + task.delay.assert_called_once() def test_payment_succeeded_chargebee_webhook( From ffbbfafd02107700278482dcb6af4af9d24fac84 Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Mon, 27 Nov 2023 18:39:20 +0000 Subject: [PATCH 6/6] Assert called once with empty args --- api/tests/unit/organisations/test_unit_organisations_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/unit/organisations/test_unit_organisations_views.py b/api/tests/unit/organisations/test_unit_organisations_views.py index ea7c1c7a8385..0007990c77da 100644 --- a/api/tests/unit/organisations/test_unit_organisations_views.py +++ b/api/tests/unit/organisations/test_unit_organisations_views.py @@ -1535,7 +1535,7 @@ def test_cache_rebuild_event_chargebee_webhook( ) # Then assert response.status_code == 200 - task.delay.assert_called_once() + task.delay.assert_called_once_with() def test_payment_succeeded_chargebee_webhook(