diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index d7818896f..be42ffd7e 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -217,7 +217,7 @@ class Whatsapp::IncomingMessageBaseService message_phone_number = whatsapp_phone_number(messages_data.first[:from]) return false if message_phone_number.blank? - [message_phone_number, normalized_phone_number(message_phone_number)].uniq.any? do |number| + phone_number_candidates(message_phone_number).any? do |number| phone_number = "+#{number}" formatted_phone_number = TelephoneNumber.parse(phone_number).international_number @contact.name == phone_number || @contact.name == formatted_phone_number diff --git a/app/services/whatsapp/incoming_message_identifier_helper.rb b/app/services/whatsapp/incoming_message_identifier_helper.rb index d29272f30..56b49d4fd 100644 --- a/app/services/whatsapp/incoming_message_identifier_helper.rb +++ b/app/services/whatsapp/incoming_message_identifier_helper.rb @@ -80,13 +80,17 @@ module Whatsapp::IncomingMessageIdentifierHelper display_name = name == phone_identifier ? formatted_phone_number : name attributes = { name: display_name, phone_number: formatted_phone_number } - normalized_number = normalized_phone_number(phone_number) - attributes[:phone_number_candidates] = ["+#{normalized_number}"] if normalized_number != phone_number + candidates = phone_number_candidates(phone_number).drop(1).map { |candidate| "+#{candidate}" } + attributes[:phone_number_candidates] = candidates if candidates.present? attributes end - def normalized_phone_number(phone_number) - Whatsapp::PhoneNumberNormalizationService.new(inbox).normalize_number(phone_number) + def phone_number_candidates(phone_number) + phone_number_normalization_service.phone_number_candidates(phone_number) + end + + def phone_number_normalization_service + @phone_number_normalization_service ||= Whatsapp::PhoneNumberNormalizationService.new(inbox) end def update_whatsapp_identifiers(source_ids: [], username: nil, phone_number: nil) diff --git a/app/services/whatsapp/phone_normalizers/base_phone_normalizer.rb b/app/services/whatsapp/phone_normalizers/base_phone_normalizer.rb index 91882b8e5..76503e361 100644 --- a/app/services/whatsapp/phone_normalizers/base_phone_normalizer.rb +++ b/app/services/whatsapp/phone_normalizers/base_phone_normalizer.rb @@ -11,6 +11,12 @@ class Whatsapp::PhoneNormalizers::BasePhoneNormalizer raise NotImplementedError, 'Subclasses must implement #normalize' end + # Contact matching needs stronger safety than source-id lookup because two + # valid phone numbers can share digits across country-specific formats. + def contact_candidates(waid) + [waid] + end + private def country_code_pattern diff --git a/app/services/whatsapp/phone_normalizers/brazil_phone_normalizer.rb b/app/services/whatsapp/phone_normalizers/brazil_phone_normalizer.rb index bd9afcb92..be246f330 100644 --- a/app/services/whatsapp/phone_normalizers/brazil_phone_normalizer.rb +++ b/app/services/whatsapp/phone_normalizers/brazil_phone_normalizer.rb @@ -20,6 +20,10 @@ class Whatsapp::PhoneNormalizers::BrazilPhoneNormalizer < Whatsapp::PhoneNormali "55#{ddd}9#{number}" end + def contact_candidates(waid) + [waid, normalize(waid)].uniq + end + private def country_code_pattern diff --git a/app/services/whatsapp/phone_number_normalization_service.rb b/app/services/whatsapp/phone_number_normalization_service.rb index a1345ea67..02e4fe7ac 100644 --- a/app/services/whatsapp/phone_number_normalization_service.rb +++ b/app/services/whatsapp/phone_number_normalization_service.rb @@ -38,6 +38,17 @@ class Whatsapp::PhoneNumberNormalizationService normalizer.normalize(clean_number) end + # Keep the provider value first so exact contact matches always win. Each + # country normalizer explicitly opts into contact-safe alternatives; source-id + # normalization alone is not enough because the alternate may be another + # valid number (for example an Argentina landline without the mobile 9). + def phone_number_candidates(clean_number) + normalizer = find_normalizer_for_country(clean_number) + return [clean_number] unless normalizer + + normalizer.contact_candidates(clean_number) + end + private attr_reader :inbox diff --git a/enterprise/app/services/whatsapp/inbound_call_identity_builder.rb b/enterprise/app/services/whatsapp/inbound_call_identity_builder.rb index 590a1f923..d9be6ee28 100644 --- a/enterprise/app/services/whatsapp/inbound_call_identity_builder.rb +++ b/enterprise/app/services/whatsapp/inbound_call_identity_builder.rb @@ -30,7 +30,14 @@ class Whatsapp::InboundCallIdentityBuilder return { name: name } unless phone.to_s.match?(/\A\d{1,15}\z/) formatted = "+#{phone}" - { name: name == phone ? formatted : name, phone_number: formatted } + attributes = { name: name == phone ? formatted : name, phone_number: formatted } + candidates = phone_number_normalization_service.phone_number_candidates(phone.to_s).drop(1).map { |candidate| "+#{candidate}" } + attributes[:phone_number_candidates] = candidates if candidates.present? + attributes + end + + def phone_number_normalization_service + @phone_number_normalization_service ||= Whatsapp::PhoneNumberNormalizationService.new(inbox) end # Match the contacts entry to THIS caller so batched payloads don't borrow another's identity. diff --git a/spec/enterprise/services/whatsapp/incoming_call_service_spec.rb b/spec/enterprise/services/whatsapp/incoming_call_service_spec.rb index cf031f1ca..c7ebf7888 100644 --- a/spec/enterprise/services/whatsapp/incoming_call_service_spec.rb +++ b/spec/enterprise/services/whatsapp/incoming_call_service_spec.rb @@ -148,6 +148,23 @@ describe Whatsapp::IncomingCallService do expect(Call.last.conversation.contact_inbox).to eq(existing) end + it 'reuses a normalized phone contact when no ContactInbox exists yet' do + allow(ActionCable.server).to receive(:broadcast) + contact = create(:contact, account: account, phone_number: '+5541988887777') + + params = { + calls: [{ id: provider_call_id, from: '554188887777', event: 'connect', + session: { sdp: sdp_offer, sdp_type: 'offer' } }], + contacts: [{ wa_id: '554188887777' }] + } + + expect { described_class.new(inbox: inbox, params: params).perform } + .to change(Call, :count).by(1).and not_change(Contact, :count) + + expect(Call.last.contact).to eq(contact) + expect(Call.last.conversation.contact_inbox).to have_attributes(contact: contact, source_id: '554188887777') + end + it 'reuses the BSUID-keyed ContactInbox messaging created for a username-only caller' do allow(ActionCable.server).to receive(:broadcast) contact = create(:contact, account: 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 index b8000d0f7..a3ecc516a 100644 --- a/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_whatsapp_cloud_service_spec.rb @@ -469,6 +469,40 @@ describe Whatsapp::IncomingMessageWhatsappCloudService do expect(whatsapp_channel.inbox.conversations.last.contact).to eq(existing_contact) end end + + context 'when an incoming Argentina number is already in the canonical form' do + let(:argentina_params) do + { + object: 'whatsapp_business_account', + entry: [{ + changes: [{ + value: { + contacts: [{ profile: { name: 'Buenos Aires Office' }, wa_id: '541123456789' }], + messages: [{ + from: '541123456789', + id: 'wamid.ARGENTINA_LANDLINE', + timestamp: '1664799904', + text: { body: 'Hello' }, + type: 'text' + }] + } + }] + }] + }.with_indifferent_access + end + + it 'does not infer a mobile 9 candidate for a valid landline' do + mobile_contact = create(:contact, phone_number: '+5491123456789', account: whatsapp_channel.account) + + expect do + described_class.new(inbox: whatsapp_channel.inbox, params: argentina_params).perform + end.to change(Contact, :count).by(1) + + contact = whatsapp_channel.inbox.conversations.last.contact + expect(contact).not_to eq(mobile_contact) + expect(contact.phone_number).to eq('+541123456789') + end + end end # Métodos auxiliares para reduzir o tamanho do exemplo