kapso-client-ruby 1.0.1 → 1.0.2
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/.rubocop.yml +81 -81
- data/CHANGELOG.md +262 -91
- data/Gemfile +20 -20
- data/RAILS_INTEGRATION.md +477 -477
- data/README.md +1053 -752
- data/Rakefile +40 -40
- data/TEMPLATE_TOOLS_GUIDE.md +120 -120
- data/WHATSAPP_24_HOUR_GUIDE.md +133 -133
- data/examples/advanced_features.rb +352 -349
- data/examples/advanced_messaging.rb +241 -0
- data/examples/basic_messaging.rb +139 -136
- data/examples/enhanced_interactive.rb +400 -0
- data/examples/flows_usage.rb +307 -0
- data/examples/interactive_messages.rb +343 -0
- data/examples/media_management.rb +256 -253
- data/examples/rails/jobs.rb +387 -387
- data/examples/rails/models.rb +239 -239
- data/examples/rails/notifications_controller.rb +226 -226
- data/examples/template_management.rb +393 -390
- data/kapso-ruby-logo.jpg +0 -0
- data/lib/kapso_client_ruby/client.rb +321 -316
- data/lib/kapso_client_ruby/errors.rb +348 -329
- data/lib/kapso_client_ruby/rails/generators/install_generator.rb +75 -75
- data/lib/kapso_client_ruby/rails/generators/templates/env.erb +20 -20
- data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +32 -32
- data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +137 -137
- data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +61 -61
- data/lib/kapso_client_ruby/rails/railtie.rb +54 -54
- data/lib/kapso_client_ruby/rails/service.rb +188 -188
- data/lib/kapso_client_ruby/rails/tasks.rake +166 -166
- data/lib/kapso_client_ruby/resources/calls.rb +172 -172
- data/lib/kapso_client_ruby/resources/contacts.rb +190 -190
- data/lib/kapso_client_ruby/resources/conversations.rb +103 -103
- data/lib/kapso_client_ruby/resources/flows.rb +382 -0
- data/lib/kapso_client_ruby/resources/media.rb +205 -205
- data/lib/kapso_client_ruby/resources/messages.rb +760 -380
- data/lib/kapso_client_ruby/resources/phone_numbers.rb +85 -85
- data/lib/kapso_client_ruby/resources/templates.rb +283 -283
- data/lib/kapso_client_ruby/types.rb +348 -262
- data/lib/kapso_client_ruby/version.rb +5 -5
- data/lib/kapso_client_ruby.rb +75 -74
- data/scripts/.env.example +17 -17
- data/scripts/kapso_template_finder.rb +91 -91
- data/scripts/sdk_setup.rb +404 -404
- data/scripts/test.rb +60 -60
- metadata +12 -3
data/examples/rails/jobs.rb
CHANGED
|
@@ -1,388 +1,388 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Background jobs for WhatsApp message sending
|
|
4
|
-
# These jobs handle asynchronous message sending to improve performance
|
|
5
|
-
|
|
6
|
-
class SendWelcomeMessageJob < ApplicationJob
|
|
7
|
-
queue_as :whatsapp_messages
|
|
8
|
-
|
|
9
|
-
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 5
|
|
10
|
-
retry_on KapsoClientRuby::TemporaryError, wait: 30.seconds, attempts: 3
|
|
11
|
-
|
|
12
|
-
discard_on KapsoClientRuby::AuthenticationError
|
|
13
|
-
discard_on KapsoClientRuby::ValidationError
|
|
14
|
-
|
|
15
|
-
def perform(user)
|
|
16
|
-
return unless user.phone_number.present?
|
|
17
|
-
|
|
18
|
-
service = KapsoMessageService.new
|
|
19
|
-
|
|
20
|
-
begin
|
|
21
|
-
result = service.send_welcome_message(user)
|
|
22
|
-
|
|
23
|
-
if result && result.dig('messages', 0, 'id')
|
|
24
|
-
# Track the message
|
|
25
|
-
WhatsappMessage.track_message(
|
|
26
|
-
user: user,
|
|
27
|
-
message_id: result.dig('messages', 0, 'id'),
|
|
28
|
-
message_type: 'welcome',
|
|
29
|
-
phone_number: user.phone_number,
|
|
30
|
-
messageable: user
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
Rails.logger.info "Welcome message sent to user #{user.id}: #{result.dig('messages', 0, 'id')}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
rescue KapsoClientRuby::Error => e
|
|
37
|
-
Rails.logger.error "Failed to send welcome message to user #{user.id}: #{e.message}"
|
|
38
|
-
|
|
39
|
-
# Track failed message
|
|
40
|
-
WhatsappMessage.create!(
|
|
41
|
-
user: user,
|
|
42
|
-
message_id: "failed_#{SecureRandom.hex(8)}",
|
|
43
|
-
message_type: 'welcome',
|
|
44
|
-
phone_number: user.phone_number,
|
|
45
|
-
messageable: user,
|
|
46
|
-
status: 'failed',
|
|
47
|
-
error_message: e.message,
|
|
48
|
-
sent_at: Time.current
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
raise # Re-raise to trigger retry logic
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
class SendOrderConfirmationJob < ApplicationJob
|
|
57
|
-
queue_as :whatsapp_messages
|
|
58
|
-
|
|
59
|
-
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 5
|
|
60
|
-
retry_on KapsoClientRuby::TemporaryError, wait: 30.seconds, attempts: 3
|
|
61
|
-
|
|
62
|
-
def perform(user, order)
|
|
63
|
-
return unless user.phone_number.present?
|
|
64
|
-
|
|
65
|
-
service = KapsoMessageService.new
|
|
66
|
-
|
|
67
|
-
begin
|
|
68
|
-
result = service.send_order_confirmation(order)
|
|
69
|
-
|
|
70
|
-
if result && result.dig('messages', 0, 'id')
|
|
71
|
-
WhatsappMessage.track_message(
|
|
72
|
-
user: user,
|
|
73
|
-
message_id: result.dig('messages', 0, 'id'),
|
|
74
|
-
message_type: 'order_confirmation',
|
|
75
|
-
phone_number: user.phone_number,
|
|
76
|
-
messageable: order
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
Rails.logger.info "Order confirmation sent for order #{order.id}: #{result.dig('messages', 0, 'id')}"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
rescue KapsoClientRuby::Error => e
|
|
83
|
-
Rails.logger.error "Failed to send order confirmation for order #{order.id}: #{e.message}"
|
|
84
|
-
raise
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class SendOrderStatusUpdateJob < ApplicationJob
|
|
90
|
-
queue_as :whatsapp_messages
|
|
91
|
-
|
|
92
|
-
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 5
|
|
93
|
-
|
|
94
|
-
def perform(user, order)
|
|
95
|
-
return unless user.phone_number.present?
|
|
96
|
-
|
|
97
|
-
# Don't spam users with too many updates
|
|
98
|
-
return if user.received_message_type_recently?('order_status_update', within: 1.hour)
|
|
99
|
-
|
|
100
|
-
service = KapsoClientRuby::Rails::Service.new
|
|
101
|
-
|
|
102
|
-
begin
|
|
103
|
-
# Use different templates based on order status
|
|
104
|
-
template_name = case order.status
|
|
105
|
-
when 'confirmed' then 'order_confirmed'
|
|
106
|
-
when 'processing' then 'order_processing'
|
|
107
|
-
when 'shipped' then 'order_shipped'
|
|
108
|
-
when 'delivered' then 'order_delivered'
|
|
109
|
-
when 'cancelled' then 'order_cancelled'
|
|
110
|
-
else 'order_status_update'
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
components = build_order_status_components(order, user)
|
|
114
|
-
|
|
115
|
-
result = service.send_template_message(
|
|
116
|
-
to: user.phone_number,
|
|
117
|
-
template_name: template_name,
|
|
118
|
-
language: user.preferred_language || 'en',
|
|
119
|
-
components: components
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
if result && result.dig('messages', 0, 'id')
|
|
123
|
-
WhatsappMessage.track_message(
|
|
124
|
-
user: user,
|
|
125
|
-
message_id: result.dig('messages', 0, 'id'),
|
|
126
|
-
message_type: 'order_status_update',
|
|
127
|
-
phone_number: user.phone_number,
|
|
128
|
-
messageable: order
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
Rails.logger.info "Order status update sent for order #{order.id}: #{order.status}"
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
rescue KapsoClientRuby::Error => e
|
|
135
|
-
Rails.logger.error "Failed to send order status update for order #{order.id}: #{e.message}"
|
|
136
|
-
raise
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
private
|
|
141
|
-
|
|
142
|
-
def build_order_status_components(order, user)
|
|
143
|
-
[
|
|
144
|
-
{
|
|
145
|
-
type: 'body',
|
|
146
|
-
parameters: [
|
|
147
|
-
{ type: 'text', text: user.first_name || 'Customer' },
|
|
148
|
-
{ type: 'text', text: order.id.to_s },
|
|
149
|
-
{ type: 'text', text: order.status.humanize },
|
|
150
|
-
{ type: 'text', text: order.formatted_total }
|
|
151
|
-
]
|
|
152
|
-
}
|
|
153
|
-
].tap do |components|
|
|
154
|
-
# Add tracking info for shipped orders
|
|
155
|
-
if order.shipped? && order.tracking_number.present?
|
|
156
|
-
components << {
|
|
157
|
-
type: 'body',
|
|
158
|
-
parameters: [
|
|
159
|
-
{ type: 'text', text: order.tracking_number }
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
class SendPhoneVerificationJob < ApplicationJob
|
|
168
|
-
queue_as :whatsapp_messages
|
|
169
|
-
|
|
170
|
-
retry_on KapsoClientRuby::Error, wait: 30.seconds, attempts: 3
|
|
171
|
-
|
|
172
|
-
def perform(user)
|
|
173
|
-
return unless user.phone_number.present?
|
|
174
|
-
|
|
175
|
-
# Generate verification code
|
|
176
|
-
verification_code = rand(100000..999999).to_s
|
|
177
|
-
|
|
178
|
-
# Store verification code (you might use Redis or database)
|
|
179
|
-
Rails.cache.write("phone_verification:#{user.id}", verification_code, expires_in: 10.minutes)
|
|
180
|
-
|
|
181
|
-
service = KapsoClientRuby::Rails::Service.new
|
|
182
|
-
|
|
183
|
-
begin
|
|
184
|
-
result = service.send_template_message(
|
|
185
|
-
to: user.phone_number,
|
|
186
|
-
template_name: 'phone_verification',
|
|
187
|
-
language: user.preferred_language || 'en',
|
|
188
|
-
components: [
|
|
189
|
-
{
|
|
190
|
-
type: 'body',
|
|
191
|
-
parameters: [
|
|
192
|
-
{ type: 'text', text: verification_code }
|
|
193
|
-
]
|
|
194
|
-
}
|
|
195
|
-
]
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
if result && result.dig('messages', 0, 'id')
|
|
199
|
-
WhatsappMessage.track_message(
|
|
200
|
-
user: user,
|
|
201
|
-
message_id: result.dig('messages', 0, 'id'),
|
|
202
|
-
message_type: 'phone_verification',
|
|
203
|
-
phone_number: user.phone_number,
|
|
204
|
-
messageable: user
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
Rails.logger.info "Phone verification sent to user #{user.id}"
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
rescue KapsoClientRuby::Error => e
|
|
211
|
-
Rails.logger.error "Failed to send phone verification to user #{user.id}: #{e.message}"
|
|
212
|
-
raise
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
class SendNotificationJob < ApplicationJob
|
|
218
|
-
queue_as :whatsapp_messages
|
|
219
|
-
|
|
220
|
-
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 3
|
|
221
|
-
|
|
222
|
-
def perform(user, message, message_type = 'general_notification')
|
|
223
|
-
return unless user.phone_number.present? && user.notifications_enabled?
|
|
224
|
-
|
|
225
|
-
service = KapsoMessageService.new
|
|
226
|
-
|
|
227
|
-
begin
|
|
228
|
-
result = service.send_text(
|
|
229
|
-
phone_number: user.phone_number,
|
|
230
|
-
message: message
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
if result && result.dig('messages', 0, 'id')
|
|
234
|
-
WhatsappMessage.track_message(
|
|
235
|
-
user: user,
|
|
236
|
-
message_id: result.dig('messages', 0, 'id'),
|
|
237
|
-
message_type: message_type,
|
|
238
|
-
phone_number: user.phone_number
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
Rails.logger.info "Notification sent to user #{user.id}: #{message.truncate(50)}"
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
rescue KapsoClientRuby::Error => e
|
|
245
|
-
Rails.logger.error "Failed to send notification to user #{user.id}: #{e.message}"
|
|
246
|
-
raise
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
class SendBulkNotificationJob < ApplicationJob
|
|
252
|
-
queue_as :bulk_whatsapp
|
|
253
|
-
|
|
254
|
-
# Process users in batches to avoid overwhelming the API
|
|
255
|
-
def perform(user_ids, message, message_type = 'marketing')
|
|
256
|
-
User.where(id: user_ids).with_phone_number.opted_in_for_notifications.find_each(batch_size: 50) do |user|
|
|
257
|
-
# Add delay between messages to respect rate limits
|
|
258
|
-
SendNotificationJob.set(wait: rand(1..5).seconds).perform_later(user, message, message_type)
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
class HandleIncomingMessageJob < ApplicationJob
|
|
264
|
-
queue_as :whatsapp_webhooks
|
|
265
|
-
|
|
266
|
-
def perform(message_data)
|
|
267
|
-
phone_number = message_data['from']
|
|
268
|
-
message_text = message_data.dig('text', 'body')
|
|
269
|
-
message_id = message_data['id']
|
|
270
|
-
|
|
271
|
-
# Find user by phone number
|
|
272
|
-
user = User.find_by(phone_number: phone_number)
|
|
273
|
-
|
|
274
|
-
unless user
|
|
275
|
-
Rails.logger.warn "Received message from unknown number: #{phone_number}"
|
|
276
|
-
return
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
Rails.logger.info "Received message from user #{user.id}: #{message_text}"
|
|
280
|
-
|
|
281
|
-
# Process the incoming message based on content
|
|
282
|
-
case message_text&.downcase&.strip
|
|
283
|
-
when 'stop', 'unsubscribe'
|
|
284
|
-
handle_unsubscribe_request(user)
|
|
285
|
-
when 'start', 'subscribe'
|
|
286
|
-
handle_subscribe_request(user)
|
|
287
|
-
when 'help', 'menu'
|
|
288
|
-
send_help_message(user)
|
|
289
|
-
when 'status'
|
|
290
|
-
send_account_status(user)
|
|
291
|
-
else
|
|
292
|
-
# Forward to customer service or handle as general inquiry
|
|
293
|
-
handle_general_inquiry(user, message_text, message_id)
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
private
|
|
298
|
-
|
|
299
|
-
def handle_unsubscribe_request(user)
|
|
300
|
-
user.update!(notifications_enabled: false)
|
|
301
|
-
|
|
302
|
-
service = KapsoMessageService.new
|
|
303
|
-
service.send_text(
|
|
304
|
-
phone_number: user.phone_number,
|
|
305
|
-
message: "You have been unsubscribed from notifications. Reply 'START' to re-enable."
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
Rails.logger.info "User #{user.id} unsubscribed from notifications"
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
def handle_subscribe_request(user)
|
|
312
|
-
user.update!(notifications_enabled: true)
|
|
313
|
-
|
|
314
|
-
service = KapsoMessageService.new
|
|
315
|
-
service.send_text(
|
|
316
|
-
phone_number: user.phone_number,
|
|
317
|
-
message: "Welcome back! You'll now receive notifications. Reply 'STOP' to unsubscribe."
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
Rails.logger.info "User #{user.id} subscribed to notifications"
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
def send_help_message(user)
|
|
324
|
-
help_text = <<~TEXT
|
|
325
|
-
Available commands:
|
|
326
|
-
• STOP - Unsubscribe from messages
|
|
327
|
-
• START - Subscribe to messages
|
|
328
|
-
• STATUS - Check your account status
|
|
329
|
-
• HELP - Show this menu
|
|
330
|
-
|
|
331
|
-
For support, contact us at support@example.com
|
|
332
|
-
TEXT
|
|
333
|
-
|
|
334
|
-
service = KapsoMessageService.new
|
|
335
|
-
service.send_text(phone_number: user.phone_number, message: help_text)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
def send_account_status(user)
|
|
339
|
-
recent_orders = user.orders.recent.limit(3)
|
|
340
|
-
|
|
341
|
-
status_text = "Account Status:\n"
|
|
342
|
-
status_text += "Name: #{user.name}\n"
|
|
343
|
-
status_text += "Email: #{user.email}\n"
|
|
344
|
-
status_text += "Recent orders: #{recent_orders.count}\n"
|
|
345
|
-
|
|
346
|
-
if recent_orders.any?
|
|
347
|
-
status_text += "\nLast order: ##{recent_orders.first.id} - #{recent_orders.first.status.humanize}"
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
service = KapsoMessageService.new
|
|
351
|
-
service.send_text(phone_number: user.phone_number, message: status_text)
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
def handle_general_inquiry(user, message_text, message_id)
|
|
355
|
-
# Create a support ticket or notification for customer service
|
|
356
|
-
SupportTicket.create!(
|
|
357
|
-
user: user,
|
|
358
|
-
subject: "WhatsApp Inquiry",
|
|
359
|
-
message: message_text,
|
|
360
|
-
source: 'whatsapp',
|
|
361
|
-
whatsapp_message_id: message_id
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
# Send auto-reply
|
|
365
|
-
service = KapsoMessageService.new
|
|
366
|
-
service.send_text(
|
|
367
|
-
phone_number: user.phone_number,
|
|
368
|
-
message: "Thanks for your message! Our team will get back to you soon. For urgent matters, call us at (555) 123-4567."
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
Rails.logger.info "Created support ticket for user #{user.id} from WhatsApp message"
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
class UpdateMessageStatusJob < ApplicationJob
|
|
376
|
-
queue_as :whatsapp_webhooks
|
|
377
|
-
|
|
378
|
-
def perform(status_data)
|
|
379
|
-
message_id = status_data['id']
|
|
380
|
-
status = status_data['status']
|
|
381
|
-
timestamp = status_data['timestamp']
|
|
382
|
-
|
|
383
|
-
# Update message status in database
|
|
384
|
-
WhatsappMessage.update_status_from_webhook(message_id, status, timestamp)
|
|
385
|
-
|
|
386
|
-
Rails.logger.debug "Updated message #{message_id} status to #{status}"
|
|
387
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Background jobs for WhatsApp message sending
|
|
4
|
+
# These jobs handle asynchronous message sending to improve performance
|
|
5
|
+
|
|
6
|
+
class SendWelcomeMessageJob < ApplicationJob
|
|
7
|
+
queue_as :whatsapp_messages
|
|
8
|
+
|
|
9
|
+
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 5
|
|
10
|
+
retry_on KapsoClientRuby::TemporaryError, wait: 30.seconds, attempts: 3
|
|
11
|
+
|
|
12
|
+
discard_on KapsoClientRuby::AuthenticationError
|
|
13
|
+
discard_on KapsoClientRuby::ValidationError
|
|
14
|
+
|
|
15
|
+
def perform(user)
|
|
16
|
+
return unless user.phone_number.present?
|
|
17
|
+
|
|
18
|
+
service = KapsoMessageService.new
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
result = service.send_welcome_message(user)
|
|
22
|
+
|
|
23
|
+
if result && result.dig('messages', 0, 'id')
|
|
24
|
+
# Track the message
|
|
25
|
+
WhatsappMessage.track_message(
|
|
26
|
+
user: user,
|
|
27
|
+
message_id: result.dig('messages', 0, 'id'),
|
|
28
|
+
message_type: 'welcome',
|
|
29
|
+
phone_number: user.phone_number,
|
|
30
|
+
messageable: user
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Rails.logger.info "Welcome message sent to user #{user.id}: #{result.dig('messages', 0, 'id')}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
rescue KapsoClientRuby::Error => e
|
|
37
|
+
Rails.logger.error "Failed to send welcome message to user #{user.id}: #{e.message}"
|
|
38
|
+
|
|
39
|
+
# Track failed message
|
|
40
|
+
WhatsappMessage.create!(
|
|
41
|
+
user: user,
|
|
42
|
+
message_id: "failed_#{SecureRandom.hex(8)}",
|
|
43
|
+
message_type: 'welcome',
|
|
44
|
+
phone_number: user.phone_number,
|
|
45
|
+
messageable: user,
|
|
46
|
+
status: 'failed',
|
|
47
|
+
error_message: e.message,
|
|
48
|
+
sent_at: Time.current
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
raise # Re-raise to trigger retry logic
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class SendOrderConfirmationJob < ApplicationJob
|
|
57
|
+
queue_as :whatsapp_messages
|
|
58
|
+
|
|
59
|
+
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 5
|
|
60
|
+
retry_on KapsoClientRuby::TemporaryError, wait: 30.seconds, attempts: 3
|
|
61
|
+
|
|
62
|
+
def perform(user, order)
|
|
63
|
+
return unless user.phone_number.present?
|
|
64
|
+
|
|
65
|
+
service = KapsoMessageService.new
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
result = service.send_order_confirmation(order)
|
|
69
|
+
|
|
70
|
+
if result && result.dig('messages', 0, 'id')
|
|
71
|
+
WhatsappMessage.track_message(
|
|
72
|
+
user: user,
|
|
73
|
+
message_id: result.dig('messages', 0, 'id'),
|
|
74
|
+
message_type: 'order_confirmation',
|
|
75
|
+
phone_number: user.phone_number,
|
|
76
|
+
messageable: order
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
Rails.logger.info "Order confirmation sent for order #{order.id}: #{result.dig('messages', 0, 'id')}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
rescue KapsoClientRuby::Error => e
|
|
83
|
+
Rails.logger.error "Failed to send order confirmation for order #{order.id}: #{e.message}"
|
|
84
|
+
raise
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class SendOrderStatusUpdateJob < ApplicationJob
|
|
90
|
+
queue_as :whatsapp_messages
|
|
91
|
+
|
|
92
|
+
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 5
|
|
93
|
+
|
|
94
|
+
def perform(user, order)
|
|
95
|
+
return unless user.phone_number.present?
|
|
96
|
+
|
|
97
|
+
# Don't spam users with too many updates
|
|
98
|
+
return if user.received_message_type_recently?('order_status_update', within: 1.hour)
|
|
99
|
+
|
|
100
|
+
service = KapsoClientRuby::Rails::Service.new
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
# Use different templates based on order status
|
|
104
|
+
template_name = case order.status
|
|
105
|
+
when 'confirmed' then 'order_confirmed'
|
|
106
|
+
when 'processing' then 'order_processing'
|
|
107
|
+
when 'shipped' then 'order_shipped'
|
|
108
|
+
when 'delivered' then 'order_delivered'
|
|
109
|
+
when 'cancelled' then 'order_cancelled'
|
|
110
|
+
else 'order_status_update'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
components = build_order_status_components(order, user)
|
|
114
|
+
|
|
115
|
+
result = service.send_template_message(
|
|
116
|
+
to: user.phone_number,
|
|
117
|
+
template_name: template_name,
|
|
118
|
+
language: user.preferred_language || 'en',
|
|
119
|
+
components: components
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if result && result.dig('messages', 0, 'id')
|
|
123
|
+
WhatsappMessage.track_message(
|
|
124
|
+
user: user,
|
|
125
|
+
message_id: result.dig('messages', 0, 'id'),
|
|
126
|
+
message_type: 'order_status_update',
|
|
127
|
+
phone_number: user.phone_number,
|
|
128
|
+
messageable: order
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
Rails.logger.info "Order status update sent for order #{order.id}: #{order.status}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
rescue KapsoClientRuby::Error => e
|
|
135
|
+
Rails.logger.error "Failed to send order status update for order #{order.id}: #{e.message}"
|
|
136
|
+
raise
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def build_order_status_components(order, user)
|
|
143
|
+
[
|
|
144
|
+
{
|
|
145
|
+
type: 'body',
|
|
146
|
+
parameters: [
|
|
147
|
+
{ type: 'text', text: user.first_name || 'Customer' },
|
|
148
|
+
{ type: 'text', text: order.id.to_s },
|
|
149
|
+
{ type: 'text', text: order.status.humanize },
|
|
150
|
+
{ type: 'text', text: order.formatted_total }
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
].tap do |components|
|
|
154
|
+
# Add tracking info for shipped orders
|
|
155
|
+
if order.shipped? && order.tracking_number.present?
|
|
156
|
+
components << {
|
|
157
|
+
type: 'body',
|
|
158
|
+
parameters: [
|
|
159
|
+
{ type: 'text', text: order.tracking_number }
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
class SendPhoneVerificationJob < ApplicationJob
|
|
168
|
+
queue_as :whatsapp_messages
|
|
169
|
+
|
|
170
|
+
retry_on KapsoClientRuby::Error, wait: 30.seconds, attempts: 3
|
|
171
|
+
|
|
172
|
+
def perform(user)
|
|
173
|
+
return unless user.phone_number.present?
|
|
174
|
+
|
|
175
|
+
# Generate verification code
|
|
176
|
+
verification_code = rand(100000..999999).to_s
|
|
177
|
+
|
|
178
|
+
# Store verification code (you might use Redis or database)
|
|
179
|
+
Rails.cache.write("phone_verification:#{user.id}", verification_code, expires_in: 10.minutes)
|
|
180
|
+
|
|
181
|
+
service = KapsoClientRuby::Rails::Service.new
|
|
182
|
+
|
|
183
|
+
begin
|
|
184
|
+
result = service.send_template_message(
|
|
185
|
+
to: user.phone_number,
|
|
186
|
+
template_name: 'phone_verification',
|
|
187
|
+
language: user.preferred_language || 'en',
|
|
188
|
+
components: [
|
|
189
|
+
{
|
|
190
|
+
type: 'body',
|
|
191
|
+
parameters: [
|
|
192
|
+
{ type: 'text', text: verification_code }
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if result && result.dig('messages', 0, 'id')
|
|
199
|
+
WhatsappMessage.track_message(
|
|
200
|
+
user: user,
|
|
201
|
+
message_id: result.dig('messages', 0, 'id'),
|
|
202
|
+
message_type: 'phone_verification',
|
|
203
|
+
phone_number: user.phone_number,
|
|
204
|
+
messageable: user
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
Rails.logger.info "Phone verification sent to user #{user.id}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
rescue KapsoClientRuby::Error => e
|
|
211
|
+
Rails.logger.error "Failed to send phone verification to user #{user.id}: #{e.message}"
|
|
212
|
+
raise
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class SendNotificationJob < ApplicationJob
|
|
218
|
+
queue_as :whatsapp_messages
|
|
219
|
+
|
|
220
|
+
retry_on KapsoClientRuby::RateLimitError, wait: :exponentially_longer, attempts: 3
|
|
221
|
+
|
|
222
|
+
def perform(user, message, message_type = 'general_notification')
|
|
223
|
+
return unless user.phone_number.present? && user.notifications_enabled?
|
|
224
|
+
|
|
225
|
+
service = KapsoMessageService.new
|
|
226
|
+
|
|
227
|
+
begin
|
|
228
|
+
result = service.send_text(
|
|
229
|
+
phone_number: user.phone_number,
|
|
230
|
+
message: message
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if result && result.dig('messages', 0, 'id')
|
|
234
|
+
WhatsappMessage.track_message(
|
|
235
|
+
user: user,
|
|
236
|
+
message_id: result.dig('messages', 0, 'id'),
|
|
237
|
+
message_type: message_type,
|
|
238
|
+
phone_number: user.phone_number
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
Rails.logger.info "Notification sent to user #{user.id}: #{message.truncate(50)}"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
rescue KapsoClientRuby::Error => e
|
|
245
|
+
Rails.logger.error "Failed to send notification to user #{user.id}: #{e.message}"
|
|
246
|
+
raise
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
class SendBulkNotificationJob < ApplicationJob
|
|
252
|
+
queue_as :bulk_whatsapp
|
|
253
|
+
|
|
254
|
+
# Process users in batches to avoid overwhelming the API
|
|
255
|
+
def perform(user_ids, message, message_type = 'marketing')
|
|
256
|
+
User.where(id: user_ids).with_phone_number.opted_in_for_notifications.find_each(batch_size: 50) do |user|
|
|
257
|
+
# Add delay between messages to respect rate limits
|
|
258
|
+
SendNotificationJob.set(wait: rand(1..5).seconds).perform_later(user, message, message_type)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
class HandleIncomingMessageJob < ApplicationJob
|
|
264
|
+
queue_as :whatsapp_webhooks
|
|
265
|
+
|
|
266
|
+
def perform(message_data)
|
|
267
|
+
phone_number = message_data['from']
|
|
268
|
+
message_text = message_data.dig('text', 'body')
|
|
269
|
+
message_id = message_data['id']
|
|
270
|
+
|
|
271
|
+
# Find user by phone number
|
|
272
|
+
user = User.find_by(phone_number: phone_number)
|
|
273
|
+
|
|
274
|
+
unless user
|
|
275
|
+
Rails.logger.warn "Received message from unknown number: #{phone_number}"
|
|
276
|
+
return
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
Rails.logger.info "Received message from user #{user.id}: #{message_text}"
|
|
280
|
+
|
|
281
|
+
# Process the incoming message based on content
|
|
282
|
+
case message_text&.downcase&.strip
|
|
283
|
+
when 'stop', 'unsubscribe'
|
|
284
|
+
handle_unsubscribe_request(user)
|
|
285
|
+
when 'start', 'subscribe'
|
|
286
|
+
handle_subscribe_request(user)
|
|
287
|
+
when 'help', 'menu'
|
|
288
|
+
send_help_message(user)
|
|
289
|
+
when 'status'
|
|
290
|
+
send_account_status(user)
|
|
291
|
+
else
|
|
292
|
+
# Forward to customer service or handle as general inquiry
|
|
293
|
+
handle_general_inquiry(user, message_text, message_id)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
private
|
|
298
|
+
|
|
299
|
+
def handle_unsubscribe_request(user)
|
|
300
|
+
user.update!(notifications_enabled: false)
|
|
301
|
+
|
|
302
|
+
service = KapsoMessageService.new
|
|
303
|
+
service.send_text(
|
|
304
|
+
phone_number: user.phone_number,
|
|
305
|
+
message: "You have been unsubscribed from notifications. Reply 'START' to re-enable."
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
Rails.logger.info "User #{user.id} unsubscribed from notifications"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def handle_subscribe_request(user)
|
|
312
|
+
user.update!(notifications_enabled: true)
|
|
313
|
+
|
|
314
|
+
service = KapsoMessageService.new
|
|
315
|
+
service.send_text(
|
|
316
|
+
phone_number: user.phone_number,
|
|
317
|
+
message: "Welcome back! You'll now receive notifications. Reply 'STOP' to unsubscribe."
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
Rails.logger.info "User #{user.id} subscribed to notifications"
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def send_help_message(user)
|
|
324
|
+
help_text = <<~TEXT
|
|
325
|
+
Available commands:
|
|
326
|
+
• STOP - Unsubscribe from messages
|
|
327
|
+
• START - Subscribe to messages
|
|
328
|
+
• STATUS - Check your account status
|
|
329
|
+
• HELP - Show this menu
|
|
330
|
+
|
|
331
|
+
For support, contact us at support@example.com
|
|
332
|
+
TEXT
|
|
333
|
+
|
|
334
|
+
service = KapsoMessageService.new
|
|
335
|
+
service.send_text(phone_number: user.phone_number, message: help_text)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def send_account_status(user)
|
|
339
|
+
recent_orders = user.orders.recent.limit(3)
|
|
340
|
+
|
|
341
|
+
status_text = "Account Status:\n"
|
|
342
|
+
status_text += "Name: #{user.name}\n"
|
|
343
|
+
status_text += "Email: #{user.email}\n"
|
|
344
|
+
status_text += "Recent orders: #{recent_orders.count}\n"
|
|
345
|
+
|
|
346
|
+
if recent_orders.any?
|
|
347
|
+
status_text += "\nLast order: ##{recent_orders.first.id} - #{recent_orders.first.status.humanize}"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
service = KapsoMessageService.new
|
|
351
|
+
service.send_text(phone_number: user.phone_number, message: status_text)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def handle_general_inquiry(user, message_text, message_id)
|
|
355
|
+
# Create a support ticket or notification for customer service
|
|
356
|
+
SupportTicket.create!(
|
|
357
|
+
user: user,
|
|
358
|
+
subject: "WhatsApp Inquiry",
|
|
359
|
+
message: message_text,
|
|
360
|
+
source: 'whatsapp',
|
|
361
|
+
whatsapp_message_id: message_id
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Send auto-reply
|
|
365
|
+
service = KapsoMessageService.new
|
|
366
|
+
service.send_text(
|
|
367
|
+
phone_number: user.phone_number,
|
|
368
|
+
message: "Thanks for your message! Our team will get back to you soon. For urgent matters, call us at (555) 123-4567."
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
Rails.logger.info "Created support ticket for user #{user.id} from WhatsApp message"
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
class UpdateMessageStatusJob < ApplicationJob
|
|
376
|
+
queue_as :whatsapp_webhooks
|
|
377
|
+
|
|
378
|
+
def perform(status_data)
|
|
379
|
+
message_id = status_data['id']
|
|
380
|
+
status = status_data['status']
|
|
381
|
+
timestamp = status_data['timestamp']
|
|
382
|
+
|
|
383
|
+
# Update message status in database
|
|
384
|
+
WhatsappMessage.update_status_from_webhook(message_id, status, timestamp)
|
|
385
|
+
|
|
386
|
+
Rails.logger.debug "Updated message #{message_id} status to #{status}"
|
|
387
|
+
end
|
|
388
388
|
end
|