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
|
@@ -1,284 +1,284 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module KapsoClientRuby
|
|
4
|
-
module Resources
|
|
5
|
-
class Templates
|
|
6
|
-
def initialize(client)
|
|
7
|
-
@client = client
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
# List templates for a business account
|
|
11
|
-
def list(business_account_id:, limit: nil, after: nil, before: nil,
|
|
12
|
-
name: nil, status: nil, category: nil, language: nil,
|
|
13
|
-
name_or_content: nil, quality_score: nil)
|
|
14
|
-
query_params = {
|
|
15
|
-
limit: limit,
|
|
16
|
-
after: after,
|
|
17
|
-
before: before,
|
|
18
|
-
name: name,
|
|
19
|
-
status: status,
|
|
20
|
-
category: category,
|
|
21
|
-
language: language,
|
|
22
|
-
name_or_content: name_or_content,
|
|
23
|
-
quality_score: quality_score
|
|
24
|
-
}.compact
|
|
25
|
-
|
|
26
|
-
response = @client.request(:get, "#{business_account_id}/message_templates",
|
|
27
|
-
query: query_params, response_type: :json)
|
|
28
|
-
Types::PagedResponse.new(response, Types::MessageTemplate)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Get a specific template
|
|
32
|
-
def get(business_account_id:, template_id:, fields: nil)
|
|
33
|
-
query_params = {}
|
|
34
|
-
query_params[:fields] = fields if fields
|
|
35
|
-
|
|
36
|
-
response = @client.request(:get, "#{business_account_id}/message_templates/#{template_id}",
|
|
37
|
-
query: query_params, response_type: :json)
|
|
38
|
-
Types::MessageTemplate.new(response)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Create a new template
|
|
42
|
-
def create(business_account_id:, name:, language:, category:, components:,
|
|
43
|
-
allow_category_change: nil, message_send_ttl_seconds: nil)
|
|
44
|
-
validate_template_data(name: name, language: language, category: category, components: components)
|
|
45
|
-
|
|
46
|
-
payload = {
|
|
47
|
-
name: name,
|
|
48
|
-
language: language,
|
|
49
|
-
category: category,
|
|
50
|
-
components: normalize_components(components)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
payload[:allow_category_change] = allow_category_change unless allow_category_change.nil?
|
|
54
|
-
payload[:message_send_ttl_seconds] = message_send_ttl_seconds if message_send_ttl_seconds
|
|
55
|
-
|
|
56
|
-
response = @client.request(:post, "#{business_account_id}/message_templates",
|
|
57
|
-
body: payload.to_json, response_type: :json)
|
|
58
|
-
Types::TemplateCreateResponse.new(response)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Update a template
|
|
62
|
-
def update(business_account_id:, template_id:, category: nil, components: nil)
|
|
63
|
-
payload = {}
|
|
64
|
-
payload[:category] = category if category
|
|
65
|
-
payload[:components] = normalize_components(components) if components
|
|
66
|
-
|
|
67
|
-
return if payload.empty?
|
|
68
|
-
|
|
69
|
-
response = @client.request(:post, "#{business_account_id}/message_templates/#{template_id}",
|
|
70
|
-
body: payload.to_json, response_type: :json)
|
|
71
|
-
Types::GraphSuccessResponse.new(response)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Delete a template
|
|
75
|
-
def delete(business_account_id:, name: nil, template_id: nil, hsm_id: nil, language: nil)
|
|
76
|
-
if template_id
|
|
77
|
-
# Delete by template ID
|
|
78
|
-
response = @client.request(:delete, "#{business_account_id}/message_templates/#{template_id}",
|
|
79
|
-
response_type: :json)
|
|
80
|
-
elsif name
|
|
81
|
-
# Delete by name and language
|
|
82
|
-
query_params = { name: name }
|
|
83
|
-
query_params[:language] = language if language
|
|
84
|
-
query_params[:hsm_id] = hsm_id if hsm_id
|
|
85
|
-
|
|
86
|
-
response = @client.request(:delete, "#{business_account_id}/message_templates",
|
|
87
|
-
query: query_params, response_type: :json)
|
|
88
|
-
else
|
|
89
|
-
raise ArgumentError, 'Must provide either template_id or name'
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
Types::GraphSuccessResponse.new(response)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Template builder helpers
|
|
96
|
-
def build_text_component(text:, example: nil)
|
|
97
|
-
component = { type: 'BODY', text: text }
|
|
98
|
-
component[:example] = example if example
|
|
99
|
-
component
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def build_header_component(type:, text: nil, image: nil, video: nil,
|
|
103
|
-
document: nil, example: nil)
|
|
104
|
-
component = { type: 'HEADER', format: type.upcase }
|
|
105
|
-
|
|
106
|
-
case type.upcase
|
|
107
|
-
when 'TEXT'
|
|
108
|
-
component[:text] = text if text
|
|
109
|
-
when 'IMAGE'
|
|
110
|
-
component[:example] = { header_handle: [image] } if image
|
|
111
|
-
when 'VIDEO'
|
|
112
|
-
component[:example] = { header_handle: [video] } if video
|
|
113
|
-
when 'DOCUMENT'
|
|
114
|
-
component[:example] = { header_handle: [document] } if document
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
component[:example] = example if example
|
|
118
|
-
component
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def build_footer_component(text: nil, code_expiration_minutes: nil)
|
|
122
|
-
component = { type: 'FOOTER' }
|
|
123
|
-
component[:text] = text if text
|
|
124
|
-
component[:code_expiration_minutes] = code_expiration_minutes if code_expiration_minutes
|
|
125
|
-
component
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def build_buttons_component(buttons:)
|
|
129
|
-
{
|
|
130
|
-
type: 'BUTTONS',
|
|
131
|
-
buttons: buttons.map { |btn| normalize_button(btn) }
|
|
132
|
-
}
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def build_button(type:, text: nil, url: nil, phone_number: nil,
|
|
136
|
-
otp_type: nil, autofill_text: nil, package_name: nil,
|
|
137
|
-
signature_hash: nil)
|
|
138
|
-
button = { type: type.upcase }
|
|
139
|
-
|
|
140
|
-
case type.upcase
|
|
141
|
-
when 'QUICK_REPLY'
|
|
142
|
-
button[:text] = text if text
|
|
143
|
-
when 'URL'
|
|
144
|
-
button[:text] = text if text
|
|
145
|
-
button[:url] = url if url
|
|
146
|
-
when 'PHONE_NUMBER'
|
|
147
|
-
button[:text] = text if text
|
|
148
|
-
button[:phone_number] = phone_number if phone_number
|
|
149
|
-
when 'OTP'
|
|
150
|
-
button[:otp_type] = otp_type if otp_type
|
|
151
|
-
button[:text] = text if text
|
|
152
|
-
button[:autofill_text] = autofill_text if autofill_text
|
|
153
|
-
button[:package_name] = package_name if package_name
|
|
154
|
-
button[:signature_hash] = signature_hash if signature_hash
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
button
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Authentication template builder
|
|
161
|
-
def build_authentication_template(name:, language:, ttl_seconds: 60,
|
|
162
|
-
add_security_recommendation: true,
|
|
163
|
-
code_expiration_minutes: 10,
|
|
164
|
-
otp_type: 'COPY_CODE')
|
|
165
|
-
components = []
|
|
166
|
-
|
|
167
|
-
# Body component with security recommendation
|
|
168
|
-
body_component = { type: 'BODY' }
|
|
169
|
-
body_component[:add_security_recommendation] = add_security_recommendation
|
|
170
|
-
components << body_component
|
|
171
|
-
|
|
172
|
-
# Footer component with expiration
|
|
173
|
-
if code_expiration_minutes
|
|
174
|
-
components << build_footer_component(code_expiration_minutes: code_expiration_minutes)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# OTP button
|
|
178
|
-
components << build_buttons_component(
|
|
179
|
-
buttons: [build_button(type: 'OTP', otp_type: otp_type)]
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
name: name,
|
|
184
|
-
language: language,
|
|
185
|
-
category: 'AUTHENTICATION',
|
|
186
|
-
message_send_ttl_seconds: ttl_seconds,
|
|
187
|
-
components: components
|
|
188
|
-
}
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Marketing template builder
|
|
192
|
-
def build_marketing_template(name:, language:, header: nil, body:, footer: nil,
|
|
193
|
-
buttons: nil, body_example: nil)
|
|
194
|
-
components = []
|
|
195
|
-
|
|
196
|
-
# Header component
|
|
197
|
-
components << header if header
|
|
198
|
-
|
|
199
|
-
# Body component
|
|
200
|
-
body_component = build_text_component(text: body, example: body_example)
|
|
201
|
-
components << body_component
|
|
202
|
-
|
|
203
|
-
# Footer component
|
|
204
|
-
components << build_footer_component(text: footer) if footer
|
|
205
|
-
|
|
206
|
-
# Buttons component
|
|
207
|
-
components << build_buttons_component(buttons: buttons) if buttons
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
name: name,
|
|
211
|
-
language: language,
|
|
212
|
-
category: 'MARKETING',
|
|
213
|
-
components: components
|
|
214
|
-
}
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Utility template builder
|
|
218
|
-
def build_utility_template(name:, language:, body:, header: nil, footer: nil,
|
|
219
|
-
buttons: nil, body_example: nil)
|
|
220
|
-
components = []
|
|
221
|
-
|
|
222
|
-
# Header component
|
|
223
|
-
components << header if header
|
|
224
|
-
|
|
225
|
-
# Body component
|
|
226
|
-
body_component = build_text_component(text: body, example: body_example)
|
|
227
|
-
components << body_component
|
|
228
|
-
|
|
229
|
-
# Footer component
|
|
230
|
-
components << build_footer_component(text: footer) if footer
|
|
231
|
-
|
|
232
|
-
# Buttons component
|
|
233
|
-
components << build_buttons_component(buttons: buttons) if buttons
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
name: name,
|
|
237
|
-
language: language,
|
|
238
|
-
category: 'UTILITY',
|
|
239
|
-
components: components
|
|
240
|
-
}
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
private
|
|
244
|
-
|
|
245
|
-
def validate_template_data(name:, language:, category:, components:)
|
|
246
|
-
raise ArgumentError, 'Template name cannot be empty' if name.nil? || name.strip.empty?
|
|
247
|
-
raise ArgumentError, 'Language cannot be empty' if language.nil? || language.strip.empty?
|
|
248
|
-
raise ArgumentError, 'Category cannot be empty' if category.nil? || category.strip.empty?
|
|
249
|
-
raise ArgumentError, 'Components cannot be empty' if components.nil? || components.empty?
|
|
250
|
-
|
|
251
|
-
# Validate category
|
|
252
|
-
valid_categories = Types::TEMPLATE_CATEGORIES
|
|
253
|
-
unless valid_categories.include?(category.upcase)
|
|
254
|
-
raise ArgumentError, "Invalid category '#{category}'. Must be one of: #{valid_categories.join(', ')}"
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
# Validate components structure
|
|
258
|
-
components.each_with_index do |component, index|
|
|
259
|
-
unless component.is_a?(Hash) && component[:type]
|
|
260
|
-
raise ArgumentError, "Component at index #{index} must be a Hash with :type key"
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def normalize_components(components)
|
|
266
|
-
components.map { |component| normalize_component(component) }
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def normalize_component(component)
|
|
270
|
-
# Ensure component keys are strings for API compatibility
|
|
271
|
-
normalized = {}
|
|
272
|
-
component.each { |key, value| normalized[key.to_s] = value }
|
|
273
|
-
normalized
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def normalize_button(button)
|
|
277
|
-
# Ensure button keys are strings for API compatibility
|
|
278
|
-
normalized = {}
|
|
279
|
-
button.each { |key, value| normalized[key.to_s] = value }
|
|
280
|
-
normalized
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KapsoClientRuby
|
|
4
|
+
module Resources
|
|
5
|
+
class Templates
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List templates for a business account
|
|
11
|
+
def list(business_account_id:, limit: nil, after: nil, before: nil,
|
|
12
|
+
name: nil, status: nil, category: nil, language: nil,
|
|
13
|
+
name_or_content: nil, quality_score: nil)
|
|
14
|
+
query_params = {
|
|
15
|
+
limit: limit,
|
|
16
|
+
after: after,
|
|
17
|
+
before: before,
|
|
18
|
+
name: name,
|
|
19
|
+
status: status,
|
|
20
|
+
category: category,
|
|
21
|
+
language: language,
|
|
22
|
+
name_or_content: name_or_content,
|
|
23
|
+
quality_score: quality_score
|
|
24
|
+
}.compact
|
|
25
|
+
|
|
26
|
+
response = @client.request(:get, "#{business_account_id}/message_templates",
|
|
27
|
+
query: query_params, response_type: :json)
|
|
28
|
+
Types::PagedResponse.new(response, Types::MessageTemplate)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get a specific template
|
|
32
|
+
def get(business_account_id:, template_id:, fields: nil)
|
|
33
|
+
query_params = {}
|
|
34
|
+
query_params[:fields] = fields if fields
|
|
35
|
+
|
|
36
|
+
response = @client.request(:get, "#{business_account_id}/message_templates/#{template_id}",
|
|
37
|
+
query: query_params, response_type: :json)
|
|
38
|
+
Types::MessageTemplate.new(response)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Create a new template
|
|
42
|
+
def create(business_account_id:, name:, language:, category:, components:,
|
|
43
|
+
allow_category_change: nil, message_send_ttl_seconds: nil)
|
|
44
|
+
validate_template_data(name: name, language: language, category: category, components: components)
|
|
45
|
+
|
|
46
|
+
payload = {
|
|
47
|
+
name: name,
|
|
48
|
+
language: language,
|
|
49
|
+
category: category,
|
|
50
|
+
components: normalize_components(components)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
payload[:allow_category_change] = allow_category_change unless allow_category_change.nil?
|
|
54
|
+
payload[:message_send_ttl_seconds] = message_send_ttl_seconds if message_send_ttl_seconds
|
|
55
|
+
|
|
56
|
+
response = @client.request(:post, "#{business_account_id}/message_templates",
|
|
57
|
+
body: payload.to_json, response_type: :json)
|
|
58
|
+
Types::TemplateCreateResponse.new(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Update a template
|
|
62
|
+
def update(business_account_id:, template_id:, category: nil, components: nil)
|
|
63
|
+
payload = {}
|
|
64
|
+
payload[:category] = category if category
|
|
65
|
+
payload[:components] = normalize_components(components) if components
|
|
66
|
+
|
|
67
|
+
return if payload.empty?
|
|
68
|
+
|
|
69
|
+
response = @client.request(:post, "#{business_account_id}/message_templates/#{template_id}",
|
|
70
|
+
body: payload.to_json, response_type: :json)
|
|
71
|
+
Types::GraphSuccessResponse.new(response)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Delete a template
|
|
75
|
+
def delete(business_account_id:, name: nil, template_id: nil, hsm_id: nil, language: nil)
|
|
76
|
+
if template_id
|
|
77
|
+
# Delete by template ID
|
|
78
|
+
response = @client.request(:delete, "#{business_account_id}/message_templates/#{template_id}",
|
|
79
|
+
response_type: :json)
|
|
80
|
+
elsif name
|
|
81
|
+
# Delete by name and language
|
|
82
|
+
query_params = { name: name }
|
|
83
|
+
query_params[:language] = language if language
|
|
84
|
+
query_params[:hsm_id] = hsm_id if hsm_id
|
|
85
|
+
|
|
86
|
+
response = @client.request(:delete, "#{business_account_id}/message_templates",
|
|
87
|
+
query: query_params, response_type: :json)
|
|
88
|
+
else
|
|
89
|
+
raise ArgumentError, 'Must provide either template_id or name'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Types::GraphSuccessResponse.new(response)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Template builder helpers
|
|
96
|
+
def build_text_component(text:, example: nil)
|
|
97
|
+
component = { type: 'BODY', text: text }
|
|
98
|
+
component[:example] = example if example
|
|
99
|
+
component
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_header_component(type:, text: nil, image: nil, video: nil,
|
|
103
|
+
document: nil, example: nil)
|
|
104
|
+
component = { type: 'HEADER', format: type.upcase }
|
|
105
|
+
|
|
106
|
+
case type.upcase
|
|
107
|
+
when 'TEXT'
|
|
108
|
+
component[:text] = text if text
|
|
109
|
+
when 'IMAGE'
|
|
110
|
+
component[:example] = { header_handle: [image] } if image
|
|
111
|
+
when 'VIDEO'
|
|
112
|
+
component[:example] = { header_handle: [video] } if video
|
|
113
|
+
when 'DOCUMENT'
|
|
114
|
+
component[:example] = { header_handle: [document] } if document
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
component[:example] = example if example
|
|
118
|
+
component
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def build_footer_component(text: nil, code_expiration_minutes: nil)
|
|
122
|
+
component = { type: 'FOOTER' }
|
|
123
|
+
component[:text] = text if text
|
|
124
|
+
component[:code_expiration_minutes] = code_expiration_minutes if code_expiration_minutes
|
|
125
|
+
component
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_buttons_component(buttons:)
|
|
129
|
+
{
|
|
130
|
+
type: 'BUTTONS',
|
|
131
|
+
buttons: buttons.map { |btn| normalize_button(btn) }
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def build_button(type:, text: nil, url: nil, phone_number: nil,
|
|
136
|
+
otp_type: nil, autofill_text: nil, package_name: nil,
|
|
137
|
+
signature_hash: nil)
|
|
138
|
+
button = { type: type.upcase }
|
|
139
|
+
|
|
140
|
+
case type.upcase
|
|
141
|
+
when 'QUICK_REPLY'
|
|
142
|
+
button[:text] = text if text
|
|
143
|
+
when 'URL'
|
|
144
|
+
button[:text] = text if text
|
|
145
|
+
button[:url] = url if url
|
|
146
|
+
when 'PHONE_NUMBER'
|
|
147
|
+
button[:text] = text if text
|
|
148
|
+
button[:phone_number] = phone_number if phone_number
|
|
149
|
+
when 'OTP'
|
|
150
|
+
button[:otp_type] = otp_type if otp_type
|
|
151
|
+
button[:text] = text if text
|
|
152
|
+
button[:autofill_text] = autofill_text if autofill_text
|
|
153
|
+
button[:package_name] = package_name if package_name
|
|
154
|
+
button[:signature_hash] = signature_hash if signature_hash
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
button
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Authentication template builder
|
|
161
|
+
def build_authentication_template(name:, language:, ttl_seconds: 60,
|
|
162
|
+
add_security_recommendation: true,
|
|
163
|
+
code_expiration_minutes: 10,
|
|
164
|
+
otp_type: 'COPY_CODE')
|
|
165
|
+
components = []
|
|
166
|
+
|
|
167
|
+
# Body component with security recommendation
|
|
168
|
+
body_component = { type: 'BODY' }
|
|
169
|
+
body_component[:add_security_recommendation] = add_security_recommendation
|
|
170
|
+
components << body_component
|
|
171
|
+
|
|
172
|
+
# Footer component with expiration
|
|
173
|
+
if code_expiration_minutes
|
|
174
|
+
components << build_footer_component(code_expiration_minutes: code_expiration_minutes)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# OTP button
|
|
178
|
+
components << build_buttons_component(
|
|
179
|
+
buttons: [build_button(type: 'OTP', otp_type: otp_type)]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
{
|
|
183
|
+
name: name,
|
|
184
|
+
language: language,
|
|
185
|
+
category: 'AUTHENTICATION',
|
|
186
|
+
message_send_ttl_seconds: ttl_seconds,
|
|
187
|
+
components: components
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Marketing template builder
|
|
192
|
+
def build_marketing_template(name:, language:, header: nil, body:, footer: nil,
|
|
193
|
+
buttons: nil, body_example: nil)
|
|
194
|
+
components = []
|
|
195
|
+
|
|
196
|
+
# Header component
|
|
197
|
+
components << header if header
|
|
198
|
+
|
|
199
|
+
# Body component
|
|
200
|
+
body_component = build_text_component(text: body, example: body_example)
|
|
201
|
+
components << body_component
|
|
202
|
+
|
|
203
|
+
# Footer component
|
|
204
|
+
components << build_footer_component(text: footer) if footer
|
|
205
|
+
|
|
206
|
+
# Buttons component
|
|
207
|
+
components << build_buttons_component(buttons: buttons) if buttons
|
|
208
|
+
|
|
209
|
+
{
|
|
210
|
+
name: name,
|
|
211
|
+
language: language,
|
|
212
|
+
category: 'MARKETING',
|
|
213
|
+
components: components
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Utility template builder
|
|
218
|
+
def build_utility_template(name:, language:, body:, header: nil, footer: nil,
|
|
219
|
+
buttons: nil, body_example: nil)
|
|
220
|
+
components = []
|
|
221
|
+
|
|
222
|
+
# Header component
|
|
223
|
+
components << header if header
|
|
224
|
+
|
|
225
|
+
# Body component
|
|
226
|
+
body_component = build_text_component(text: body, example: body_example)
|
|
227
|
+
components << body_component
|
|
228
|
+
|
|
229
|
+
# Footer component
|
|
230
|
+
components << build_footer_component(text: footer) if footer
|
|
231
|
+
|
|
232
|
+
# Buttons component
|
|
233
|
+
components << build_buttons_component(buttons: buttons) if buttons
|
|
234
|
+
|
|
235
|
+
{
|
|
236
|
+
name: name,
|
|
237
|
+
language: language,
|
|
238
|
+
category: 'UTILITY',
|
|
239
|
+
components: components
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private
|
|
244
|
+
|
|
245
|
+
def validate_template_data(name:, language:, category:, components:)
|
|
246
|
+
raise ArgumentError, 'Template name cannot be empty' if name.nil? || name.strip.empty?
|
|
247
|
+
raise ArgumentError, 'Language cannot be empty' if language.nil? || language.strip.empty?
|
|
248
|
+
raise ArgumentError, 'Category cannot be empty' if category.nil? || category.strip.empty?
|
|
249
|
+
raise ArgumentError, 'Components cannot be empty' if components.nil? || components.empty?
|
|
250
|
+
|
|
251
|
+
# Validate category
|
|
252
|
+
valid_categories = Types::TEMPLATE_CATEGORIES
|
|
253
|
+
unless valid_categories.include?(category.upcase)
|
|
254
|
+
raise ArgumentError, "Invalid category '#{category}'. Must be one of: #{valid_categories.join(', ')}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Validate components structure
|
|
258
|
+
components.each_with_index do |component, index|
|
|
259
|
+
unless component.is_a?(Hash) && component[:type]
|
|
260
|
+
raise ArgumentError, "Component at index #{index} must be a Hash with :type key"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def normalize_components(components)
|
|
266
|
+
components.map { |component| normalize_component(component) }
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def normalize_component(component)
|
|
270
|
+
# Ensure component keys are strings for API compatibility
|
|
271
|
+
normalized = {}
|
|
272
|
+
component.each { |key, value| normalized[key.to_s] = value }
|
|
273
|
+
normalized
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def normalize_button(button)
|
|
277
|
+
# Ensure button keys are strings for API compatibility
|
|
278
|
+
normalized = {}
|
|
279
|
+
button.each { |key, value| normalized[key.to_s] = value }
|
|
280
|
+
normalized
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
284
|
end
|