From c715e396f017f76bd84c899d7c022076616e80b1 Mon Sep 17 00:00:00 2001 From: "Chamath K.B. Attanayaka" <84182411+ChamathKB@users.noreply.github.com> Date: Fri, 9 Jun 2023 15:20:45 +0530 Subject: [PATCH] feat: added input_select type message support for whatsapp (#6886) - Added input_select message type support for Whatsapp Cloud API and Whatsapp 360dialog. Co-authored-by: Sojan Jose --- .../whatsapp/providers/base_service.rb | 50 +++++++++++++++ .../providers/whatsapp_360_dialog_service.rb | 18 ++++++ .../providers/whatsapp_cloud_service.rb | 19 ++++++ .../whatsapp360_dialog_service_spec.rb | 63 ++++++++++++++++++- .../providers/whatsapp_cloud_service_spec.rb | 59 +++++++++++++++++ 5 files changed, 208 insertions(+), 1 deletion(-) diff --git a/app/services/whatsapp/providers/base_service.rb b/app/services/whatsapp/providers/base_service.rb index 62401337e..9923017dd 100644 --- a/app/services/whatsapp/providers/base_service.rb +++ b/app/services/whatsapp/providers/base_service.rb @@ -26,4 +26,54 @@ class Whatsapp::Providers::BaseService def validate_provider_config raise 'Overwrite this method in child class' end + + def create_buttons(items) + buttons = [] + items.each do |item| + button = { :type => 'reply', 'reply' => { 'id' => item['value'], 'title' => item['title'] } } + buttons << button + end + buttons + end + + def create_rows(items) + rows = [] + items.each do |item| + row = { 'id' => item['value'], 'title' => item['title'] } + rows << row + end + rows + end + + def create_payload(type, message_content, action) + { + 'type': type, + 'body': { + 'text': message_content + }, + 'action': action + } + end + + def create_payload_based_on_items(message) + if message.content_attributes['items'].length <= 3 + create_button_payload(message) + else + create_list_payload(message) + end + end + + def create_button_payload(message) + buttons = create_buttons(message.content_attributes['items']) + json_hash = { 'buttons' => buttons } + create_payload('button', message.content, JSON.generate(json_hash)) + end + + def create_list_payload(message) + rows = create_rows(message.content_attributes['items']) + section1 = { 'rows' => rows } + sections = [section1] + json_hash = { :button => 'Choose an item', 'sections' => sections } + create_payload('list', message.content, JSON.generate(json_hash)) + end end diff --git a/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb b/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb index 3f09f2467..668162905 100644 --- a/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb +++ b/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb @@ -2,6 +2,8 @@ class Whatsapp::Providers::Whatsapp360DialogService < Whatsapp::Providers::BaseS def send_message(phone_number, message) if message.attachments.present? send_attachment_message(phone_number, message) + elsif message.content_type == 'input_select' + send_interactive_text_message(phone_number, message) else send_text_message(phone_number, message) end @@ -112,4 +114,20 @@ class Whatsapp::Providers::Whatsapp360DialogService < Whatsapp::Providers::BaseS }] } end + + def send_interactive_text_message(phone_number, message) + payload = create_payload_based_on_items(message) + + response = HTTParty.post( + "#{api_base_path}/messages", + headers: api_headers, + body: { + to: phone_number, + interactive: payload, + type: 'interactive' + }.to_json + ) + + process_response(response) + end end diff --git a/app/services/whatsapp/providers/whatsapp_cloud_service.rb b/app/services/whatsapp/providers/whatsapp_cloud_service.rb index 9fff873e5..d1092e2ff 100644 --- a/app/services/whatsapp/providers/whatsapp_cloud_service.rb +++ b/app/services/whatsapp/providers/whatsapp_cloud_service.rb @@ -2,6 +2,8 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi def send_message(phone_number, message) if message.attachments.present? send_attachment_message(phone_number, message) + elsif message.content_type == 'input_select' + send_interactive_text_message(phone_number, message) else send_text_message(phone_number, message) end @@ -129,4 +131,21 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi }] } end + + def send_interactive_text_message(phone_number, message) + payload = create_payload_based_on_items(message) + + response = HTTParty.post( + "#{phone_id_path}/messages", + headers: api_headers, + body: { + messaging_product: 'whatsapp', + to: phone_number, + interactive: payload, + type: 'interactive' + }.to_json + ) + + process_response(response) + end end diff --git a/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb b/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb index 477674497..bba3f5914 100644 --- a/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb +++ b/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb @@ -1,10 +1,12 @@ -## the specs are covered in send in spec/services/whatsapp/send_on_whatsapp_service_spec.rb +## the older specs are covered in send in spec/services/whatsapp/send_on_whatsapp_service_spec.rb require 'rails_helper' describe Whatsapp::Providers::Whatsapp360DialogService do subject(:service) { described_class.new(whatsapp_channel: whatsapp_channel) } let!(:whatsapp_channel) { create(:channel_whatsapp, sync_templates: false, validate_provider_config: false) } + let(:response_headers) { { 'Content-Type' => 'application/json' } } + let(:whatsapp_response) { { messages: [{ id: 'message_id' }] } } describe '#sync_templates' do context 'when called' do @@ -18,4 +20,63 @@ describe Whatsapp::Providers::Whatsapp360DialogService do end end end + + describe '#send_interactive message' do + context 'when called' do + it 'calls message endpoints with button payload when number of items is less than or equal to 3' do + message = create(:message, message_type: :outgoing, content: 'test', + inbox: whatsapp_channel.inbox, content_type: 'input_select', + content_attributes: { + items: [ + { title: 'Burito', value: 'Burito' }, + { title: 'Pasta', value: 'Pasta' }, + { title: 'Sushi', value: 'Sushi' } + ] + }) + stub_request(:post, 'https://waba.360dialog.io/v1/messages') + .with( + body: { + to: '+123456789', + interactive: { + type: 'button', + body: { + text: 'test' + }, + action: '{"buttons":[{"type":"reply","reply":{"id":"Burito","title":"Burito"}},{"type":"reply",' \ + '"reply":{"id":"Pasta","title":"Pasta"}},{"type":"reply","reply":{"id":"Sushi","title":"Sushi"}}]}' + }, type: 'interactive' + }.to_json + ).to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers) + expect(service.send_message('+123456789', message)).to eq 'message_id' + end + + it 'calls message endpoints with list payload when number of items is greater than 3' do + message = create(:message, message_type: :outgoing, content: 'test', inbox: whatsapp_channel.inbox, + content_type: 'input_select', + content_attributes: { + items: [ + { title: 'Burito', value: 'Burito' }, + { title: 'Pasta', value: 'Pasta' }, + { title: 'Sushi', value: 'Sushi' }, + { title: 'Salad', value: 'Salad' } + ] + }) + stub_request(:post, 'https://waba.360dialog.io/v1/messages') + .with( + body: { + to: '+123456789', + interactive: { + type: 'list', + body: { + text: 'test' + }, + action: '{"button":"Choose an item","sections":[{"rows":[{"id":"Burito","title":"Burito"},' \ + '{"id":"Pasta","title":"Pasta"},{"id":"Sushi","title":"Sushi"},{"id":"Salad","title":"Salad"}]}]}' + }, type: 'interactive' + }.to_json + ).to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers) + expect(service.send_message('+123456789', message)).to eq 'message_id' + end + end + end end diff --git a/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb b/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb index 32cd654ee..1bf115476 100644 --- a/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb +++ b/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb @@ -66,6 +66,65 @@ describe Whatsapp::Providers::WhatsappCloudService do end end + describe '#send_interactive message' do + context 'when called' do + it 'calls message endpoints with button payload when number of items is less than or equal to 3' do + message = create(:message, message_type: :outgoing, content: 'test', + inbox: whatsapp_channel.inbox, content_type: 'input_select', + content_attributes: { + items: [ + { title: 'Burito', value: 'Burito' }, + { title: 'Pasta', value: 'Pasta' }, + { title: 'Sushi', value: 'Sushi' } + ] + }) + stub_request(:post, 'https://graph.facebook.com/v13.0/123456789/messages') + .with( + body: { + messaging_product: 'whatsapp', to: '+123456789', + interactive: { + type: 'button', + body: { + text: 'test' + }, + action: '{"buttons":[{"type":"reply","reply":{"id":"Burito","title":"Burito"}},{"type":"reply",' \ + '"reply":{"id":"Pasta","title":"Pasta"}},{"type":"reply","reply":{"id":"Sushi","title":"Sushi"}}]}' + }, type: 'interactive' + }.to_json + ).to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers) + expect(service.send_message('+123456789', message)).to eq 'message_id' + end + + it 'calls message endpoints with list payload when number of items is greater than 3' do + message = create(:message, message_type: :outgoing, content: 'test', inbox: whatsapp_channel.inbox, + content_type: 'input_select', + content_attributes: { + items: [ + { title: 'Burito', value: 'Burito' }, + { title: 'Pasta', value: 'Pasta' }, + { title: 'Sushi', value: 'Sushi' }, + { title: 'Salad', value: 'Salad' } + ] + }) + stub_request(:post, 'https://graph.facebook.com/v13.0/123456789/messages') + .with( + body: { + messaging_product: 'whatsapp', to: '+123456789', + interactive: { + type: 'list', + body: { + text: 'test' + }, + action: '{"button":"Choose an item","sections":[{"rows":[{"id":"Burito","title":"Burito"},' \ + '{"id":"Pasta","title":"Pasta"},{"id":"Sushi","title":"Sushi"},{"id":"Salad","title":"Salad"}]}]}' + }, type: 'interactive' + }.to_json + ).to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers) + expect(service.send_message('+123456789', message)).to eq 'message_id' + end + end + end + describe '#send_template' do let(:template_info) do {