Class: KapsoClientRuby::Resources::Flows

Inherits:
Object
  • Object
show all
Defined in:
lib/kapso_client_ruby/resources/flows.rb

Overview

Manages WhatsApp Flows - interactive forms and data collection Flows allow you to build rich, multi-step experiences within WhatsApp

Instance Method Summary collapse

Constructor Details

#initialize(client) ⇒ Flows

Returns a new instance of Flows.



12
13
14
# File 'lib/kapso_client_ruby/resources/flows.rb', line 12

def initialize(client)
  @client = client
end

Instance Method Details

#create(business_account_id:, name:, categories: ['OTHER'], **options) ⇒ Hash

Create a new Flow

Parameters:

  • business_account_id (String)

    WhatsApp Business Account ID

  • name (String)

    Flow name

  • categories (Array<String>) (defaults to: ['OTHER'])

    Flow categories (e.g., [‘APPOINTMENT_BOOKING’])

  • options (Hash)

    Additional options

Options Hash (**options):

  • :endpoint_uri (String)

    Data endpoint URL for Flow callbacks

  • :application_id (String)

    Application ID for the Flow

Returns:

  • (Hash)

    Created Flow data with ID



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/kapso_client_ruby/resources/flows.rb', line 24

def create(business_account_id:, name:, categories: ['OTHER'], **options)
  payload = {
    name: name,
    categories: categories
  }
  
  payload[:endpoint_uri] = options[:endpoint_uri] if options[:endpoint_uri]
  payload[:application_id] = options[:application_id] if options[:application_id]
  
  response = @client.request(
    :post,
    "#{business_account_id}/flows",
    body: payload.to_json,
    response_type: :json
  )
  
  Types::FlowResponse.new(response)
end

#delete(flow_id:) ⇒ Hash

Delete a Flow

Parameters:

  • flow_id (String)

    Flow ID

Returns:

  • (Hash)

    Success response



66
67
68
69
70
71
72
73
74
# File 'lib/kapso_client_ruby/resources/flows.rb', line 66

def delete(flow_id:)
  response = @client.request(
    :delete,
    "#{flow_id}",
    response_type: :json
  )
  
  Types::GraphSuccessResponse.new(response)
end

#deploy(business_account_id:, name:, flow_json:, categories: ['OTHER'], endpoint_uri: nil, application_id: nil) ⇒ Hash

Idempotent Flow deployment - creates or updates, then publishes

Parameters:

  • business_account_id (String)

    WhatsApp Business Account ID

  • name (String)

    Flow name

  • flow_json (Hash)

    Flow JSON definition

  • categories (Array<String>) (defaults to: ['OTHER'])

    Flow categories

  • endpoint_uri (String, nil) (defaults to: nil)

    Data endpoint URL

  • application_id (String, nil) (defaults to: nil)

    Application ID

Returns:

  • (Hash)

    Deployment result with flow ID and status



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/kapso_client_ruby/resources/flows.rb', line 207

def deploy(business_account_id:, name:, flow_json:, categories: ['OTHER'],
           endpoint_uri: nil, application_id: nil)
  # Check if flow exists by name

  existing_flows = list(business_account_id: )
  flow = existing_flows.dig('data')&.find { |f| f['name'] == name }
  
  if flow.nil?
    # Create new flow

    @client.logger.debug "Creating new Flow: #{name}"
    created = create(
      business_account_id: ,
      name: name,
      categories: categories,
      endpoint_uri: endpoint_uri,
      application_id: application_id
    )
    flow_id = created['id']
  else
    # Use existing flow

    flow_id = flow['id']
    @client.logger.debug "Using existing Flow: #{name} (#{flow_id})"
    
    # Update attributes if provided

    update_attrs = {}
    update_attrs[:categories] = categories if categories != ['OTHER']
    update_attrs[:endpoint_uri] = endpoint_uri if endpoint_uri
    update_attrs[:application_id] = application_id if application_id
    
    unless update_attrs.empty?
      update(flow_id: flow_id, **update_attrs)
    end
  end
  
  # Update asset

  @client.logger.debug "Updating Flow asset for #{flow_id}"
  update_asset(flow_id: flow_id, asset: flow_json)
  
  # Publish

  @client.logger.debug "Publishing Flow #{flow_id}"
  publish(flow_id: flow_id)
  
  {
    id: flow_id,
    name: name,
    status: 'published',
    message: flow.nil? ? 'Flow created and published' : 'Flow updated and published'
  }
end

#deprecate(flow_id:, phone_number_id: nil, business_account_id: nil) ⇒ Hash

Deprecate a Flow

Parameters:

  • flow_id (String)

    Flow ID

  • phone_number_id (String, nil) (defaults to: nil)

    Phone number ID (for query params)

  • business_account_id (String, nil) (defaults to: nil)

    Business account ID (for query params)

Returns:

  • (Hash)

    Success response



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/kapso_client_ruby/resources/flows.rb', line 136

def deprecate(flow_id:, phone_number_id: nil, business_account_id: nil)
  query_params = build_query_params(phone_number_id, )
  
  response = @client.request(
    :post,
    "#{flow_id}/deprecate",
    query: query_params,
    body: {}.to_json,
    response_type: :json
  )
  
  Types::GraphSuccessResponse.new(response)
end

#download_flow_media(media_url:, access_token: nil) ⇒ String

Download media from Flow

Parameters:

  • media_url (String)

    Media URL from Flow event

  • access_token (String, nil) (defaults to: nil)

    Access token (uses client token if not provided)

Returns:

  • (String)

    Binary media content



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/kapso_client_ruby/resources/flows.rb', line 355

def download_flow_media(media_url:, access_token: nil)
  token = access_token || @client.access_token
  
  unless token
    raise Errors::ConfigurationError, 'Access token required to download Flow media'
  end
  
  # Make authenticated request to media URL

  response = @client.fetch(
    media_url,
    headers: { 'Authorization' => "Bearer #{token}" }
  )
  
  response.body
end

#get(flow_id:, fields: nil) ⇒ Hash

Get Flow details

Parameters:

  • flow_id (String)

    Flow ID

  • fields (Array<String>, nil) (defaults to: nil)

    Specific fields to retrieve

Returns:

  • (Hash)

    Flow data



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/kapso_client_ruby/resources/flows.rb', line 80

def get(flow_id:, fields: nil)
  query_params = {}
  query_params[:fields] = fields.join(',') if fields
  
  response = @client.request(
    :get,
    "#{flow_id}",
    query: query_params,
    response_type: :json
  )
  
  Types::FlowData.new(response)
end

#list(business_account_id:, fields: nil) ⇒ Hash

List all Flows for a business account

Parameters:

  • business_account_id (String)

    WhatsApp Business Account ID

  • fields (Array<String>, nil) (defaults to: nil)

    Specific fields to retrieve

Returns:

  • (Hash)

    List of Flows



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/kapso_client_ruby/resources/flows.rb', line 98

def list(business_account_id:, fields: nil)
  query_params = {}
  query_params[:fields] = fields.join(',') if fields
  
  response = @client.request(
    :get,
    "#{business_account_id}/flows",
    query: query_params,
    response_type: :json
  )
  
  Types::PagedResponse.new(response)
end

#preview(flow_id:, phone_number_id: nil, business_account_id: nil) ⇒ Hash

Get Flow preview URL

Parameters:

  • flow_id (String)

    Flow ID

  • phone_number_id (String, nil) (defaults to: nil)

    Phone number ID (for query params)

  • business_account_id (String, nil) (defaults to: nil)

    Business account ID (for query params)

Returns:

  • (Hash)

    Preview URL response



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/kapso_client_ruby/resources/flows.rb', line 185

def preview(flow_id:, phone_number_id: nil, business_account_id: nil)
  query_params = build_query_params(phone_number_id, )
  query_params[:fields] = 'preview.preview_url,preview.expires_at'
  
  response = @client.request(
    :get,
    "#{flow_id}",
    query: query_params,
    response_type: :json
  )
  
  Types::FlowPreviewResponse.new(response)
end

#publish(flow_id:, phone_number_id: nil, business_account_id: nil) ⇒ Hash

Publish a Flow

Parameters:

  • flow_id (String)

    Flow ID

  • phone_number_id (String, nil) (defaults to: nil)

    Phone number ID (for query params)

  • business_account_id (String, nil) (defaults to: nil)

    Business account ID (for query params)

Returns:

  • (Hash)

    Success response



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/kapso_client_ruby/resources/flows.rb', line 117

def publish(flow_id:, phone_number_id: nil, business_account_id: nil)
  query_params = build_query_params(phone_number_id, )
  
  response = @client.request(
    :post,
    "#{flow_id}/publish",
    query: query_params,
    body: {}.to_json,
    response_type: :json
  )
  
  Types::GraphSuccessResponse.new(response)
end

#receive_flow_event(encrypted_request:, private_key:, passphrase: nil) ⇒ Hash

Receive and decrypt Flow event from webhook

Parameters:

  • encrypted_request (String)

    Encrypted request body from webhook

  • private_key (String, OpenSSL::PKey::RSA)

    Private key for decryption (PEM format or key object)

  • passphrase (String, nil) (defaults to: nil)

    Passphrase for encrypted private key

Returns:

  • (Hash)

    Decrypted Flow event data



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/kapso_client_ruby/resources/flows.rb', line 261

def receive_flow_event(encrypted_request:, private_key:, passphrase: nil)
  # Parse encrypted request

  request_data = JSON.parse(encrypted_request)
  
  # Extract encrypted components

  encrypted_aes_key = Base64.decode64(request_data['encrypted_aes_key'])
  encrypted_flow_data = Base64.decode64(request_data['encrypted_flow_data'])
  initial_vector = Base64.decode64(request_data['initial_vector'])
  
  # Load private key if it's a string

  rsa_key = if private_key.is_a?(String)
              OpenSSL::PKey::RSA.new(private_key, passphrase)
            else
              private_key
            end
  
  # Decrypt AES key using RSA private key

  aes_key = rsa_key.private_decrypt(encrypted_aes_key)
  
  # Decrypt flow data using AES

  cipher = OpenSSL::Cipher.new('AES-128-GCM')
  cipher.decrypt
  cipher.key = aes_key
  cipher.iv = initial_vector
  
  # Extract authentication tag (last 16 bytes)

  auth_tag = encrypted_flow_data[-16..]
  ciphertext = encrypted_flow_data[0...-16]
  cipher.auth_tag = auth_tag
  
  decrypted_data = cipher.update(ciphertext) + cipher.final
  
  # Parse and return decrypted JSON

  flow_event = JSON.parse(decrypted_data)
  Types::FlowEventData.new(flow_event)
rescue OpenSSL::PKey::RSAError => e
  raise Errors::FlowDecryptionError.new("Failed to decrypt with private key: #{e.message}")
rescue OpenSSL::Cipher::CipherError => e
  raise Errors::FlowDecryptionError.new("Failed to decrypt flow data: #{e.message}")
rescue JSON::ParserError => e
  raise Errors::FlowDecryptionError.new("Invalid JSON in encrypted request: #{e.message}")
end

#respond_to_flow(response_data:, private_key:, passphrase: nil) ⇒ String

Encrypt and send response to Flow

Parameters:

  • response_data (Hash)

    Response data to send to Flow

  • private_key (String, OpenSSL::PKey::RSA)

    Private key for signing (PEM format or key object)

  • passphrase (String, nil) (defaults to: nil)

    Passphrase for encrypted private key

Returns:

  • (String)

    Encrypted response JSON



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/kapso_client_ruby/resources/flows.rb', line 309

def respond_to_flow(response_data:, private_key:, passphrase: nil)
  # Load private key if it's a string

  rsa_key = if private_key.is_a?(String)
              OpenSSL::PKey::RSA.new(private_key, passphrase)
            else
              private_key
            end
  
  # Generate random AES key and IV

  aes_key = OpenSSL::Cipher.new('AES-128-GCM').random_key
  initial_vector = OpenSSL::Cipher.new('AES-128-GCM').random_iv
  
  # Encrypt response data using AES

  cipher = OpenSSL::Cipher.new('AES-128-GCM')
  cipher.encrypt
  cipher.key = aes_key
  cipher.iv = initial_vector
  
  response_json = response_data.to_json
  encrypted_data = cipher.update(response_json) + cipher.final
  auth_tag = cipher.auth_tag
  
  # Combine ciphertext and auth tag

  encrypted_flow_data = encrypted_data + auth_tag
  
  # Encrypt AES key using RSA public key

  encrypted_aes_key = rsa_key.public_encrypt(aes_key)
  
  # Build encrypted response

  encrypted_response = {
    encrypted_aes_key: Base64.encode64(encrypted_aes_key),
    encrypted_flow_data: Base64.encode64(encrypted_flow_data),
    initial_vector: Base64.encode64(initial_vector)
  }
  
  encrypted_response.to_json
rescue OpenSSL::PKey::RSAError => e
  raise Errors::FlowEncryptionError.new("Failed to encrypt with private key: #{e.message}")
rescue OpenSSL::Cipher::CipherError => e
  raise Errors::FlowEncryptionError.new("Failed to encrypt flow response: #{e.message}")
end

#update(flow_id:, **attributes) ⇒ Hash

Update an existing Flow

Parameters:

  • flow_id (String)

    Flow ID

  • attributes (Hash)

    Attributes to update (name, categories, endpoint_uri, application_id)

Returns:

  • (Hash)

    Updated Flow data

Raises:

  • (ArgumentError)


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/kapso_client_ruby/resources/flows.rb', line 47

def update(flow_id:, **attributes)
  valid_attributes = [:name, :categories, :endpoint_uri, :application_id]
  payload = attributes.select { |k, _| valid_attributes.include?(k) }
  
  raise ArgumentError, 'No valid attributes provided' if payload.empty?
  
  response = @client.request(
    :post,
    "#{flow_id}",
    body: payload.to_json,
    response_type: :json
  )
  
  Types::FlowResponse.new(response)
end

#update_asset(flow_id:, asset:, phone_number_id: nil, business_account_id: nil) ⇒ Hash

Update Flow asset (JSON definition)

Parameters:

  • flow_id (String)

    Flow ID

  • asset (Hash, String)

    Flow JSON definition (Hash or JSON string)

  • phone_number_id (String, nil) (defaults to: nil)

    Phone number ID (for query params)

  • business_account_id (String, nil) (defaults to: nil)

    Business account ID (for query params)

Returns:

  • (Hash)

    Asset update response with validation results



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/kapso_client_ruby/resources/flows.rb', line 156

def update_asset(flow_id:, asset:, phone_number_id: nil, business_account_id: nil)
  query_params = build_query_params(phone_number_id, )
  
  # Convert asset to JSON string if it's a Hash

  asset_json = asset.is_a?(String) ? asset : asset.to_json
  
  # Create multipart form data

  payload = {
    messaging_product: 'whatsapp',
    asset_type: 'FLOW_JSON',
    asset: asset_json
  }
  
  response = @client.request(
    :post,
    "#{flow_id}/assets",
    query: query_params,
    body: payload.to_json,
    response_type: :json
  )
  
  Types::FlowAssetResponse.new(response)
end