flow_chat 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +408 -102
- data/examples/media_prompts_examples.rb +28 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -8
- data/examples/whatsapp_controller.rb +1 -2
- data/examples/whatsapp_media_examples.rb +406 -0
- data/examples/whatsapp_message_job.rb +111 -0
- data/lib/flow_chat/base_processor.rb +7 -3
- data/lib/flow_chat/config.rb +36 -0
- data/lib/flow_chat/simulator/controller.rb +78 -0
- data/lib/flow_chat/simulator/views/simulator.html.erb +1707 -0
- data/lib/flow_chat/ussd/processor.rb +0 -1
- data/lib/flow_chat/ussd/prompt.rb +39 -5
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +7 -1
- data/lib/flow_chat/whatsapp/client.rb +439 -0
- data/lib/flow_chat/whatsapp/configuration.rb +41 -3
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +114 -114
- data/lib/flow_chat/whatsapp/processor.rb +0 -10
- data/lib/flow_chat/whatsapp/prompt.rb +118 -73
- data/lib/flow_chat/whatsapp/send_job_support.rb +79 -0
- metadata +8 -3
- data/lib/flow_chat/ussd/simulator/controller.rb +0 -51
- data/lib/flow_chat/ussd/simulator/views/simulator.html.erb +0 -239
@@ -0,0 +1,406 @@
|
|
1
|
+
# WhatsApp Media Messaging Examples
|
2
|
+
# This file demonstrates how to send different types of media via WhatsApp using FlowChat
|
3
|
+
|
4
|
+
# ============================================================================
|
5
|
+
# BASIC MEDIA SENDING (Out-of-Band Messaging)
|
6
|
+
# ============================================================================
|
7
|
+
|
8
|
+
# Initialize the WhatsApp client
|
9
|
+
config = FlowChat::Whatsapp::Configuration.from_credentials
|
10
|
+
client = FlowChat::Whatsapp::Client.new(config)
|
11
|
+
|
12
|
+
# Send an image from URL with caption
|
13
|
+
client.send_image("+1234567890", "https://example.com/images/product.jpg", "Check out this amazing photo!")
|
14
|
+
|
15
|
+
# Send a document from URL
|
16
|
+
client.send_document("+1234567890", "https://example.com/reports/monthly_report.pdf", "Here's the monthly report")
|
17
|
+
|
18
|
+
# Send an audio file from URL
|
19
|
+
client.send_audio("+1234567890", "https://example.com/audio/greeting.mp3")
|
20
|
+
|
21
|
+
# Send a video from URL with caption
|
22
|
+
client.send_video("+1234567890", "https://example.com/videos/demo.mp4", "Product demo video")
|
23
|
+
|
24
|
+
# Send a sticker from URL
|
25
|
+
client.send_sticker("+1234567890", "https://example.com/stickers/happy.webp")
|
26
|
+
|
27
|
+
# You can also still use existing WhatsApp media IDs
|
28
|
+
client.send_image("+1234567890", "1234567890", "Image from existing media ID")
|
29
|
+
|
30
|
+
# ============================================================================
|
31
|
+
# USING MEDIA IN FLOWS
|
32
|
+
# ============================================================================
|
33
|
+
|
34
|
+
class MediaSupportFlow < FlowChat::Flow
|
35
|
+
def main_page
|
36
|
+
# Handle incoming media from user
|
37
|
+
if app.media
|
38
|
+
handle_received_media
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
choice = app.screen(:main_menu) do |prompt|
|
43
|
+
prompt.select "Welcome! How can I help you?", {
|
44
|
+
"catalog" => "📷 View Product Catalog",
|
45
|
+
"report" => "📄 Get Report",
|
46
|
+
"support" => "🎵 Voice Support",
|
47
|
+
"feedback" => "📝 Send Feedback"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
case choice
|
52
|
+
when "catalog"
|
53
|
+
send_product_catalog
|
54
|
+
when "report"
|
55
|
+
send_report
|
56
|
+
when "support"
|
57
|
+
send_voice_message
|
58
|
+
when "feedback"
|
59
|
+
collect_feedback
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def handle_received_media
|
66
|
+
media_type = app.media['type']
|
67
|
+
media_id = app.media['id']
|
68
|
+
|
69
|
+
Rails.logger.info "Received #{media_type} from #{app.phone_number}: #{media_id}"
|
70
|
+
|
71
|
+
case media_type
|
72
|
+
when 'image'
|
73
|
+
app.say "Thanks for the image! I can see it's a #{media_type} file. Let me process it for you."
|
74
|
+
when 'document'
|
75
|
+
app.say "I've received your document. I'll review it and get back to you shortly."
|
76
|
+
when 'audio'
|
77
|
+
app.say "Got your voice message! I'll listen to it and respond appropriately."
|
78
|
+
when 'video'
|
79
|
+
app.say "Thanks for the video! I'll analyze it and provide feedback."
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def send_product_catalog
|
84
|
+
# Send multiple product images from URLs
|
85
|
+
client = get_whatsapp_client
|
86
|
+
|
87
|
+
app.say "Here's our latest product catalog:"
|
88
|
+
|
89
|
+
# Product images stored in cloud storage (CDN, S3, etc.)
|
90
|
+
product_images = [
|
91
|
+
"https://cdn.example.com/products/product1.jpg",
|
92
|
+
"https://cdn.example.com/products/product2.jpg",
|
93
|
+
"https://cdn.example.com/products/product3.jpg"
|
94
|
+
]
|
95
|
+
|
96
|
+
product_images.each_with_index do |image_url, index|
|
97
|
+
client.send_image(app.phone_number, image_url, "Product #{index + 1}")
|
98
|
+
sleep(0.5) # Small delay to avoid rate limiting
|
99
|
+
end
|
100
|
+
|
101
|
+
app.say "Which product interests you the most?"
|
102
|
+
end
|
103
|
+
|
104
|
+
def send_report
|
105
|
+
# Send a PDF report from cloud storage
|
106
|
+
report_url = generate_report_url # Your method to generate/get report URL
|
107
|
+
|
108
|
+
if report_url
|
109
|
+
client = get_whatsapp_client
|
110
|
+
client.send_document(app.phone_number, report_url, "Your monthly report is ready!")
|
111
|
+
|
112
|
+
app.say "📊 Report sent! Please check the document above."
|
113
|
+
else
|
114
|
+
app.say "Sorry, I couldn't generate the report right now. Please try again later."
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def send_voice_message
|
119
|
+
# Send a pre-recorded voice message from cloud storage
|
120
|
+
audio_url = "https://cdn.example.com/audio/support_greeting.mp3"
|
121
|
+
|
122
|
+
client = get_whatsapp_client
|
123
|
+
client.send_audio(app.phone_number, audio_url)
|
124
|
+
|
125
|
+
app.say "🎵 Please listen to the voice message above. You can also send me a voice message with your question!"
|
126
|
+
end
|
127
|
+
|
128
|
+
def collect_feedback
|
129
|
+
feedback = app.screen(:feedback_text) do |prompt|
|
130
|
+
prompt.ask "Please share your feedback. You can also send images or documents if needed:"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Save feedback to database
|
134
|
+
save_feedback(feedback, app.phone_number)
|
135
|
+
|
136
|
+
# Send a thank you sticker from cloud storage
|
137
|
+
sticker_url = "https://cdn.example.com/stickers/thanks.webp"
|
138
|
+
client = get_whatsapp_client
|
139
|
+
client.send_sticker(app.phone_number, sticker_url)
|
140
|
+
|
141
|
+
app.say "Thank you for your feedback! We really appreciate it. 🙏"
|
142
|
+
end
|
143
|
+
|
144
|
+
def get_whatsapp_client
|
145
|
+
config = FlowChat::Whatsapp::Configuration.from_credentials
|
146
|
+
FlowChat::Whatsapp::Client.new(config)
|
147
|
+
end
|
148
|
+
|
149
|
+
def generate_report_url
|
150
|
+
# Your report generation logic here
|
151
|
+
# This could return a signed URL from S3, Google Cloud Storage, etc.
|
152
|
+
"https://storage.example.com/reports/monthly_report_#{Time.current.strftime('%Y%m')}.pdf"
|
153
|
+
end
|
154
|
+
|
155
|
+
def save_feedback(feedback, phone_number)
|
156
|
+
# Your feedback saving logic here
|
157
|
+
Rails.logger.info "Feedback from #{phone_number}: #{feedback}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# ============================================================================
|
162
|
+
# ADVANCED MEDIA SERVICE CLASS
|
163
|
+
# ============================================================================
|
164
|
+
|
165
|
+
class WhatsAppMediaService
|
166
|
+
def initialize
|
167
|
+
@config = FlowChat::Whatsapp::Configuration.from_credentials
|
168
|
+
@client = FlowChat::Whatsapp::Client.new(@config)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Send welcome package with multiple media types from URLs
|
172
|
+
def send_welcome_package(phone_number, user_name)
|
173
|
+
# Welcome image from CDN
|
174
|
+
welcome_image_url = "https://cdn.example.com/welcome/banner.jpg"
|
175
|
+
@client.send_image(phone_number, welcome_image_url, "Welcome to our service, #{user_name}! 🎉")
|
176
|
+
|
177
|
+
# Welcome guide document from cloud storage
|
178
|
+
guide_url = "https://storage.example.com/guides/user_guide.pdf"
|
179
|
+
@client.send_document(phone_number, guide_url, "Here's your user guide")
|
180
|
+
|
181
|
+
# Welcome audio message from media server
|
182
|
+
audio_url = "https://media.example.com/audio/welcome.mp3"
|
183
|
+
@client.send_audio(phone_number, audio_url)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Send order confirmation with invoice from cloud storage
|
187
|
+
def send_order_confirmation(phone_number, order_id, invoice_url)
|
188
|
+
# Send invoice document from cloud storage
|
189
|
+
@client.send_document(
|
190
|
+
phone_number,
|
191
|
+
invoice_url,
|
192
|
+
"Order ##{order_id} confirmed! Here's your invoice.",
|
193
|
+
"Invoice_#{order_id}.pdf"
|
194
|
+
)
|
195
|
+
|
196
|
+
# Send confirmation buttons
|
197
|
+
@client.send_buttons(
|
198
|
+
phone_number,
|
199
|
+
"Your order has been confirmed! 🛍️",
|
200
|
+
[
|
201
|
+
{ id: 'track_order', title: '📦 Track Order' },
|
202
|
+
{ id: 'modify_order', title: '✏️ Modify Order' },
|
203
|
+
{ id: 'support', title: '💬 Contact Support' }
|
204
|
+
]
|
205
|
+
)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Send promotional content from CDN
|
209
|
+
def send_promotion(phone_number, promo_image_url, promo_video_url = nil)
|
210
|
+
# Send promotional image from CDN
|
211
|
+
@client.send_image(phone_number, promo_image_url, "🔥 Special offer just for you!")
|
212
|
+
|
213
|
+
# Optionally send promotional video from video server
|
214
|
+
if promo_video_url
|
215
|
+
@client.send_video(phone_number, promo_video_url, "Watch this exciting video!")
|
216
|
+
end
|
217
|
+
|
218
|
+
# Follow up with action buttons
|
219
|
+
@client.send_buttons(
|
220
|
+
phone_number,
|
221
|
+
"Don't miss out on this amazing deal!",
|
222
|
+
[
|
223
|
+
{ id: 'buy_now', title: '🛒 Buy Now' },
|
224
|
+
{ id: 'more_info', title: 'ℹ️ More Info' },
|
225
|
+
{ id: 'remind_later', title: '⏰ Remind Later' }
|
226
|
+
]
|
227
|
+
)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Handle media uploads with processing
|
231
|
+
def process_uploaded_media(media_id, media_type, user_phone)
|
232
|
+
begin
|
233
|
+
# Download the media from WhatsApp
|
234
|
+
media_url = @client.get_media_url(media_id)
|
235
|
+
media_content = @client.download_media(media_id) if media_url
|
236
|
+
|
237
|
+
if media_content
|
238
|
+
# Upload to your cloud storage (S3, Google Cloud, etc.)
|
239
|
+
cloud_url = upload_to_cloud_storage(media_content, media_type, media_id)
|
240
|
+
|
241
|
+
# Process based on media type
|
242
|
+
case media_type
|
243
|
+
when 'image'
|
244
|
+
process_image(cloud_url, user_phone)
|
245
|
+
when 'document'
|
246
|
+
process_document(cloud_url, user_phone)
|
247
|
+
when 'audio'
|
248
|
+
process_audio(cloud_url, user_phone)
|
249
|
+
when 'video'
|
250
|
+
process_video(cloud_url, user_phone)
|
251
|
+
end
|
252
|
+
|
253
|
+
Rails.logger.info "Successfully processed #{media_type} from #{user_phone}"
|
254
|
+
end
|
255
|
+
rescue => e
|
256
|
+
Rails.logger.error "Error processing media: #{e.message}"
|
257
|
+
@client.send_text(user_phone, "Sorry, I couldn't process your file. Please try again.")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Send personalized content based on user data
|
262
|
+
def send_personalized_content(phone_number, user_id)
|
263
|
+
# Get user's preferred content from your system
|
264
|
+
user_content = fetch_user_content(user_id)
|
265
|
+
|
266
|
+
# Send personalized image
|
267
|
+
if user_content[:image_url]
|
268
|
+
@client.send_image(phone_number, user_content[:image_url], user_content[:image_caption])
|
269
|
+
end
|
270
|
+
|
271
|
+
# Send personalized document
|
272
|
+
if user_content[:document_url]
|
273
|
+
@client.send_document(phone_number, user_content[:document_url], user_content[:document_description])
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Send real-time generated content
|
278
|
+
def send_qr_code(phone_number, data)
|
279
|
+
# Generate QR code and get URL (using your QR service)
|
280
|
+
qr_url = generate_qr_code_url(data)
|
281
|
+
|
282
|
+
@client.send_image(phone_number, qr_url, "Here's your QR code!")
|
283
|
+
end
|
284
|
+
|
285
|
+
# Send chart/graph from analytics service
|
286
|
+
def send_analytics_chart(phone_number, chart_type, period)
|
287
|
+
# Generate chart URL from your analytics service
|
288
|
+
chart_url = generate_analytics_chart_url(chart_type, period)
|
289
|
+
|
290
|
+
@client.send_image(phone_number, chart_url, "#{chart_type.humanize} for #{period}")
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
def upload_to_cloud_storage(content, media_type, media_id)
|
296
|
+
# Your cloud storage upload logic here
|
297
|
+
# Return the public URL of the uploaded file
|
298
|
+
"https://storage.example.com/uploads/#{media_id}.#{get_file_extension(media_type)}"
|
299
|
+
end
|
300
|
+
|
301
|
+
def process_image(cloud_url, user_phone)
|
302
|
+
# Your image processing logic here
|
303
|
+
@client.send_text(user_phone, "Thanks for the image! I've processed it successfully. ✅")
|
304
|
+
end
|
305
|
+
|
306
|
+
def process_document(cloud_url, user_phone)
|
307
|
+
# Your document processing logic here
|
308
|
+
@client.send_text(user_phone, "Document received and processed! 📄")
|
309
|
+
end
|
310
|
+
|
311
|
+
def process_audio(cloud_url, user_phone)
|
312
|
+
# Your audio processing logic here
|
313
|
+
@client.send_text(user_phone, "Audio message processed! 🎵")
|
314
|
+
end
|
315
|
+
|
316
|
+
def process_video(cloud_url, user_phone)
|
317
|
+
# Your video processing logic here
|
318
|
+
@client.send_text(user_phone, "Video processed successfully! 🎥")
|
319
|
+
end
|
320
|
+
|
321
|
+
def fetch_user_content(user_id)
|
322
|
+
# Fetch personalized content URLs from your database
|
323
|
+
{
|
324
|
+
image_url: "https://cdn.example.com/personal/#{user_id}/welcome.jpg",
|
325
|
+
image_caption: "Your personalized welcome image!",
|
326
|
+
document_url: "https://storage.example.com/personal/#{user_id}/guide.pdf",
|
327
|
+
document_description: "Your personalized guide"
|
328
|
+
}
|
329
|
+
end
|
330
|
+
|
331
|
+
def generate_qr_code_url(data)
|
332
|
+
# Generate QR code URL using your service (or external API like QR Server)
|
333
|
+
"https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=#{CGI.escape(data)}"
|
334
|
+
end
|
335
|
+
|
336
|
+
def generate_analytics_chart_url(chart_type, period)
|
337
|
+
# Generate chart URL from your analytics service
|
338
|
+
"https://charts.example.com/api/generate?type=#{chart_type}&period=#{period}"
|
339
|
+
end
|
340
|
+
|
341
|
+
def get_file_extension(media_type)
|
342
|
+
case media_type
|
343
|
+
when 'image' then 'jpg'
|
344
|
+
when 'document' then 'pdf'
|
345
|
+
when 'audio' then 'mp3'
|
346
|
+
when 'video' then 'mp4'
|
347
|
+
else 'bin'
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# ============================================================================
|
353
|
+
# USAGE IN CONTROLLERS
|
354
|
+
# ============================================================================
|
355
|
+
|
356
|
+
class NotificationController < ApplicationController
|
357
|
+
def send_media_notification
|
358
|
+
service = WhatsAppMediaService.new
|
359
|
+
|
360
|
+
# Send welcome package to new users
|
361
|
+
service.send_welcome_package(params[:phone_number], params[:user_name])
|
362
|
+
|
363
|
+
render json: { status: 'sent' }
|
364
|
+
end
|
365
|
+
|
366
|
+
def send_order_confirmation
|
367
|
+
service = WhatsAppMediaService.new
|
368
|
+
|
369
|
+
# Get invoice URL from your system (could be from S3, Google Cloud, etc.)
|
370
|
+
invoice_url = get_invoice_url(params[:order_id])
|
371
|
+
|
372
|
+
service.send_order_confirmation(
|
373
|
+
params[:phone_number],
|
374
|
+
params[:order_id],
|
375
|
+
invoice_url
|
376
|
+
)
|
377
|
+
|
378
|
+
render json: { status: 'sent' }
|
379
|
+
end
|
380
|
+
|
381
|
+
def send_promo
|
382
|
+
service = WhatsAppMediaService.new
|
383
|
+
|
384
|
+
# Promotional content from CDN
|
385
|
+
promo_image = "https://cdn.example.com/promos/#{params[:promo_id]}/banner.jpg"
|
386
|
+
promo_video = "https://cdn.example.com/promos/#{params[:promo_id]}/video.mp4"
|
387
|
+
|
388
|
+
service.send_promotion(params[:phone_number], promo_image, promo_video)
|
389
|
+
|
390
|
+
render json: { status: 'sent' }
|
391
|
+
end
|
392
|
+
|
393
|
+
def send_qr_code
|
394
|
+
service = WhatsAppMediaService.new
|
395
|
+
service.send_qr_code(params[:phone_number], params[:qr_data])
|
396
|
+
|
397
|
+
render json: { status: 'sent' }
|
398
|
+
end
|
399
|
+
|
400
|
+
private
|
401
|
+
|
402
|
+
def get_invoice_url(order_id)
|
403
|
+
# Your logic to get invoice URL from cloud storage
|
404
|
+
"https://storage.example.com/invoices/#{order_id}.pdf"
|
405
|
+
end
|
406
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# Example Background Jobs for WhatsApp Response Delivery
|
2
|
+
# Add these to your Rails application
|
3
|
+
|
4
|
+
# Example: Basic WhatsApp Send Job
|
5
|
+
# Only handles sending responses - flows are processed synchronously in the controller
|
6
|
+
class WhatsappMessageJob < ApplicationJob
|
7
|
+
include FlowChat::Whatsapp::SendJobSupport
|
8
|
+
|
9
|
+
def perform(send_data)
|
10
|
+
perform_whatsapp_send(send_data)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Example: Advanced WhatsApp Send Job with custom callbacks
|
15
|
+
class AdvancedWhatsappMessageJob < ApplicationJob
|
16
|
+
include FlowChat::Whatsapp::SendJobSupport
|
17
|
+
|
18
|
+
def perform(send_data)
|
19
|
+
perform_whatsapp_send(send_data)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Override for custom success handling
|
25
|
+
def on_whatsapp_send_success(send_data, result)
|
26
|
+
Rails.logger.info "Successfully sent WhatsApp message to #{send_data[:msisdn]}"
|
27
|
+
UserEngagementTracker.track_message_sent(phone: send_data[:msisdn])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Override for custom error handling
|
31
|
+
def on_whatsapp_send_error(error, send_data)
|
32
|
+
ErrorTracker.notify(error, user_phone: send_data[:msisdn])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Example: Priority send job for urgent messages
|
37
|
+
class UrgentWhatsappSendJob < ApplicationJob
|
38
|
+
include FlowChat::Whatsapp::SendJobSupport
|
39
|
+
|
40
|
+
queue_as :urgent_whatsapp # Different queue for priority
|
41
|
+
retry_on StandardError, wait: 1.second, attempts: 5 # Override retry policy
|
42
|
+
|
43
|
+
def perform(send_data)
|
44
|
+
perform_whatsapp_send(send_data)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Override error handling for urgent messages
|
50
|
+
def handle_whatsapp_send_error(error, send_data, config = nil)
|
51
|
+
# Immediately escalate urgent message failures
|
52
|
+
AlertingService.send_urgent_alert(
|
53
|
+
"Urgent WhatsApp send job failed",
|
54
|
+
error: error.message,
|
55
|
+
user: send_data[:msisdn]
|
56
|
+
)
|
57
|
+
|
58
|
+
# Still send user notification
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Example: Multi-tenant send job
|
64
|
+
class MultiTenantWhatsappSendJob < ApplicationJob
|
65
|
+
include FlowChat::Whatsapp::SendJobSupport
|
66
|
+
|
67
|
+
def perform(send_data)
|
68
|
+
perform_whatsapp_send(send_data)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Override config resolution for tenant-specific configs
|
74
|
+
def resolve_whatsapp_config(send_data)
|
75
|
+
# Try tenant-specific config first
|
76
|
+
tenant_name = extract_tenant_from_phone(send_data[:msisdn])
|
77
|
+
if tenant_name && FlowChat::Whatsapp::Configuration.exists?(tenant_name)
|
78
|
+
return FlowChat::Whatsapp::Configuration.get(tenant_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Fallback to default resolution
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_tenant_from_phone(phone)
|
86
|
+
# Extract tenant from phone number prefix or other identifier
|
87
|
+
case phone
|
88
|
+
when /^\+1800/
|
89
|
+
:enterprise
|
90
|
+
when /^\+1888/
|
91
|
+
:premium
|
92
|
+
else
|
93
|
+
:standard
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Usage in Rails configuration
|
99
|
+
#
|
100
|
+
# Add to config/application.rb:
|
101
|
+
# config.active_job.queue_adapter = :sidekiq
|
102
|
+
#
|
103
|
+
# Add to config/initializers/flowchat.rb:
|
104
|
+
# FlowChat::Config.whatsapp.message_handling_mode = :background
|
105
|
+
# FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
|
106
|
+
#
|
107
|
+
# How it works:
|
108
|
+
# 1. Controller receives WhatsApp webhook
|
109
|
+
# 2. Flow is processed synchronously (maintains controller context)
|
110
|
+
# 3. Response is queued for async delivery via background job
|
111
|
+
# 4. Job only handles sending the response, not processing flows
|
@@ -2,7 +2,7 @@ require "middleware"
|
|
2
2
|
|
3
3
|
module FlowChat
|
4
4
|
class BaseProcessor
|
5
|
-
attr_reader :middleware
|
5
|
+
attr_reader :middleware
|
6
6
|
|
7
7
|
def initialize(controller)
|
8
8
|
@context = FlowChat::Context.new
|
@@ -12,8 +12,9 @@ module FlowChat
|
|
12
12
|
yield self if block_given?
|
13
13
|
end
|
14
14
|
|
15
|
-
def use_gateway(
|
16
|
-
@
|
15
|
+
def use_gateway(gateway_class, *args)
|
16
|
+
@gateway_class = gateway_class
|
17
|
+
@gateway_args = args
|
17
18
|
self
|
18
19
|
end
|
19
20
|
|
@@ -51,7 +52,10 @@ module FlowChat
|
|
51
52
|
|
52
53
|
# Helper method for building stacks
|
53
54
|
def create_middleware_stack(name)
|
55
|
+
raise ArgumentError, "Gateway is required. Call use_gateway(gateway_class, *args) before running." unless @gateway_class
|
56
|
+
|
54
57
|
::Middleware::Builder.new(name: name) do |b|
|
58
|
+
b.use @gateway_class, *@gateway_args
|
55
59
|
configure_middleware_stack(b)
|
56
60
|
end.inject_logger(Rails.logger)
|
57
61
|
end
|
data/lib/flow_chat/config.rb
CHANGED
@@ -9,6 +9,11 @@ module FlowChat
|
|
9
9
|
@ussd ||= UssdConfig.new
|
10
10
|
end
|
11
11
|
|
12
|
+
# WhatsApp-specific configuration object
|
13
|
+
def self.whatsapp
|
14
|
+
@whatsapp ||= WhatsappConfig.new
|
15
|
+
end
|
16
|
+
|
12
17
|
class UssdConfig
|
13
18
|
attr_accessor :pagination_page_size, :pagination_back_option, :pagination_back_text,
|
14
19
|
:pagination_next_option, :pagination_next_text,
|
@@ -25,5 +30,36 @@ module FlowChat
|
|
25
30
|
@resumable_sessions_timeout_seconds = 300
|
26
31
|
end
|
27
32
|
end
|
33
|
+
|
34
|
+
class WhatsappConfig
|
35
|
+
attr_accessor :message_handling_mode, :background_job_class
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@message_handling_mode = :inline
|
39
|
+
@background_job_class = 'WhatsappMessageJob'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Validate message handling mode
|
43
|
+
def message_handling_mode=(mode)
|
44
|
+
valid_modes = [:inline, :background, :simulator]
|
45
|
+
unless valid_modes.include?(mode.to_sym)
|
46
|
+
raise ArgumentError, "Invalid message handling mode: #{mode}. Valid modes: #{valid_modes.join(', ')}"
|
47
|
+
end
|
48
|
+
@message_handling_mode = mode.to_sym
|
49
|
+
end
|
50
|
+
|
51
|
+
# Helper methods for mode checking
|
52
|
+
def inline_mode?
|
53
|
+
@message_handling_mode == :inline
|
54
|
+
end
|
55
|
+
|
56
|
+
def background_mode?
|
57
|
+
@message_handling_mode == :background
|
58
|
+
end
|
59
|
+
|
60
|
+
def simulator_mode?
|
61
|
+
@message_handling_mode == :simulator
|
62
|
+
end
|
63
|
+
end
|
28
64
|
end
|
29
65
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module FlowChat
|
2
|
+
module Simulator
|
3
|
+
module Controller
|
4
|
+
def flowchat_simulator
|
5
|
+
respond_to do |format|
|
6
|
+
format.html do
|
7
|
+
render inline: simulator_view_template, layout: false, locals: simulator_locals
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def default_phone_number
|
15
|
+
"+233244123456"
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_contact_name
|
19
|
+
"John Doe"
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_config_key
|
23
|
+
"ussd"
|
24
|
+
end
|
25
|
+
|
26
|
+
def simulator_configurations
|
27
|
+
{
|
28
|
+
"ussd" => {
|
29
|
+
name: "USSD (Nalo)",
|
30
|
+
description: "Local development USSD testing",
|
31
|
+
processor_type: "ussd",
|
32
|
+
provider: "nalo",
|
33
|
+
endpoint: "/ussd",
|
34
|
+
icon: "📱",
|
35
|
+
color: "#28a745",
|
36
|
+
settings: {
|
37
|
+
phone_number: default_phone_number,
|
38
|
+
session_timeout: 300
|
39
|
+
}
|
40
|
+
},
|
41
|
+
"whatsapp" => {
|
42
|
+
name: "WhatsApp",
|
43
|
+
description: "Local development WhatsApp testing",
|
44
|
+
processor_type: "whatsapp",
|
45
|
+
provider: "cloud_api",
|
46
|
+
endpoint: "/whatsapp/webhook",
|
47
|
+
icon: "💬",
|
48
|
+
color: "#25D366",
|
49
|
+
settings: {
|
50
|
+
phone_number: default_phone_number,
|
51
|
+
contact_name: default_contact_name,
|
52
|
+
verify_token: "local_verify_token",
|
53
|
+
webhook_url: "http://localhost:3000/whatsapp/webhook"
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def simulator_view_template
|
60
|
+
File.read simulator_view_path
|
61
|
+
end
|
62
|
+
|
63
|
+
def simulator_view_path
|
64
|
+
File.join FlowChat.root.join("flow_chat", "simulator", "views", "simulator.html.erb")
|
65
|
+
end
|
66
|
+
|
67
|
+
def simulator_locals
|
68
|
+
{
|
69
|
+
pagesize: FlowChat::Config.ussd.pagination_page_size,
|
70
|
+
default_phone_number: default_phone_number,
|
71
|
+
default_contact_name: default_contact_name,
|
72
|
+
default_config_key: default_config_key,
|
73
|
+
configurations: simulator_configurations
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|