flow_chat 0.3.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 +642 -86
- data/examples/initializer.rb +31 -0
- data/examples/media_prompts_examples.rb +28 -0
- data/examples/multi_tenant_whatsapp_controller.rb +244 -0
- data/examples/ussd_controller.rb +264 -0
- data/examples/whatsapp_controller.rb +140 -0
- data/examples/whatsapp_media_examples.rb +406 -0
- data/examples/whatsapp_message_job.rb +111 -0
- data/lib/flow_chat/base_processor.rb +67 -0
- data/lib/flow_chat/config.rb +36 -0
- data/lib/flow_chat/session/cache_session_store.rb +84 -0
- data/lib/flow_chat/session/middleware.rb +14 -6
- 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/app.rb +25 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +2 -0
- data/lib/flow_chat/ussd/gateway/nsano.rb +6 -0
- data/lib/flow_chat/ussd/middleware/resumable_session.rb +1 -1
- data/lib/flow_chat/ussd/processor.rb +14 -42
- data/lib/flow_chat/ussd/prompt.rb +39 -5
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +64 -0
- data/lib/flow_chat/whatsapp/client.rb +439 -0
- data/lib/flow_chat/whatsapp/configuration.rb +113 -0
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +213 -0
- data/lib/flow_chat/whatsapp/middleware/executor.rb +30 -0
- data/lib/flow_chat/whatsapp/processor.rb +26 -0
- data/lib/flow_chat/whatsapp/prompt.rb +251 -0
- data/lib/flow_chat/whatsapp/send_job_support.rb +79 -0
- data/lib/flow_chat/whatsapp/template_manager.rb +162 -0
- data/lib/flow_chat.rb +1 -0
- metadata +21 -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,31 @@
|
|
1
|
+
# Example FlowChat Initializer
|
2
|
+
# Add this to your Rails application as config/initializers/flow_chat.rb
|
3
|
+
|
4
|
+
# Configure cache for session storage
|
5
|
+
# This is required when using FlowChat::Session::CacheSessionStore
|
6
|
+
FlowChat::Config.cache = Rails.cache
|
7
|
+
|
8
|
+
# Alternative cache configurations:
|
9
|
+
|
10
|
+
# Use a specific cache store
|
11
|
+
# FlowChat::Config.cache = ActiveSupport::Cache::MemoryStore.new
|
12
|
+
|
13
|
+
# Use Redis (requires redis gem)
|
14
|
+
# FlowChat::Config.cache = ActiveSupport::Cache::RedisCacheStore.new(url: "redis://localhost:6379/1")
|
15
|
+
|
16
|
+
# Use Memcached (requires dalli gem)
|
17
|
+
# FlowChat::Config.cache = ActiveSupport::Cache::MemCacheStore.new("localhost:11211")
|
18
|
+
|
19
|
+
# Configure logger (optional)
|
20
|
+
FlowChat::Config.logger = Rails.logger
|
21
|
+
|
22
|
+
# Configure USSD pagination (optional)
|
23
|
+
FlowChat::Config.ussd.pagination_page_size = 140
|
24
|
+
FlowChat::Config.ussd.pagination_back_option = "0"
|
25
|
+
FlowChat::Config.ussd.pagination_back_text = "Back"
|
26
|
+
FlowChat::Config.ussd.pagination_next_option = "#"
|
27
|
+
FlowChat::Config.ussd.pagination_next_text = "More"
|
28
|
+
|
29
|
+
# Configure resumable sessions (optional)
|
30
|
+
FlowChat::Config.ussd.resumable_sessions_enabled = true
|
31
|
+
FlowChat::Config.ussd.resumable_sessions_timeout_seconds = 300 # 5 minutes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Media Prompts Examples
|
2
|
+
# This file demonstrates how to attach media to prompts in FlowChat
|
3
|
+
|
4
|
+
# ============================================================================
|
5
|
+
# BASIC MEDIA PROMPTS
|
6
|
+
# ============================================================================
|
7
|
+
|
8
|
+
class MediaPromptFlow < FlowChat::Flow
|
9
|
+
def main_page
|
10
|
+
# ✅ Simple text input with attached image
|
11
|
+
# The prompt text becomes the image caption
|
12
|
+
feedback = app.screen(:feedback) do |prompt|
|
13
|
+
prompt.ask "What do you think of our new product?",
|
14
|
+
media: {
|
15
|
+
type: :image,
|
16
|
+
url: "https://cdn.example.com/products/new_product.jpg"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
# ✅ Send media message with say
|
21
|
+
app.say "Thank you for your feedback! Here's what's coming next:",
|
22
|
+
media: {
|
23
|
+
type: :video,
|
24
|
+
url: "https://videos.example.com/roadmap.mp4"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,244 @@
|
|
1
|
+
# Example Multi-Tenant WhatsApp Controller
|
2
|
+
# This shows how to configure different WhatsApp accounts per tenant/client
|
3
|
+
|
4
|
+
class MultiTenantWhatsappController < ApplicationController
|
5
|
+
skip_forgery_protection
|
6
|
+
|
7
|
+
def webhook
|
8
|
+
# Determine tenant from subdomain, path, or other logic
|
9
|
+
tenant = determine_tenant(request)
|
10
|
+
|
11
|
+
# Get tenant-specific WhatsApp configuration
|
12
|
+
whatsapp_config = get_whatsapp_config_for_tenant(tenant)
|
13
|
+
|
14
|
+
processor = FlowChat::Whatsapp::Processor.new(self) do |config|
|
15
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
|
16
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
17
|
+
end
|
18
|
+
|
19
|
+
# Use tenant-specific flow
|
20
|
+
flow_class = get_flow_for_tenant(tenant)
|
21
|
+
processor.run flow_class, :main_page
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def determine_tenant(request)
|
27
|
+
# Option 1: From subdomain
|
28
|
+
return request.subdomain if request.subdomain.present?
|
29
|
+
|
30
|
+
# Option 2: From path
|
31
|
+
tenant_from_path = request.path.match(%r{^/whatsapp/(\w+)/})&.captures&.first
|
32
|
+
return tenant_from_path if tenant_from_path
|
33
|
+
|
34
|
+
# Option 3: From custom header
|
35
|
+
return request.headers['X-Tenant-ID'] if request.headers['X-Tenant-ID']
|
36
|
+
|
37
|
+
# Fallback to default
|
38
|
+
'default'
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_whatsapp_config_for_tenant(tenant)
|
42
|
+
case tenant
|
43
|
+
when 'acme_corp'
|
44
|
+
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
45
|
+
config.access_token = ENV['ACME_WHATSAPP_ACCESS_TOKEN']
|
46
|
+
config.phone_number_id = ENV['ACME_WHATSAPP_PHONE_NUMBER_ID']
|
47
|
+
config.verify_token = ENV['ACME_WHATSAPP_VERIFY_TOKEN']
|
48
|
+
config.app_id = ENV['ACME_WHATSAPP_APP_ID']
|
49
|
+
config.app_secret = ENV['ACME_WHATSAPP_APP_SECRET']
|
50
|
+
config.business_account_id = ENV['ACME_WHATSAPP_BUSINESS_ACCOUNT_ID']
|
51
|
+
end
|
52
|
+
|
53
|
+
when 'tech_startup'
|
54
|
+
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
55
|
+
config.access_token = ENV['TECHSTARTUP_WHATSAPP_ACCESS_TOKEN']
|
56
|
+
config.phone_number_id = ENV['TECHSTARTUP_WHATSAPP_PHONE_NUMBER_ID']
|
57
|
+
config.verify_token = ENV['TECHSTARTUP_WHATSAPP_VERIFY_TOKEN']
|
58
|
+
config.app_id = ENV['TECHSTARTUP_WHATSAPP_APP_ID']
|
59
|
+
config.app_secret = ENV['TECHSTARTUP_WHATSAPP_APP_SECRET']
|
60
|
+
config.business_account_id = ENV['TECHSTARTUP_WHATSAPP_BUSINESS_ACCOUNT_ID']
|
61
|
+
end
|
62
|
+
|
63
|
+
when 'retail_store'
|
64
|
+
# Load from database
|
65
|
+
tenant_config = WhatsappConfiguration.find_by(tenant: tenant)
|
66
|
+
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
67
|
+
config.access_token = tenant_config.access_token
|
68
|
+
config.phone_number_id = tenant_config.phone_number_id
|
69
|
+
config.verify_token = tenant_config.verify_token
|
70
|
+
config.app_id = tenant_config.app_id
|
71
|
+
config.app_secret = tenant_config.app_secret
|
72
|
+
config.business_account_id = tenant_config.business_account_id
|
73
|
+
end
|
74
|
+
|
75
|
+
else
|
76
|
+
# Use default/global configuration
|
77
|
+
FlowChat::Whatsapp::Configuration.from_credentials
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_flow_for_tenant(tenant)
|
82
|
+
case tenant
|
83
|
+
when 'acme_corp'
|
84
|
+
AcmeCorpFlow
|
85
|
+
when 'tech_startup'
|
86
|
+
TechStartupFlow
|
87
|
+
when 'retail_store'
|
88
|
+
RetailStoreFlow
|
89
|
+
else
|
90
|
+
WelcomeFlow # Default flow
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Example: Dynamic Configuration from Database
|
96
|
+
class DatabaseWhatsappController < ApplicationController
|
97
|
+
skip_forgery_protection
|
98
|
+
|
99
|
+
def webhook
|
100
|
+
# Get account from business phone number or other identifier
|
101
|
+
business_account = find_business_account(params)
|
102
|
+
|
103
|
+
if business_account.nil?
|
104
|
+
return head :not_found
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create configuration from database record
|
108
|
+
whatsapp_config = FlowChat::Whatsapp::Configuration.new.tap do |config|
|
109
|
+
config.access_token = business_account.whatsapp_access_token
|
110
|
+
config.phone_number_id = business_account.whatsapp_phone_number_id
|
111
|
+
config.verify_token = business_account.whatsapp_verify_token
|
112
|
+
config.app_id = business_account.whatsapp_app_id
|
113
|
+
config.app_secret = business_account.whatsapp_app_secret
|
114
|
+
config.business_account_id = business_account.whatsapp_business_account_id
|
115
|
+
end
|
116
|
+
|
117
|
+
processor = FlowChat::Whatsapp::Processor.new(self) do |config|
|
118
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
|
119
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
120
|
+
end
|
121
|
+
|
122
|
+
processor.run business_account.flow_class.constantize, :main_page
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def find_business_account(params)
|
128
|
+
# You could identify the account by:
|
129
|
+
# 1. Phone number ID from webhook
|
130
|
+
# 2. Business account ID from webhook
|
131
|
+
# 3. Custom routing parameter
|
132
|
+
|
133
|
+
# Example: Find by phone number ID in webhook
|
134
|
+
phone_number_id = extract_phone_number_id_from_webhook(params)
|
135
|
+
BusinessAccount.find_by(whatsapp_phone_number_id: phone_number_id)
|
136
|
+
end
|
137
|
+
|
138
|
+
def extract_phone_number_id_from_webhook(params)
|
139
|
+
# Extract from webhook payload structure
|
140
|
+
# This would need to be implemented based on your webhook structure
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Example: Environment-based Configuration
|
146
|
+
class EnvironmentWhatsappController < ApplicationController
|
147
|
+
skip_forgery_protection
|
148
|
+
|
149
|
+
def webhook
|
150
|
+
# Different configurations for different environments
|
151
|
+
whatsapp_config = case Rails.env
|
152
|
+
when 'production'
|
153
|
+
production_whatsapp_config
|
154
|
+
when 'staging'
|
155
|
+
staging_whatsapp_config
|
156
|
+
when 'development'
|
157
|
+
development_whatsapp_config
|
158
|
+
else
|
159
|
+
FlowChat::Whatsapp::Configuration.from_credentials
|
160
|
+
end
|
161
|
+
|
162
|
+
processor = FlowChat::Whatsapp::Processor.new(self) do |config|
|
163
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
|
164
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
165
|
+
end
|
166
|
+
|
167
|
+
processor.run WelcomeFlow, :main_page
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def production_whatsapp_config
|
173
|
+
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
174
|
+
config.access_token = ENV['PROD_WHATSAPP_ACCESS_TOKEN']
|
175
|
+
config.phone_number_id = ENV['PROD_WHATSAPP_PHONE_NUMBER_ID']
|
176
|
+
config.verify_token = ENV['PROD_WHATSAPP_VERIFY_TOKEN']
|
177
|
+
config.app_id = ENV['PROD_WHATSAPP_APP_ID']
|
178
|
+
config.app_secret = ENV['PROD_WHATSAPP_APP_SECRET']
|
179
|
+
config.business_account_id = ENV['PROD_WHATSAPP_BUSINESS_ACCOUNT_ID']
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def staging_whatsapp_config
|
184
|
+
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
185
|
+
config.access_token = ENV['STAGING_WHATSAPP_ACCESS_TOKEN']
|
186
|
+
config.phone_number_id = ENV['STAGING_WHATSAPP_PHONE_NUMBER_ID']
|
187
|
+
config.verify_token = ENV['STAGING_WHATSAPP_VERIFY_TOKEN']
|
188
|
+
config.app_id = ENV['STAGING_WHATSAPP_APP_ID']
|
189
|
+
config.app_secret = ENV['STAGING_WHATSAPP_APP_SECRET']
|
190
|
+
config.business_account_id = ENV['STAGING_WHATSAPP_BUSINESS_ACCOUNT_ID']
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def development_whatsapp_config
|
195
|
+
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
196
|
+
config.access_token = ENV['DEV_WHATSAPP_ACCESS_TOKEN']
|
197
|
+
config.phone_number_id = ENV['DEV_WHATSAPP_PHONE_NUMBER_ID']
|
198
|
+
config.verify_token = ENV['DEV_WHATSAPP_VERIFY_TOKEN']
|
199
|
+
config.app_id = ENV['DEV_WHATSAPP_APP_ID']
|
200
|
+
config.app_secret = ENV['DEV_WHATSAPP_APP_SECRET']
|
201
|
+
config.business_account_id = ENV['DEV_WHATSAPP_BUSINESS_ACCOUNT_ID']
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Example: Simple Custom Configuration
|
207
|
+
class CustomWhatsappController < ApplicationController
|
208
|
+
skip_forgery_protection
|
209
|
+
|
210
|
+
def webhook
|
211
|
+
# Create custom configuration for this specific endpoint
|
212
|
+
my_config = FlowChat::Whatsapp::Configuration.new
|
213
|
+
my_config.access_token = "EAABs..." # Your specific access token
|
214
|
+
my_config.phone_number_id = "123456789"
|
215
|
+
my_config.verify_token = "my_verify_token"
|
216
|
+
my_config.app_id = "your_app_id"
|
217
|
+
my_config.app_secret = "your_app_secret"
|
218
|
+
my_config.business_account_id = "your_business_account_id"
|
219
|
+
|
220
|
+
processor = FlowChat::Whatsapp::Processor.new(self) do |config|
|
221
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, my_config
|
222
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
223
|
+
end
|
224
|
+
|
225
|
+
processor.run CustomFlow, :main_page
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Add routes for different tenants:
|
230
|
+
# Rails.application.routes.draw do
|
231
|
+
# # Subdomain-based routing
|
232
|
+
# constraints subdomain: /\w+/ do
|
233
|
+
# post '/whatsapp/webhook', to: 'multi_tenant_whatsapp#webhook'
|
234
|
+
# end
|
235
|
+
#
|
236
|
+
# # Path-based routing
|
237
|
+
# post '/whatsapp/:tenant/webhook', to: 'multi_tenant_whatsapp#webhook'
|
238
|
+
#
|
239
|
+
# # Environment-specific
|
240
|
+
# post '/whatsapp/env/webhook', to: 'environment_whatsapp#webhook'
|
241
|
+
#
|
242
|
+
# # Custom endpoint
|
243
|
+
# post '/whatsapp/custom/webhook', to: 'custom_whatsapp#webhook'
|
244
|
+
# end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# Example USSD Controller
|
2
|
+
# Add this to your Rails application as app/controllers/ussd_controller.rb
|
3
|
+
|
4
|
+
class UssdController < ApplicationController
|
5
|
+
skip_forgery_protection
|
6
|
+
|
7
|
+
def process_request
|
8
|
+
processor = FlowChat::Ussd::Processor.new(self) do |config|
|
9
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
10
|
+
# Use Rails session for USSD (shorter sessions)
|
11
|
+
config.use_session_store FlowChat::Session::RailsSessionStore
|
12
|
+
|
13
|
+
# Enable resumable sessions (optional)
|
14
|
+
config.use_resumable_sessions
|
15
|
+
end
|
16
|
+
|
17
|
+
processor.run WelcomeFlow, :main_page
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Example Flow for USSD
|
22
|
+
# Add this to your Rails application as app/flow_chat/welcome_flow.rb
|
23
|
+
|
24
|
+
class WelcomeFlow < FlowChat::Flow
|
25
|
+
def main_page
|
26
|
+
# Welcome the user
|
27
|
+
name = app.screen(:name) do |prompt|
|
28
|
+
prompt.ask "Welcome to our service! What's your name?",
|
29
|
+
transform: ->(input) { input.strip.titleize }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Show main menu with numbered options (USSD style)
|
33
|
+
choice = app.screen(:main_menu) do |prompt|
|
34
|
+
prompt.select "Hi #{name}! Choose an option:", {
|
35
|
+
"1" => "Account Info",
|
36
|
+
"2" => "Make Payment",
|
37
|
+
"3" => "Get Balance",
|
38
|
+
"4" => "Customer Support"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
case choice
|
43
|
+
when "1"
|
44
|
+
show_account_info
|
45
|
+
when "2"
|
46
|
+
make_payment
|
47
|
+
when "3"
|
48
|
+
get_balance
|
49
|
+
when "4"
|
50
|
+
customer_support
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def show_account_info
|
57
|
+
info_choice = app.screen(:account_info) do |prompt|
|
58
|
+
prompt.select "Account Information:", {
|
59
|
+
"1" => "Personal Details",
|
60
|
+
"2" => "Account Balance",
|
61
|
+
"3" => "Transaction History",
|
62
|
+
"0" => "Back to Main Menu"
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
case info_choice
|
67
|
+
when "1"
|
68
|
+
app.say "Name: John Doe\\nPhone: #{app.phone_number}\\nAccount: Active"
|
69
|
+
when "2"
|
70
|
+
app.say "Current Balance: $150.75\\nAvailable Credit: $1,000.00"
|
71
|
+
when "3"
|
72
|
+
app.say "Last 3 Transactions:\\n1. +$50.00 - Deposit\\n2. -$25.50 - Purchase\\n3. -$15.00 - Transfer"
|
73
|
+
when "0"
|
74
|
+
main_page # Go back to main menu
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def make_payment
|
79
|
+
amount = app.screen(:payment_amount) do |prompt|
|
80
|
+
prompt.ask "Enter amount to pay:",
|
81
|
+
convert: ->(input) { input.to_f },
|
82
|
+
validate: ->(amount) {
|
83
|
+
return "Amount must be greater than 0" unless amount > 0
|
84
|
+
return "Maximum payment is $500" unless amount <= 500
|
85
|
+
nil
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
recipient = app.screen(:payment_recipient) do |prompt|
|
90
|
+
prompt.ask "Enter recipient phone number:",
|
91
|
+
validate: ->(input) {
|
92
|
+
return "Phone number must be 10 digits" unless input.match?(/\\A\\d{10}\\z/)
|
93
|
+
nil
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Confirmation screen
|
98
|
+
confirmed = app.screen(:payment_confirmation) do |prompt|
|
99
|
+
prompt.yes? "Pay $#{amount} to #{recipient}?\\nConfirm payment?"
|
100
|
+
end
|
101
|
+
|
102
|
+
if confirmed
|
103
|
+
# Process payment (your business logic here)
|
104
|
+
transaction_id = process_payment(amount, recipient)
|
105
|
+
app.say "Payment successful!\\nTransaction ID: #{transaction_id}\\nAmount: $#{amount}\\nTo: #{recipient}"
|
106
|
+
else
|
107
|
+
app.say "Payment cancelled"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_balance
|
112
|
+
# Simulate balance check
|
113
|
+
balance = check_account_balance(app.phone_number)
|
114
|
+
app.say "Account Balance\\n\\nAvailable: $#{balance[:available]}\\nPending: $#{balance[:pending]}\\nTotal: $#{balance[:total]}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def customer_support
|
118
|
+
support_choice = app.screen(:support_menu) do |prompt|
|
119
|
+
prompt.select "Customer Support:", {
|
120
|
+
"1" => "Report an Issue",
|
121
|
+
"2" => "Account Questions",
|
122
|
+
"3" => "Technical Support",
|
123
|
+
"4" => "Speak to Agent",
|
124
|
+
"0" => "Main Menu"
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
case support_choice
|
129
|
+
when "1"
|
130
|
+
report_issue
|
131
|
+
when "2"
|
132
|
+
app.say "For account questions:\\nCall: 123-456-7890\\nEmail: support@company.com\\nHours: 9AM-5PM Mon-Fri"
|
133
|
+
when "3"
|
134
|
+
app.say "Technical Support:\\nCall: 123-456-7891\\nEmail: tech@company.com\\n24/7 Support Available"
|
135
|
+
when "4"
|
136
|
+
app.say "Connecting you to an agent...\\nPlease call 123-456-7890\\nOr visit our nearest branch"
|
137
|
+
when "0"
|
138
|
+
main_page
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def report_issue
|
143
|
+
issue_type = app.screen(:issue_type) do |prompt|
|
144
|
+
prompt.select "Select issue type:", {
|
145
|
+
"1" => "Payment Problem",
|
146
|
+
"2" => "Account Access",
|
147
|
+
"3" => "Service Error",
|
148
|
+
"4" => "Other"
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
description = app.screen(:issue_description) do |prompt|
|
153
|
+
prompt.ask "Briefly describe the issue:",
|
154
|
+
validate: ->(input) {
|
155
|
+
return "Description must be at least 10 characters" unless input.length >= 10
|
156
|
+
nil
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
# Save the issue (your business logic here)
|
161
|
+
ticket_id = create_support_ticket(issue_type, description, app.phone_number)
|
162
|
+
|
163
|
+
app.say "Issue reported successfully!\\n\\nTicket ID: #{ticket_id}\\nWe'll contact you within 24 hours.\\n\\nThank you!"
|
164
|
+
end
|
165
|
+
|
166
|
+
# Helper methods (implement your business logic)
|
167
|
+
|
168
|
+
def process_payment(amount, recipient)
|
169
|
+
# Your payment processing logic here
|
170
|
+
# Return transaction ID
|
171
|
+
"TXN#{rand(100000..999999)}"
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_account_balance(phone_number)
|
175
|
+
# Your balance checking logic here
|
176
|
+
{
|
177
|
+
available: "150.75",
|
178
|
+
pending: "25.00",
|
179
|
+
total: "175.75"
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def create_support_ticket(issue_type, description, phone_number)
|
184
|
+
# Your ticket creation logic here
|
185
|
+
Rails.logger.info "Support ticket created: #{issue_type} - #{description} from #{phone_number}"
|
186
|
+
"TICKET#{rand(10000..99999)}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Configuration Examples:
|
191
|
+
|
192
|
+
# 1. Basic configuration with custom pagination
|
193
|
+
class UssdController < ApplicationController
|
194
|
+
skip_forgery_protection
|
195
|
+
|
196
|
+
def process_request
|
197
|
+
# Configure pagination for shorter messages
|
198
|
+
FlowChat::Config.ussd.pagination_page_size = 120
|
199
|
+
FlowChat::Config.ussd.pagination_next_option = "#"
|
200
|
+
FlowChat::Config.ussd.pagination_back_option = "*"
|
201
|
+
|
202
|
+
processor = FlowChat::Ussd::Processor.new(self) do |config|
|
203
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
204
|
+
config.use_session_store FlowChat::Session::RailsSessionStore
|
205
|
+
end
|
206
|
+
|
207
|
+
processor.run WelcomeFlow, :main_page
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# 2. Configuration with custom middleware
|
212
|
+
class LoggingMiddleware
|
213
|
+
def initialize(app)
|
214
|
+
@app = app
|
215
|
+
end
|
216
|
+
|
217
|
+
def call(context)
|
218
|
+
Rails.logger.info "USSD Request from #{context['request.msisdn']}: #{context.input}"
|
219
|
+
start_time = Time.current
|
220
|
+
|
221
|
+
result = @app.call(context)
|
222
|
+
|
223
|
+
duration = Time.current - start_time
|
224
|
+
Rails.logger.info "USSD Response (#{duration.round(3)}s): #{result[1]}"
|
225
|
+
|
226
|
+
result
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
class UssdController < ApplicationController
|
231
|
+
skip_forgery_protection
|
232
|
+
|
233
|
+
def process_request
|
234
|
+
processor = FlowChat::Ussd::Processor.new(self) do |config|
|
235
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
236
|
+
config.use_session_store FlowChat::Session::RailsSessionStore
|
237
|
+
config.use_middleware LoggingMiddleware # Add custom logging
|
238
|
+
config.use_resumable_sessions # Enable resumable sessions
|
239
|
+
end
|
240
|
+
|
241
|
+
processor.run WelcomeFlow, :main_page
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# 3. Configuration with cache-based sessions for longer persistence
|
246
|
+
class UssdController < ApplicationController
|
247
|
+
skip_forgery_protection
|
248
|
+
|
249
|
+
def process_request
|
250
|
+
processor = FlowChat::Ussd::Processor.new(self) do |config|
|
251
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
252
|
+
# Use cache store for longer session persistence
|
253
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
254
|
+
end
|
255
|
+
|
256
|
+
processor.run WelcomeFlow, :main_page
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Add this route to your config/routes.rb:
|
261
|
+
# post '/ussd', to: 'ussd#process_request'
|
262
|
+
|
263
|
+
# For Nsano gateway, use:
|
264
|
+
# config.use_gateway FlowChat::Ussd::Gateway::Nsano
|