flow_chat 0.2.1 โ†’ 0.4.0

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.
data/README.md CHANGED
@@ -1,85 +1,702 @@
1
1
  # FlowChat
2
2
 
3
- FlowChat is a Rails framework designed for crafting Menu-based conversation workflows, such as those used in USSD systems. It introduces an intuitive approach to defining conversation flows in Ruby, facilitating clear and logical flow development. Currently supporting USSD with plans to extend functionality to WhatsApp and Telegram, FlowChat makes multi-channel user interaction seamless and efficient.
3
+ FlowChat is a Rails framework designed for building sophisticated conversational workflows for both USSD (Unstructured Supplementary Service Data) systems and WhatsApp messaging. It provides an intuitive Ruby DSL for creating multi-step, menu-driven conversations with automatic session management, input validation, and flow control.
4
4
 
5
- The framework's architecture leverages a middleware processing pipeline, offering flexibility in customizing the conversation handling process.
5
+ **Key Features:**
6
+ - ๐ŸŽฏ **Declarative Flow Definition** - Define conversation flows as Ruby classes
7
+ - ๐Ÿ”„ **Automatic Session Management** - Persistent state across requests
8
+ - โœ… **Input Validation & Transformation** - Built-in validation and data conversion
9
+ - ๐ŸŒŠ **Middleware Architecture** - Flexible request processing pipeline
10
+ - ๐Ÿ“ฑ **USSD Gateway Support** - Currently supports Nalo gateways
11
+ - ๐Ÿ’ฌ **WhatsApp Integration** - Full WhatsApp Cloud API support with interactive messages
12
+ - ๐Ÿงช **Built-in Testing Tools** - USSD simulator for local development
6
13
 
7
- ## Getting Started
14
+ ## Architecture Overview
8
15
 
9
- ### Installation
16
+ FlowChat uses a **request-per-interaction** model where each user input creates a new request. The framework maintains conversation state through session storage while processing each interaction through a middleware pipeline.
10
17
 
11
- Incorporate FlowChat into your Rails project by adding the following line to your Gemfile:
18
+ ```
19
+ User Input โ†’ Gateway โ†’ Session โ†’ Pagination โ†’ Custom โ†’ Executor โ†’ Flow โ†’ Response
20
+ โ†“
21
+ Session Storage
22
+ ```
23
+
24
+ **Middleware Pipeline:**
25
+ - **Gateway**: Communication with providers (USSD: Nalo, WhatsApp: Cloud API)
26
+ - **Session**: Load/save conversation state
27
+ - **Pagination**: Split long responses into pages (USSD only)
28
+ - **Custom**: Your application middleware (logging, auth, etc.)
29
+ - **Executor**: Execute flow methods and handle interrupts
30
+
31
+ ## Installation
32
+
33
+ Add FlowChat to your Rails application's Gemfile:
12
34
 
13
35
  ```ruby
14
- gem 'flow_chat', '~> 0.2.0'
36
+ gem 'flow_chat'
15
37
  ```
16
38
 
17
- Then, execute:
39
+ Then execute:
18
40
 
19
41
  ```bash
20
42
  bundle install
21
43
  ```
22
44
 
23
- Alternatively, you can install it directly using:
45
+ ## Quick Start
46
+
47
+ FlowChat supports both USSD and WhatsApp. Choose the platform that fits your needs:
48
+
49
+ ### USSD Setup
50
+
51
+ ### 1. Create Your First Flow
52
+
53
+ Create a flow class in `app/flow_chat/welcome_flow.rb`:
54
+
55
+ ```ruby
56
+ class WelcomeFlow < FlowChat::Flow
57
+ def main_page
58
+ name = app.screen(:name) do |prompt|
59
+ prompt.ask "Welcome! What's your name?",
60
+ transform: ->(input) { input.strip.titleize }
61
+ end
62
+
63
+ app.say "Hello, #{name}! Welcome to FlowChat."
64
+ end
65
+ end
66
+ ```
67
+
68
+ ### 2. Set Up the USSD Controller
69
+
70
+ Create a controller to handle USSD requests:
71
+
72
+ ```ruby
73
+ class UssdController < ApplicationController
74
+ skip_forgery_protection
75
+
76
+ def process_request
77
+ processor = FlowChat::Ussd::Processor.new(self) do |config|
78
+ config.use_gateway FlowChat::Ussd::Gateway::Nalo
79
+ config.use_session_store FlowChat::Session::RailsSessionStore
80
+ end
81
+
82
+ processor.run WelcomeFlow, :main_page
83
+ end
84
+ end
85
+ ```
86
+
87
+ ### 3. Configure Routes
88
+
89
+ Add the route to `config/routes.rb`:
90
+
91
+ ```ruby
92
+ Rails.application.routes.draw do
93
+ post 'ussd' => 'ussd#process_request'
94
+ end
95
+ ```
96
+
97
+ ๐Ÿ’ก **Tip**: See [examples/ussd_controller.rb](examples/ussd_controller.rb) for a complete USSD controller example with payment flows, customer support, and custom middleware.
98
+
99
+ ### WhatsApp Setup
100
+
101
+ ### 1. Configure WhatsApp Credentials
102
+
103
+ FlowChat supports two ways to configure WhatsApp credentials:
104
+
105
+ **Option A: Using Rails Credentials**
106
+
107
+ Add your WhatsApp credentials to Rails credentials:
108
+
109
+ ```bash
110
+ rails credentials:edit
111
+ ```
112
+
113
+ ```yaml
114
+ whatsapp:
115
+ access_token: "your_access_token"
116
+ phone_number_id: "your_phone_number_id"
117
+ verify_token: "your_verify_token"
118
+ app_id: "your_app_id"
119
+ app_secret: "your_app_secret"
120
+ webhook_url: "your_webhook_url"
121
+ business_account_id: "your_business_account_id"
122
+ ```
123
+
124
+ **Option B: Using Environment Variables**
125
+
126
+ Alternatively, you can use environment variables:
24
127
 
25
128
  ```bash
26
- gem install flow_chat
129
+ # Add to your .env file or environment
130
+ export WHATSAPP_ACCESS_TOKEN="your_access_token"
131
+ export WHATSAPP_PHONE_NUMBER_ID="your_phone_number_id"
132
+ export WHATSAPP_VERIFY_TOKEN="your_verify_token"
133
+ export WHATSAPP_APP_ID="your_app_id"
134
+ export WHATSAPP_APP_SECRET="your_app_secret"
135
+ export WHATSAPP_WEBHOOK_URL="your_webhook_url"
136
+ export WHATSAPP_BUSINESS_ACCOUNT_ID="your_business_account_id"
27
137
  ```
28
138
 
29
- ### Basic Usage
139
+ FlowChat will automatically use Rails credentials first, falling back to environment variables if credentials are not available.
30
140
 
31
- #### Building Your First Flow
141
+ **Option C: Per-Setup Configuration**
32
142
 
33
- Create a new class derived from `FlowChat::Flow` to define your conversation flow. It's recommended to place your flow definitions under `app/flow_chat`.
143
+ For multi-tenant applications or when you need different WhatsApp accounts per endpoint:
34
144
 
35
- For a simple "Hello World" flow:
145
+ ```ruby
146
+ # Create custom configuration
147
+ custom_config = FlowChat::Whatsapp::Configuration.new
148
+ custom_config.access_token = "your_specific_access_token"
149
+ custom_config.phone_number_id = "your_specific_phone_number_id"
150
+ custom_config.verify_token = "your_specific_verify_token"
151
+ custom_config.app_id = "your_specific_app_id"
152
+ custom_config.app_secret = "your_specific_app_secret"
153
+ custom_config.business_account_id = "your_specific_business_account_id"
154
+
155
+ # Use in processor
156
+ processor = FlowChat::Whatsapp::Processor.new(self) do |config|
157
+ config.use_whatsapp_config(custom_config) # Pass custom config
158
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
159
+ config.use_session_store FlowChat::Session::CacheSessionStore
160
+ end
161
+ ```
162
+
163
+ ๐Ÿ’ก **Tip**: See [examples/multi_tenant_whatsapp_controller.rb](examples/multi_tenant_whatsapp_controller.rb) for comprehensive multi-tenant and per-setup configuration examples.
164
+
165
+ ### 2. Create WhatsApp Controller
36
166
 
37
167
  ```ruby
38
- class HelloWorldFlow < FlowChat::Flow
168
+ class WhatsappController < ApplicationController
169
+ skip_forgery_protection
170
+
171
+ def webhook
172
+ processor = FlowChat::Whatsapp::Processor.new(self) do |config|
173
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
174
+ config.use_session_store FlowChat::Session::CacheSessionStore
175
+ end
176
+
177
+ processor.run WelcomeFlow, :main_page
178
+ end
179
+ end
180
+ ```
181
+
182
+ ### 3. Add WhatsApp Route
183
+
184
+ ```ruby
185
+ Rails.application.routes.draw do
186
+ match '/whatsapp/webhook', to: 'whatsapp#webhook', via: [:get, :post]
187
+ end
188
+ ```
189
+
190
+ ### 4. Enhanced Features for WhatsApp
191
+
192
+ The same flow works for both USSD and WhatsApp, but WhatsApp provides additional data and better interactive features:
193
+
194
+ ```ruby
195
+ class WelcomeFlow < FlowChat::Flow
39
196
  def main_page
40
- app.say "Hello World!"
197
+ # Access WhatsApp-specific data
198
+ Rails.logger.info "Contact: #{app.contact_name}, Phone: #{app.phone_number}"
199
+ Rails.logger.info "Message ID: #{app.message_id}, Timestamp: #{app.timestamp}"
200
+
201
+ # Handle location sharing
202
+ if app.location
203
+ app.say "Thanks for sharing your location! We see you're at #{app.location['latitude']}, #{app.location['longitude']}"
204
+ return
205
+ end
206
+
207
+ # Handle media messages
208
+ if app.media
209
+ app.say "Thanks for the #{app.media['type']} file! We received: #{app.media['id']}"
210
+ return
211
+ end
212
+
213
+ name = app.screen(:name) do |prompt|
214
+ prompt.ask "Hello! Welcome to our WhatsApp service. What's your name?",
215
+ transform: ->(input) { input.strip.titleize }
216
+ end
217
+
218
+ # WhatsApp supports interactive buttons and lists via prompt.select
219
+ choice = app.screen(:main_menu) do |prompt|
220
+ prompt.select "Hi #{name}! How can I help you?", {
221
+ "info" => "๐Ÿ“‹ Get Information",
222
+ "support" => "๐Ÿ†˜ Contact Support",
223
+ "feedback" => "๐Ÿ’ฌ Give Feedback"
224
+ }
225
+ end
226
+
227
+ case choice
228
+ when "info"
229
+ show_information_menu
230
+ when "support"
231
+ contact_support
232
+ when "feedback"
233
+ collect_feedback
234
+ end
235
+ end
236
+
237
+ private
238
+
239
+ def show_information_menu
240
+ info_choice = app.screen(:info_menu) do |prompt|
241
+ prompt.select "What information do you need?", {
242
+ "hours" => "๐Ÿ•’ Business Hours",
243
+ "location" => "๐Ÿ“ Our Location",
244
+ "services" => "๐Ÿ’ผ Our Services"
245
+ }
246
+ end
247
+
248
+ case info_choice
249
+ when "hours"
250
+ app.say "We're open Monday-Friday 9AM-6PM, Saturday 10AM-4PM. Closed Sundays."
251
+ when "location"
252
+ app.say "๐Ÿ“ Visit us at 123 Main Street, Downtown. We're next to the coffee shop!"
253
+ when "services"
254
+ app.say "๐Ÿ’ผ We offer: Web Development, Mobile Apps, Cloud Services, and IT Consulting."
255
+ end
256
+ end
257
+
258
+ def contact_support
259
+ support_choice = app.screen(:support_menu) do |prompt|
260
+ prompt.select "How would you like to contact support?", {
261
+ "call" => "๐Ÿ“ž Call Us",
262
+ "email" => "๐Ÿ“ง Email Us",
263
+ "chat" => "๐Ÿ’ฌ Continue Here"
264
+ }
265
+ end
266
+
267
+ case support_choice
268
+ when "call"
269
+ app.say "๐Ÿ“ž Call us at: +1-555-HELP (4357)\nAvailable Mon-Fri 9AM-5PM"
270
+ when "email"
271
+ app.say "๐Ÿ“ง Email us at: support@company.com\nWe typically respond within 24 hours"
272
+ when "chat"
273
+ app.say "๐Ÿ’ฌ Great! Please describe your issue and we'll help you right away."
274
+ end
275
+ end
276
+
277
+ def collect_feedback
278
+ rating = app.screen(:rating) do |prompt|
279
+ prompt.select "How would you rate our service?", {
280
+ "5" => "โญโญโญโญโญ Excellent",
281
+ "4" => "โญโญโญโญ Good",
282
+ "3" => "โญโญโญ Average",
283
+ "2" => "โญโญ Poor",
284
+ "1" => "โญ Very Poor"
285
+ }
286
+ end
287
+
288
+ feedback = app.screen(:feedback_text) do |prompt|
289
+ prompt.ask "Thank you for the #{rating}-star rating! Please share any additional feedback:"
290
+ end
291
+
292
+ # Use WhatsApp-specific data for logging
293
+ Rails.logger.info "Feedback from #{app.contact_name} (#{app.phone_number}): #{rating} stars - #{feedback}"
294
+
295
+ app.say "Thank you for your feedback! We really appreciate it. ๐Ÿ™"
41
296
  end
42
297
  end
43
298
  ```
44
299
 
45
- The `app` instance within `FlowChat::Flow` provides methods to interact with and respond to the user, such as `app.say`, which sends a message to the user.
300
+ For detailed WhatsApp setup instructions, see [WhatsApp Integration Guide](docs/whatsapp_setup.md).
301
+
302
+ ## Cross-Platform Compatibility
46
303
 
47
- #### Integration with USSD
304
+ FlowChat provides a unified API that works across both USSD and WhatsApp platforms, with graceful degradation for platform-specific features:
48
305
 
49
- Given that most USSD gateways interact via HTTP, set up a controller to handle the conversation flow:
306
+ ### Shared Features (Both USSD & WhatsApp)
307
+ - โœ… `app.screen()` - Interactive screens with prompts
308
+ - โœ… `app.say()` - Send messages to users
309
+ - โœ… `prompt.ask()` - Text input collection
310
+ - โœ… `prompt.select()` - Menu selection (renders as numbered list in USSD, interactive buttons/lists in WhatsApp)
311
+ - โœ… `prompt.yes?()` - Yes/no questions
312
+ - โœ… `app.phone_number` - User's phone number
313
+ - โœ… `app.message_id` - Unique message identifier
314
+ - โœ… `app.timestamp` - Message timestamp
315
+
316
+ ### WhatsApp-Only Features
317
+ - โœ… `app.contact_name` - WhatsApp contact name (returns `nil` in USSD)
318
+ - โœ… `app.location` - Location sharing data (returns `nil` in USSD)
319
+ - โœ… `app.media` - Media file attachments (returns `nil` in USSD)
320
+ - โœ… Rich interactive elements (buttons, lists) automatically generated from `prompt.select()`
321
+
322
+ This design allows you to write flows once and deploy them on both platforms, with WhatsApp users getting enhanced interactive features automatically.
323
+
324
+ ## Core Concepts
325
+
326
+ ### Flows and Screens
327
+
328
+ **Flows** are Ruby classes that define conversation logic. **Screens** represent individual interaction points where you collect user input.
50
329
 
51
330
  ```ruby
52
- class UssdDemoController < ApplicationController
53
- skip_forgery_protection
331
+ class RegistrationFlow < FlowChat::Flow
332
+ def main_page
333
+ # Each screen captures one piece of user input
334
+ phone = app.screen(:phone) do |prompt|
335
+ prompt.ask "Enter your phone number:",
336
+ validate: ->(input) { "Invalid phone number" unless valid_phone?(input) }
337
+ end
54
338
 
55
- def hello_world
56
- ussd_processor.run HelloWorldFlow, :main_page
339
+ age = app.screen(:age) do |prompt|
340
+ prompt.ask "Enter your age:",
341
+ convert: ->(input) { input.to_i },
342
+ validate: ->(input) { "Must be 18 or older" unless input >= 18 }
343
+ end
344
+
345
+ # Process the collected data
346
+ create_user(phone: phone, age: age)
347
+ app.say "Registration complete!"
57
348
  end
58
349
 
59
350
  private
60
351
 
61
- def ussd_processor
62
- @ussd_processor ||= FlowChat::Ussd::Processor.new(self) do |processor|
63
- processor.use_gateway FlowChat::Ussd::Gateway::Nalo
64
- processor.use_session_store FlowChat::Session::RailsSessionStore
352
+ def valid_phone?(phone)
353
+ phone.match?(/\A\+?[\d\s\-\(\)]+\z/)
354
+ end
355
+
356
+ def create_user(phone:, age:)
357
+ # Your user creation logic here
358
+ end
359
+ end
360
+ ```
361
+
362
+ ### Input Validation and Transformation
363
+
364
+ FlowChat provides powerful input processing capabilities:
365
+
366
+ ```ruby
367
+ app.screen(:email) do |prompt|
368
+ prompt.ask "Enter your email:",
369
+ # Transform input before validation
370
+ transform: ->(input) { input.strip.downcase },
371
+
372
+ # Validate the input
373
+ validate: ->(input) {
374
+ "Invalid email format" unless input.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
375
+ },
376
+
377
+ # Convert to final format
378
+ convert: ->(input) { input }
379
+ end
380
+ ```
381
+
382
+ ### Menu Selection
383
+
384
+ Create selection menus with automatic validation:
385
+
386
+ ```ruby
387
+ # Array-based choices
388
+ language = app.screen(:language) do |prompt|
389
+ prompt.select "Choose your language:", ["English", "French", "Spanish"]
390
+ end
391
+
392
+ # Hash-based choices (keys are returned values)
393
+ plan = app.screen(:plan) do |prompt|
394
+ prompt.select "Choose a plan:", {
395
+ "basic" => "Basic Plan ($10/month)",
396
+ "premium" => "Premium Plan ($25/month)",
397
+ "enterprise" => "Enterprise Plan ($100/month)"
398
+ }
399
+ end
400
+ ```
401
+
402
+ ### Yes/No Prompts
403
+
404
+ Simplified boolean input collection:
405
+
406
+ ```ruby
407
+ confirmed = app.screen(:confirmation) do |prompt|
408
+ prompt.yes? "Do you want to proceed with the payment?"
409
+ end
410
+
411
+ if confirmed
412
+ process_payment
413
+ app.say "Payment processed successfully!"
414
+ else
415
+ app.say "Payment cancelled."
416
+ end
417
+ ```
418
+
419
+ ## Advanced Features
420
+
421
+ ### Session Management and Flow State
422
+
423
+ FlowChat automatically manages session state across requests. Each screen's result is cached, so users can navigate back and forth without losing data:
424
+
425
+ ```ruby
426
+ class OrderFlow < FlowChat::Flow
427
+ def main_page
428
+ # These values persist across requests
429
+ product = app.screen(:product) { |p| p.select "Choose product:", products }
430
+ quantity = app.screen(:quantity) { |p| p.ask "Quantity:", convert: :to_i }
431
+
432
+ # Show summary
433
+ total = calculate_total(product, quantity)
434
+ confirmed = app.screen(:confirm) do |prompt|
435
+ prompt.yes? "Order #{quantity}x #{product} for $#{total}. Confirm?"
436
+ end
437
+
438
+ if confirmed
439
+ process_order(product, quantity)
440
+ app.say "Order placed successfully!"
441
+ else
442
+ app.say "Order cancelled."
65
443
  end
66
444
  end
67
445
  end
68
446
  ```
69
447
 
70
- This controller initializes a `FlowChat::Ussd::Processor` specifying the use of Nalo Solutions' gateway and a session storage mechanism. Here, `RailsSessionStore` is chosen for simplicity and demonstration purposes.
448
+ ### Error Handling
71
449
 
72
- Bind the controller action to a route:
450
+ Handle validation errors gracefully:
73
451
 
74
452
  ```ruby
75
- Rails.application.routes.draw do
76
- post 'ussd_hello_world' => 'ussd_demo#hello_world'
453
+ app.screen(:credit_card) do |prompt|
454
+ prompt.ask "Enter credit card number:",
455
+ validate: ->(input) {
456
+ return "Card number must be 16 digits" unless input.length == 16
457
+ return "Invalid card number" unless luhn_valid?(input)
458
+ nil # Return nil for valid input
459
+ }
460
+ end
461
+ ```
462
+
463
+ ### Middleware Configuration
464
+
465
+ FlowChat uses a **middleware architecture** to process USSD requests through a configurable pipeline. Each request flows through multiple middleware layers in a specific order.
466
+
467
+ #### Default Middleware Stack
468
+
469
+ When you run a flow, FlowChat automatically builds this middleware stack:
470
+
471
+ ```
472
+ User Input โ†’ Gateway โ†’ Session โ†’ Pagination โ†’ Custom Middleware โ†’ Executor โ†’ Flow
473
+ ```
474
+
475
+ 1. **Gateway Middleware** - Handles USSD provider communication (Nalo)
476
+ 2. **Session Middleware** - Manages session storage and retrieval
477
+ 3. **Pagination Middleware** - Automatically splits long responses across pages
478
+ 4. **Custom Middleware** - Your application-specific middleware (optional)
479
+ 5. **Executor Middleware** - Executes the actual flow logic
480
+
481
+ #### Basic Configuration
482
+
483
+ ```ruby
484
+ processor = FlowChat::Ussd::Processor.new(self) do |config|
485
+ # Gateway configuration (required)
486
+ config.use_gateway FlowChat::Ussd::Gateway::Nalo
487
+
488
+ # Session storage (required)
489
+ config.use_session_store FlowChat::Session::RailsSessionStore
490
+
491
+ # Add custom middleware (optional)
492
+ config.use_middleware MyLoggingMiddleware
493
+
494
+ # Enable resumable sessions (optional)
495
+ config.use_resumable_sessions
77
496
  end
78
497
  ```
79
498
 
80
- #### Testing with the USSD Simulator
499
+ #### Runtime Middleware Modification
81
500
 
82
- FlowChat comes with a USSD simulator for local testing:
501
+ You can modify the middleware stack at runtime for advanced use cases:
502
+
503
+ ```ruby
504
+ processor.run(MyFlow, :main_page) do |stack|
505
+ # Add authentication middleware
506
+ stack.use AuthenticationMiddleware
507
+
508
+ # Insert rate limiting before execution
509
+ stack.insert_before FlowChat::Ussd::Middleware::Executor, RateLimitMiddleware
510
+
511
+ # Add logging after gateway
512
+ stack.insert_after gateway, RequestLoggingMiddleware
513
+ end
514
+ ```
515
+
516
+ #### Built-in Middleware
517
+
518
+ **Pagination Middleware** automatically handles responses longer than 182 characters (configurable):
519
+
520
+ ```ruby
521
+ # Configure pagination behavior
522
+ FlowChat::Config.ussd.pagination_page_size = 140 # Default: 140 characters
523
+ FlowChat::Config.ussd.pagination_next_option = "#" # Default: "#"
524
+ FlowChat::Config.ussd.pagination_next_text = "More" # Default: "More"
525
+ FlowChat::Config.ussd.pagination_back_option = "0" # Default: "0"
526
+ FlowChat::Config.ussd.pagination_back_text = "Back" # Default: "Back"
527
+ ```
528
+
529
+ **Resumable Sessions** allow users to continue interrupted conversations:
530
+
531
+ ```ruby
532
+ processor = FlowChat::Ussd::Processor.new(self) do |config|
533
+ config.use_gateway FlowChat::Ussd::Gateway::Nalo
534
+ config.use_session_store FlowChat::Session::RailsSessionStore
535
+ config.use_resumable_sessions # Enable resumable sessions
536
+ end
537
+ ```
538
+
539
+ #### Creating Custom Middleware
540
+
541
+ ```ruby
542
+ class LoggingMiddleware
543
+ def initialize(app)
544
+ @app = app
545
+ end
546
+
547
+ def call(context)
548
+ Rails.logger.info "Processing USSD request: #{context.input}"
549
+
550
+ # Call the next middleware in the stack
551
+ result = @app.call(context)
552
+
553
+ Rails.logger.info "Response: #{result[1]}"
554
+ result
555
+ end
556
+ end
557
+
558
+ # Use your custom middleware
559
+ processor = FlowChat::Ussd::Processor.new(self) do |config|
560
+ config.use_gateway FlowChat::Ussd::Gateway::Nalo
561
+ config.use_session_store FlowChat::Session::RailsSessionStore
562
+ config.use_middleware LoggingMiddleware
563
+ end
564
+ ```
565
+
566
+ ### Multiple Gateways
567
+
568
+ FlowChat supports multiple USSD gateways:
569
+
570
+ ```ruby
571
+ # Nalo Solutions Gateway
572
+ config.use_gateway FlowChat::Ussd::Gateway::Nalo
573
+ ```
574
+
575
+ ## Testing
576
+
577
+ ### Unit Testing Flows
578
+
579
+ Test your flows in isolation using the provided test helpers:
580
+
581
+ ```ruby
582
+ require 'test_helper'
583
+
584
+ class WelcomeFlowTest < Minitest::Test
585
+ def setup
586
+ @context = FlowChat::Context.new
587
+ @context.session = create_test_session_store
588
+ end
589
+
590
+ def test_welcome_flow_with_name
591
+ @context.input = "John Doe"
592
+ app = FlowChat::Ussd::App.new(@context)
593
+
594
+ error = assert_raises(FlowChat::Interrupt::Terminate) do
595
+ flow = WelcomeFlow.new(app)
596
+ flow.main_page
597
+ end
598
+
599
+ assert_equal "Hello, John Doe! Welcome to FlowChat.", error.prompt
600
+ end
601
+
602
+ def test_welcome_flow_without_input
603
+ @context.input = nil
604
+ app = FlowChat::Ussd::App.new(@context)
605
+
606
+ error = assert_raises(FlowChat::Interrupt::Prompt) do
607
+ flow = WelcomeFlow.new(app)
608
+ flow.main_page
609
+ end
610
+
611
+ assert_equal "Welcome! What's your name?", error.prompt
612
+ end
613
+ end
614
+ ```
615
+
616
+ ### Integration Testing
617
+
618
+ Test complete user journeys:
619
+
620
+ ```ruby
621
+ class RegistrationFlowIntegrationTest < Minitest::Test
622
+ def test_complete_registration_flow
623
+ controller = mock_controller
624
+ processor = FlowChat::Ussd::Processor.new(controller) do |config|
625
+ config.use_gateway MockGateway
626
+ config.use_session_store FlowChat::Session::RailsSessionStore
627
+ end
628
+
629
+ # Simulate the complete flow
630
+ # First request - ask for phone
631
+ # Second request - provide phone, ask for age
632
+ # Third request - provide age, complete registration
633
+ end
634
+ end
635
+ ```
636
+
637
+ ### Testing Middleware
638
+
639
+ Test your custom middleware in isolation:
640
+
641
+ ```ruby
642
+ class LoggingMiddlewareTest < Minitest::Test
643
+ def test_logs_request_and_response
644
+ # Mock the next app in the chain
645
+ app = lambda { |context| [:prompt, "Test response", []] }
646
+ middleware = LoggingMiddleware.new(app)
647
+
648
+ context = FlowChat::Context.new
649
+ context.input = "test input"
650
+
651
+ # Capture log output
652
+ log_output = StringIO.new
653
+ Rails.stub(:logger, Logger.new(log_output)) do
654
+ type, prompt, choices = middleware.call(context)
655
+
656
+ assert_equal :prompt, type
657
+ assert_equal "Test response", prompt
658
+ assert_includes log_output.string, "Processing USSD request: test input"
659
+ assert_includes log_output.string, "Response: Test response"
660
+ end
661
+ end
662
+ end
663
+ ```
664
+
665
+ ### Testing Middleware Stack Modification
666
+
667
+ Test runtime middleware modifications:
668
+
669
+ ```ruby
670
+ class ProcessorMiddlewareTest < Minitest::Test
671
+ def test_custom_middleware_insertion
672
+ controller = mock_controller
673
+ processor = FlowChat::Ussd::Processor.new(controller) do |config|
674
+ config.use_gateway MockGateway
675
+ config.use_session_store FlowChat::Session::RailsSessionStore
676
+ end
677
+
678
+ custom_middleware_called = false
679
+ custom_middleware = Class.new do
680
+ define_method(:initialize) { |app| @app = app }
681
+ define_method(:call) do |context|
682
+ custom_middleware_called = true
683
+ @app.call(context)
684
+ end
685
+ end
686
+
687
+ processor.run(TestFlow, :main_page) do |stack|
688
+ stack.use custom_middleware
689
+ stack.insert_before FlowChat::Ussd::Middleware::Executor, custom_middleware
690
+ end
691
+
692
+ assert custom_middleware_called, "Custom middleware should have been executed"
693
+ end
694
+ end
695
+ ```
696
+
697
+ ### USSD Simulator
698
+
699
+ Use the built-in simulator for interactive testing:
83
700
 
84
701
  ```ruby
85
702
  class UssdSimulatorController < ApplicationController
@@ -88,7 +705,7 @@ class UssdSimulatorController < ApplicationController
88
705
  protected
89
706
 
90
707
  def default_endpoint
91
- '/ussd_hello_world'
708
+ '/ussd'
92
709
  end
93
710
 
94
711
  def default_provider
@@ -97,62 +714,166 @@ class UssdSimulatorController < ApplicationController
97
714
  end
98
715
  ```
99
716
 
100
- And set up the corresponding route:
717
+ Add to routes and visit `http://localhost:3000/ussd_simulator`.
718
+
719
+ ## Best Practices
720
+
721
+ ### 1. Keep Flows Focused
722
+
723
+ Create separate flows for different user journeys:
101
724
 
102
725
  ```ruby
103
- Rails.application.routes.draw do
104
- get 'ussd_simulator' => 'ussd_simulator#ussd_simulator'
726
+ # Good: Focused flows
727
+ class LoginFlow < FlowChat::Flow
728
+ # Handle user authentication
729
+ end
730
+
731
+ class RegistrationFlow < FlowChat::Flow
732
+ # Handle user registration
733
+ end
734
+
735
+ class AccountFlow < FlowChat::Flow
736
+ # Handle account management
105
737
  end
106
738
  ```
107
739
 
108
- Visit [http://localhost:3000/ussd_simulator](http://localhost:3000/ussd_simulator) to initiate and test your flow.
740
+ ### 2. Use Descriptive Screen Names
741
+
742
+ Screen names should clearly indicate their purpose:
743
+
744
+ ```ruby
745
+ # Good
746
+ app.screen(:customer_phone_number) { |p| p.ask "Phone:" }
747
+ app.screen(:payment_confirmation) { |p| p.yes? "Confirm payment?" }
748
+
749
+ # Avoid
750
+ app.screen(:input1) { |p| p.ask "Phone:" }
751
+ app.screen(:confirm) { |p| p.yes? "Confirm payment?" }
752
+ ```
109
753
 
110
- ### Advanced Usage: Implementing Multiple Screens
754
+ ### 3. Validate Early and Often
111
755
 
112
- To engage users with a multi-step interaction, define a flow with multiple screens:
756
+ Always validate user input to provide clear feedback:
113
757
 
114
758
  ```ruby
115
- class MultipleScreensFlow < FlowChat::Flow
116
- def main_page
117
- name = app.screen(:name) { |prompt|
118
- prompt.ask "What is your name?", transform: ->(input) { input.squish }
759
+ app.screen(:amount) do |prompt|
760
+ prompt.ask "Enter amount:",
761
+ convert: ->(input) { input.to_f },
762
+ validate: ->(amount) {
763
+ return "Amount must be positive" if amount <= 0
764
+ return "Maximum amount is $1000" if amount > 1000
765
+ nil
119
766
  }
767
+ end
768
+ ```
120
769
 
121
- age = app.screen(:age) do |prompt|
122
- prompt.ask "How old are you?",
123
- convert: ->(input) { input.to_i },
124
- validate: ->(input) { "You must be at least 13 years old" unless input >= 13 }
125
- end
126
-
127
- gender = app.screen(:gender) { |prompt| prompt.select "What is your gender", ["Male", "Female"] }
770
+ ### 4. Handle Edge Cases
128
771
 
129
- confirm = app.screen(:confirm) do |prompt|
130
- prompt.yes?("Is this correct?\n\nName: #{name}\nAge: #{age}\nGender: #{gender}")
131
- end
772
+ Consider error scenarios and provide helpful messages:
132
773
 
133
- app.say confirm ? "Thank you for confirming" : "Please try again"
774
+ ```ruby
775
+ def main_page
776
+ begin
777
+ process_user_request
778
+ rescue PaymentError => e
779
+ app.say "Payment failed: #{e.message}. Please try again."
780
+ rescue SystemError
781
+ app.say "System temporarily unavailable. Please try again later."
134
782
  end
135
783
  end
136
784
  ```
137
785
 
138
- This example illustrates a flow that collects and confirms user information across multiple interaction steps, showcasing FlowChat's capability to handle complex conversation logic effortlessly.
786
+ ## Configuration
787
+
788
+ ### Cache Configuration
789
+
790
+ The `CacheSessionStore` requires a cache to be configured. Set it up in your Rails application:
791
+
792
+ ```ruby
793
+ # config/application.rb or config/environments/*.rb
794
+ FlowChat::Config.cache = Rails.cache
795
+
796
+ # Or use a specific cache store
797
+ FlowChat::Config.cache = ActiveSupport::Cache::MemoryStore.new
798
+
799
+ # For Redis (requires redis gem)
800
+ FlowChat::Config.cache = ActiveSupport::Cache::RedisCacheStore.new(url: "redis://localhost:6379/1")
801
+ ```
802
+
803
+ ๐Ÿ’ก **Tip**: See [examples/initializer.rb](examples/initializer.rb) for a complete configuration example.
804
+
805
+ ### Session Storage Options
139
806
 
140
- TODO:
807
+ Configure different session storage backends:
141
808
 
142
- ### Sub Flows
809
+ ```ruby
810
+ # Cache session store (default) - uses FlowChat::Config.cache
811
+ config.use_session_store FlowChat::Session::CacheSessionStore
143
812
 
144
- TODO:
813
+ # Rails session (for USSD)
814
+ config.use_session_store FlowChat::Session::RailsSessionStore
815
+
816
+ # Custom session store
817
+ class MySessionStore
818
+ def initialize(context)
819
+ @context = context
820
+ end
821
+
822
+ def get(key)
823
+ # Your storage logic
824
+ end
825
+
826
+ def set(key, value)
827
+ # Your storage logic
828
+ end
829
+ end
830
+
831
+ config.use_session_store MySessionStore
832
+ ```
145
833
 
146
834
  ## Development
147
835
 
148
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
836
+ ### Running Tests
149
837
 
150
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
838
+ FlowChat uses Minitest for testing:
151
839
 
152
- ## Contributing
840
+ ```bash
841
+ # Run all tests
842
+ bundle exec rake test
153
843
 
154
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/flow_chat.
844
+ # Run specific test file
845
+ bundle exec rake test TEST=test/unit/flow_test.rb
846
+
847
+ # Run specific test
848
+ bundle exec rake test TESTOPTS="--name=test_flow_initialization"
849
+ ```
850
+
851
+ ### Contributing
852
+
853
+ 1. Fork the repository
854
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
855
+ 3. Add tests for your changes
856
+ 4. Ensure all tests pass (`bundle exec rake test`)
857
+ 5. Commit your changes (`git commit -am 'Add amazing feature'`)
858
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
859
+ 7. Open a Pull Request
860
+
861
+ ## Roadmap
862
+
863
+ - ๐Ÿ’ฌ **Telegram Bot Support** - Native Telegram bot integration
864
+ - ๐Ÿ”„ **Sub-flows** - Reusable conversation components and flow composition
865
+ - ๐Ÿ“Š **Analytics Integration** - Built-in conversation analytics and user journey tracking
866
+ - ๐ŸŒ **Multi-language Support** - Internationalization and localization features
867
+ - โšก **Performance Optimizations** - Improved middleware performance and caching
868
+ - ๐ŸŽฏ **Advanced Validation** - More validation helpers and custom validators
869
+ - ๐Ÿ” **Enhanced Security** - Rate limiting, input sanitization, and fraud detection
155
870
 
156
871
  ## License
157
872
 
158
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
873
+ FlowChat is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
874
+
875
+ ## Support
876
+
877
+ - ๐Ÿ“– **Documentation**: [GitHub Repository](https://github.com/radioactive-labs/flow_chat)
878
+ - ๐Ÿ› **Bug Reports**: [GitHub Issues](https://github.com/radioactive-labs/flow_chat/issues)
879
+ - ๐Ÿ’ฌ **Community**: Join our discussions for help and feature requests