diff --git a/app/services/whatsapp/providers/wuzapi_service.rb b/app/services/whatsapp/providers/wuzapi_service.rb index ccd67461d..8e8ee8a9c 100644 --- a/app/services/whatsapp/providers/wuzapi_service.rb +++ b/app/services/whatsapp/providers/wuzapi_service.rb @@ -1,5 +1,6 @@ require_relative 'base_service' +# rubocop:disable Metrics/ClassLength class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService attr_reader :whatsapp_channel @@ -52,6 +53,24 @@ class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService response end + # Dispatches an interactive message (buttons / list / url_button). + # Called by the lifecycle dispatcher when a rule has message_type != 'text'. + def send_interactive_message(phone_number, payload) + normalized_phone = normalize_phone(phone_number) + user_token = @whatsapp_channel.wuzapi_user_token + + case payload['type'].to_s + when 'quick_reply', 'buttons' + dispatch_buttons(user_token, normalized_phone, payload) + when 'url_button' + dispatch_url_button(user_token, normalized_phone, payload) + when 'list' + dispatch_list(user_token, normalized_phone, payload) + else + raise ArgumentError, "unsupported interactive type: #{payload['type'].inspect}" + end + end + def send_template(_phone_number, _template_info) # Placeholder for template support if Wuzapi supports it. # For now, just logging or no-op as per initial text-focused plan. @@ -104,6 +123,30 @@ class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService private + def dispatch_buttons(user_token, phone, payload) + buttons = Array(payload['buttons']).map { |b| { text: b['text'] || b[:text] } } + client.send_buttons(user_token, phone, payload['body'].to_s, buttons) + end + + def dispatch_url_button(user_token, phone, payload) + button = payload['button'] || {} + client.send_url_button( + user_token, phone, + text: payload['body'].to_s, + button_text: button['text'].to_s, + url: button['url'].to_s + ) + end + + def dispatch_list(user_token, phone, payload) + client.send_list( + user_token, phone, + text: payload['body'].to_s, + button_text: payload['button_text'].to_s, + sections: payload['sections'] || [] + ) + end + def normalize_phone(phone_number) phone_number.gsub(/[+\s\-()]/, '') end @@ -202,3 +245,4 @@ class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService target_message.outgoing? || target_message.template? end end +# rubocop:enable Metrics/ClassLength diff --git a/spec/services/whatsapp/providers/wuzapi_interactive_spec.rb b/spec/services/whatsapp/providers/wuzapi_interactive_spec.rb new file mode 100644 index 000000000..9855f9b8b --- /dev/null +++ b/spec/services/whatsapp/providers/wuzapi_interactive_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# rubocop:disable RSpec/SpecFilePathFormat +RSpec.describe Whatsapp::Providers::WuzapiService, '#send_interactive_message' do + let(:channel) do + create(:channel_whatsapp, provider: 'wuzapi', validate_provider_config: false, sync_templates: false, + provider_config: { 'wuzapi_base_url' => 'http://localhost:8080' }) + end + let(:service) { described_class.new(whatsapp_channel: channel) } + let(:phone) { '+5561999999999' } + let(:wuzapi_client) { instance_double(Wuzapi::Client) } + + before do + allow(Wuzapi::Client).to receive(:new).and_return(wuzapi_client) + allow(channel).to receive(:wuzapi_user_token).and_return('tok') + end + + describe '#send_interactive_message' do + it 'dispatches quick_reply buttons' do + payload = { 'type' => 'quick_reply', 'body' => 'Curtiu?', + 'buttons' => [{ 'id' => 'yes', 'text' => 'Sim' }] } + expect(wuzapi_client).to receive(:send_buttons) + .with('tok', '5561999999999', 'Curtiu?', [{ text: 'Sim' }]) + .and_return({ 'Id' => 'm-1' }) + + service.send_interactive_message(phone, payload) + end + + it 'dispatches url_button' do + payload = { 'type' => 'url_button', 'body' => 'Avalie', + 'button' => { 'text' => 'Abrir', 'url' => 'https://g.page/r/1' } } + expect(wuzapi_client).to receive(:send_url_button) + .with('tok', '5561999999999', text: 'Avalie', button_text: 'Abrir', url: 'https://g.page/r/1') + .and_return({ 'Id' => 'm-2' }) + + service.send_interactive_message(phone, payload) + end + + it 'dispatches list' do + payload = { 'type' => 'list', 'body' => 'Cardápio', 'button_text' => 'Ver', + 'sections' => [{ 'title' => 'Bebidas', 'rows' => [{ 'title' => 'Água', 'row_id' => 'a1' }] }] } + expect(wuzapi_client).to receive(:send_list).and_return({ 'Id' => 'm-3' }) + service.send_interactive_message(phone, payload) + end + + it 'raises for unknown type' do + expect { service.send_interactive_message(phone, 'type' => 'xyz') } + .to raise_error(ArgumentError, /unsupported interactive type/) + end + end +end +# rubocop:enable RSpec/SpecFilePathFormat