diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js index 858c0ecbc..80c62c0a1 100644 --- a/app/javascript/dashboard/featureFlags.js +++ b/app/javascript/dashboard/featureFlags.js @@ -44,6 +44,7 @@ export const FEATURE_FLAGS = { COMPANIES: 'companies', ADVANCED_SEARCH: 'advanced_search', CONVERSATION_REQUIRED_ATTRIBUTES: 'conversation_required_attributes', + WHATSAPP_CALL: 'whatsapp_call', }; export const PREMIUM_FEATURES = [ @@ -55,4 +56,5 @@ export const PREMIUM_FEATURES = [ FEATURE_FLAGS.SAML, FEATURE_FLAGS.CONVERSATION_REQUIRED_ATTRIBUTES, FEATURE_FLAGS.ADVANCED_ASSIGNMENT, + FEATURE_FLAGS.WHATSAPP_CALL, ]; diff --git a/app/jobs/webhooks/whatsapp_events_job.rb b/app/jobs/webhooks/whatsapp_events_job.rb index 94e09e1aa..10307b4f3 100644 --- a/app/jobs/webhooks/whatsapp_events_job.rb +++ b/app/jobs/webhooks/whatsapp_events_job.rb @@ -1,5 +1,5 @@ class Webhooks::WhatsappEventsJob < ApplicationJob - queue_as :default + queue_as :low def perform(params = {}) channel = find_channel_from_whatsapp_business_payload(params) @@ -59,16 +59,6 @@ class Webhooks::WhatsappEventsJob < ApplicationJob end def handle_message_events(channel, params) - if call_event?(params) - handle_call_events(channel, params) - return - end - - if call_permission_reply?(params) - handle_call_permission_reply(channel, params) - return - end - case channel.provider when 'whatsapp_cloud' Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: channel.inbox, params: params).perform @@ -77,30 +67,6 @@ class Webhooks::WhatsappEventsJob < ApplicationJob end end - def handle_call_events(channel, params) - Whatsapp::IncomingCallService.new( - inbox: channel.inbox, - params: extract_call_params(params) - ).perform - end - - def call_event?(params) - params.dig(:entry, 0, :changes, 0, :field) == 'calls' - end - - def call_permission_reply?(params) - message = params.dig(:entry, 0, :changes, 0, :value, :messages, 0) - message&.dig(:type) == 'interactive' && message&.dig(:interactive, :type) == 'call_permission_reply' - end - - def handle_call_permission_reply(channel, params) - Whatsapp::CallPermissionReplyService.new(inbox: channel.inbox, params: params).perform - end - - def extract_call_params(params) - params.dig(:entry, 0, :changes, 0, :value) || {} - end - private def channel_is_inactive?(channel) @@ -134,3 +100,5 @@ class Webhooks::WhatsappEventsJob < ApplicationJob return channel if channel && channel.provider_config['phone_number_id'] == phone_number_id end end + +Webhooks::WhatsappEventsJob.prepend_mod_with('Webhooks::WhatsappEventsJob') diff --git a/app/models/account.rb b/app/models/account.rb index 29c51563d..087226710 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -133,7 +133,6 @@ class Account < ApplicationRecord has_many :twitter_profiles, dependent: :destroy_async, class_name: '::Channel::TwitterProfile' has_many :users, through: :account_users has_many :web_widgets, dependent: :destroy_async, class_name: '::Channel::WebWidget' - has_many :whatsapp_calls, dependent: :destroy_async has_many :webhooks, dependent: :destroy_async has_many :whatsapp_channels, dependent: :destroy_async, class_name: '::Channel::Whatsapp' has_many :working_hours, dependent: :destroy_async diff --git a/app/services/whatsapp/facebook_api_client.rb b/app/services/whatsapp/facebook_api_client.rb index 94e46dabd..5906a294d 100644 --- a/app/services/whatsapp/facebook_api_client.rb +++ b/app/services/whatsapp/facebook_api_client.rb @@ -86,7 +86,7 @@ class Whatsapp::FacebookApiClient body: { override_callback_uri: callback_url, verify_token: verify_token, - subscribed_fields: %w[messages smb_message_echoes calls] + subscribed_fields: webhook_subscribed_fields }.to_json ) @@ -102,6 +102,10 @@ class Whatsapp::FacebookApiClient handle_response(response, 'Webhook unsubscription failed') end + def webhook_subscribed_fields + %w[messages smb_message_echoes] + end + private def request_headers @@ -123,3 +127,5 @@ class Whatsapp::FacebookApiClient response.parsed_response end end + +Whatsapp::FacebookApiClient.prepend_mod_with('Whatsapp::FacebookApiClient') diff --git a/app/services/whatsapp/providers/whatsapp_cloud_service.rb b/app/services/whatsapp/providers/whatsapp_cloud_service.rb index 84491fc83..11b2d885f 100644 --- a/app/services/whatsapp/providers/whatsapp_cloud_service.rb +++ b/app/services/whatsapp/providers/whatsapp_cloud_service.rb @@ -1,6 +1,4 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseService - include Whatsapp::Providers::WhatsappCloudCallMethods - def send_message(phone_number, message) @message = message @@ -207,3 +205,5 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi process_response(response, message) end end + +Whatsapp::Providers::WhatsappCloudService.include_mod_with('Whatsapp::Providers::WhatsappCloudService') diff --git a/config/features.yml b/config/features.yml index 41515ff64..f4e2d0af4 100644 --- a/config/features.yml +++ b/config/features.yml @@ -104,10 +104,10 @@ display_name: Audit Logs enabled: false premium: true -- name: response_bot - display_name: Response Bot +- name: whatsapp_call + display_name: WhatsApp Calling enabled: false - deprecated: true + premium: true - name: message_reply_to display_name: Message Reply To enabled: false diff --git a/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb b/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb similarity index 95% rename from app/controllers/api/v1/accounts/whatsapp_calls_controller.rb rename to enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb index 80b8014ed..3662a32a9 100644 --- a/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb @@ -1,4 +1,5 @@ class Api::V1::Accounts::WhatsappCallsController < Api::V1::Accounts::BaseController + before_action :ensure_whatsapp_call_enabled before_action :set_whatsapp_call, only: [:accept, :reject, :terminate] def accept @@ -85,6 +86,10 @@ class Api::V1::Accounts::WhatsappCallsController < Api::V1::Accounts::BaseContro nil end + def ensure_whatsapp_call_enabled + render_payment_required('WhatsApp calling is not enabled for this account') unless current_account.feature_enabled?('whatsapp_call') + end + def set_whatsapp_call @whatsapp_call = current_account.whatsapp_calls.find(params[:id]) authorize @whatsapp_call.conversation, :show? diff --git a/enterprise/app/jobs/enterprise/webhooks/whatsapp_events_job.rb b/enterprise/app/jobs/enterprise/webhooks/whatsapp_events_job.rb new file mode 100644 index 000000000..f08aeb40e --- /dev/null +++ b/enterprise/app/jobs/enterprise/webhooks/whatsapp_events_job.rb @@ -0,0 +1,41 @@ +module Enterprise::Webhooks::WhatsappEventsJob + def handle_message_events(channel, params) + if call_event?(params) + handle_call_events(channel, params) + return + end + + if call_permission_reply?(params) + handle_call_permission_reply(channel, params) + return + end + + super + end + + private + + def call_event?(params) + params.dig(:entry, 0, :changes, 0, :field) == 'calls' + end + + def call_permission_reply?(params) + message = params.dig(:entry, 0, :changes, 0, :value, :messages, 0) + message&.dig(:type) == 'interactive' && message&.dig(:interactive, :type) == 'call_permission_reply' + end + + def handle_call_events(channel, params) + Whatsapp::IncomingCallService.new( + inbox: channel.inbox, + params: extract_call_params(params) + ).perform + end + + def handle_call_permission_reply(channel, params) + Whatsapp::CallPermissionReplyService.new(inbox: channel.inbox, params: params).perform + end + + def extract_call_params(params) + params.dig(:entry, 0, :changes, 0, :value) || {} + end +end diff --git a/enterprise/app/models/enterprise/concerns/account.rb b/enterprise/app/models/enterprise/concerns/account.rb index 01693ac79..c8c260917 100644 --- a/enterprise/app/models/enterprise/concerns/account.rb +++ b/enterprise/app/models/enterprise/concerns/account.rb @@ -17,6 +17,7 @@ module Enterprise::Concerns::Account has_many :copilot_threads, dependent: :destroy_async has_many :companies, dependent: :destroy_async has_many :voice_channels, dependent: :destroy_async, class_name: '::Channel::Voice' + has_many :whatsapp_calls, dependent: :destroy_async has_one :saml_settings, dependent: :destroy_async, class_name: 'AccountSamlSettings' end diff --git a/app/models/whatsapp_call.rb b/enterprise/app/models/whatsapp_call.rb similarity index 100% rename from app/models/whatsapp_call.rb rename to enterprise/app/models/whatsapp_call.rb diff --git a/enterprise/app/services/enterprise/whatsapp/facebook_api_client.rb b/enterprise/app/services/enterprise/whatsapp/facebook_api_client.rb new file mode 100644 index 000000000..6be4e574e --- /dev/null +++ b/enterprise/app/services/enterprise/whatsapp/facebook_api_client.rb @@ -0,0 +1,5 @@ +module Enterprise::Whatsapp::FacebookApiClient + def webhook_subscribed_fields + super + %w[calls] + end +end diff --git a/enterprise/app/services/enterprise/whatsapp/providers/whatsapp_cloud_service.rb b/enterprise/app/services/enterprise/whatsapp/providers/whatsapp_cloud_service.rb new file mode 100644 index 000000000..ab3416f52 --- /dev/null +++ b/enterprise/app/services/enterprise/whatsapp/providers/whatsapp_cloud_service.rb @@ -0,0 +1,7 @@ +module Enterprise::Whatsapp::Providers::WhatsappCloudService + extend ActiveSupport::Concern + + included do + include ::Whatsapp::Providers::WhatsappCloudCallMethods + end +end diff --git a/app/services/whatsapp/call_permission_reply_service.rb b/enterprise/app/services/whatsapp/call_permission_reply_service.rb similarity index 96% rename from app/services/whatsapp/call_permission_reply_service.rb rename to enterprise/app/services/whatsapp/call_permission_reply_service.rb index 75f5753e8..0a07a19fb 100644 --- a/app/services/whatsapp/call_permission_reply_service.rb +++ b/enterprise/app/services/whatsapp/call_permission_reply_service.rb @@ -2,6 +2,8 @@ class Whatsapp::CallPermissionReplyService pattr_initialize [:inbox!, :params!] def perform + return unless inbox.account.feature_enabled?('whatsapp_call') + reply_data = extract_reply_data return unless reply_data&.dig(:accepted) diff --git a/app/services/whatsapp/call_service.rb b/enterprise/app/services/whatsapp/call_service.rb similarity index 100% rename from app/services/whatsapp/call_service.rb rename to enterprise/app/services/whatsapp/call_service.rb diff --git a/app/services/whatsapp/incoming_call_service.rb b/enterprise/app/services/whatsapp/incoming_call_service.rb similarity index 98% rename from app/services/whatsapp/incoming_call_service.rb rename to enterprise/app/services/whatsapp/incoming_call_service.rb index 445b2345c..8bd7f7504 100644 --- a/app/services/whatsapp/incoming_call_service.rb +++ b/enterprise/app/services/whatsapp/incoming_call_service.rb @@ -2,6 +2,8 @@ class Whatsapp::IncomingCallService pattr_initialize [:inbox!, :params!] def perform + return unless inbox.account.feature_enabled?('whatsapp_call') + calls = params[:calls] return if calls.blank? diff --git a/app/services/whatsapp/providers/whatsapp_cloud_call_methods.rb b/enterprise/app/services/whatsapp/providers/whatsapp_cloud_call_methods.rb similarity index 100% rename from app/services/whatsapp/providers/whatsapp_cloud_call_methods.rb rename to enterprise/app/services/whatsapp/providers/whatsapp_cloud_call_methods.rb diff --git a/lib/whatsapp/call_errors.rb b/enterprise/lib/whatsapp/call_errors.rb similarity index 100% rename from lib/whatsapp/call_errors.rb rename to enterprise/lib/whatsapp/call_errors.rb diff --git a/spec/services/whatsapp/facebook_api_client_spec.rb b/spec/services/whatsapp/facebook_api_client_spec.rb index a33999bee..74fb2f6e2 100644 --- a/spec/services/whatsapp/facebook_api_client_spec.rb +++ b/spec/services/whatsapp/facebook_api_client_spec.rb @@ -177,7 +177,7 @@ describe Whatsapp::FacebookApiClient do .with( headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }, body: { override_callback_uri: callback_url, verify_token: verify_token, - subscribed_fields: %w[messages smb_message_echoes calls] }.to_json + subscribed_fields: %w[messages smb_message_echoes] }.to_json ) .to_return( status: 200, @@ -224,7 +224,7 @@ describe Whatsapp::FacebookApiClient do .with( headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }, body: { override_callback_uri: callback_url, verify_token: verify_token, - subscribed_fields: %w[messages smb_message_echoes calls] }.to_json + subscribed_fields: %w[messages smb_message_echoes] }.to_json ) .to_return(status: 400, body: { error: 'Webhook callback override failed' }.to_json) end