kapso-client-ruby 1.0.0 → 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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -81
  3. data/CHANGELOG.md +262 -91
  4. data/Gemfile +20 -20
  5. data/RAILS_INTEGRATION.md +478 -0
  6. data/README.md +1053 -734
  7. data/Rakefile +40 -40
  8. data/TEMPLATE_TOOLS_GUIDE.md +120 -120
  9. data/WHATSAPP_24_HOUR_GUIDE.md +133 -133
  10. data/examples/advanced_features.rb +352 -349
  11. data/examples/advanced_messaging.rb +241 -0
  12. data/examples/basic_messaging.rb +139 -136
  13. data/examples/enhanced_interactive.rb +400 -0
  14. data/examples/flows_usage.rb +307 -0
  15. data/examples/interactive_messages.rb +343 -0
  16. data/examples/media_management.rb +256 -253
  17. data/examples/rails/jobs.rb +388 -0
  18. data/examples/rails/models.rb +240 -0
  19. data/examples/rails/notifications_controller.rb +227 -0
  20. data/examples/template_management.rb +393 -390
  21. data/kapso-ruby-logo.jpg +0 -0
  22. data/lib/kapso_client_ruby/client.rb +321 -316
  23. data/lib/kapso_client_ruby/errors.rb +348 -329
  24. data/lib/kapso_client_ruby/rails/generators/install_generator.rb +76 -0
  25. data/lib/kapso_client_ruby/rails/generators/templates/env.erb +21 -0
  26. data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +33 -0
  27. data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +138 -0
  28. data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +62 -0
  29. data/lib/kapso_client_ruby/rails/railtie.rb +55 -0
  30. data/lib/kapso_client_ruby/rails/service.rb +189 -0
  31. data/lib/kapso_client_ruby/rails/tasks.rake +167 -0
  32. data/lib/kapso_client_ruby/resources/calls.rb +172 -172
  33. data/lib/kapso_client_ruby/resources/contacts.rb +190 -190
  34. data/lib/kapso_client_ruby/resources/conversations.rb +103 -103
  35. data/lib/kapso_client_ruby/resources/flows.rb +382 -0
  36. data/lib/kapso_client_ruby/resources/media.rb +205 -205
  37. data/lib/kapso_client_ruby/resources/messages.rb +760 -380
  38. data/lib/kapso_client_ruby/resources/phone_numbers.rb +85 -85
  39. data/lib/kapso_client_ruby/resources/templates.rb +283 -283
  40. data/lib/kapso_client_ruby/types.rb +348 -262
  41. data/lib/kapso_client_ruby/version.rb +5 -5
  42. data/lib/kapso_client_ruby.rb +75 -68
  43. data/scripts/.env.example +17 -17
  44. data/scripts/kapso_template_finder.rb +91 -91
  45. data/scripts/sdk_setup.rb +404 -404
  46. data/scripts/test.rb +60 -60
  47. metadata +24 -3
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :kapso do
4
+ desc 'Test Kapso configuration and send a test message'
5
+ task test: :environment do
6
+ puts "šŸ”§ Testing Kapso configuration..."
7
+
8
+ # Check configuration
9
+ client = KapsoClientRuby::Client.new
10
+ puts "āœ… Kapso client initialized"
11
+ puts "šŸ“± Phone Number ID: #{client.phone_number_id}"
12
+ puts "šŸ¢ Business Account ID: #{client.business_account_id}"
13
+
14
+ # Test API connection
15
+ begin
16
+ templates = client.templates.list(limit: 1)
17
+ puts "āœ… API connection successful"
18
+ puts "šŸ“‹ Found #{templates.dig('data')&.length || 0} templates"
19
+ rescue => e
20
+ puts "āŒ API connection failed: #{e.message}"
21
+ exit 1
22
+ end
23
+
24
+ # Send test message if phone number is provided
25
+ test_number = ENV['KAPSO_TEST_PHONE_NUMBER']
26
+ if test_number
27
+ puts "\nšŸ“¤ Sending test message to #{test_number}..."
28
+ service = KapsoMessageService.new
29
+ service.send_test_message
30
+ else
31
+ puts "\nšŸ’” Set KAPSO_TEST_PHONE_NUMBER to test messaging"
32
+ end
33
+ end
34
+
35
+ desc 'List available WhatsApp message templates'
36
+ task templates: :environment do
37
+ puts "šŸ“‹ Fetching WhatsApp templates..."
38
+
39
+ service = KapsoMessageService.new
40
+ templates_response = service.list_templates
41
+
42
+ if templates_response && templates_response['data']
43
+ templates = templates_response['data']
44
+ puts "Found #{templates.length} templates:\n\n"
45
+
46
+ templates.each do |template|
47
+ puts "šŸ“„ #{template['name']}"
48
+ puts " Status: #{template['status']}"
49
+ puts " Language: #{template['language']}"
50
+ puts " Category: #{template['category']}"
51
+ puts " Created: #{Time.at(template['created_time']).to_s}" if template['created_time']
52
+ puts ""
53
+ end
54
+ else
55
+ puts "āŒ Failed to fetch templates or no templates found"
56
+ end
57
+ end
58
+
59
+ desc 'Check message status'
60
+ task :message_status, [:message_id] => :environment do |task, args|
61
+ message_id = args[:message_id]
62
+
63
+ unless message_id
64
+ puts "āŒ Please provide a message ID: rake kapso:message_status[message_id]"
65
+ exit 1
66
+ end
67
+
68
+ puts "šŸ” Checking status for message: #{message_id}"
69
+
70
+ service = KapsoMessageService.new
71
+ status = service.get_message_status(message_id)
72
+
73
+ if status
74
+ puts "šŸ“Š Message Status:"
75
+ puts " ID: #{status['id']}"
76
+ puts " Status: #{status['status']}"
77
+ puts " Timestamp: #{Time.at(status['timestamp']).to_s}" if status['timestamp']
78
+ puts " Recipient: #{status['recipient_id']}" if status['recipient_id']
79
+
80
+ if status['errors']
81
+ puts " Errors: #{status['errors']}"
82
+ end
83
+ else
84
+ puts "āŒ Failed to get message status"
85
+ end
86
+ end
87
+
88
+ desc 'Validate webhook configuration'
89
+ task validate_webhook: :environment do
90
+ puts "šŸ”— Validating webhook configuration..."
91
+
92
+ # Check required environment variables
93
+ required_vars = %w[KAPSO_WEBHOOK_VERIFY_TOKEN]
94
+ optional_vars = %w[KAPSO_WEBHOOK_SECRET]
95
+
96
+ required_vars.each do |var|
97
+ if ENV[var].present?
98
+ puts "āœ… #{var} is set"
99
+ else
100
+ puts "āŒ #{var} is not set (required)"
101
+ end
102
+ end
103
+
104
+ optional_vars.each do |var|
105
+ if ENV[var].present?
106
+ puts "āœ… #{var} is set (recommended for security)"
107
+ else
108
+ puts "āš ļø #{var} is not set (optional but recommended)"
109
+ end
110
+ end
111
+
112
+ # Check if routes are properly configured
113
+ begin
114
+ webhook_path = Rails.application.routes.url_helpers.kapso_webhooks_path rescue nil
115
+ if webhook_path
116
+ puts "āœ… Webhook routes are configured"
117
+ puts " Webhook URL: #{Rails.application.config.force_ssl ? 'https' : 'http'}://yourapp.com#{webhook_path}"
118
+ else
119
+ puts "āš ļø Webhook routes may not be configured. Make sure to add:"
120
+ puts " post '/webhooks/kapso', to: 'kapso_webhooks#create'"
121
+ puts " get '/webhooks/kapso', to: 'kapso_webhooks#verify'"
122
+ end
123
+ rescue => e
124
+ puts "āš ļø Could not check webhook routes: #{e.message}"
125
+ end
126
+ end
127
+
128
+ desc 'Generate sample environment file'
129
+ task :sample_env do
130
+ puts "šŸ“ Generating .env.kapso.sample file..."
131
+
132
+ env_content = <<~ENV
133
+ # Kapso API Configuration
134
+ # Copy this to your .env file and fill in your actual values
135
+
136
+ # Required: Your Kapso API access token
137
+ KAPSO_API_KEY=your_api_key_here
138
+
139
+ # Required: Your WhatsApp Business phone number ID
140
+ KAPSO_PHONE_NUMBER_ID=your_phone_number_id_here
141
+
142
+ # Required: Your WhatsApp Business account ID
143
+ KAPSO_BUSINESS_ACCOUNT_ID=your_business_account_id_here
144
+
145
+ # Optional: API configuration
146
+ KAPSO_API_HOST=https://graph.facebook.com
147
+ KAPSO_API_VERSION=v18.0
148
+ KAPSO_TIMEOUT=30
149
+
150
+ # Optional: Debug and retry settings
151
+ KAPSO_DEBUG=false
152
+ KAPSO_RETRY_ON_FAILURE=true
153
+ KAPSO_MAX_RETRIES=3
154
+
155
+ # Webhook configuration
156
+ KAPSO_WEBHOOK_VERIFY_TOKEN=your_webhook_verify_token_here
157
+ KAPSO_WEBHOOK_SECRET=your_webhook_secret_here
158
+
159
+ # Testing
160
+ KAPSO_TEST_PHONE_NUMBER=+1234567890
161
+ ENV
162
+
163
+ File.write('.env.kapso.sample', env_content)
164
+ puts "āœ… Created .env.kapso.sample"
165
+ puts "šŸ’” Copy this to .env and update with your actual credentials"
166
+ end
167
+ end
@@ -1,173 +1,173 @@
1
- # frozen_string_literal: true
2
-
3
- module KapsoClientRuby
4
- module Resources
5
- class Calls
6
- def initialize(client)
7
- @client = client
8
- end
9
-
10
- # Initiate a call
11
- def connect(phone_number_id:, to:, session: nil, biz_opaque_callback_data: nil)
12
- payload = {
13
- messaging_product: 'whatsapp',
14
- to: to,
15
- action: 'connect'
16
- }
17
-
18
- payload[:session] = session if session
19
- payload[:biz_opaque_callback_data] = biz_opaque_callback_data if biz_opaque_callback_data
20
-
21
- response = @client.request(:post, "#{phone_number_id}/calls",
22
- body: payload.to_json, response_type: :json)
23
- Types::CallConnectResponse.new(response)
24
- end
25
-
26
- # Pre-accept a call
27
- def pre_accept(phone_number_id:, call_id:, session:)
28
- raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
29
- raise ArgumentError, 'session cannot be nil' if session.nil?
30
-
31
- payload = {
32
- messaging_product: 'whatsapp',
33
- call_id: call_id,
34
- action: 'pre_accept',
35
- session: session
36
- }
37
-
38
- response = @client.request(:post, "#{phone_number_id}/calls",
39
- body: payload.to_json, response_type: :json)
40
- Types::CallActionResponse.new(response)
41
- end
42
-
43
- # Accept a call
44
- def accept(phone_number_id:, call_id:, session:, biz_opaque_callback_data: nil)
45
- raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
46
- raise ArgumentError, 'session cannot be nil' if session.nil?
47
-
48
- payload = {
49
- messaging_product: 'whatsapp',
50
- call_id: call_id,
51
- action: 'accept',
52
- session: session
53
- }
54
-
55
- payload[:biz_opaque_callback_data] = biz_opaque_callback_data if biz_opaque_callback_data
56
-
57
- response = @client.request(:post, "#{phone_number_id}/calls",
58
- body: payload.to_json, response_type: :json)
59
- Types::CallActionResponse.new(response)
60
- end
61
-
62
- # Reject a call
63
- def reject(phone_number_id:, call_id:)
64
- raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
65
-
66
- payload = {
67
- messaging_product: 'whatsapp',
68
- call_id: call_id,
69
- action: 'reject'
70
- }
71
-
72
- response = @client.request(:post, "#{phone_number_id}/calls",
73
- body: payload.to_json, response_type: :json)
74
- Types::CallActionResponse.new(response)
75
- end
76
-
77
- # Terminate a call
78
- def terminate(phone_number_id:, call_id:)
79
- raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
80
-
81
- payload = {
82
- messaging_product: 'whatsapp',
83
- call_id: call_id,
84
- action: 'terminate'
85
- }
86
-
87
- response = @client.request(:post, "#{phone_number_id}/calls",
88
- body: payload.to_json, response_type: :json)
89
- Types::CallActionResponse.new(response)
90
- end
91
-
92
- # List calls (Kapso Proxy only)
93
- def list(phone_number_id:, direction: nil, status: nil, since: nil,
94
- until_time: nil, call_id: nil, limit: nil, after: nil,
95
- before: nil, fields: nil)
96
- assert_kapso_proxy('Call history API')
97
-
98
- query_params = {
99
- direction: direction,
100
- status: status,
101
- since: since,
102
- until: until_time,
103
- call_id: call_id,
104
- limit: limit,
105
- after: after,
106
- before: before,
107
- fields: fields
108
- }.compact
109
-
110
- response = @client.request(:get, "#{phone_number_id}/calls",
111
- query: query_params, response_type: :json)
112
- Types::PagedResponse.new(response, Types::CallRecord)
113
- end
114
-
115
- # Get call details (Kapso Proxy only)
116
- def get(phone_number_id:, call_id:, fields: nil)
117
- assert_kapso_proxy('Call details API')
118
-
119
- query_params = {}
120
- query_params[:fields] = fields if fields
121
-
122
- response = @client.request(:get, "#{phone_number_id}/calls/#{call_id}",
123
- query: query_params, response_type: :json)
124
- Types::CallRecord.new(response)
125
- end
126
-
127
- # Call permissions management
128
- class Permissions
129
- def initialize(client)
130
- @client = client
131
- end
132
-
133
- # Get call permissions
134
- def get(phone_number_id:, user_wa_id:)
135
- raise ArgumentError, 'user_wa_id cannot be empty' if user_wa_id.nil? || user_wa_id.strip.empty?
136
-
137
- query_params = { user_wa_id: user_wa_id }
138
-
139
- response = @client.request(:get, "#{phone_number_id}/call_permissions",
140
- query: query_params, response_type: :json)
141
- response
142
- end
143
-
144
- # Update call permissions
145
- def update(phone_number_id:, user_wa_id:, permission:)
146
- raise ArgumentError, 'user_wa_id cannot be empty' if user_wa_id.nil? || user_wa_id.strip.empty?
147
- raise ArgumentError, 'permission cannot be empty' if permission.nil?
148
-
149
- payload = {
150
- user_wa_id: user_wa_id,
151
- permission: permission
152
- }
153
-
154
- response = @client.request(:post, "#{phone_number_id}/call_permissions",
155
- body: payload.to_json, response_type: :json)
156
- Types::GraphSuccessResponse.new(response)
157
- end
158
- end
159
-
160
- def permissions
161
- @permissions ||= Permissions.new(@client)
162
- end
163
-
164
- private
165
-
166
- def assert_kapso_proxy(feature)
167
- unless @client.kapso_proxy?
168
- raise Errors::KapsoProxyRequiredError.new(feature)
169
- end
170
- end
171
- end
172
- end
1
+ # frozen_string_literal: true
2
+
3
+ module KapsoClientRuby
4
+ module Resources
5
+ class Calls
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ # Initiate a call
11
+ def connect(phone_number_id:, to:, session: nil, biz_opaque_callback_data: nil)
12
+ payload = {
13
+ messaging_product: 'whatsapp',
14
+ to: to,
15
+ action: 'connect'
16
+ }
17
+
18
+ payload[:session] = session if session
19
+ payload[:biz_opaque_callback_data] = biz_opaque_callback_data if biz_opaque_callback_data
20
+
21
+ response = @client.request(:post, "#{phone_number_id}/calls",
22
+ body: payload.to_json, response_type: :json)
23
+ Types::CallConnectResponse.new(response)
24
+ end
25
+
26
+ # Pre-accept a call
27
+ def pre_accept(phone_number_id:, call_id:, session:)
28
+ raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
29
+ raise ArgumentError, 'session cannot be nil' if session.nil?
30
+
31
+ payload = {
32
+ messaging_product: 'whatsapp',
33
+ call_id: call_id,
34
+ action: 'pre_accept',
35
+ session: session
36
+ }
37
+
38
+ response = @client.request(:post, "#{phone_number_id}/calls",
39
+ body: payload.to_json, response_type: :json)
40
+ Types::CallActionResponse.new(response)
41
+ end
42
+
43
+ # Accept a call
44
+ def accept(phone_number_id:, call_id:, session:, biz_opaque_callback_data: nil)
45
+ raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
46
+ raise ArgumentError, 'session cannot be nil' if session.nil?
47
+
48
+ payload = {
49
+ messaging_product: 'whatsapp',
50
+ call_id: call_id,
51
+ action: 'accept',
52
+ session: session
53
+ }
54
+
55
+ payload[:biz_opaque_callback_data] = biz_opaque_callback_data if biz_opaque_callback_data
56
+
57
+ response = @client.request(:post, "#{phone_number_id}/calls",
58
+ body: payload.to_json, response_type: :json)
59
+ Types::CallActionResponse.new(response)
60
+ end
61
+
62
+ # Reject a call
63
+ def reject(phone_number_id:, call_id:)
64
+ raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
65
+
66
+ payload = {
67
+ messaging_product: 'whatsapp',
68
+ call_id: call_id,
69
+ action: 'reject'
70
+ }
71
+
72
+ response = @client.request(:post, "#{phone_number_id}/calls",
73
+ body: payload.to_json, response_type: :json)
74
+ Types::CallActionResponse.new(response)
75
+ end
76
+
77
+ # Terminate a call
78
+ def terminate(phone_number_id:, call_id:)
79
+ raise ArgumentError, 'call_id cannot be empty' if call_id.nil? || call_id.strip.empty?
80
+
81
+ payload = {
82
+ messaging_product: 'whatsapp',
83
+ call_id: call_id,
84
+ action: 'terminate'
85
+ }
86
+
87
+ response = @client.request(:post, "#{phone_number_id}/calls",
88
+ body: payload.to_json, response_type: :json)
89
+ Types::CallActionResponse.new(response)
90
+ end
91
+
92
+ # List calls (Kapso Proxy only)
93
+ def list(phone_number_id:, direction: nil, status: nil, since: nil,
94
+ until_time: nil, call_id: nil, limit: nil, after: nil,
95
+ before: nil, fields: nil)
96
+ assert_kapso_proxy('Call history API')
97
+
98
+ query_params = {
99
+ direction: direction,
100
+ status: status,
101
+ since: since,
102
+ until: until_time,
103
+ call_id: call_id,
104
+ limit: limit,
105
+ after: after,
106
+ before: before,
107
+ fields: fields
108
+ }.compact
109
+
110
+ response = @client.request(:get, "#{phone_number_id}/calls",
111
+ query: query_params, response_type: :json)
112
+ Types::PagedResponse.new(response, Types::CallRecord)
113
+ end
114
+
115
+ # Get call details (Kapso Proxy only)
116
+ def get(phone_number_id:, call_id:, fields: nil)
117
+ assert_kapso_proxy('Call details API')
118
+
119
+ query_params = {}
120
+ query_params[:fields] = fields if fields
121
+
122
+ response = @client.request(:get, "#{phone_number_id}/calls/#{call_id}",
123
+ query: query_params, response_type: :json)
124
+ Types::CallRecord.new(response)
125
+ end
126
+
127
+ # Call permissions management
128
+ class Permissions
129
+ def initialize(client)
130
+ @client = client
131
+ end
132
+
133
+ # Get call permissions
134
+ def get(phone_number_id:, user_wa_id:)
135
+ raise ArgumentError, 'user_wa_id cannot be empty' if user_wa_id.nil? || user_wa_id.strip.empty?
136
+
137
+ query_params = { user_wa_id: user_wa_id }
138
+
139
+ response = @client.request(:get, "#{phone_number_id}/call_permissions",
140
+ query: query_params, response_type: :json)
141
+ response
142
+ end
143
+
144
+ # Update call permissions
145
+ def update(phone_number_id:, user_wa_id:, permission:)
146
+ raise ArgumentError, 'user_wa_id cannot be empty' if user_wa_id.nil? || user_wa_id.strip.empty?
147
+ raise ArgumentError, 'permission cannot be empty' if permission.nil?
148
+
149
+ payload = {
150
+ user_wa_id: user_wa_id,
151
+ permission: permission
152
+ }
153
+
154
+ response = @client.request(:post, "#{phone_number_id}/call_permissions",
155
+ body: payload.to_json, response_type: :json)
156
+ Types::GraphSuccessResponse.new(response)
157
+ end
158
+ end
159
+
160
+ def permissions
161
+ @permissions ||= Permissions.new(@client)
162
+ end
163
+
164
+ private
165
+
166
+ def assert_kapso_proxy(feature)
167
+ unless @client.kapso_proxy?
168
+ raise Errors::KapsoProxyRequiredError.new(feature)
169
+ end
170
+ end
171
+ end
172
+ end
173
173
  end