diff --git a/app/jobs/webhooks/whatsapp_events_job.rb b/app/jobs/webhooks/whatsapp_events_job.rb index fabd127a2..99323f49e 100644 --- a/app/jobs/webhooks/whatsapp_events_job.rb +++ b/app/jobs/webhooks/whatsapp_events_job.rb @@ -4,6 +4,7 @@ class Webhooks::WhatsappEventsJob < ApplicationJob def perform(params = {}) channel = find_channel_from_whatsapp_business_payload(params) || find_channel(params) return if channel.blank? + return if channel.reauthorization_required? case channel.provider when 'whatsapp_cloud' diff --git a/app/mailers/administrator_notifications/channel_notifications_mailer.rb b/app/mailers/administrator_notifications/channel_notifications_mailer.rb index e7c0318c4..daeb44ece 100644 --- a/app/mailers/administrator_notifications/channel_notifications_mailer.rb +++ b/app/mailers/administrator_notifications/channel_notifications_mailer.rb @@ -15,6 +15,14 @@ class AdministratorNotifications::ChannelNotificationsMailer < ApplicationMailer send_mail_with_liquid(to: admin_emails, subject: subject) and return end + def whatsapp_disconnect(inbox) + return unless smtp_config_set_or_development? + + subject = 'Your Whatsapp connection has expired' + @action_url = "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{Current.account.id}/settings/inboxes/#{inbox.id}" + send_mail_with_liquid(to: admin_emails, subject: subject) and return + end + def email_disconnect(inbox) return unless smtp_config_set_or_development? diff --git a/app/models/channel/whatsapp.rb b/app/models/channel/whatsapp.rb index 8765cdf42..140538a54 100644 --- a/app/models/channel/whatsapp.rb +++ b/app/models/channel/whatsapp.rb @@ -19,6 +19,7 @@ class Channel::Whatsapp < ApplicationRecord include Channelable + include Reauthorizable self.table_name = 'channel_whatsapp' EDITABLE_ATTRS = [:phone_number, :provider, { provider_config: {} }].freeze diff --git a/app/models/concerns/reauthorizable.rb b/app/models/concerns/reauthorizable.rb index cc78b56a0..6186dcc15 100644 --- a/app/models/concerns/reauthorizable.rb +++ b/app/models/concerns/reauthorizable.rb @@ -39,11 +39,14 @@ module Reauthorizable def prompt_reauthorization! ::Redis::Alfred.set(reauthorization_required_key, true) - if (is_a? Integrations::Hook) && slack? - AdministratorNotifications::ChannelNotificationsMailer.with(account: account).slack_disconnect.deliver_later - elsif is_a? Channel::FacebookPage + case self.class.name + when 'Integrations::Hook' + AdministratorNotifications::ChannelNotificationsMailer.with(account: account).slack_disconnect.deliver_later if slack? + when 'Channel::FacebookPage' AdministratorNotifications::ChannelNotificationsMailer.with(account: account).facebook_disconnect(inbox).deliver_later - elsif is_a? Channel::Email + when 'Channel::Whatsapp' + AdministratorNotifications::ChannelNotificationsMailer.with(account: account).whatsapp_disconnect(inbox).deliver_later + when 'Channel::Email' AdministratorNotifications::ChannelNotificationsMailer.with(account: account).email_disconnect(inbox).deliver_later end end diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index 3c0059f3a..b3c7d56d7 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -96,9 +96,11 @@ class Whatsapp::IncomingMessageBaseService return if %w[text button interactive location].include?(message_type) attachment_payload = @processed_params[:messages].first[message_type.to_sym] - attachment_file = download_attachment_file(attachment_payload) - @message.content ||= attachment_payload[:caption] + + attachment_file = download_attachment_file(attachment_payload) + return if attachment_file.blank? + @message.attachments.new( account_id: @message.account_id, file_type: file_content_type(message_type), diff --git a/app/services/whatsapp/incoming_message_whatsapp_cloud_service.rb b/app/services/whatsapp/incoming_message_whatsapp_cloud_service.rb index 4cd93942f..b5bedaf8f 100644 --- a/app/services/whatsapp/incoming_message_whatsapp_cloud_service.rb +++ b/app/services/whatsapp/incoming_message_whatsapp_cloud_service.rb @@ -10,6 +10,8 @@ class Whatsapp::IncomingMessageWhatsappCloudService < Whatsapp::IncomingMessageB def download_attachment_file(attachment_payload) url_response = HTTParty.get(inbox.channel.media_url(attachment_payload[:id]), headers: inbox.channel.api_headers) - Down.download(url_response.parsed_response['url'], headers: inbox.channel.api_headers) + # This url response will be failure if the access token has expired. + inbox.channel.authorization_error! if url_response.unauthorized? + Down.download(url_response.parsed_response['url'], headers: inbox.channel.api_headers) if url_response.success? end end diff --git a/app/views/mailers/administrator_notifications/channel_notifications_mailer/whatsapp_disconnect.liquid b/app/views/mailers/administrator_notifications/channel_notifications_mailer/whatsapp_disconnect.liquid new file mode 100644 index 000000000..ec13bbe01 --- /dev/null +++ b/app/views/mailers/administrator_notifications/channel_notifications_mailer/whatsapp_disconnect.liquid @@ -0,0 +1,8 @@ +

Hello,

+ +

Your Whatsapp Access has expired.

+

Please reconnect Whatsapp to continue receiving messages.

+ +

+Click here to re-connect. +

diff --git a/spec/jobs/webhooks/whatsapp_events_job_spec.rb b/spec/jobs/webhooks/whatsapp_events_job_spec.rb index 00fde3d41..e4e06b5da 100644 --- a/spec/jobs/webhooks/whatsapp_events_job_spec.rb +++ b/spec/jobs/webhooks/whatsapp_events_job_spec.rb @@ -23,6 +23,13 @@ RSpec.describe Webhooks::WhatsappEventsJob, type: :job do expect(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new) job.perform_now(params) end + + it 'will not enques Whatsapp::IncomingMessageWhatsappCloudService if channel reauthorization required' do + channel.prompt_reauthorization! + allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service) + expect(Whatsapp::IncomingMessageWhatsappCloudService).not_to receive(:new) + job.perform_now(params) + end end context 'when default provider' do diff --git a/spec/mailers/administrator_notifications/channel_notifications_mailer_spec.rb b/spec/mailers/administrator_notifications/channel_notifications_mailer_spec.rb index aad74af0a..fa90b7970 100644 --- a/spec/mailers/administrator_notifications/channel_notifications_mailer_spec.rb +++ b/spec/mailers/administrator_notifications/channel_notifications_mailer_spec.rb @@ -41,4 +41,18 @@ RSpec.describe AdministratorNotifications::ChannelNotificationsMailer, type: :ma expect(mail.to).to eq([administrator.email]) end end + + describe 'whatsapp_disconnect' do + let!(:whatsapp_channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) } + let!(:whatsapp_inbox) { create(:inbox, channel: whatsapp_channel, account: account) } + let(:mail) { described_class.with(account: account).whatsapp_disconnect(whatsapp_inbox).deliver_now } + + it 'renders the subject' do + expect(mail.subject).to eq('Your Whatsapp connection has expired') + end + + it 'renders the receiver email' do + expect(mail.to).to eq([administrator.email]) + end + end end diff --git a/spec/models/channel/whatsapp_spec.rb b/spec/models/channel/whatsapp_spec.rb index 9a6620a5e..4368807fe 100644 --- a/spec/models/channel/whatsapp_spec.rb +++ b/spec/models/channel/whatsapp_spec.rb @@ -1,8 +1,33 @@ # frozen_string_literal: true require 'rails_helper' +require Rails.root.join 'spec/models/concerns/reauthorizable_shared.rb' RSpec.describe Channel::Whatsapp do + describe 'concerns' do + let(:channel) { create(:channel_whatsapp) } + + before do + stub_request(:post, 'https://waba.360dialog.io/v1/configs/webhook') + stub_request(:get, 'https://waba.360dialog.io/v1/configs/templates') + end + + it_behaves_like 'reauthorizable' + + context 'when prompt_reauthorization!' do + it 'calls channel notifier mail for whatsapp' do + admin_mailer = double + mailer_double = double + + expect(AdministratorNotifications::ChannelNotificationsMailer).to receive(:with).and_return(admin_mailer) + expect(admin_mailer).to receive(:whatsapp_disconnect).with(channel.inbox).and_return(mailer_double) + expect(mailer_double).to receive(:deliver_later) + + channel.prompt_reauthorization! + end + end + end + describe 'validate_provider_config' do let(:channel) { build(:channel_whatsapp, provider: 'whatsapp_cloud', account: create(:account)) } diff --git a/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb b/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb new file mode 100644 index 000000000..5498f2b8c --- /dev/null +++ b/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb @@ -0,0 +1,70 @@ +require 'rails_helper' + +describe Whatsapp::IncomingMessageWhatsappCloudService do + describe '#perform' do + let!(:whatsapp_channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) } + let(:params) do + { + phone_number: whatsapp_channel.phone_number, + object: 'whatsapp_business_account', + entry: [{ + changes: [{ + value: { + contacts: [{ profile: { name: 'Sojan Jose' }, wa_id: '2423423243' }], + messages: [{ + from: '2423423243', + image: { + id: 'b1c68f38-8734-4ad3-b4a1-ef0c10d683', + mime_type: 'image/jpeg', + sha256: '29ed500fa64eb55fc19dc4124acb300e5dcca0f822a301ae99944db', + caption: 'Check out my product!' + }, + timestamp: '1664799904', type: 'image' + }] + } + }] + }] + }.with_indifferent_access + end + + context 'when valid attachment message params' do + it 'creates appropriate conversations, message and contacts' do + stub_request(:get, whatsapp_channel.media_url('b1c68f38-8734-4ad3-b4a1-ef0c10d683')).to_return( + status: 200, + body: { + messaging_product: 'whatsapp', + url: 'https://chatwoot-assets.local/sample.png', + mime_type: 'image/jpeg', + sha256: 'sha256', + file_size: 'SIZE', + id: 'b1c68f38-8734-4ad3-b4a1-ef0c10d683' + }.to_json, + headers: { 'content-type' => 'application/json' } + ) + stub_request(:get, 'https://chatwoot-assets.local/sample.png').to_return( + status: 200, + body: File.read('spec/assets/sample.png') + ) + + described_class.new(inbox: whatsapp_channel.inbox, params: params).perform + expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) + expect(Contact.all.first.name).to eq('Sojan Jose') + expect(whatsapp_channel.inbox.messages.first.content).to eq('Check out my product!') + expect(whatsapp_channel.inbox.messages.first.attachments.present?).to be true + end + + it 'increments reauthorization count if fetching attachment fails' do + stub_request(:get, whatsapp_channel.media_url('b1c68f38-8734-4ad3-b4a1-ef0c10d683')).to_return( + status: 401 + ) + + described_class.new(inbox: whatsapp_channel.inbox, params: params).perform + expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) + expect(Contact.all.first.name).to eq('Sojan Jose') + expect(whatsapp_channel.inbox.messages.first.content).to eq('Check out my product!') + expect(whatsapp_channel.inbox.messages.first.attachments.present?).to be false + expect(whatsapp_channel.authorization_error_count).to eq(1) + end + end + end +end