diff --git a/app/services/whatsapp/phone_normalizers/mexico_phone_normalizer.rb b/app/services/whatsapp/phone_normalizers/mexico_phone_normalizer.rb new file mode 100644 index 000000000..00a2df1aa --- /dev/null +++ b/app/services/whatsapp/phone_normalizers/mexico_phone_normalizer.rb @@ -0,0 +1,23 @@ +# Handles Mexico phone number normalization +# +# WhatsApp may include a "1" after Mexico's country code (521), while contacts +# can already be stored without it (52). Normalize to the stored WAID (digits-only) +# format; provider formatting (whatsapp:+...) happens in the normalization service. +# The mobile "1" only appears on the 13-digit form (52 + 1 + 10 digits), so guard +# on length to avoid stripping a legitimate "1" from a shorter national number. +class Whatsapp::PhoneNormalizers::MexicoPhoneNormalizer < Whatsapp::PhoneNormalizers::BasePhoneNormalizer + COUNTRY_CODE_WITH_MOBILE_LENGTH = 13 + + def normalize(waid) + return waid unless handles_country?(waid) + return waid unless waid.length == COUNTRY_CODE_WITH_MOBILE_LENGTH + + waid.sub(/^521/, '52') + end + + private + + def country_code_pattern + /^52/ + end +end diff --git a/app/services/whatsapp/phone_number_normalization_service.rb b/app/services/whatsapp/phone_number_normalization_service.rb index 1e52d9b02..853c7d24e 100644 --- a/app/services/whatsapp/phone_number_normalization_service.rb +++ b/app/services/whatsapp/phone_number_normalization_service.rb @@ -1,5 +1,5 @@ # Service to handle phone number normalization for WhatsApp messages -# Currently supports Brazil and Argentina phone number format variations +# Currently supports Brazil, Argentina, and Mexico phone number format variations # Supports both WhatsApp Cloud API and Twilio WhatsApp providers class Whatsapp::PhoneNumberNormalizationService def initialize(inbox) @@ -64,6 +64,7 @@ class Whatsapp::PhoneNumberNormalizationService NORMALIZERS = [ Whatsapp::PhoneNormalizers::BrazilPhoneNormalizer, - Whatsapp::PhoneNormalizers::ArgentinaPhoneNormalizer + Whatsapp::PhoneNormalizers::ArgentinaPhoneNormalizer, + Whatsapp::PhoneNormalizers::MexicoPhoneNormalizer ].freeze end diff --git a/spec/services/twilio/incoming_message_service_spec.rb b/spec/services/twilio/incoming_message_service_spec.rb index dc30f6e62..642425fd5 100644 --- a/spec/services/twilio/incoming_message_service_spec.rb +++ b/spec/services/twilio/incoming_message_service_spec.rb @@ -788,6 +788,57 @@ describe Twilio::IncomingMessageService do expect(whatsapp_twilio_channel.inbox.contact_inboxes.first.source_id).to eq('whatsapp:+541123456789') end end + + describe 'When the incoming number is a Mexican WhatsApp number with 1 after country code' 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 'appends to existing contact when contact inbox exists without the extra 1' do + contact = create(:contact, account: account, phone_number: '+525512345678') + contact_inbox = create(:contact_inbox, + source_id: 'whatsapp:+525512345678', + contact: contact, + inbox: whatsapp_twilio_channel.inbox) + last_conversation = create(:conversation, inbox: whatsapp_twilio_channel.inbox, contact_inbox: contact_inbox) + + params = { + SmsSid: 'SMxx', + From: 'whatsapp:+5215512345678', + AccountSid: 'ACxxx', + MessagingServiceSid: whatsapp_twilio_channel.messaging_service_sid, + Body: 'Test message from Mexico', + ProfileName: 'Maria Lopez' + } + + described_class.new(params: params).perform + + expect(whatsapp_twilio_channel.inbox.conversations.count).to eq(1) + expect(last_conversation.messages.last.content).to eq('Test message from Mexico') + expect(whatsapp_twilio_channel.inbox.contact_inboxes.first.source_id) + .to eq('whatsapp:+525512345678') + end + + it 'creates contact inbox with incoming number when no existing contact matches' do + params = { + SmsSid: 'SMxx', + From: 'whatsapp:+5215512345678', + AccountSid: 'ACxxx', + MessagingServiceSid: whatsapp_twilio_channel.messaging_service_sid, + Body: 'Test message from Mexico', + ProfileName: 'Maria Lopez' + } + + described_class.new(params: params).perform + + expect(whatsapp_twilio_channel.inbox.conversations.count).not_to eq(0) + expect(whatsapp_twilio_channel.inbox.contacts.first.name).to eq('Maria Lopez') + expect(whatsapp_twilio_channel.inbox.messages.first.content).to eq('Test message from Mexico') + expect(whatsapp_twilio_channel.inbox.contact_inboxes.first.source_id) + .to eq('whatsapp:+5215512345678') + end + end end end end 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 60b153775..ef1aba3fc 100644 --- a/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb @@ -224,6 +224,49 @@ describe Whatsapp::IncomingMessageWhatsappCloudService do end end + describe 'when the incoming waid is a Mexican number with 1 after country code' do + let(:mexico_params) do + { + phone_number: whatsapp_channel.phone_number, + object: 'whatsapp_business_account', + entry: [{ + changes: [{ + value: { + contacts: [{ profile: { name: 'Maria Lopez' }, wa_id: '5215512345678' }], + messages: [{ + from: '5215512345678', + id: 'wamid.cloud-mexico-message', + text: { body: 'Test message from Mexico' }, + timestamp: '1664799904', + type: 'text' + }] + } + }] + }] + }.with_indifferent_access + end + + it 'appends to existing contact when a contact inbox exists without the extra 1' do + contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: '525512345678') + last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) + + described_class.new(inbox: whatsapp_channel.inbox, params: mexico_params).perform + + expect(whatsapp_channel.inbox.conversations.count).to eq(1) + expect(last_conversation.messages.last.content).to eq('Test message from Mexico') + expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq('525512345678') + end + + it 'creates a contact inbox with the incoming waid when no existing contact matches' do + described_class.new(inbox: whatsapp_channel.inbox, params: mexico_params).perform + + expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) + expect(whatsapp_channel.inbox.contacts.first.name).to eq('Maria Lopez') + expect(whatsapp_channel.inbox.messages.first.content).to eq('Test message from Mexico') + expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq('5215512345678') + end + end + context 'when invalid params' do it 'will not throw error' do described_class.new(inbox: whatsapp_channel.inbox, params: { phone_number: whatsapp_channel.phone_number, diff --git a/spec/services/whatsapp/phone_normalizers/mexico_phone_normalizer_spec.rb b/spec/services/whatsapp/phone_normalizers/mexico_phone_normalizer_spec.rb new file mode 100644 index 000000000..ad5216afb --- /dev/null +++ b/spec/services/whatsapp/phone_normalizers/mexico_phone_normalizer_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe Whatsapp::PhoneNormalizers::MexicoPhoneNormalizer do + subject(:normalizer) { described_class.new } + + describe '#normalize' do + it 'strips the mobile "1" from the 13-digit form (521 + 10 digits)' do + expect(normalizer.normalize('5215512345678')).to eq('525512345678') + end + + it 'leaves the already-normalized 12-digit form untouched' do + expect(normalizer.normalize('525512345678')).to eq('525512345678') + end + + it 'does not strip a legitimate leading "1" from a 12-digit national number' do + expect(normalizer.normalize('521512345678')).to eq('521512345678') + end + + it 'returns non-Mexico numbers unchanged' do + expect(normalizer.normalize('5511999998888')).to eq('5511999998888') + end + end +end