diff --git a/app/controllers/twilio/callback_controller.rb b/app/controllers/twilio/callback_controller.rb index 53075a555..ed1a05376 100644 --- a/app/controllers/twilio/callback_controller.rb +++ b/app/controllers/twilio/callback_controller.rb @@ -35,7 +35,17 @@ class Twilio::CallbackController < ApplicationController :ExternalUserId, :ParentExternalUserId, :ProfileUsername, - :Username + :Username, + :ReferralBody, + :ReferralHeadline, + :ReferralSourceId, + :ReferralSourceType, + :ReferralSourceUrl, + :ReferralMediaId, + :ReferralMediaContentType, + :ReferralMediaUrl, + :ReferralNumMedia, + :ReferralCtwaClid ) end end diff --git a/app/services/twilio/incoming_message_service.rb b/app/services/twilio/incoming_message_service.rb index dab55e521..02a1636ed 100644 --- a/app/services/twilio/incoming_message_service.rb +++ b/app/services/twilio/incoming_message_service.rb @@ -1,6 +1,7 @@ class Twilio::IncomingMessageService include ::FileTypeHelper include ::Twilio::WhatsappIdentifierHelper + include ::Twilio::ReferralParamsHelper pattr_initialize [:params!] @@ -15,7 +16,8 @@ class Twilio::IncomingMessageService inbox_id: @inbox.id, message_type: :incoming, sender: @contact, - source_id: params[:SmsSid] + source_id: params[:SmsSid], + content_attributes: message_content_attributes ) attach_files attach_location if location_message? diff --git a/app/services/twilio/referral_params_helper.rb b/app/services/twilio/referral_params_helper.rb new file mode 100644 index 000000000..0bc032dbb --- /dev/null +++ b/app/services/twilio/referral_params_helper.rb @@ -0,0 +1,28 @@ +module Twilio::ReferralParamsHelper + REFERRAL_PARAM_MAPPING = { + source_id: :ReferralSourceId, + source_type: :ReferralSourceType, + source_url: :ReferralSourceUrl, + headline: :ReferralHeadline, + body: :ReferralBody, + media_id: :ReferralMediaId, + media_content_type: :ReferralMediaContentType, + media_url: :ReferralMediaUrl, + num_media: :ReferralNumMedia, + ctwa_clid: :ReferralCtwaClid + }.freeze + + def message_content_attributes + referral_attributes.present? ? { referral: referral_attributes } : {} + end + + def referral_attributes + return {} unless twilio_channel.whatsapp? + return {} if params[:ReferralSourceId].blank? + + REFERRAL_PARAM_MAPPING.each_with_object({}) do |(attribute, param_key), result| + value = params[param_key] + result[attribute] = value if value.present? + end + end +end diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index 9e0720f74..00936ff15 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -91,7 +91,7 @@ class Whatsapp::IncomingMessageBaseService def create_contact_messages(message) message['contacts'].each do |contact| # Pass source_id from parent message since contact objects don't have :id - create_message(contact, source_id: message[:id]) + create_message(contact, source_id: message[:id], content_attributes_source: message) attach_contact(contact) @message.save! end @@ -158,10 +158,7 @@ class Whatsapp::IncomingMessageBaseService ) end - def create_message(message, source_id: nil) - content_attrs = outgoing_echo ? { external_echo: true } : {} - content_attrs[:in_reply_to_external_id] = @in_reply_to_external_id if @in_reply_to_external_id.present? - + def create_message(message, source_id: nil, content_attributes_source: message) @message = @conversation.messages.build( content: message_content(message), account_id: @inbox.account_id, @@ -171,10 +168,18 @@ class Whatsapp::IncomingMessageBaseService status: outgoing_echo ? :delivered : :sent, sender: outgoing_echo ? nil : @contact, source_id: (source_id || message[:id]).to_s, - content_attributes: content_attrs + content_attributes: message_content_attributes(content_attributes_source) ) end + def message_content_attributes(message) + content_attrs = outgoing_echo ? { external_echo: true } : {} + content_attrs[:in_reply_to_external_id] = @in_reply_to_external_id if @in_reply_to_external_id.present? + referral_content_attrs = referral_attributes(message) + content_attrs[:referral] = referral_content_attrs if referral_content_attrs.present? + content_attrs + end + def attach_contact(contact) phones = contact[:phones] phones = [{ phone: 'Phone number is not available' }] if phones.blank? diff --git a/app/services/whatsapp/incoming_message_service_helpers.rb b/app/services/whatsapp/incoming_message_service_helpers.rb index 27a854479..fd0d44cbe 100644 --- a/app/services/whatsapp/incoming_message_service_helpers.rb +++ b/app/services/whatsapp/incoming_message_service_helpers.rb @@ -71,6 +71,12 @@ module Whatsapp::IncomingMessageServiceHelpers @in_reply_to_external_id = message['context']&.[]('id') end + def referral_attributes(message) + return {} if outgoing_echo + + message[:referral]&.to_h&.deep_stringify_keys || {} + end + def find_message_by_source_id(source_id) return unless source_id diff --git a/spec/controllers/twilio/callbacks_controller_spec.rb b/spec/controllers/twilio/callbacks_controller_spec.rb index 1dc2991ae..3a4ef16c1 100644 --- a/spec/controllers/twilio/callbacks_controller_spec.rb +++ b/spec/controllers/twilio/callbacks_controller_spec.rb @@ -13,7 +13,15 @@ RSpec.describe 'Twilio::CallbacksController', type: :request do 'SmsSid' => 'SM123', 'ExternalUserId' => 'IN.2081978709342942', 'ParentExternalUserId' => 'IN.ENT.9081726354', - 'ProfileUsername' => 'muhsin' + 'ProfileUsername' => 'muhsin', + 'ReferralCtwaClid' => 'AfjyUDlaIoiweZDnlzmDTEaG', + 'ReferralSourceId' => '120237244350960485', + 'ReferralSourceUrl' => 'https://fb.me/4tBfhWhjr', + 'ReferralSourceType' => 'ad', + 'ReferralHeadline' => 'German citizenship lawyer', + 'ReferralBody' => 'Fast-track your German citizenship', + 'ReferralMediaId' => '', + 'ReferralNumMedia' => '0' } end diff --git a/spec/services/twilio/incoming_message_service_spec.rb b/spec/services/twilio/incoming_message_service_spec.rb index d50a0113d..dc30f6e62 100644 --- a/spec/services/twilio/incoming_message_service_spec.rb +++ b/spec/services/twilio/incoming_message_service_spec.rb @@ -511,6 +511,60 @@ describe Twilio::IncomingMessageService do end end + describe 'When the incoming WhatsApp message has CTWA referral parameters' do + let!(:whatsapp_twilio_channel) do + create(:channel_twilio_sms, :whatsapp, account: account, account_sid: 'ACxxx', + inbox: create(:inbox, account: account, greeting_enabled: false)) + end + + it 'stores normalized referral attributes on the message' do + params = { + SmsSid: 'SMxx', + From: 'whatsapp:+491741763110', + AccountSid: 'ACxxx', + MessagingServiceSid: whatsapp_twilio_channel.messaging_service_sid, + Body: 'Hallo! Kann ich hierzu mehr Informationen erhalten?', + ReferralCtwaClid: 'AfjyUDlaIoiweZDnlzmDTEaG', + ReferralSourceId: '120237244350960485', + ReferralSourceUrl: 'https://fb.me/4tBfhWhjr', + ReferralSourceType: 'ad', + ReferralHeadline: 'German citizenship lawyer', + ReferralBody: 'Fast-track your German citizenship', + ReferralMediaId: '', + ReferralNumMedia: '0' + } + + described_class.new(params: params).perform + + message = whatsapp_twilio_channel.inbox.messages.last + expect(message.content_attributes['referral']).to eq( + 'ctwa_clid' => 'AfjyUDlaIoiweZDnlzmDTEaG', + 'source_id' => '120237244350960485', + 'source_url' => 'https://fb.me/4tBfhWhjr', + 'source_type' => 'ad', + 'headline' => 'German citizenship lawyer', + 'body' => 'Fast-track your German citizenship', + 'num_media' => '0' + ) + end + + it 'does not add referral attributes when ReferralSourceId is absent' do + params = { + SmsSid: 'SMxx', + From: 'whatsapp:+491741763110', + AccountSid: 'ACxxx', + MessagingServiceSid: whatsapp_twilio_channel.messaging_service_sid, + Body: 'Regular WhatsApp message', + ReferralCtwaClid: 'AfjyUDlaIoiweZDnlzmDTEaG' + } + + described_class.new(params: params).perform + + message = whatsapp_twilio_channel.inbox.messages.last + expect(message.content_attributes).not_to have_key('referral') + end + end + describe 'When the incoming number is a Brazilian number in new format with 9 included' do let!(:whatsapp_twilio_channel) do create(:channel_twilio_sms, :whatsapp, account: account, account_sid: 'ACxxx', diff --git a/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb b/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb index 1bbd6d8f4..60b153775 100644 --- a/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb @@ -234,6 +234,88 @@ describe Whatsapp::IncomingMessageWhatsappCloudService do end end + context 'when message contains referral data' do + let(:referral_params) do + { + phone_number: whatsapp_channel.phone_number, + object: 'whatsapp_business_account', + entry: [{ + changes: [{ + value: { + contacts: [{ profile: { name: 'Mom' }, wa_id: '255718573302', user_id: 'TZ.1040042605869930' }], + messages: [{ + referral: { + source_url: 'https://fb.me/3TYpooaRT', + source_id: '52558118838064', + source_type: 'ad', + body: 'washa data tu', + headline: 'Diana Digital', + media_type: 'video', + video_url: 'https://www.facebook.com/reel/1438165771395493/', + thumbnail_url: 'https://scontent.xx.fbcdn.net/sample.jpg', + ctwa_clid: 'AfhcQdP2E4A8wWpeb1FqUzUi', + welcome_message: { + text: 'Hi! Please let us know how we can help you.' + } + }, + from: '255718573302', + from_user_id: 'TZ.1040042605869930', + id: 'wamid.CTWA_REFERRAL_MESSAGE', + timestamp: '1780649766', + text: { body: 'Hello nielekeze' }, + type: 'text' + }] + } + }] + }] + }.with_indifferent_access + end + + it 'stores the referral payload in message content attributes' do + described_class.new(inbox: whatsapp_channel.inbox, params: referral_params).perform + + message = whatsapp_channel.inbox.messages.last + expect(message.content).to eq('Hello nielekeze') + expect(message.content_attributes['referral']).to include( + 'source_url' => 'https://fb.me/3TYpooaRT', + 'source_id' => '52558118838064', + 'source_type' => 'ad', + 'body' => 'washa data tu', + 'headline' => 'Diana Digital', + 'media_type' => 'video', + 'video_url' => 'https://www.facebook.com/reel/1438165771395493/', + 'thumbnail_url' => 'https://scontent.xx.fbcdn.net/sample.jpg', + 'ctwa_clid' => 'AfhcQdP2E4A8wWpeb1FqUzUi', + 'welcome_message' => { 'text' => 'Hi! Please let us know how we can help you.' } + ) + end + + it 'preserves the referral payload when the message contains contacts' do + contacts_referral_params = referral_params.deep_dup + parent_message = contacts_referral_params.dig(:entry, 0, :changes, 0, :value, :messages, 0) + parent_message[:type] = 'contacts' + parent_message.delete(:text) + parent_message[:contacts] = [{ + name: { + formatted_name: 'Diana Digital', + first_name: 'Diana', + last_name: 'Digital' + }, + phones: [{ phone: '+255718573302' }] + }] + + described_class.new(inbox: whatsapp_channel.inbox, params: contacts_referral_params).perform + + message = whatsapp_channel.inbox.messages.last + expect(message.content).to eq('Diana Digital') + expect(message.content_attributes['referral']).to include( + 'source_id' => '52558118838064', + 'headline' => 'Diana Digital', + 'ctwa_clid' => 'AfhcQdP2E4A8wWpeb1FqUzUi' + ) + end + end + context 'when message is a reply (has context)' do let(:reply_params) do {