From 66b7d379f4869eba5b93bf0dc37a488f96590160 Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Fri, 9 Feb 2024 13:07:03 -0300 Subject: [PATCH] feat: new embed signup process --- temba/channels/types/whatsapp_cloud/type.py | 21 +++++-- temba/channels/types/whatsapp_cloud/views.py | 20 +++--- temba/orgs/views.py | 27 ++++++-- temba/settings_common.py | 2 + .../orgs/org_whatsapp_cloud_connect.haml | 63 ++++++++++++------- 5 files changed, 93 insertions(+), 40 deletions(-) diff --git a/temba/channels/types/whatsapp_cloud/type.py b/temba/channels/types/whatsapp_cloud/type.py index 88578615bab..8c6e7d041fd 100644 --- a/temba/channels/types/whatsapp_cloud/type.py +++ b/temba/channels/types/whatsapp_cloud/type.py @@ -56,21 +56,30 @@ def get_urls(self): def activate(self, channel): waba_id = channel.config.get("wa_waba_id") wa_pin = channel.config.get("wa_pin") + wa_user_auth_token = channel.config.get("wa_user_auth_token") - headers = {"Authorization": f"Bearer {settings.WHATSAPP_ADMIN_SYSTEM_USER_TOKEN}"} + weni_headers = {"Authorization": f"Bearer {settings.WHATSAPP_ADMIN_SYSTEM_USER_TOKEN}"} + user_headers = {"Authorization": f"Bearer {wa_user_auth_token}"} # Subscribe to events - url = f"https://graph.facebook.com/v13.0/{waba_id}/subscribed_apps" - resp = requests.post(url, headers=headers) + url = f"https://graph.facebook.com/v18.0/{waba_id}/subscribed_apps" + resp = requests.post(url, headers=user_headers) if resp.status_code != 200: # pragma: no cover raise ValidationError(_("Unable to subscribe to app to WABA with ID %s" % waba_id)) - # register numbers - url = f"https://graph.facebook.com/v13.0/{channel.address}/register" + url = f"https://graph.facebook.com/v18.0/{settings.WHATSAPP_CLOUD_EXTENDED_CREDIT_ID}/whatsapp_credit_sharing_and_attach" + params = dict(waba_id=waba_id, waba_currency="USD") + resp = requests.post(url, params=params, headers=weni_headers) + + if resp.status_code != 200: # pragma: no cover + raise ValidationError(_("Unable to share credit line to WABA with ID %s" % waba_id)) + + # # register numbers + url = f"https://graph.facebook.com/v18.0/{channel.address}/register" data = {"messaging_product": "whatsapp", "pin": wa_pin} - resp = requests.post(url, data=data, headers=headers) + resp = requests.post(url, data=data, headers=weni_headers) if resp.status_code != 200: # pragma: no cover raise ValidationError( diff --git a/temba/channels/types/whatsapp_cloud/views.py b/temba/channels/types/whatsapp_cloud/views.py index 7dfc5e9dee6..ee660012e99 100644 --- a/temba/channels/types/whatsapp_cloud/views.py +++ b/temba/channels/types/whatsapp_cloud/views.py @@ -39,7 +39,7 @@ def pre_process(self, request, *args, **kwargs): app_id = settings.WHATSAPP_APPLICATION_ID app_secret = settings.WHATSAPP_APPLICATION_SECRET - url = "https://graph.facebook.com/v13.0/debug_token" + url = "https://graph.facebook.com/v18.0/debug_token" params = {"access_token": f"{app_id}|{app_secret}", "input_token": oauth_user_token} response = requests.get(url, params=params) @@ -65,7 +65,7 @@ def get_context_data(self, **kwargs): app_id = settings.WHATSAPP_APPLICATION_ID app_secret = settings.WHATSAPP_APPLICATION_SECRET - url = "https://graph.facebook.com/v13.0/debug_token" + url = "https://graph.facebook.com/v18.0/debug_token" params = {"access_token": f"{app_id}|{app_secret}", "input_token": oauth_user_token} response = requests.get(url, params=params) @@ -90,7 +90,7 @@ def get_context_data(self, **kwargs): seen_waba.append(target_waba) - url = f"https://graph.facebook.com/v13.0/{target_waba}" + url = f"https://graph.facebook.com/v18.0/{target_waba}" params = { "access_token": oauth_user_token, "fields": "id,name,currency,message_template_namespace,owner_business_info,account_review_status,on_behalf_of_business_info,primary_funding_id,purchase_order_number,timezone_id", @@ -102,7 +102,7 @@ def get_context_data(self, **kwargs): business_id = target_waba_details["on_behalf_of_business_info"]["id"] - url = f"https://graph.facebook.com/v13.0/{target_waba}/phone_numbers" + url = f"https://graph.facebook.com/v18.0/{target_waba}/phone_numbers" params = {"access_token": oauth_user_token} response = requests.get(url, params=params) response_json = response.json() @@ -140,6 +140,7 @@ def get_context_data(self, **kwargs): return context def form_valid(self, form): + user_auth = self.request.session.get(Channel.CONFIG_WHATSAPP_CLOUD_USER_TOKEN, None) org = self.request.org number = form.cleaned_data["number"] @@ -161,6 +162,7 @@ def form_valid(self, form): "wa_business_id": business_id, "wa_message_template_namespace": message_template_namespace, "wa_pin": pin, + "wa_user_auth_token": user_auth, } # don't add the same number twice to the same account @@ -184,9 +186,13 @@ def form_valid(self, form): return self.form_invalid(form) # assign system user to WABA - url = f"https://graph.facebook.com/v13.0/{waba_id}/assigned_users" - params = {"user": f"{settings.WHATSAPP_ADMIN_SYSTEM_USER_ID}", "tasks": ["MANAGE"]} - headers = {"Authorization": f"Bearer {settings.WHATSAPP_ADMIN_SYSTEM_USER_TOKEN}"} + url = f"https://graph.facebook.com/v18.0/{waba_id}/assigned_users" + params = { + "user": f"{settings.WHATSAPP_ADMIN_SYSTEM_USER_ID}", + "access_token": {settings.WHATSAPP_ADMIN_SYSTEM_USER_TOKEN}, + "tasks": ["MANAGE"], + } + headers = {"Authorization": f"Bearer {user_auth}"} resp = requests.post(url, params=params, headers=headers) diff --git a/temba/orgs/views.py b/temba/orgs/views.py index b4542aae215..d117782125b 100644 --- a/temba/orgs/views.py +++ b/temba/orgs/views.py @@ -1748,18 +1748,36 @@ def get_context_data(self, **kwargs): class WhatsappCloudConnect(InferOrgMixin, OrgPermsMixin, SmartFormView): class WhatsappCloudConnectForm(forms.Form): - user_access_token = forms.CharField(min_length=32, required=True) + user_access_code = forms.CharField(min_length=32, required=True) + user_auth_token = forms.CharField(min_length=32, required=False) def clean(self): try: - auth_token = self.cleaned_data.get("user_access_token", None) + access_code = self.cleaned_data.get("user_access_code", None) app_id = settings.WHATSAPP_APPLICATION_ID app_secret = settings.WHATSAPP_APPLICATION_SECRET - url = "https://graph.facebook.com/v13.0/debug_token" + # Exchange user auth code for a permanent token + url = "https://graph.facebook.com/v18.0/oauth/access_token" + params = dict( + client_id=app_id, + client_secret=app_secret, + code=access_code, + ) + + response = requests.get(url, params=params) + if response.status_code != 200: + raise Exception("Failed to exchange user auth code") + + auth_token = response.json().get("access_token") params = {"access_token": f"{app_id}|{app_secret}", "input_token": auth_token} + # Save auth token in form + self.cleaned_data["user_auth_token"] = auth_token + + # Debug user auth_token to check if we have required permissions + url = "https://graph.facebook.com/v18.0/debug_token" response = requests.get(url, params=params) if response.status_code != 200: # pragma: no cover raise Exception("Failed to debug user token") @@ -1790,7 +1808,7 @@ def pre_process(self, request, *args, **kwargs): return super().pre_process(request, *args, **kwargs) def form_valid(self, form): - auth_token = form.cleaned_data["user_access_token"] + auth_token = form.cleaned_data["user_auth_token"] # add the credentials to the session self.request.session[Channel.CONFIG_WHATSAPP_CLOUD_USER_TOKEN] = auth_token @@ -1800,6 +1818,7 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["connect_url"] = reverse("orgs.org_whatsapp_cloud_connect") context["whatsapp_app_id"] = settings.WHATSAPP_APPLICATION_ID + context["whatsapp_config_id"] = settings.WHATSAPP_CONFIGURATION_ID claim_error = None if context["form"].errors: diff --git a/temba/settings_common.py b/temba/settings_common.py index 477b32291d7..78475bbaa10 100644 --- a/temba/settings_common.py +++ b/temba/settings_common.py @@ -1145,6 +1145,8 @@ WHATSAPP_APPLICATION_ID = os.environ.get("WHATSAPP_APPLICATION_ID", "") WHATSAPP_APPLICATION_SECRET = os.environ.get("WHATSAPP_APPLICATION_SECRET", "") WHATSAPP_WEBHOOK_SECRET = os.environ.get("WHATSAPP_WEBHOOK_SECRET", "") +WHATSAPP_CONFIGURATION_ID = os.environ.get("WHATSAPP_CONFIGURATION_ID", "") +WHATSAPP_CLOUD_EXTENDED_CREDIT_ID = os.environ.get("WHATSAPP_CLOUD_EXTENDED_CREDIT_ID", "") # ----------------------------------------------------------------------------------- # IP Addresses diff --git a/templates/orgs/org_whatsapp_cloud_connect.haml b/templates/orgs/org_whatsapp_cloud_connect.haml index 6e8f3d16631..de7b6ecc9f0 100644 --- a/templates/orgs/org_whatsapp_cloud_connect.haml +++ b/templates/orgs/org_whatsapp_cloud_connect.haml @@ -27,16 +27,19 @@ -trans "Approve the permissions, these are required for us to access the API on your behalf." #fb-app-connect.flex.mt-4 - .button-primary(onclick="launchWhatsAppSignup()") + #fb-app-connect-button.button-primary -trans "Add Facebook Business" %form#claim-form(style="display:none;" method="POST" action="{{ connect_url }}") {% csrf_token %} - %input#user-access-token(type="text" name="user_access_token") + %input#user-access-code(type="text" name="user_access_code") -block extra-script {{ block.super }} :javascript + + let fbq = null; + $(document).ready(function(){ var hash = window.location.hash.substring(1) var result = hash.split('&').reduce(function (res, item) { @@ -47,19 +50,15 @@ var accessToken = result.long_lived_token || result.access_token; if (accessToken) { - $("#user-access-token").val(accessToken); + $("#user-access-code").val(accessToken); $("#claim-form").submit(); } - }); - window.fbAsyncInit = function () { - // JavaScript SDK configuration and setup - FB.init({ - appId: '{{ whatsapp_app_id }}', // Meta App ID - xfbml: true, // parse social plugins on this page - version: 'v14.0' //Graph API version - }); - }; + // add launchWhatsAppSignup to the button + $("#fb-app-connect-button").click(function() { + launchWhatsAppSignup(); + }); + }); // Load the JavaScript SDK asynchronously (function (d, s, id) { @@ -70,21 +69,39 @@ fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); + window.fbAsyncInit = function () { + // JavaScript SDK configuration and setup + FB.init({ + appId: '{{ whatsapp_app_id }}', // Meta App ID + xfbml: true, // parse social plugins on this page + version: 'v18.0', //Graph API version, + status: true + }); + }; + + function loginCallback(response) { + if (response.authResponse) { + const code = response.authResponse.code; + if (code) { + $("#user-access-code").val(code); + $("#claim-form").submit(); + } + } else { + console.log('User cancelled login or did not fully authorize.'); + } + } + // Facebook Login with JavaScript SDK function launchWhatsAppSignup() { // Launch Facebook login - FB.login(function (response) { - if (response.authResponse) { - const accessToken = response.authResponse.accessToken; - if (accessToken) { - $("#user-access-token").val(accessToken); - $("#claim-form").submit(); - } - } - }, { - scope: 'business_management,whatsapp_business_management,whatsapp_business_messaging', + FB.login(loginCallback, { + config_id: '{{ whatsapp_config_id }}', // configuration ID goes here + response_type: 'code', // must be set to 'code' for System User access token + override_default_response_type: true, // when true, any response types passed in the "response_type" will take precedence over the default types extras: { - feature: 'whatsapp_embedded_signup', + sessionInfoVersion: 2, // Receive Session Logging Info + featureType: "only_waba_sharing" // Bypass phone number selection } }); + }