kapso-client-ruby 1.0.0 → 1.0.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/CHANGELOG.md +2 -2
- data/RAILS_INTEGRATION.md +478 -0
- data/README.md +33 -15
- data/examples/rails/jobs.rb +388 -0
- data/examples/rails/models.rb +240 -0
- data/examples/rails/notifications_controller.rb +227 -0
- data/examples/template_management.rb +10 -10
- data/lib/kapso_client_ruby/client.rb +2 -2
- data/lib/kapso_client_ruby/rails/generators/install_generator.rb +76 -0
- data/lib/kapso_client_ruby/rails/generators/templates/env.erb +21 -0
- data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +33 -0
- data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +138 -0
- data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +62 -0
- data/lib/kapso_client_ruby/rails/railtie.rb +55 -0
- data/lib/kapso_client_ruby/rails/service.rb +189 -0
- data/lib/kapso_client_ruby/rails/tasks.rake +167 -0
- data/lib/kapso_client_ruby/version.rb +1 -1
- data/lib/kapso_client_ruby.rb +6 -0
- data/scripts/kapso_template_finder.rb +2 -2
- data/scripts/sdk_setup.rb +15 -15
- metadata +13 -1
@@ -0,0 +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
|
388
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example Rails model integration showing WhatsApp messaging hooks
|
4
|
+
class User < ApplicationRecord
|
5
|
+
has_many :orders, dependent: :destroy
|
6
|
+
has_many :whatsapp_messages, dependent: :destroy
|
7
|
+
|
8
|
+
# Validations
|
9
|
+
validates :email, presence: true, uniqueness: true
|
10
|
+
validates :phone_number, format: { with: /\A\+\d{10,15}\z/, message: "must be in E.164 format" }, allow_blank: true
|
11
|
+
validates :preferred_language, inclusion: { in: %w[en es fr], message: "must be a supported language" }
|
12
|
+
|
13
|
+
# Callbacks for WhatsApp integration
|
14
|
+
after_create :send_welcome_message, if: :phone_number?
|
15
|
+
after_update :send_phone_verification, if: :phone_number_changed?
|
16
|
+
|
17
|
+
# Scopes
|
18
|
+
scope :with_phone_number, -> { where.not(phone_number: nil) }
|
19
|
+
scope :opted_in_for_notifications, -> { where(notifications_enabled: true) }
|
20
|
+
|
21
|
+
# Instance methods
|
22
|
+
|
23
|
+
def send_welcome_message
|
24
|
+
SendWelcomeMessageJob.perform_later(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_phone_verification
|
28
|
+
return unless phone_number.present?
|
29
|
+
|
30
|
+
SendPhoneVerificationJob.perform_later(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def send_notification(message, type: 'general')
|
34
|
+
return unless phone_number.present? && notifications_enabled?
|
35
|
+
|
36
|
+
SendNotificationJob.perform_later(self, message, type)
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_order_confirmation(order)
|
40
|
+
return unless phone_number.present?
|
41
|
+
|
42
|
+
SendOrderConfirmationJob.perform_later(self, order)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Format phone number for WhatsApp (ensure E.164 format)
|
46
|
+
def formatted_phone_number
|
47
|
+
return nil unless phone_number.present?
|
48
|
+
|
49
|
+
# Remove any non-digit characters except the leading +
|
50
|
+
cleaned = phone_number.gsub(/[^\d+]/, '')
|
51
|
+
|
52
|
+
# Ensure it starts with +
|
53
|
+
cleaned.start_with?('+') ? cleaned : "+#{cleaned}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check if user can receive WhatsApp messages
|
57
|
+
def can_receive_whatsapp?
|
58
|
+
phone_number.present? && notifications_enabled?
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get recent WhatsApp messages
|
62
|
+
def recent_whatsapp_messages(limit = 10)
|
63
|
+
whatsapp_messages.order(created_at: :desc).limit(limit)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if user has received a specific message type recently
|
67
|
+
def received_message_type_recently?(message_type, within: 24.hours)
|
68
|
+
whatsapp_messages
|
69
|
+
.where(message_type: message_type)
|
70
|
+
.where('created_at > ?', within.ago)
|
71
|
+
.exists?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def phone_number_changed?
|
77
|
+
saved_change_to_phone_number? && phone_number.present?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Example Order model with WhatsApp integration
|
82
|
+
class Order < ApplicationRecord
|
83
|
+
belongs_to :user
|
84
|
+
has_many :order_items, dependent: :destroy
|
85
|
+
has_many :whatsapp_messages, through: :user
|
86
|
+
|
87
|
+
# Order statuses
|
88
|
+
enum status: {
|
89
|
+
pending: 0,
|
90
|
+
confirmed: 1,
|
91
|
+
processing: 2,
|
92
|
+
shipped: 3,
|
93
|
+
delivered: 4,
|
94
|
+
cancelled: 5
|
95
|
+
}
|
96
|
+
|
97
|
+
# Callbacks for WhatsApp notifications
|
98
|
+
after_create :send_order_confirmation
|
99
|
+
after_update :send_status_update, if: :saved_change_to_status?
|
100
|
+
|
101
|
+
# Instance methods
|
102
|
+
|
103
|
+
def send_order_confirmation
|
104
|
+
return unless user.can_receive_whatsapp?
|
105
|
+
|
106
|
+
SendOrderConfirmationJob.perform_later(user, self)
|
107
|
+
end
|
108
|
+
|
109
|
+
def send_status_update
|
110
|
+
return unless user.can_receive_whatsapp?
|
111
|
+
|
112
|
+
# Don't send updates for pending status (already sent confirmation)
|
113
|
+
return if status == 'pending'
|
114
|
+
|
115
|
+
SendOrderStatusUpdateJob.perform_later(user, self)
|
116
|
+
end
|
117
|
+
|
118
|
+
def total_amount_in_cents
|
119
|
+
(total_amount * 100).to_i
|
120
|
+
end
|
121
|
+
|
122
|
+
def formatted_total
|
123
|
+
"$#{'%.2f' % total_amount}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def estimated_delivery_date
|
127
|
+
return nil unless shipped?
|
128
|
+
|
129
|
+
shipped_at + 3.days # Example: 3 days for delivery
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Example WhatsAppMessage model for tracking sent messages
|
134
|
+
class WhatsappMessage < ApplicationRecord
|
135
|
+
belongs_to :user
|
136
|
+
belongs_to :messageable, polymorphic: true, optional: true # Could be Order, User, etc.
|
137
|
+
|
138
|
+
# Message types
|
139
|
+
enum message_type: {
|
140
|
+
welcome: 0,
|
141
|
+
order_confirmation: 1,
|
142
|
+
order_status_update: 2,
|
143
|
+
phone_verification: 3,
|
144
|
+
general_notification: 4,
|
145
|
+
marketing: 5,
|
146
|
+
support: 6
|
147
|
+
}
|
148
|
+
|
149
|
+
# Message status from WhatsApp API
|
150
|
+
enum status: {
|
151
|
+
sent: 0,
|
152
|
+
delivered: 1,
|
153
|
+
read: 2,
|
154
|
+
failed: 3
|
155
|
+
}
|
156
|
+
|
157
|
+
# Validations
|
158
|
+
validates :message_id, presence: true, uniqueness: true
|
159
|
+
validates :phone_number, presence: true
|
160
|
+
validates :message_type, presence: true
|
161
|
+
|
162
|
+
# Scopes
|
163
|
+
scope :recent, -> { order(created_at: :desc) }
|
164
|
+
scope :successful, -> { where(status: [:sent, :delivered, :read]) }
|
165
|
+
scope :failed, -> { where(status: :failed) }
|
166
|
+
|
167
|
+
# Class methods
|
168
|
+
|
169
|
+
def self.track_message(user:, message_id:, message_type:, phone_number:, messageable: nil)
|
170
|
+
create!(
|
171
|
+
user: user,
|
172
|
+
message_id: message_id,
|
173
|
+
message_type: message_type,
|
174
|
+
phone_number: phone_number,
|
175
|
+
messageable: messageable,
|
176
|
+
status: :sent,
|
177
|
+
sent_at: Time.current
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.update_status_from_webhook(message_id, new_status, timestamp = nil)
|
182
|
+
message = find_by(message_id: message_id)
|
183
|
+
return unless message
|
184
|
+
|
185
|
+
status_mapping = {
|
186
|
+
'sent' => :sent,
|
187
|
+
'delivered' => :delivered,
|
188
|
+
'read' => :read,
|
189
|
+
'failed' => :failed
|
190
|
+
}
|
191
|
+
|
192
|
+
mapped_status = status_mapping[new_status.to_s.downcase]
|
193
|
+
return unless mapped_status
|
194
|
+
|
195
|
+
message.update!(
|
196
|
+
status: mapped_status,
|
197
|
+
status_updated_at: timestamp ? Time.at(timestamp) : Time.current
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Instance methods
|
202
|
+
|
203
|
+
def delivered?
|
204
|
+
%w[delivered read].include?(status)
|
205
|
+
end
|
206
|
+
|
207
|
+
def failed?
|
208
|
+
status == 'failed'
|
209
|
+
end
|
210
|
+
|
211
|
+
def delivery_time
|
212
|
+
return nil unless delivered? && sent_at.present? && status_updated_at.present?
|
213
|
+
|
214
|
+
status_updated_at - sent_at
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Example migration for WhatsApp messages tracking
|
219
|
+
#
|
220
|
+
# class CreateWhatsappMessages < ActiveRecord::Migration[8.0]
|
221
|
+
# def change
|
222
|
+
# create_table :whatsapp_messages do |t|
|
223
|
+
# t.references :user, null: false, foreign_key: true
|
224
|
+
# t.references :messageable, polymorphic: true, null: true
|
225
|
+
# t.string :message_id, null: false, index: { unique: true }
|
226
|
+
# t.string :phone_number, null: false
|
227
|
+
# t.integer :message_type, null: false
|
228
|
+
# t.integer :status, default: 0
|
229
|
+
# t.datetime :sent_at
|
230
|
+
# t.datetime :status_updated_at
|
231
|
+
# t.text :error_message
|
232
|
+
# t.json :metadata # Store additional message data
|
233
|
+
#
|
234
|
+
# t.timestamps
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# add_index :whatsapp_messages, [:user_id, :message_type]
|
238
|
+
# add_index :whatsapp_messages, [:message_type, :status]
|
239
|
+
# end
|
240
|
+
# end
|