From 1c09cc5aa5731b8b6c546146f7d14bf511d3979f Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 11 Jun 2025 10:12:57 +0530 Subject: [PATCH 1/2] chore: design fixes --- .../settings/inbox/channels/Whatsapp.vue | 2 +- .../inbox/channels/WhatsappEmbeddedSignup.vue | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue index 01adfdf43..3dc9e860c 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue @@ -89,7 +89,7 @@ const shouldShowCloudWhatsapp = provider => {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue index efe7ad1cf..5383046bd 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue @@ -61,15 +61,19 @@ onBeforeUnmount(() => {
- - {{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUBMIT_BUTTON') }} - +
+ + {{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUBMIT_BUTTON') }} + +
+

Date: Wed, 11 Jun 2025 13:00:19 +0530 Subject: [PATCH 2/2] make whatsapp embedded signup api version dynamic --- .../super_admin/app_configs_controller.rb | 2 +- .../composables/useWhatsappEmbeddedSignup.js | 2 +- .../whatsapp/embedded_signup_service.rb | 43 +++++-------- app/views/layouts/vueapp.html.erb | 1 + config/installation_config.yml | 5 ++ .../whatsapp/embedded_controller_spec.rb | 1 + .../whatsapp/embedded_signup_service_spec.rb | 62 ++++++++++--------- 7 files changed, 55 insertions(+), 61 deletions(-) diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index ae0073045..0910d81b2 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -40,7 +40,7 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController 'linear' => %w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET], 'slack' => %w[SLACK_CLIENT_ID SLACK_CLIENT_SECRET], 'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT], - 'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID] + 'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID WHATSAPP_API_VERSION] } @allowed_configs = mapping.fetch(@config, %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]) diff --git a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js index a4d0c95ee..6adab5bf6 100644 --- a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js +++ b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js @@ -293,7 +293,7 @@ export function useWhatsappEmbeddedSignup() { appId: window.chatwootConfig?.whatsappAppId, status: true, xfbml: true, - version: window.chatwootConfig?.fbApiVersion || 'v21.0', + version: window.chatwootConfig?.whatsappApiVersion || 'v22.0', }); fbSdkLoaded.value = true; }; diff --git a/app/services/whatsapp/embedded_signup_service.rb b/app/services/whatsapp/embedded_signup_service.rb index 0a264ec12..95ecfac2b 100644 --- a/app/services/whatsapp/embedded_signup_service.rb +++ b/app/services/whatsapp/embedded_signup_service.rb @@ -35,9 +35,13 @@ class Whatsapp::EmbeddedSignupService private + def whatsapp_api_version + @whatsapp_api_version ||= GlobalConfigService.load('WHATSAPP_API_VERSION', 'v22.0') + end + def exchange_code_for_token response = Faraday.get( - 'https://graph.facebook.com/v21.0/oauth/access_token', + "https://graph.facebook.com/#{whatsapp_api_version}/oauth/access_token", { client_id: GlobalConfigService.load('WHATSAPP_APP_ID', ''), client_secret: GlobalConfigService.load('WHATSAPP_APP_SECRET', ''), @@ -56,7 +60,7 @@ class Whatsapp::EmbeddedSignupService def fetch_phone_info_via_waba(waba_id, phone_number_id, access_token) # Get all phone numbers for the WABA response = Faraday.get( - "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers", + "https://graph.facebook.com/#{whatsapp_api_version}/#{waba_id}/phone_numbers", { access_token: access_token } ) @@ -64,11 +68,7 @@ class Whatsapp::EmbeddedSignupService data = JSON.parse(response.body) phone_numbers = data['data'] - - # Find the specific phone number we're looking for - phone_data = phone_numbers.find { |phone| phone['id'] == phone_number_id } - - phone_data = phone_numbers.first if phone_data.nil? + phone_data = phone_numbers.find { |phone| phone['id'] == phone_number_id } || phone_numbers.first raise "No phone numbers found for WABA #{waba_id}" if phone_data.nil? @@ -98,22 +98,16 @@ class Whatsapp::EmbeddedSignupService def register_phone_number(phone_number_id, access_token) HTTParty.post( - "https://graph.facebook.com/v21.0/#{phone_number_id}/register", + "https://graph.facebook.com/#{whatsapp_api_version}/#{phone_number_id}/register", { headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }, - body: { - messaging_product: 'whatsapp', - pin: '212834' # TODO: figure out the correct value of this field - }.to_json + body: { messaging_product: 'whatsapp', pin: '212834' }.to_json } ) end def find_existing_channel(phone_number) - Channel::Whatsapp.find_by( - account: @account, - phone_number: phone_number - ) + Channel::Whatsapp.find_by(account: @account, phone_number: phone_number) end def build_channel_attributes(waba_info, phone_info, access_token) @@ -130,29 +124,20 @@ class Whatsapp::EmbeddedSignupService end def create_new_channel(attributes, phone_info) - channel = Channel::Whatsapp.create!( - account: @account, - **attributes - ) - + channel = Channel::Whatsapp.create!(account: @account, **attributes) create_inbox_for_channel(channel, phone_info) channel.reload channel end def create_inbox_for_channel(channel, phone_info) - inbox_name = generate_inbox_name(phone_info) Inbox.create!( account: @account, - name: inbox_name, + name: "#{phone_info[:business_name]} WhatsApp", channel: channel ) end - def generate_inbox_name(phone_info) - "#{phone_info[:business_name]} WhatsApp" - end - def sanitize_phone_number(phone_number) return phone_number if phone_number.blank? @@ -167,7 +152,7 @@ class Whatsapp::EmbeddedSignupService def fetch_token_debug_data(access_token) response = Faraday.get( - 'https://graph.facebook.com/v21.0/debug_token', + "https://graph.facebook.com/#{whatsapp_api_version}/debug_token", { input_token: access_token, access_token: build_app_access_token @@ -207,7 +192,7 @@ class Whatsapp::EmbeddedSignupService verify_token = channel.provider_config['webhook_verify_token'] response = HTTParty.post( - "https://graph.facebook.com/v21.0/#{waba_id}/subscribed_apps", + "https://graph.facebook.com/#{whatsapp_api_version}/#{waba_id}/subscribed_apps", { headers: { 'Authorization' => "Bearer #{access_token}", diff --git a/app/views/layouts/vueapp.html.erb b/app/views/layouts/vueapp.html.erb index 366ebc191..8ccac6c48 100644 --- a/app/views/layouts/vueapp.html.erb +++ b/app/views/layouts/vueapp.html.erb @@ -41,6 +41,7 @@ fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>', whatsappAppId: '<%= @global_config['WHATSAPP_APP_ID'] %>', whatsappConfigurationId: '<%= @global_config['WHATSAPP_CONFIGURATION_ID'] %>', + whatsappApiVersion: '<%= @global_config['WHATSAPP_API_VERSION'] %>', signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>', isEnterprise: '<%= @global_config['IS_ENTERPRISE'] %>', <% if @global_config['IS_ENTERPRISE'] %> diff --git a/config/installation_config.yml b/config/installation_config.yml index 38b6b8d58..93f508bc5 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -139,6 +139,11 @@ display_title: 'WhatsApp App Secret' description: 'The App Secret for WhatsApp Embedded Signup flow (required for embedded signup)' locked: false +- name: WHATSAPP_API_VERSION + display_title: 'WhatsApp API Version' + description: 'Configure this if you want to use a different WhatsApp API version. Make sure its prefixed with `v`' + value: 'v22.0' + locked: false # ------- End of WhatsApp Channel Related Config ------- # # MARK: Microsoft Email Channel Config diff --git a/spec/controllers/whatsapp/embedded_controller_spec.rb b/spec/controllers/whatsapp/embedded_controller_spec.rb index 490a94c09..9d91a6488 100644 --- a/spec/controllers/whatsapp/embedded_controller_spec.rb +++ b/spec/controllers/whatsapp/embedded_controller_spec.rb @@ -9,6 +9,7 @@ RSpec.describe 'WhatsApp Embedded API', type: :request do allow(GlobalConfigService).to receive(:load).with('WHATSAPP_APP_ID', '').and_return('test_app_id') allow(GlobalConfigService).to receive(:load).with('WHATSAPP_CONFIGURATION_ID', '').and_return('test_config_id') allow(GlobalConfigService).to receive(:load).with('WHATSAPP_APP_SECRET', '').and_return('test_app_secret') + allow(GlobalConfigService).to receive(:load).with('WHATSAPP_API_VERSION', '').and_return('v22.0') end context 'when user is authenticated' do diff --git a/spec/services/whatsapp/embedded_signup_service_spec.rb b/spec/services/whatsapp/embedded_signup_service_spec.rb index 8bbc0c4cb..37269e5e1 100644 --- a/spec/services/whatsapp/embedded_signup_service_spec.rb +++ b/spec/services/whatsapp/embedded_signup_service_spec.rb @@ -9,6 +9,7 @@ describe Whatsapp::EmbeddedSignupService do let(:access_token) { 'test_access_token' } let(:app_id) { 'test_app_id' } let(:app_secret) { 'test_app_secret' } + let(:api_version) { 'v22.0' } let(:service) do described_class.new( @@ -24,6 +25,7 @@ describe Whatsapp::EmbeddedSignupService do # Mock global configuration allow(GlobalConfigService).to receive(:load).with('WHATSAPP_APP_ID', '').and_return(app_id) allow(GlobalConfigService).to receive(:load).with('WHATSAPP_APP_SECRET', '').and_return(app_secret) + allow(GlobalConfigService).to receive(:load).with('WHATSAPP_API_VERSION', 'v22.0').and_return(api_version) allow(GlobalConfig).to receive(:clear_cache) # Mock environment variables - allow any calls to ENV.fetch @@ -41,7 +43,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when all parameters are valid' do before do # Stub the token exchange - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -54,7 +56,7 @@ describe Whatsapp::EmbeddedSignupService do ) # Stub the phone numbers fetch - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -72,7 +74,7 @@ describe Whatsapp::EmbeddedSignupService do ) # Stub the token validation - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -102,7 +104,7 @@ describe Whatsapp::EmbeddedSignupService do ) # Stub the phone number registration - stub_request(:post, "https://graph.facebook.com/v21.0/#{phone_number_id}/register") + stub_request(:post, "https://graph.facebook.com/#{api_version}/#{phone_number_id}/register") .to_return( status: 200, body: { success: true }.to_json, @@ -110,7 +112,7 @@ describe Whatsapp::EmbeddedSignupService do ) # Stub the webhook subscription - stub_request(:post, "https://graph.facebook.com/v21.0/#{waba_id}/subscribed_apps") + stub_request(:post, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps") .to_return( status: 200, body: { success: true }.to_json, @@ -142,7 +144,7 @@ describe Whatsapp::EmbeddedSignupService do it 'registers the phone number' do service.perform - expect(WebMock).to have_requested(:post, "https://graph.facebook.com/v21.0/#{phone_number_id}/register") + expect(WebMock).to have_requested(:post, "https://graph.facebook.com/#{api_version}/#{phone_number_id}/register") .with( body: { messaging_product: 'whatsapp', @@ -157,7 +159,7 @@ describe Whatsapp::EmbeddedSignupService do channel = Channel::Whatsapp.find_by(account: account, phone_number: '+1234567890') callback_url = "https://app.chatwoot.com/webhooks/whatsapp/#{channel.phone_number}" - expect(WebMock).to have_requested(:post, "https://graph.facebook.com/v21.0/#{waba_id}/subscribed_apps") + expect(WebMock).to have_requested(:post, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps") .with( body: hash_including( override_callback_uri: callback_url, @@ -220,7 +222,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when channel already exists' do before do # Stub all the required requests for successful flow - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -232,7 +234,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -249,7 +251,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -295,7 +297,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when token exchange fails' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -311,7 +313,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when token has no access to WABA' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -323,7 +325,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -340,7 +342,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -368,7 +370,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when phone numbers fetch fails' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -380,7 +382,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return(status: 400, body: { error: 'Phone numbers fetch failed' }.to_json) end @@ -393,7 +395,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when webhook override fails' do before do # Stub all the successful requests - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -405,7 +407,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -422,7 +424,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -450,7 +452,7 @@ describe Whatsapp::EmbeddedSignupService do headers: { 'Content-Type' => 'application/json' } ) - stub_request(:post, "https://graph.facebook.com/v21.0/#{phone_number_id}/register") + stub_request(:post, "https://graph.facebook.com/#{api_version}/#{phone_number_id}/register") .to_return( status: 200, body: { success: true }.to_json, @@ -458,7 +460,7 @@ describe Whatsapp::EmbeddedSignupService do ) # Stub the failing webhook request - stub_request(:post, "https://graph.facebook.com/v21.0/#{waba_id}/subscribed_apps") + stub_request(:post, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps") .to_return(status: 400, body: { error: 'Webhook failed' }.to_json) end @@ -472,7 +474,7 @@ describe Whatsapp::EmbeddedSignupService do describe '#exchange_code_for_token' do context 'when token exchange is successful' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -493,7 +495,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when response has no access token' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/oauth/access_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token") .with(query: hash_including( 'client_id' => app_id, 'client_secret' => app_secret, @@ -514,7 +516,7 @@ describe Whatsapp::EmbeddedSignupService do describe '#fetch_phone_info_via_waba' do before do - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -544,7 +546,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when specific phone number is not found' do before do - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -571,7 +573,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when no phone numbers are available' do before do - stub_request(:get, "https://graph.facebook.com/v21.0/#{waba_id}/phone_numbers") + stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers") .with(query: hash_including('access_token' => access_token)) .to_return( status: 200, @@ -591,7 +593,7 @@ describe Whatsapp::EmbeddedSignupService do describe '#validate_token_waba_access' do context 'when token has access to WABA' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -619,7 +621,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when token validation fails' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -636,7 +638,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when token does not have access to WABA' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}" @@ -666,7 +668,7 @@ describe Whatsapp::EmbeddedSignupService do context 'when no WABA scope is found' do before do - stub_request(:get, 'https://graph.facebook.com/v21.0/debug_token') + stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token") .with(query: hash_including( 'input_token' => access_token, 'access_token' => "#{app_id}|#{app_secret}"