From db3c7c091351c5a138b58ea29bbc2a510ae411d0 Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Thu, 20 Mar 2025 11:34:10 -0300 Subject: [PATCH 1/2] feat: add coexistence integration support for WhatsApp Cloud - Introduced `sync_coexistence_history` method in Facebook client to sync SMB app data. - Updated WhatsAppCloudConfigureSerializer to include `integration_type` field with choices for "default" and "coexistence". - Modified WhatsAppCloudViewSet to handle phone number registration based on integration type and sync contacts for coexistence integration. --- marketplace/clients/facebook/client.py | 8 ++++++++ .../channels/whatsapp_cloud/serializers.py | 8 ++++++++ .../types/channels/whatsapp_cloud/views.py | 20 +++++++++++++------ marketplace/services/facebook/service.py | 9 ++++++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/marketplace/clients/facebook/client.py b/marketplace/clients/facebook/client.py index 304b511a..1155b541 100644 --- a/marketplace/clients/facebook/client.py +++ b/marketplace/clients/facebook/client.py @@ -657,6 +657,14 @@ def register_phone_number( response = self.make_request(url, method="POST", headers=headers, data=data) return response.json() + def sync_coexistence_history(self, phone_number_id: str) -> dict: + url = f"{self.get_url}/{phone_number_id}/smb_app_data" + data = {"messaging_product": "whatsapp", "sync_type": "smb_app_state_sync"} + response = self.make_request( + url, method="POST", headers=self._get_headers(), data=data + ) + return response.json() + class FacebookClient( CatalogsRequests, diff --git a/marketplace/core/types/channels/whatsapp_cloud/serializers.py b/marketplace/core/types/channels/whatsapp_cloud/serializers.py index 9be66da0..60026cdf 100644 --- a/marketplace/core/types/channels/whatsapp_cloud/serializers.py +++ b/marketplace/core/types/channels/whatsapp_cloud/serializers.py @@ -24,6 +24,14 @@ class Meta: class WhatsAppCloudConfigureSerializer(serializers.Serializer): + INTEGRATION_TYPE_CHOICES = ( + ("default", "Default"), + ("coexistence", "Coexistence"), + ) + waba_id = serializers.CharField(required=True) phone_number_id = serializers.CharField(required=True) auth_code = serializers.CharField(required=True) + integration_type = serializers.ChoiceField( + choices=INTEGRATION_TYPE_CHOICES, default="default" + ) diff --git a/marketplace/core/types/channels/whatsapp_cloud/views.py b/marketplace/core/types/channels/whatsapp_cloud/views.py index 5e8167df..5d41d4d9 100644 --- a/marketplace/core/types/channels/whatsapp_cloud/views.py +++ b/marketplace/core/types/channels/whatsapp_cloud/views.py @@ -94,6 +94,7 @@ def create(self, request, *args, **kwargs): waba_id = serializer.validated_data.get("waba_id") phone_number_id = serializer.validated_data.get("phone_number_id") auth_code = serializer.validated_data.get("auth_code") + integration_type = serializer.validated_data.get("integration_type", "default") waba_currency = "USD" whatsapp_system_user_access_token = settings.WHATSAPP_SYSTEM_USER_ACCESS_TOKEN @@ -116,11 +117,6 @@ def create(self, request, *args, **kwargs): ) phone_number = phone_number_request.get_phone_number(phone_number_id) - # Register phone number - pin = get_random_string(6, string.digits) - data = dict(messaging_product="whatsapp", pin=pin) - business_service.register_phone_number(phone_number_id, user_access_token, data) - config = dict( wa_number=phone_number.get("display_phone_number"), wa_verified_name=phone_number.get("verified_name"), @@ -128,10 +124,19 @@ def create(self, request, *args, **kwargs): wa_currency=waba_currency, wa_business_id=business_id, wa_message_template_namespace=message_template_namespace, - wa_pin=pin, wa_user_token=user_access_token, + integration_type=integration_type, ) + # Register phone number if it is a default integration + if integration_type == "default": + pin = get_random_string(6, string.digits) + data = dict(messaging_product="whatsapp", pin=pin) + business_service.register_phone_number( + phone_number_id, user_access_token, data + ) + config["wa_pin"] = pin + flows_service = FlowsService(client=FlowsClient()) channel = flows_service.create_wac_channel( request.user.email, project_uuid, phone_number_id, config @@ -155,6 +160,9 @@ def create(self, request, *args, **kwargs): celery_app.send_task(name="sync_whatsapp_cloud_wabas") celery_app.send_task(name="sync_whatsapp_cloud_phone_numbers") + if integration_type == "coexistence": + business_service.sync_coexistence_contacts(phone_number_id) + response_data = { **serializer.validated_data, "app_uuid": str(app.uuid), diff --git a/marketplace/services/facebook/service.py b/marketplace/services/facebook/service.py index e6ee8b45..9244ae98 100644 --- a/marketplace/services/facebook/service.py +++ b/marketplace/services/facebook/service.py @@ -326,9 +326,9 @@ def save_template_in_db(self, app: App, template_data: dict, response_data: dict translation=returned_translation, button_type=button["type"], url=button["url"]["base_url"] if "url" in button else None, - example=button["url"]["url_suffix_example"] - if "url" in button - else None, + example=( + button["url"]["url_suffix_example"] if "url" in button else None + ), text=button.get("text", ""), phone_number=None, ) @@ -453,3 +453,6 @@ def configure_whatsapp_cloud( "message_template_namespace": message_template_namespace, "allocation_config_id": allocation_config_id, } + + def sync_coexistence_contacts(self, phone_number_id: str) -> Dict[str, Any]: + return self.client.sync_coexistence_contacts(phone_number_id) From c98eba7d4602fb3c506c707c641c959932a077fb Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Thu, 20 Mar 2025 12:52:51 -0300 Subject: [PATCH 2/2] refactor: rename sync method and add tests for coexistence contacts - Renamed `sync_coexistence_history` to `sync_coexistence_contacts` in the Facebook client. - Added mock implementation for `sync_coexistence_contacts` in the test classes. - Implemented a new test case to verify the functionality of the `sync_coexistence_contacts` method. --- marketplace/clients/facebook/client.py | 2 +- .../whatsapp_cloud/tests/test_views.py | 38 +++++++++++++++++++ .../services/facebook/tests/test_services.py | 7 ++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/marketplace/clients/facebook/client.py b/marketplace/clients/facebook/client.py index 1155b541..a6a3b90e 100644 --- a/marketplace/clients/facebook/client.py +++ b/marketplace/clients/facebook/client.py @@ -657,7 +657,7 @@ def register_phone_number( response = self.make_request(url, method="POST", headers=headers, data=data) return response.json() - def sync_coexistence_history(self, phone_number_id: str) -> dict: + def sync_coexistence_contacts(self, phone_number_id: str) -> dict: url = f"{self.get_url}/{phone_number_id}/smb_app_data" data = {"messaging_product": "whatsapp", "sync_type": "smb_app_state_sync"} response = self.make_request( diff --git a/marketplace/core/types/channels/whatsapp_cloud/tests/test_views.py b/marketplace/core/types/channels/whatsapp_cloud/tests/test_views.py index 14ca7c5f..8419068b 100644 --- a/marketplace/core/types/channels/whatsapp_cloud/tests/test_views.py +++ b/marketplace/core/types/channels/whatsapp_cloud/tests/test_views.py @@ -402,6 +402,9 @@ def configure_whatsapp_cloud( def register_phone_number(self, phone_number_id, user_access_token, data): pass + def sync_coexistence_contacts(self, phone_number_id): + return {"success": True} + class MockPhoneNumbersService: def get_phone_number(self, phone_number_id): @@ -489,6 +492,41 @@ def test_create_whatsapp_cloud_success(self): self.assertEqual(len(app.config["wa_pin"]), 6) self.assertEqual(app.config["wa_user_token"], "mock_user_access_token") + def test_create_whatsapp_cloud_coexistence_success(self): + coexistence_payload = { + **self.payload, + "integration_type": "coexistence", + } + response = self.request.post(self.url, body=coexistence_payload) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertTrue( + App.objects.filter( + uuid=response.json["app_uuid"], + project_uuid=self.payload["project_uuid"], + ).exists() + ) + coexistence_app = App.objects.get( + uuid=response.json["app_uuid"], project_uuid=self.payload["project_uuid"] + ) + self.assertEqual(coexistence_app.config["integration_type"], "coexistence") + self.assertEqual( + coexistence_app.config["wa_number"], "mock_display_phone_number" + ) + self.assertEqual( + coexistence_app.config["wa_verified_name"], "mock_verified_name" + ) + self.assertEqual(coexistence_app.config["wa_waba_id"], self.payload["waba_id"]) + self.assertEqual(coexistence_app.config["wa_currency"], "USD") + self.assertEqual(coexistence_app.config["wa_business_id"], "mock_business_id") + self.assertEqual( + coexistence_app.config["wa_message_template_namespace"], + "mock_message_template_namespace", + ) + self.assertEqual( + coexistence_app.config["wa_user_token"], "mock_user_access_token" + ) + def test_create_whatsapp_cloud_failure_on_exchange_auth_code(self): self.mock_business_meta_service.configure_whatsapp_cloud = Mock( side_effect=ValidationError("Invalid auth code") diff --git a/marketplace/services/facebook/tests/test_services.py b/marketplace/services/facebook/tests/test_services.py index 4887eaba..669ba385 100644 --- a/marketplace/services/facebook/tests/test_services.py +++ b/marketplace/services/facebook/tests/test_services.py @@ -169,6 +169,9 @@ def create_library_template_message(self, waba_id, template_data): "category": template_data.get("category", "UTILITY"), } + def sync_coexistence_contacts(self, phone_number_id): + return {"success": True} + class TestFacebookService(TestCase): def generate_unique_facebook_catalog_id(self): @@ -682,3 +685,7 @@ def test_configure_whatsapp_cloud(self): "allocation_config_id": "mock_allocation_id", }, ) + + def test_sync_coexistence_contacts(self): + response = self.service.sync_coexistence_contacts("phone_number_id") + self.assertEqual(response, {"success": True})