refactor: move whatsapp calling backend to enterprise edition
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f120acbd9f
commit
84fd4588cc
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+5
@@ -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?
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module Enterprise::Whatsapp::FacebookApiClient
|
||||
def webhook_subscribed_fields
|
||||
super + %w[calls]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
module Enterprise::Whatsapp::Providers::WhatsappCloudService
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ::Whatsapp::Providers::WhatsappCloudCallMethods
|
||||
end
|
||||
end
|
||||
+2
@@ -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)
|
||||
|
||||
+2
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user