From fdcfed2cd7330b72fa9df375993b47abaac09e04 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 12 Aug 2025 14:20:52 +0530 Subject: [PATCH] feat: Add WhatsApp profile for contact name resolution (#12123) Fixes https://linear.app/chatwoot/issue/CW-4397/whatsapp-contacts-name-update-after-responsd-to-template --- .../whatsapp/incoming_message_base_service.rb | 20 ++++ .../whatsapp/incoming_message_service_spec.rb | 95 +++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index 94ad5c7d1..0aed8dba0 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -92,6 +92,9 @@ class Whatsapp::IncomingMessageBaseService @contact_inbox = contact_inbox @contact = contact_inbox.contact + + # Update existing contact name if ProfileName is available and current name is just phone number + update_contact_with_profile_name(contact_params) end def set_conversation @@ -171,4 +174,21 @@ class Whatsapp::IncomingMessageBaseService ) end end + + def update_contact_with_profile_name(contact_params) + profile_name = contact_params.dig(:profile, :name) + return if profile_name.blank? + return if @contact.name == profile_name + + # Only update if current name exactly matches the phone number or formatted phone number + return unless contact_name_matches_phone_number? + + @contact.update!(name: profile_name) + end + + def contact_name_matches_phone_number? + phone_number = "+#{@processed_params[:messages].first[:from]}" + formatted_phone_number = TelephoneNumber.parse(phone_number).international_number + @contact.name == phone_number || @contact.name == formatted_phone_number + end end diff --git a/spec/services/whatsapp/incoming_message_service_spec.rb b/spec/services/whatsapp/incoming_message_service_spec.rb index 4035a47df..ede1ba824 100644 --- a/spec/services/whatsapp/incoming_message_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_service_spec.rb @@ -371,5 +371,100 @@ describe Whatsapp::IncomingMessageService do Redis::Alfred.delete(key) end end + + context 'when profile name is available for contact updates' do + let(:wa_id) { '1234567890' } + let(:phone_number) { "+#{wa_id}" } + + it 'updates existing contact name when current name matches phone number' do + # Create contact with phone number as name + existing_contact = create(:contact, + account: whatsapp_channel.inbox.account, + name: phone_number, + phone_number: phone_number) + create(:contact_inbox, + contact: existing_contact, + inbox: whatsapp_channel.inbox, + source_id: wa_id) + + params = { + 'contacts' => [{ 'profile' => { 'name' => 'Jane Smith' }, 'wa_id' => wa_id }], + 'messages' => [{ 'from' => wa_id, 'id' => 'message123', 'text' => { 'body' => 'Hello' }, + 'timestamp' => '1633034394', 'type' => 'text' }] + }.with_indifferent_access + + described_class.new(inbox: whatsapp_channel.inbox, params: params).perform + existing_contact.reload + expect(existing_contact.name).to eq('Jane Smith') + end + + it 'does not update contact name when current name is different from phone number' do + # Create contact with human name + existing_contact = create(:contact, + account: whatsapp_channel.inbox.account, + name: 'John Doe', + phone_number: phone_number) + create(:contact_inbox, + contact: existing_contact, + inbox: whatsapp_channel.inbox, + source_id: wa_id) + + params = { + 'contacts' => [{ 'profile' => { 'name' => 'Jane Smith' }, 'wa_id' => wa_id }], + 'messages' => [{ 'from' => wa_id, 'id' => 'message123', 'text' => { 'body' => 'Hello' }, + 'timestamp' => '1633034394', 'type' => 'text' }] + }.with_indifferent_access + + described_class.new(inbox: whatsapp_channel.inbox, params: params).perform + existing_contact.reload + expect(existing_contact.name).to eq('John Doe') # Should not change + end + + it 'updates contact name when current name matches formatted phone number' do + formatted_number = TelephoneNumber.parse(phone_number).international_number + + # Create contact with formatted phone number as name + existing_contact = create(:contact, + account: whatsapp_channel.inbox.account, + name: formatted_number, + phone_number: phone_number) + create(:contact_inbox, + contact: existing_contact, + inbox: whatsapp_channel.inbox, + source_id: wa_id) + + params = { + 'contacts' => [{ 'profile' => { 'name' => 'Alice Johnson' }, 'wa_id' => wa_id }], + 'messages' => [{ 'from' => wa_id, 'id' => 'message123', 'text' => { 'body' => 'Hello' }, + 'timestamp' => '1633034394', 'type' => 'text' }] + }.with_indifferent_access + + described_class.new(inbox: whatsapp_channel.inbox, params: params).perform + existing_contact.reload + expect(existing_contact.name).to eq('Alice Johnson') + end + + it 'does not update when profile name is blank' do + # Create contact with phone number as name + existing_contact = create(:contact, + account: whatsapp_channel.inbox.account, + name: phone_number, + phone_number: phone_number) + create(:contact_inbox, + contact: existing_contact, + inbox: whatsapp_channel.inbox, + source_id: wa_id) + + params = { + 'contacts' => [{ 'profile' => { 'name' => '' }, 'wa_id' => wa_id }], + 'messages' => [{ 'from' => wa_id, 'id' => 'message123', 'text' => { 'body' => 'Hello' }, + 'timestamp' => '1633034394', 'type' => 'text' }] + }.with_indifferent_access + + described_class.new(inbox: whatsapp_channel.inbox, params: params).perform + existing_contact.reload + expect(existing_contact.name).to eq(phone_number) # Should not change + end + end end end