zuora_api 1.7.66 → 1.11.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.
@@ -7,8 +7,9 @@ module ZuoraAPI
7
7
  class Login
8
8
  ENVIRONMENTS = [TEST = 'Test', SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
9
9
  REGIONS = [EU = 'EU', US = 'US', NA = 'NA' ]
10
- MIN_Endpoint = '96.0'
10
+ MIN_Endpoints = {'Test': '126.0', 'Sandbox': '126.0', 'Production': '126.0', 'Performance': '126.0', 'Services': '96.0', 'Unknown': '96.0', 'Staging': '127.0'}.freeze
11
11
  XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
12
+ USER_AGENT = "Zuora#{ENV['Z_APPLICATION_NAME']&.capitalize}/#{ENV['Z_APPLICATION_VERSION']&.delete('v')}"
12
13
 
13
14
  CONNECTION_EXCEPTIONS = [
14
15
  Net::OpenTimeout,
@@ -21,7 +22,7 @@ module ZuoraAPI
21
22
  ].freeze
22
23
 
23
24
  CONNECTION_READ_EXCEPTIONS = [
24
- Net::ReadTimeout,
25
+ Timeout::Error,
25
26
  Errno::ECONNRESET,
26
27
  Errno::EPIPE
27
28
  ].freeze
@@ -39,25 +40,29 @@ module ZuoraAPI
39
40
 
40
41
  ZUORA_SERVER_ERRORS = [
41
42
  ZuoraAPI::Exceptions::ZuoraAPIInternalServerError,
42
- ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout,
43
- ZuoraAPI::Exceptions::ZuoraAPIReadTimeout,
43
+ ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout,
44
+ ZuoraAPI::Exceptions::ZuoraAPIReadTimeout,
44
45
  ZuoraAPI::Exceptions::ZuoraUnexpectedError
45
46
  ].freeze
46
-
47
- attr_accessor :region, :url, :wsdl_number, :current_session, :bearer_token, :oauth_session_expires_at, :environment, :status, :errors, :current_error, :user_info, :tenant_id, :tenant_name, :entity_id, :timeout_sleep, :hostname, :zconnect_provider
48
47
 
49
- def initialize(url: nil, entity_id: nil, session: nil, status: nil, bearer_token: nil, oauth_session_expires_at: nil, **keyword_args)
48
+ attr_accessor :region, :url, :wsdl_number, :current_session, :bearer_token, :oauth_session_expires_at, :environment, :status, :errors, :current_error, :user_info, :tenant_id, :tenant_name, :entity_id, :entity_identifier, :entity_header_type, :timeout_sleep, :hostname, :zconnect_provider
49
+
50
+ def initialize(url: nil, entity_id: nil, entity_identifier: nil, session: nil, status: nil, bearer_token: nil, oauth_session_expires_at: nil, **keyword_args)
50
51
  raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
51
52
  # raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
52
53
  self.hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
54
+ self.update_environment
55
+ min_endpoint = MIN_Endpoints[self.environment.to_sym]
53
56
  if !/apps\/services\/a\/\d+\.\d$/.match(url.strip)
54
- self.url = "https://#{hostname}/apps/services/a/#{MIN_Endpoint}"
55
- elsif MIN_Endpoint.to_f > url.scan(/(\d+\.\d)$/).dig(0,0).to_f
56
- self.url = url.gsub(/(\d+\.\d)$/, MIN_Endpoint)
57
+ self.url = "https://#{hostname}/apps/services/a/#{min_endpoint}"
58
+ elsif min_endpoint.to_f > url.scan(/(\d+\.\d)$/).dig(0,0).to_f
59
+ self.url = url.gsub(/(\d+\.\d)$/, min_endpoint)
57
60
  else
58
61
  self.url = url
59
62
  end
60
63
  self.entity_id = get_entity_id(entity_id: entity_id)
64
+ self.entity_identifier = entity_identifier
65
+ self.entity_header_type = :entity_id
61
66
  self.errors = Hash.new
62
67
  self.current_session = session
63
68
  self.bearer_token = bearer_token
@@ -65,36 +70,18 @@ module ZuoraAPI
65
70
  self.status = status.blank? ? "Active" : status
66
71
  self.user_info = Hash.new
67
72
  self.update_region
68
- self.update_environment
69
73
  self.update_zconnect_provider
70
74
  @timeout_sleep = 5
71
75
  end
72
76
 
73
77
  def get_identity(cookies)
74
78
  zsession = cookies["ZSession"]
75
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
76
79
  begin
77
80
  if !zsession.blank?
78
- response = HTTParty.get("https://#{self.hostname}/apps/v1/identity", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
79
- output_json = JSON.parse(response.body)
80
- elsif zconnect_accesstoken.present?
81
- begin
82
- code = zconnect_accesstoken.split("#!").last
83
- encrypted_token, tenant_id = Base64.decode64(code).split(":")
84
- body = {'token' => encrypted_token}.to_json
85
- rescue => ex
86
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Invalid ZConnect Cookie")
87
- end
88
- Rails.logger.info("Using ZConnect cookie in get_identity method")
89
-
90
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/identity", :body => body, :headers => { 'Content-Type' => 'application/json' })
81
+ response = HTTParty.get("https://#{self.hostname}/apps/v1/identity", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json', "User-Agent" => USER_AGENT})
91
82
  output_json = JSON.parse(response.body)
92
83
  else
93
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
94
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
95
- else
96
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
97
- end
84
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
98
85
  end
99
86
  rescue JSON::ParserError => ex
100
87
  output_json = {}
@@ -105,21 +92,12 @@ module ZuoraAPI
105
92
 
106
93
  def get_full_nav(cookies)
107
94
  zsession = cookies["ZSession"]
108
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
109
95
  begin
110
96
  if zsession.present?
111
- response = HTTParty.get("https://#{self.hostname}/apps/v1/navigation", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
112
- output_json = JSON.parse(response.body)
113
- elsif zconnect_accesstoken.present?
114
- Rails.logger.info("Using ZConnect cookie in get_full_nav method")
115
- response = HTTParty.get("https://#{self.hostname}/apps/zconnectsession/navigation", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}",'Content-Type' => 'application/json'})
97
+ response = HTTParty.get("https://#{self.hostname}/apps/v1/navigation", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json', "User-Agent" => USER_AGENT})
116
98
  output_json = JSON.parse(response.body)
117
99
  else
118
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
119
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
120
- else
121
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
122
- end
100
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
123
101
  end
124
102
  rescue JSON::ParserError => ex
125
103
  output_json = {}
@@ -130,21 +108,12 @@ module ZuoraAPI
130
108
 
131
109
  def set_nav(state, cookies)
132
110
  zsession = cookies["ZSession"]
133
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
134
111
  begin
135
112
  if !zsession.blank?
136
- response = HTTParty.put("https://#{self.hostname}/apps/v1/preference/navigation", :body => state.to_json, :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
137
- output_json = JSON.parse(response.body)
138
- elsif !zconnect_accesstoken.blank?
139
- Rails.logger.info("Using ZConnect cookie in set_nav method")
140
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/navigationstate", :body => state.to_json, :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
113
+ response = HTTParty.put("https://#{self.hostname}/apps/v1/preference/navigation", :body => state.to_json, :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json', "User-Agent" => USER_AGENT})
141
114
  output_json = JSON.parse(response.body)
142
115
  else
143
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
144
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
145
- else
146
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
147
- end
116
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
148
117
  end
149
118
  rescue JSON::ParserError => ex
150
119
  output_json = {}
@@ -155,21 +124,12 @@ module ZuoraAPI
155
124
 
156
125
  def refresh_nav(cookies)
157
126
  zsession = cookies["ZSession"]
158
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
159
127
  begin
160
128
  if !zsession.blank?
161
- response = HTTParty.post("https://#{self.hostname}/apps/v1/navigation/fetch", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
162
- output_json = JSON.parse(response.body)
163
- elsif !zconnect_accesstoken.blank?
164
- Rails.logger.info("Using ZConnect cookie in refresh_nav method")
165
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/refresh-navbarcache", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
129
+ response = HTTParty.post("https://#{self.hostname}/apps/v1/navigation/fetch", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json', "User-Agent" => USER_AGENT})
166
130
  output_json = JSON.parse(response.body)
167
131
  else
168
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
169
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
170
- else
171
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
172
- end
132
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
173
133
  end
174
134
  rescue JSON::ParserError => ex
175
135
  output_json = {}
@@ -178,25 +138,17 @@ module ZuoraAPI
178
138
  return output_json
179
139
  end
180
140
 
181
- def get_zconnect_accesstoken(cookies)
182
- accesstoken = nil
183
- self.update_zconnect_provider
184
- if !cookies[self.zconnect_provider].nil? && !cookies[self.zconnect_provider].empty?
185
- accesstoken = cookies[self.zconnect_provider]
186
- end
187
- return accesstoken
188
- end
189
-
190
141
  def reporting_url(path)
191
142
  map = {"US" => {"Sandbox" => "https://zconnectsandbox.zuora.com/api/rest/v1/",
192
143
  "Production" => "https://zconnect.zuora.com/api/rest/v1/",
193
- "Test" => "https://reporting-sbx.zan.0001.sbx.auw2.zuora.com/api/rest/v1/",
144
+ "Test" => "https://zconnect-services0001.test.zuora.com/api/rest/v1/",
194
145
  "Staging" => "https://reporting-stg11.zan.svc.auw2.zuora.com/api/rest/v1/",
195
146
  "Performance" => "https://zconnectpt1.zuora.com/api/rest/v1/",
196
147
  "Services" => "https://reporting-svc08.svc.auw2.zuora.com/api/rest/v1/"},
197
148
  "EU" => {"Sandbox" => "https://zconnect.sandbox.eu.zuora.com/api/rest/v1/",
198
149
  "Production" => "https://zconnect.eu.zuora.com/api/rest/v1/",
199
- "Services"=> "https://reporting-sbx0000.sbx.aec1.zuora.com/api/rest/v1/"},
150
+ "Services"=> "https://reporting-sbx0000.sbx.aec1.zuora.com/api/rest/v1/",
151
+ "Test" => "https://zconnect-services0002.test.eu.zuora.com/api/rest/v1/"},
200
152
  "NA" => {"Sandbox" => "https://zconnect.sandbox.na.zuora.com/api/rest/v1/",
201
153
  "Production" => "https://zconnect.na.zuora.com/api/rest/v1/",
202
154
  "Services"=> ""}
@@ -208,7 +160,7 @@ module ZuoraAPI
208
160
  # 1. Pass in cookies and optionally custom_authorities, name, and description
209
161
  # 2. Pass in user_id, entity_ids, client_id, client_secret, and optionally custom_authorities, name, and description
210
162
  # https://intranet.zuora.com/confluence/display/Sunburst/Create+an+OAuth+Client+through+API+Gateway#CreateanOAuthClientthroughAPIGateway-ZSession
211
- def get_oauth_client (custom_authorities = [], info_name: "No Name", info_desc: "This client was created without a description.", user_id: nil, entity_ids: nil, client_id: nil, client_secret: nil, new_client_id: nil, new_client_secret: nil, cookies: nil)
163
+ def get_oauth_client (custom_authorities = [], info_name: "No Name", info_desc: "This client was created without a description.", user_id: nil, entity_ids: nil, client_id: nil, client_secret: nil, new_client_id: nil, new_client_secret: nil, cookies: nil, chomp_v1_from_genesis_endpoint: false, use_api_generated_client_secret: false)
212
164
  authorization = ""
213
165
  new_client_id = SecureRandom.uuid if new_client_id.blank?
214
166
  new_client_secret = SecureRandom.hex(10) if new_client_secret.blank?
@@ -227,18 +179,18 @@ module ZuoraAPI
227
179
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Zuora User ID not provided")
228
180
  end
229
181
  elsif !client_id.nil? && !client_secret.nil?
230
- bearer_response = HTTParty.post("https://#{self.hostname}/oauth/token", :headers => {'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json'}, :body => {'client_id' => client_id, 'client_secret' => URI::encode(client_secret), 'grant_type' => 'client_credentials'})
182
+ bearer_response = HTTParty.post("https://#{self.hostname}/oauth/token", :headers => {'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json', "User-Agent" => USER_AGENT}, :body => {'client_id' => client_id, 'client_secret' => URI::encode(client_secret), 'grant_type' => 'client_credentials'})
231
183
  bearer_hash = JSON.parse(bearer_response.body)
232
184
  bearer_token = bearer_hash["access_token"]
233
185
  authorization = "Bearer #{bearer_token}"
234
186
  end
235
187
 
236
188
  if !authorization.blank? && !user_id.blank? && !entity_ids.blank?
237
- endpoint = self.rest_endpoint("genesis/clients")
238
- oauth_response = HTTParty.post(endpoint, :headers => {'authorization' => authorization, 'Content-Type' => 'application/json'}, :body => {'clientId' => new_client_id, 'clientSecret' => new_client_secret, 'userId' => user_id, 'entityIds' => entity_ids, 'customAuthorities' => custom_authorities, 'additionalInformation' => {'description' => info_desc, 'name' => info_name}}.to_json)
189
+ endpoint = chomp_v1_from_genesis_endpoint ? self.rest_endpoint.chomp("v1/").concat("genesis/clients") : self.rest_endpoint("genesis/clients")
190
+ oauth_response = HTTParty.post(endpoint, :headers => {'authorization' => authorization, 'Content-Type' => 'application/json', "User-Agent" => USER_AGENT}, :body => {'clientId' => new_client_id, 'clientSecret' => new_client_secret, 'userId' => user_id, 'entityIds' => entity_ids, 'customAuthorities' => custom_authorities, 'additionalInformation' => {'description' => info_desc, 'name' => info_name}}.to_json)
239
191
  output_json = JSON.parse(oauth_response.body)
240
192
  if oauth_response.code == 201
241
- output_json["clientSecret"] = new_client_secret
193
+ output_json["clientSecret"] = new_client_secret if !use_api_generated_client_secret
242
194
  return output_json
243
195
  elsif oauth_response.code == 401 && !oauth_response.message.blank?
244
196
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(output_json["message"], oauth_response)
@@ -273,7 +225,8 @@ module ZuoraAPI
273
225
  "NA" => {"Sandbox" => "https://sandbox.na.zuora.com/apps/services/a/",
274
226
  "Production" => "https://na.zuora.com/apps/services/a/",
275
227
  "Performance" => "https://pt1.na.zuora.com/apps/services/a/",
276
- "Services" => "https://services347.na.zuora.com/apps/services/a/"}
228
+ "Services" => "https://services347.na.zuora.com/apps/services/a/",
229
+ "Test" => "https://test.zuora.com/apps/services/a/"}
277
230
  }
278
231
  end
279
232
 
@@ -305,7 +258,7 @@ module ZuoraAPI
305
258
  end
306
259
 
307
260
  def update_environment
308
- if !self.url.blank?
261
+ if !self.hostname.blank?
309
262
  case self.hostname
310
263
  when /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/
311
264
  self.environment = 'Sandbox'
@@ -328,13 +281,13 @@ module ZuoraAPI
328
281
  end
329
282
 
330
283
  def update_zconnect_provider
331
- region = update_region
332
- environment = update_environment
284
+ update_region if self.region.blank?
285
+ update_environment if self.environment.blank?
333
286
  mappings = {"US" => {"Sandbox" => "ZConnectSbx", "Services" => "ZConnectSvcUS", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Test" => "ZConnectTest", "Staging" => "ZConnectQA", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev"},
334
287
  "NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectSvcNA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
335
288
  "EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectSvcEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU", "Test" => "ZConnectTest"},
336
289
  "Unknown" => {"Unknown" => "Unknown"}}
337
- self.zconnect_provider = mappings[region][environment]
290
+ self.zconnect_provider = mappings[self.region][self.environment]
338
291
  end
339
292
 
340
293
  def aqua_endpoint(url="")
@@ -351,7 +304,7 @@ module ZuoraAPI
351
304
  update_environment
352
305
  endpoint = url
353
306
  url_postfix = {"US" => ".", "EU" => ".eu.", "NA" => ".na."}[self.region]
354
-
307
+
355
308
  case self.environment
356
309
  when 'Test'
357
310
  endpoint = "https://rest.test#{url_postfix}zuora.com"
@@ -374,8 +327,8 @@ module ZuoraAPI
374
327
  return domain ? endpoint.concat(prefix).concat(url) : prefix.concat(url)
375
328
  end
376
329
 
377
- def rest_domain
378
- return URI(self.rest_endpoint).host
330
+ def rest_domain(endpoint: self.rest_endpoint)
331
+ return URI(endpoint).host
379
332
  end
380
333
 
381
334
  def fileURL(url="")
@@ -387,10 +340,41 @@ module ZuoraAPI
387
340
  end
388
341
 
389
342
  def new_session(auth_type: :basic, debug: false, zuora_track_id: nil)
343
+ retries ||= 2
344
+ yield
345
+
346
+ rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
347
+ self.status = 'Invalid'
348
+ self.current_error = ex.message
349
+ raise
350
+ rescue ZuoraAPI::Exceptions::ZuoraAPIError => ex
351
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(ex.message, ex.response)
352
+
353
+ rescue ZuoraAPI::Exceptions::ZuoraAPIInternalServerError => ex
354
+ raise ex if retries.zero?
355
+
356
+ retries -= 1
357
+ sleep(self.timeout_sleep)
358
+ retry
359
+
360
+ rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
361
+ self.log(location: "BasicLogin", exception: ex, message: "Timed out", level: :error)
362
+
363
+ self.current_error = "Request timed out. Try again"
364
+ self.status = 'Timeout'
365
+
366
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
367
+
368
+ rescue EOFError
369
+ if self.url.match?(/.*services\d{1,}.zuora.com*/)
370
+ self.current_error = "Services tenant '#{self.url.scan(/.*\/\/(services\d{1,}).zuora.com*/).last.first}' is no longer available."
371
+ self.status = 'Not Available'
372
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
373
+ end
374
+
390
375
  end
391
376
 
392
377
  def get_session(prefix: false, auth_type: :basic, zuora_track_id: nil)
393
- Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}") if Rails.env.to_s == 'development'
394
378
  case auth_type
395
379
  when :basic
396
380
  if self.current_session.blank?
@@ -399,14 +383,13 @@ module ZuoraAPI
399
383
  if self.bearer_token.blank? || self.oauth_expired?
400
384
  self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
401
385
  end
402
- self.get_z_session(zuora_track_id: zuora_track_id) if self.status == 'Active'
386
+ self.get_z_session(zuora_track_id: zuora_track_id)
403
387
  when 'ZuoraAPI::Basic'
404
388
  self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
405
389
  else
406
390
  self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
407
391
  end
408
392
  end
409
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
410
393
  return prefix ? "ZSession #{self.current_session}" : self.current_session.to_s
411
394
  when :bearer
412
395
  case self.class.to_s
@@ -419,25 +402,25 @@ module ZuoraAPI
419
402
  else
420
403
  raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Unknown Login, does not support Authentication of Type: #{auth_type}")
421
404
  end
422
-
423
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
424
405
  return prefix ? "Bearer #{self.bearer_token}" : self.bearer_token.to_s
425
406
  end
426
407
  end
427
408
 
428
409
  def soap_call(
429
- ns1: 'ns1',
430
- ns2: 'ns2',
431
- batch_size: nil,
432
- single_transaction: false,
433
- debug: false,
434
- zuora_track_id: nil,
435
- errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS),
436
- z_session: true,
437
- timeout_retry: false,
438
- timeout: 120,
439
- timeout_sleep_interval: self.timeout_sleep,
410
+ ns1: 'ns1',
411
+ ns2: 'ns2',
412
+ batch_size: nil,
413
+ headers: {},
414
+ single_transaction: false,
415
+ debug: false,
416
+ zuora_track_id: nil,
417
+ errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS),
418
+ z_session: true,
419
+ timeout_retry: false,
420
+ timeout: 130,
421
+ timeout_sleep_interval: self.timeout_sleep,
440
422
  output_exception_messages: true,
423
+ skip_session: false,
441
424
  **keyword_args)
442
425
  tries ||= 2
443
426
  xml = Nokogiri::XML::Builder.new do |xml|
@@ -447,17 +430,19 @@ module ZuoraAPI
447
430
  'xmlns:api' => "http://api.zuora.com/",
448
431
  "xmlns:#{ns1}" => "http://api.zuora.com/") do
449
432
  xml['SOAP-ENV'].Header do
450
- xml["#{ns1}"].SessionHeader do
451
- xml["#{ns1}"].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: zuora_track_id)
433
+ if !skip_session
434
+ xml["#{ns1}"].SessionHeader do
435
+ xml["#{ns1}"].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: zuora_track_id)
436
+ end
452
437
  end
453
438
  if single_transaction
454
439
  xml["#{ns1}"].CallOptions do
455
- xml.useSingleTransaction single_transaction
440
+ xml["#{ns1}"].useSingleTransaction single_transaction
456
441
  end
457
442
  end
458
443
  if batch_size
459
444
  xml["#{ns1}"].QueryOptions do
460
- xml.batchSize batch_size
445
+ xml["#{ns1}"].batchSize batch_size
461
446
  end
462
447
  end
463
448
  end
@@ -466,13 +451,14 @@ module ZuoraAPI
466
451
  end
467
452
  end
468
453
  end
469
-
470
454
  input_xml = Nokogiri::XML(xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip)
471
455
  input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
472
456
  Rails.logger.debug("Request SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
473
457
 
474
- headers = { 'Content-Type' => "text/xml; charset=utf-8", 'Accept' => 'text/xml'}
458
+ headers.merge!({ 'Content-Type' => "text/xml; charset=utf-8", 'Accept' => 'text/xml'})
475
459
  headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
460
+ headers['X-Amzn-Trace-Id'] = zuora_track_id if zuora_track_id.present?
461
+ headers["User-Agent"] = USER_AGENT
476
462
 
477
463
  request = HTTParty::Request.new(
478
464
  Net::HTTP::Post,
@@ -488,7 +474,11 @@ module ZuoraAPI
488
474
  Rails.logger.debug("Response SOAP XML: #{output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
489
475
 
490
476
  raise_errors(type: :SOAP, body: output_xml, response: response)
477
+
478
+ return output_xml, input_xml, response
479
+
491
480
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
481
+ raise if skip_session
492
482
  if !tries.zero? && z_session
493
483
  tries -= 1
494
484
  Rails.logger.debug("SOAP Call - Session Invalid")
@@ -500,39 +490,33 @@ module ZuoraAPI
500
490
  end
501
491
 
502
492
  retry
503
- else
504
- if errors.include?(ex.class)
505
- raise ex
506
- else
507
- return output_xml, input_xml, response
508
- end
509
493
  end
494
+
495
+ raise ex if errors.include?(ex.class)
496
+
497
+ return output_xml, input_xml, response
498
+
510
499
  rescue *ZUORA_API_ERRORS => ex
511
- if errors.include?(ex.class)
512
- raise ex
513
- else
514
- response = ex.response unless response
515
- return output_xml, input_xml, response
516
- end
500
+ raise ex if errors.include?(ex.class)
501
+
502
+ response = ex.response unless response
503
+ return output_xml, input_xml, response
504
+
517
505
  rescue *CONNECTION_EXCEPTIONS => ex
518
- if tries.zero?
519
- if output_exception_messages
520
- if Rails.logger.class.to_s == "Ougai::Logger"
521
- Rails.logger.error("SOAP Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
522
- else
523
- Rails.logger.error("SOAP Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
524
- end
525
- end
526
- raise ex
506
+ if !tries.zero?
507
+ tries -= 1
508
+ self.log(location: "SOAP Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
509
+ sleep(timeout_sleep_interval)
510
+ retry
527
511
  end
528
512
 
529
- tries -= 1
530
- sleep(timeout_sleep_interval)
531
- retry
513
+ self.log(location: "SOAP Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
514
+ raise ex
515
+
532
516
  rescue *CONNECTION_READ_EXCEPTIONS => ex
533
517
  if !tries.zero?
534
518
  tries -= 1
535
-
519
+ self.log(location: "SOAP Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
536
520
  if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
537
521
  retry
538
522
  elsif timeout_retry
@@ -541,19 +525,79 @@ module ZuoraAPI
541
525
  end
542
526
  end
543
527
 
544
- if output_exception_messages
545
- if Rails.logger.class.to_s == "Ougai::Logger"
546
- Rails.logger.error("SOAP Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
547
- else
548
- Rails.logger.error("SOAP Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
549
- end
550
- end
551
- raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
528
+ self.log(location: "SOAP Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
529
+ ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read/write timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.is_a?(Timeout::Error) && !ex.instance_of?(ZuoraAPI::Exceptions::ZuoraAPIReadTimeout)
552
530
  raise ex
531
+
553
532
  rescue => ex
554
533
  raise ex
555
- else
556
- return output_xml, input_xml, response
534
+ ensure
535
+ self.error_logger(ex) if defined?(ex)
536
+ end
537
+
538
+ def error_logger(ex)
539
+ return unless Rails.logger.is_a? Ougai::Logger
540
+
541
+ exception_args = Rails.logger.with_fields.merge(self.exception_args(ex))
542
+ case ex
543
+ when ZuoraAPI::Exceptions::ZuoraAPIUnkownError, ZuoraAPI::Exceptions::ZuoraDataIntegrity
544
+ Rails.logger.error('Zuora Unknown/Integrity Error', ex, exception_args)
545
+ when ZuoraAPI::Exceptions::ZuoraAPIRequestLimit
546
+ if ex.logged.nil? || !ex.logged
547
+ ex.logged = true
548
+ Rails.logger.info('Zuora APILimit Reached', ex, exception_args)
549
+ end
550
+ when *(ZuoraAPI::Login::ZUORA_API_ERRORS-ZuoraAPI::Login::ZUORA_SERVER_ERRORS)
551
+ #Rails.logger.debug('Zuora API Error', ex, self.exception_args(ex))
552
+ when *ZuoraAPI::Login::ZUORA_SERVER_ERRORS
553
+ Rails.logger.error('Zuora Server Error', ex, exception_args)
554
+ end
555
+ end
556
+
557
+ def log(location: "Rest Call", exception: nil, message: "Timed out will retry after #{self.timeout_sleep} seconds", level: :info )
558
+ level = :debug if ![:debug, :info, :warn, :error, :fatal].include?(level)
559
+ if Rails.logger.is_a? Ougai::Logger
560
+ Rails.logger.send(level.to_sym, "#{location} - #{message}", exception)
561
+ else
562
+ Rails.logger.send(level.to_sym, "#{location} - #{exception.class} #{message}")
563
+ end
564
+ end
565
+
566
+ def exception_args(ex)
567
+ args = {}
568
+ if defined?(ex.response) && ex.response.present?
569
+ args.merge!({
570
+ url: {full: ex.response.request.path.to_s},
571
+ request: {
572
+ method: ex.response.request.http_method.to_s.split("Net::HTTP::").last.upcase,
573
+ params: ex.response.request.raw_body.to_s,
574
+ headers: ex.response.request.options[:headers].map{|k,v| [k.to_s, k.to_s.downcase.strip == "authorization" ? "VALUE FILTERED" : v]}.to_h,
575
+ },
576
+ response: {
577
+ status: ex.response.code,
578
+ params: ex.response.body.to_s,
579
+ headers: ex.response.headers,
580
+ },
581
+ zuora_trace_id: ex.response.headers["zuora-request-id"],
582
+ zuora_track_id: ex.response.request.options[:headers]["Zuora-Track-Id"],
583
+ })
584
+ elsif defined?(ex.request) && ex.request.present?
585
+ args.merge!({
586
+ url: {full: ex.request.path.to_s},
587
+ request: {
588
+ method: ex.request.http_method.to_s.split("Net::HTTP::").last.upcase,
589
+ params: ex.request.options[:body],
590
+ headers: ex.request.options[:headers].map{|k,v| [k.to_s, k.to_s.downcase.strip == "authorization" ? "VALUE FILTERED" : v]}.to_h
591
+ }
592
+ })
593
+ args.merge!({
594
+ zuora_track_id: ex.request.options[:headers]["Zuora-Track-Id"]
595
+ }) if ex.request.options[:headers]["Zuora-Track-Id"].present?
596
+ end
597
+ rescue => ex
598
+ Rails.logger.error("Failed to create exception arguments", ex, args)
599
+ ensure
600
+ return args
557
601
  end
558
602
 
559
603
  def raise_errors(type: :SOAP, body: nil, response: nil)
@@ -570,24 +614,24 @@ module ZuoraAPI
570
614
  end
571
615
 
572
616
  if [502,503].include?(response.code)
573
- raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from #{request_uri}", response)
617
+ raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from 'https://#{rest_domain(endpoint: request_uri)}'", response)
574
618
  end
575
619
 
576
620
  # Check failure response code
577
621
  case response.code
578
622
  when 504
579
- raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from #{request_uri}", response)
623
+ raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from 'https://#{rest_domain(endpoint: request_uri)}'", response)
580
624
  when 429
581
625
  raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("The total number of concurrent requests has exceeded the limit allowed by the system. Please resubmit your request later.", response)
582
626
  when 401
583
-
627
+
584
628
  else
585
629
  if body.class == Hash
586
630
  case request_path
587
631
  when /^\/v1\/connections$/
588
- response_headers = response.headers.to_h
632
+ response_headers = response.headers.to_h
589
633
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Missing cookies for authentication call", response) if response_headers['set-cookie'].blank?
590
- z_session_cookie = response_headers.fetch('set-cookie', []).select{|x| x.match(/^ZSession=.*/) }.first
634
+ z_session_cookie = response_headers.fetch('set-cookie', []).select{|x| x.match(/^ZSession=.*/) }.first
591
635
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Missing ZSession cookie for authentication call", response) if z_session_cookie.blank?
592
636
  end
593
637
  end
@@ -597,6 +641,10 @@ module ZuoraAPI
597
641
  when :SOAP
598
642
  error, success, message = get_soap_error_and_message(body)
599
643
 
644
+ if body.xpath('//fns:LoginFault', 'fns' =>'http://fault.api.zuora.com/').present?
645
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(message, response)
646
+ end
647
+
600
648
  if body.xpath('//ns1:queryResponse', 'ns1' => 'http://api.zuora.com/').present? &&
601
649
  body.xpath(
602
650
  '//ns1:records[@xsi:type="ns2:Export"]',
@@ -604,12 +652,20 @@ module ZuoraAPI
604
652
  ).present?
605
653
  result = body.xpath('//ns2:Status', 'ns2' => 'http://object.api.zuora.com/').text
606
654
  if result == 'Failed'
607
- reason = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
608
- if reason.present?
609
- message = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
610
- error = message.match(/^[\w\d]{16}\: (Unexpected error.|No HTTP Response|Socket Timeout|There is an internal error, please try again later)/).present? ? 'UNEXPECTED_ERROR' : 'FATAL_ERROR'
655
+ message = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
656
+ error = 'UNEXPECTED_ERROR'
657
+ if message.present?
658
+ identifier, new_message = message.scan(/^([\w\d]{16})\: (.*)/).first
659
+ if new_message.present?
660
+ if new_message.include?("The query exceeded maximum processing time")
661
+ error, message = ['TRANSACTION_FAILED', new_message.concat(" Please see KC for the Max Timeout Specification https://community.zuora.com/t5/Release-Notifications/Upcoming-Change-for-AQuA-and-Data-Source-Export-January-2021/ba-p/35024")]
662
+ else
663
+ error, message = ['UNEXPECTED_ERROR', new_message]
664
+ end
665
+ else
666
+ error, message = ['UNEXPECTED_ERROR', message]
667
+ end
611
668
  else
612
- error = 'FATAL_ERROR'
613
669
  message = 'Export failed due to unknown reason. Consult api logs.'
614
670
  end
615
671
  end
@@ -630,19 +686,20 @@ module ZuoraAPI
630
686
  end
631
687
 
632
688
  self.errors_via_content_type(response: response, type: :xml)
633
-
689
+
634
690
  when :JSON
635
691
  case request_path
636
692
  when /^\/query\/jobs.*/ #DataQuery Paths
637
693
  return if body.class != Hash
638
694
  case match_string
639
- when /^GET::200::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job, Capture of the id is present if needed in future error responses.
695
+ when /^GET::200::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job, Capture of the id is present if needed in future error responses.
640
696
  if body.dig('data', "errorCode") == "LINK_10000005"
641
697
  raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body.dig('data', "errorMessage"), response)
642
- elsif (body.dig('data', "errorMessage").present? || body.dig('data', "queryStatus") == "failed")
698
+ elsif (body.dig('data', "errorMessage").present? || body.dig('data', "queryStatus") == "failed")
699
+ raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new("Data query failed for unknown reasons. No error message.", response) if body.dig('data', "errorMessage").blank?
643
700
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('data', "errorMessage"), response)
644
701
  end
645
- when /^GET::404::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job not found, capture of the id is present if needed in future error responses.
702
+ when /^GET::404::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job not found, capture of the id is present if needed in future error responses.
646
703
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('message'), response) if body.dig('message').present?
647
704
  when /^POST::400::\/query\/jobs$/ #Create DQ job
648
705
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('message'), response) if body.dig('message').present?
@@ -658,14 +715,31 @@ module ZuoraAPI
658
715
  when /^GET::400::\/api\/rest\/v1\/reports\/(reportlabels\/)?([a-zA-Z0-9\-_]+)\/report-details$/ # Get report, capture of the id is present if needed in future error responses.
659
716
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(reporting_message, response) if reporting_message.present?
660
717
  end
718
+ when /\/objects\/batch\//
719
+ if body['code'].present? && /61$/.match(body['code'].to_s).present? # if last 2 digits of code are 61
720
+ raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body['message'], nil, body['details'])
721
+ end
722
+ when /^\/api\/v1\/payment_plans.*/
723
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['error'], response) if body['error']
661
724
  end
662
725
 
663
726
  body = body.dig("results").present? ? body["results"] : body if body.class == Hash
664
- if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
665
- messages_array = body.fetch("reasons", []).map {|error| error['message']}.compact
666
- messages_array = messages_array.push(body.dig("error", 'message')).compact if body.dig('error').class == Hash
667
- codes_array = body.fetch("reasons", []).map {|error| error['code']}.compact
668
- codes_array = codes_array.push(body.dig("error", 'code')).compact if body.dig('error').class == Hash
727
+ if body.class == Hash && (!(body["success"] || body["Success"]) || response.code != 200)
728
+ reason_keys = %w(reasons errors)
729
+ message_keys = %w(message title)
730
+ messages_array, codes_array = [[],[]]
731
+ reason_keys.each do |rsn_key|
732
+ message_keys.each do |msg_key|
733
+ messages_array = body.fetch(rsn_key, []).map {|error| error[msg_key]}.compact
734
+ break if messages_array.present?
735
+ end
736
+ codes_array = body.fetch(rsn_key, []).map {|error| error['code']}.compact
737
+ break if messages_array.present? && codes_array.present?
738
+ end
739
+ if body.dig('error').class == Hash
740
+ messages_array = messages_array.push(body.dig("error", 'message')).compact
741
+ codes_array = codes_array.push(body.dig("error", 'code')).compact
742
+ end
669
743
 
670
744
  if body['message'] == 'request exceeded limit'
671
745
  raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("The total number of concurrent requests has exceeded the limit allowed by the system. Please resubmit your request later.", response)
@@ -688,7 +762,7 @@ module ZuoraAPI
688
762
  end
689
763
 
690
764
  #Oauth Tokens - User deactivated
691
- if body['path'] == '/oauth/token'
765
+ if body['path'] == '/oauth/token'
692
766
  if body['status'] == 403 && response.code == 403
693
767
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Forbidden", response)
694
768
  elsif body['status'] == 400 && response.code == 400 && body['message'].include?("Invalid client id")
@@ -697,7 +771,11 @@ module ZuoraAPI
697
771
  end
698
772
 
699
773
  if body['error'] == 'Unauthorized' && body['status'] == 401
700
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
774
+ if body['message'].present?
775
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(body['message'], response)
776
+ else
777
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
778
+ end
701
779
  end
702
780
  #Authentication failed
703
781
  if (codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(11) || response.code == 401) && !codes_array.include?(422)
@@ -720,7 +798,7 @@ module ZuoraAPI
720
798
  end
721
799
  #Internal Server Error
722
800
  if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(60)
723
- if messages_array.uniq.size == 1
801
+ if messages_array.uniq.size == 1
724
802
  if messages_array.first.match(/^Transaction declined.*|^There is an invoice pending tax.*|^The Zuora GetTax call to Avalara.*|^The tax calculation call to Zuora Connect returned the following error: Status Code: 4.*/)
725
803
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(messages_array.first, response)
726
804
  end
@@ -793,18 +871,18 @@ module ZuoraAPI
793
871
  self.errors_via_content_type(response: response, type: :json)
794
872
 
795
873
  #All other errors
796
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new(response.body, response) if ![200,201].include?(response.code)
874
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(response.body, response) if ![200,201].include?(response.code)
797
875
  end
798
876
  end
799
877
 
800
878
  def errors_via_content_type(response: nil, type: :xml)
801
879
  response_content_types = response.headers.transform_keys(&:downcase).fetch('content-type', []).first || ""
802
-
880
+
803
881
  if response_content_types.include?('application/json') && type != :json
804
882
  output_json = JSON.parse(response.body)
805
883
  self.raise_errors(type: :JSON, body: output_json, response: response)
806
-
807
- elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml')) and type != :xml
884
+
885
+ elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml') || response_content_types.include?('application/soap+xml')) and type != :xml
808
886
  output_xml = Nokogiri::XML(response.body)
809
887
  self.raise_errors(type: :SOAP, body: output_xml, response: response)
810
888
 
@@ -823,8 +901,12 @@ module ZuoraAPI
823
901
  when /Service Unavailable/
824
902
  raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new(error_message, response)
825
903
  when /Client sent a bad request./, /Bad Request/, /403 Forbidden/
826
- raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
827
- else
904
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
905
+ when /414 Request-URI Too Large/
906
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Request URL is too long", response)
907
+ when /Not Found/
908
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("The page you were looking for could not be found. If you believe you reached this page in error, please email support.", response)
909
+ else
828
910
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
829
911
  end
830
912
  end
@@ -834,7 +916,7 @@ module ZuoraAPI
834
916
 
835
917
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(response.body, response) if response.code == 500
836
918
  end
837
-
919
+
838
920
 
839
921
  def get_soap_error_and_message(body)
840
922
  error = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
@@ -850,6 +932,11 @@ module ZuoraAPI
850
932
  message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
851
933
  end
852
934
 
935
+ if error.blank? || message.blank?
936
+ error = body.xpath('//soapenv:Value', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
937
+ message = body.xpath('//soapenv:Text', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
938
+ end
939
+
853
940
  #Update/Create/Delete Calls with multiple requests and responses
854
941
  if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
855
942
  error = []
@@ -893,18 +980,23 @@ module ZuoraAPI
893
980
  raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
894
981
  end
895
982
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
896
- when /invalid/, /^DUPLICATE_VALUE/, /^REQUEST_REJECTED/, /INVALID_ID/, /MAX_RECORDS_EXCEEDED/, /INVALID_FIELD/, /MALFORMED_QUERY/, /NO_PERMISSION/, /PDF_QUERY_ERROR/, /MISSING_REQUIRED_VALUE/, /INVALID_TYPE/, /TRANSACTION_FAILED/, /API_DISABLED/, /CANNOT_DELETE/, /ACCOUNTING_PERIOD_CLOSED/
983
+ when /^EXTENSION_ERROR/, /^INVALID_VERSION/, /invalid/, /^DUPLICATE_VALUE/, /^REQUEST_REJECTED/, /INVALID_ID/, /MAX_RECORDS_EXCEEDED/, /INVALID_FIELD/, /MALFORMED_QUERY/, /NO_PERMISSION/, /PDF_QUERY_ERROR/, /MISSING_REQUIRED_VALUE/, /INVALID_TYPE/, /TRANSACTION_FAILED/, /API_DISABLED/, /CANNOT_DELETE/, /ACCOUNTING_PERIOD_CLOSED/
897
984
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
898
985
  when /.*UNEXPECTED_ERROR/
899
986
  raise ZuoraAPI::Exceptions::ZuoraUnexpectedError.new(message, response, errors, success)
900
987
  when /.*soapenv:Server.*/
901
988
  if /^Invalid value.*for type.*|^Id is invalid|^date string can not be less than 19 charactors$/.match(message).present?
902
989
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
903
- elsif /^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
990
+ elsif /^unknown$|^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
904
991
  raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
905
992
  end
906
993
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
907
- else
994
+ when /soapenv:Receiver/
995
+ if /^com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character.*$/.match(message).present?
996
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
997
+ end
998
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
999
+ else
908
1000
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Z:#{error}::#{message}", response, errors, success)
909
1001
  end
910
1002
  end
@@ -947,13 +1039,42 @@ module ZuoraAPI
947
1039
  return self.get_file(url: self.aqua_endpoint("file/#{fileId}"))
948
1040
  end
949
1041
 
1042
+ def entity_header
1043
+ if self.entity_header_type == :entity_name && self.entity_identifier.present?
1044
+ { "entityName" => self.entity_identifier }
1045
+ elsif self.entity_id.present?
1046
+ { "Zuora-Entity-Ids" => self.entity_id }
1047
+ else
1048
+ {}
1049
+ end
1050
+ end
1051
+
1052
+ def insert_entity_header(destination_headers, lookup_headers: nil)
1053
+ # The entity header may be added to a place other than where we look for it
1054
+ lookup_headers = destination_headers if lookup_headers.nil?
1055
+
1056
+ entity_header_options = %w(zuora-entity-ids entityid entityname)
1057
+ # If the customer doesn't supply an entity header, fill it in
1058
+ if (entity_header_options & lookup_headers.keys.map(&:downcase)).blank?
1059
+ entity_header = self.entity_header
1060
+ if entity_header.present?
1061
+ destination_headers.merge!(entity_header)
1062
+ entity_header_options_to_exclude =
1063
+ entity_header_options.
1064
+ reject { |header| header == entity_header.keys.first&.downcase }
1065
+ destination_headers.delete_if { |key, _| entity_header_options_to_exclude.include?(key.to_s.downcase) }
1066
+ end
1067
+ end
1068
+ end
1069
+
950
1070
  def describe_call(object = nil, log_errors = true)
951
1071
  tries ||= 2
952
-
953
1072
  base = self.url.include?(".com") ? self.url.split(".com")[0].concat(".com") : self.url.split(".eu")[0].concat(".eu")
954
- url = object ? "#{base}/apps/api/describe/#{object}" : "#{base}/apps/api/describe/"
955
- headers = self.entity_id.present? ? {"Zuora-Entity-Ids" => self.entity_id, 'Content-Type' => "text/xml; charset=utf-8"} : {'Content-Type' => "text/xml; charset=utf-8"}
956
- response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic)}.merge(headers), :timeout => 120)
1073
+ version = self.url.scan(/(\d+\.\d)$/).dig(0,0).to_f
1074
+ url = object ? "#{base}/apps/api/#{version}/describe/#{object}" : "#{base}/apps/api/#{version}/describe/"
1075
+
1076
+ headers = { "Content-Type" => "text/xml; charset=utf-8" }.merge(self.entity_header)
1077
+ response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic), "User-Agent" => USER_AGENT}.merge(headers), :timeout => 130)
957
1078
 
958
1079
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error.present? ? self.current_error : 'Describe call 401', response) if response.code == 401
959
1080
 
@@ -986,35 +1107,31 @@ module ZuoraAPI
986
1107
  end
987
1108
  des_hash[:related_objects] = output_xml.xpath(".//related-objects").xpath(".//object").map{ |x| [x.xpath(".//name").text.to_sym, [ [:url, x.attributes["href"].value], [:label, x.xpath(".//name").text ] ].to_h] }.to_h
988
1109
  end
1110
+
1111
+ return des_hash
989
1112
  rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
990
- if tries.zero?
991
- if log_errors
992
- if Rails.logger.class.to_s == "Ougai::Logger"
993
- Rails.logger.error("Describe - Timed out will retry after #{self.timeout_sleep} seconds", ex)
994
- else
995
- Rails.logger.error("Describe - #{ex.class} Timed out will retry after #{self.timeout_sleep} seconds")
996
- end
997
- end
998
- raise ex
1113
+ if !tries.zero?
1114
+ tries -= 1
1115
+ self.log(location: "Describe", exception: ex, message: "Timed out will retry after #{self.timeout_sleep} seconds", level: :debug)
1116
+ sleep(self.timeout_sleep)
1117
+ retry
999
1118
  end
1000
1119
 
1001
- tries -= 1
1002
- sleep(self.timeout_sleep)
1003
- retry
1120
+ self.log(location: "Describe", exception: ex, message: "Timed out", level: :error) if log_errors
1121
+ raise ex
1122
+
1004
1123
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
1005
1124
  if !tries.zero? && self.status == 'Active'
1006
1125
  tries -= 1
1007
1126
  Rails.logger.debug("Describe session expired. Starting new session.")
1008
1127
  self.new_session
1009
1128
  retry
1010
- else
1011
- Rails.logger.error("Describe session expired. Starting new session.") if log_errors
1012
- raise ex
1013
1129
  end
1130
+
1131
+ Rails.logger.error("Describe session expired. Starting new session.") if log_errors
1132
+ raise ex
1014
1133
  rescue => ex
1015
1134
  raise ex
1016
- else
1017
- return des_hash
1018
1135
  end
1019
1136
 
1020
1137
  def rest_call(
@@ -1027,11 +1144,12 @@ module ZuoraAPI
1027
1144
  z_session: true,
1028
1145
  session_type: :basic,
1029
1146
  timeout_retry: false,
1030
- timeout: 120,
1031
- timeout_sleep_interval: self.timeout_sleep,
1147
+ timeout: 130,
1148
+ timeout_sleep_interval: self.timeout_sleep,
1032
1149
  multipart: false,
1033
1150
  stream_body: false,
1034
1151
  output_exception_messages: true,
1152
+ zuora_track_id: nil,
1035
1153
  **keyword_args,
1036
1154
  &block
1037
1155
  )
@@ -1041,12 +1159,13 @@ module ZuoraAPI
1041
1159
 
1042
1160
  authentication_headers = {}
1043
1161
  if z_session
1044
- authentication_headers = {"Authorization" => self.get_session(prefix: true, auth_type: session_type) }
1045
- if self.entity_id.present?
1046
- authentication_headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
1047
- authentication_headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
1048
- end
1162
+ authentication_headers = {"Authorization" => self.get_session(prefix: true, auth_type: session_type, zuora_track_id: zuora_track_id) }
1163
+
1164
+ self.insert_entity_header(authentication_headers, lookup_headers: headers)
1049
1165
  end
1166
+ headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
1167
+ headers['X-Amzn-Trace-Id'] = zuora_track_id if zuora_track_id.present?
1168
+ headers['User-Agent'] = USER_AGENT
1050
1169
 
1051
1170
  modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
1052
1171
 
@@ -1072,18 +1191,20 @@ module ZuoraAPI
1072
1191
  Rails.logger.debug("Response JSON: #{output_json}") if debug && output_json.present?
1073
1192
 
1074
1193
  raise_errors(type: :JSON, body: output_json, response: response)
1075
- rescue
1194
+ rescue => ex
1076
1195
  reset_files(body) if multipart
1077
1196
  raise
1078
1197
  end
1198
+
1199
+ return [output_json, response]
1079
1200
  rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
1080
1201
  if self.class.to_s == 'ZuoraAPI::Oauth' && ex.message.include?("Authentication type is not supported by this Login")
1081
1202
  session_type = :bearer
1082
1203
  retry
1083
- else
1084
- Rails.logger.debug("Rest Call - Session Bad Auth type")
1085
- raise ex
1086
1204
  end
1205
+ Rails.logger.debug("Rest Call - Session Bad Auth type")
1206
+ raise ex
1207
+
1087
1208
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
1088
1209
  if !tries.zero? && z_session
1089
1210
  tries -= 1
@@ -1096,40 +1217,35 @@ module ZuoraAPI
1096
1217
  end
1097
1218
 
1098
1219
  retry
1099
- else
1100
- if errors.include?(ex.class)
1101
- raise ex
1102
- else
1103
- return [output_json, response]
1104
- end
1105
1220
  end
1221
+
1222
+ raise ex if errors.include?(ex.class)
1223
+ return [output_json, response]
1224
+
1106
1225
  rescue *ZUORA_API_ERRORS => ex
1107
- if errors.include?(ex.class)
1108
- raise ex
1109
- else
1110
- response = ex.response unless response
1111
- return [output_json, response]
1112
- end
1226
+ raise ex if errors.include?(ex.class)
1227
+
1228
+ response = ex.response unless response
1229
+ return [output_json, response]
1230
+
1113
1231
  rescue ZuoraAPI::Exceptions::BadEntityError => ex
1114
1232
  raise ex
1115
1233
  rescue *CONNECTION_EXCEPTIONS => ex
1116
- if tries.zero?
1117
- if output_exception_messages
1118
- if Rails.logger.class.to_s == "Ougai::Logger"
1119
- Rails.logger.error("Rest Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
1120
- else
1121
- Rails.logger.error("Rest Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
1122
- end
1123
- end
1124
- raise ex
1234
+ if !tries.zero?
1235
+ tries -= 1
1236
+ self.log(location: "Rest Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
1237
+ sleep(timeout_sleep_interval)
1238
+ retry
1125
1239
  end
1126
1240
 
1127
- tries -= 1
1128
- sleep(timeout_sleep_interval)
1129
- retry
1241
+ self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
1242
+ raise ex
1243
+
1130
1244
  rescue *CONNECTION_READ_EXCEPTIONS => ex
1245
+
1131
1246
  if !tries.zero?
1132
1247
  tries -= 1
1248
+ self.log(location: "Rest Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
1133
1249
  if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
1134
1250
  retry
1135
1251
  elsif timeout_retry
@@ -1137,20 +1253,15 @@ module ZuoraAPI
1137
1253
  retry
1138
1254
  end
1139
1255
  end
1140
-
1141
- if output_exception_messages
1142
- if Rails.logger.class.to_s == "Ougai::Logger"
1143
- Rails.logger.error("Rest Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
1144
- else
1145
- Rails.logger.error("Rest Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
1146
- end
1147
- end
1148
- raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
1256
+
1257
+ self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
1258
+ ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read/write timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.is_a?(Timeout::Error) && !ex.instance_of?(ZuoraAPI::Exceptions::ZuoraAPIReadTimeout)
1149
1259
  raise ex
1260
+
1150
1261
  rescue => ex
1151
1262
  raise ex
1152
- else
1153
- return [output_json, response]
1263
+ ensure
1264
+ self.error_logger(ex) if defined?(ex)
1154
1265
  end
1155
1266
 
1156
1267
  def update_create_tenant
@@ -1172,8 +1283,9 @@ module ZuoraAPI
1172
1283
  while !response["nextPage"].blank?
1173
1284
  url = self.rest_endpoint(response["nextPage"].split('/v1/').last)
1174
1285
  Rails.logger.debug("Fetch Catalog URL #{url}")
1175
- output_json, response = self.rest_call(:debug => false, :url => url, :errors => [ZuoraAPI::Exceptions::ZuoraAPISessionError], :timeout_retry => true )
1176
- if !output_json['success'] =~ (/(true|t|yes|y|1)$/i) || output_json['success'].class != TrueClass
1286
+ output_json, response = self.rest_call(debug: false, url: url, timeout_retry: true)
1287
+
1288
+ if !/(true|t|yes|y|1)$/.match(output_json['success'].to_s) || output_json['success'].class != TrueClass
1177
1289
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}", response)
1178
1290
  end
1179
1291
  output_json["products"].each do |product|
@@ -1199,14 +1311,14 @@ module ZuoraAPI
1199
1311
  return products, catalog_map
1200
1312
  end
1201
1313
 
1202
- def get_file(url: nil, headers: {}, z_session: true, tempfile: true, output_file_name: nil, zuora_track_id: nil, add_timestamp: true, file_path: defined?(Rails.root.join('tmp')) ? Rails.root.join('tmp') : Pathname.new(Dir.pwd), timeout_retries: 3, timeout: 120, session_type: :basic, **execute_params)
1314
+ def get_file(url: nil, headers: {}, z_session: true, tempfile: true, output_file_name: nil, zuora_track_id: nil, add_timestamp: true, file_path: defined?(Rails.root.join('tmp')) ? Rails.root.join('tmp') : Pathname.new(Dir.pwd), timeout_retries: 3, timeout: 130, session_type: :basic, **execute_params)
1203
1315
  raise "file_path must be of class Pathname" if file_path.class != Pathname
1204
1316
 
1205
1317
  retry_count ||= timeout_retries
1206
1318
 
1207
1319
  #Make sure directory exists
1208
1320
  require 'fileutils'
1209
- FileUtils.mkdir_p(file_path) unless File.exists?(file_path)
1321
+ FileUtils.mkdir_p(file_path) unless File.exist?(file_path)
1210
1322
 
1211
1323
  status_code = nil
1212
1324
  uri = URI.parse(url)
@@ -1215,10 +1327,13 @@ module ZuoraAPI
1215
1327
  http.use_ssl = true if !uri.scheme.nil? && uri.scheme.downcase == 'https'
1216
1328
  if z_session
1217
1329
  headers = headers.merge({"Authorization" => self.get_session(prefix: true)})
1218
- headers = headers.merge({"Zuora-Entity-Ids" => self.entity_id}) if !self.entity_id.blank?
1330
+
1331
+ self.insert_entity_header(headers)
1219
1332
  end
1220
1333
 
1221
1334
  headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
1335
+ headers['X-Amzn-Trace-Id'] = zuora_track_id if zuora_track_id.present?
1336
+ headers["User-Agent"] = USER_AGENT
1222
1337
 
1223
1338
  response_save = nil
1224
1339
  http.request_get(uri.request_uri, headers) do |response|
@@ -1226,11 +1341,7 @@ module ZuoraAPI
1226
1341
  status_code = response.code if response
1227
1342
  case response
1228
1343
  when Net::HTTPOK
1229
- headers = {}
1230
- response.each_header do |k,v|
1231
- headers[k] = v
1232
- end
1233
- Rails.logger.debug("Headers: #{headers.to_s}")
1344
+ Rails.logger.warn("Headers: #{response.to_hash.to_s}")
1234
1345
  if output_file_name.present?
1235
1346
  file_ending ||= output_file_name.end_with?(".csv.zip") ? ".csv.zip" : File.extname(output_file_name)
1236
1347
  filename ||= File.basename(output_file_name, file_ending)
@@ -1238,15 +1349,16 @@ module ZuoraAPI
1238
1349
 
1239
1350
  size, export_progress = [0, 0]
1240
1351
  encoding, type, full_filename = [nil, nil, nil]
1241
- if response.header["Content-Disposition"].present?
1242
- case response.header["Content-Disposition"]
1352
+ if response.get_fields("Content-Disposition").present?
1353
+ content_disposition = response.get_fields("Content-Disposition")[0]
1354
+ case content_disposition
1243
1355
  when /.*; filename\*=.*/
1244
- full_filename ||= /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[2].strip
1245
- encoding = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[1].strip
1356
+ full_filename ||= /.*; filename\*=(.*)''(.*)/.match(content_disposition)[2].strip
1357
+ encoding = /.*; filename\*=(.*)''(.*)/.match(content_disposition)[1].strip
1246
1358
  when /.*; filename=/
1247
- full_filename ||= /.*; filename=(.*)/.match(response.header["Content-Disposition"])[1].strip
1359
+ full_filename ||= /.*; filename=(.*)/.match(content_disposition)[1].strip
1248
1360
  else
1249
- raise "Can't parse Content-Disposition header: #{response.header["Content-Disposition"]}"
1361
+ raise "Can't parse Content-Disposition header: #{content_disposition}"
1250
1362
  end
1251
1363
  file_ending ||= full_filename.end_with?(".csv.zip") ? ".csv.zip" : File.extname(full_filename)
1252
1364
  filename ||= File.basename(full_filename, file_ending)
@@ -1256,21 +1368,22 @@ module ZuoraAPI
1256
1368
  file_ending ||= uri.path.end_with?(".csv.zip") ? ".csv.zip" : File.extname(uri.path)
1257
1369
  filename ||= File.basename(uri.path, file_ending)
1258
1370
 
1259
- if response.header["Content-Type"].present?
1260
- case response.header["Content-Type"]
1371
+ if response.get_fields("Content-Type").present?
1372
+ content_type = response.get_fields("Content-Type")[0]
1373
+ case content_type
1261
1374
  when /.*;charset=.*/
1262
- type = /(.*);charset=(.*)/.match(response.header["Content-Type"])[1]
1263
- encoding = /(.*);charset=(.*)/.match(response.header["Content-Type"])[2]
1375
+ type = /(.*);charset=(.*)/.match(content_type)[1]
1376
+ encoding = /(.*);charset=(.*)/.match(content_type)[2]
1264
1377
  else
1265
- type = response.header["Content-Type"]
1378
+ type = content_type
1266
1379
  encoding ||= 'UTF-8'
1267
1380
  end
1268
1381
  end
1269
1382
 
1270
- if response.header["Content-Length"].present?
1271
- export_size = response.header["Content-Length"].to_i
1272
- elsif response.header["ContentLength"].present?
1273
- export_size = response.header["ContentLength"].to_i
1383
+ if response.get_fields("Content-Length").present?
1384
+ export_size = response.get_fields("Content-Length")[0].to_i
1385
+ elsif response.get_fields("ContentLength").present?
1386
+ export_size = response.get_fields("ContentLength")[0].to_i
1274
1387
  end
1275
1388
 
1276
1389
  Rails.logger.info("File: #{filename}#{file_ending} #{encoding} #{type} #{export_size}")
@@ -1304,14 +1417,20 @@ module ZuoraAPI
1304
1417
  return file_handle
1305
1418
  when Net::HTTPUnauthorized
1306
1419
  if z_session
1307
- if !(retry_count -= 1).zero?
1420
+ unless (retry_count -= 1).zero?
1308
1421
  self.new_session
1309
- raise response.class
1310
- else
1311
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
1422
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError, 'Retrying'
1312
1423
  end
1424
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
1425
+ end
1426
+ raise
1427
+ when Net::HTTPNotFound
1428
+ if url.include?(self.fileURL)
1429
+ raise ZuoraAPI::Exceptions::FileDownloadError.new(
1430
+ "The current tenant does not have a file with id '#{url.split('/').last}'"
1431
+ )
1313
1432
  else
1314
- raise
1433
+ raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
1315
1434
  end
1316
1435
  else
1317
1436
  raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
@@ -1322,133 +1441,79 @@ module ZuoraAPI
1322
1441
  sleep(5)
1323
1442
  if (retry_count -= 1) >= 0
1324
1443
  retry
1325
- else
1326
- Rails.logger.error("File Download Failed")
1327
- raise
1328
1444
  end
1445
+ Rails.logger.error("File Download Failed")
1446
+ raise
1329
1447
  end
1330
1448
 
1331
1449
  def getDataSourceExport(query, extract: true, encrypted: false, zip: true, z_track_id: "")
1332
- begin
1333
- tries ||= 3
1334
- request = Nokogiri::XML::Builder.new do |xml|
1335
- xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/", 'xmlns:ns2' => "http://object.api.zuora.com/", 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 'xmlns:ns1' => "http://api.zuora.com/") do
1336
- xml['SOAP-ENV'].Header do
1337
- xml['ns1'].SessionHeader do
1338
- xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
1339
- end
1340
- end
1341
- xml['SOAP-ENV'].Body do
1342
- xml['ns1'].create do
1343
- xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1344
- xml['ns2'].Format 'csv'
1345
- xml['ns2'].Zip zip
1346
- xml['ns2'].Name 'googman'
1347
- xml['ns2'].Query query
1348
- xml['ns2'].Encrypted encrypted
1349
- end
1350
- end
1351
- end
1450
+ tries ||= 3
1451
+
1452
+ output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true, zuora_track_id: z_track_id) do |xml|
1453
+ xml['ns1'].create do
1454
+ xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1455
+ xml['ns2'].Format 'csv'
1456
+ xml['ns2'].Zip zip
1457
+ xml['ns2'].Name 'googman'
1458
+ xml['ns2'].Query query
1459
+ xml['ns2'].Encrypted encrypted
1352
1460
  end
1353
1461
  end
1354
-
1355
- response_query = HTTParty.post(self.url, body: request.to_xml(:save_with => XML_SAVE_OPTIONS).strip, headers: {'Content-Type' => "application/json; charset=utf-8", "Z-Track-Id" => z_track_id}, :timeout => 120)
1356
-
1357
- output_xml = Nokogiri::XML(response_query.body)
1358
- raise_errors(type: :SOAP, body: output_xml, response: response_query) if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1359
-
1360
- # raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1361
-
1362
- id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
1363
-
1364
- confirmRequest = Nokogiri::XML::Builder.new do |xml|
1365
- xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/", 'xmlns:ns2' => "http://object.api.zuora.com/", 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 'xmlns:ns1' => "http://api.zuora.com/") do
1366
- xml['SOAP-ENV'].Header do
1367
- xml['ns1'].SessionHeader do
1368
- xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
1369
- end
1370
- end
1371
- xml['SOAP-ENV'].Body do
1372
- xml['ns1'].query do
1373
- xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
1374
- end
1375
- end
1462
+ end
1463
+ id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
1464
+
1465
+ result = 'Waiting'
1466
+ while result != "Completed"
1467
+ sleep 3
1468
+ output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true, zuora_track_id: z_track_id) do |xml|
1469
+ xml['ns1'].query do
1470
+ xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
1376
1471
  end
1377
1472
  end
1378
- result = 'Waiting'
1379
-
1380
- while result != "Completed"
1381
- sleep 3
1382
- response_query = HTTParty.post(self.url, body: confirmRequest.to_xml(:save_with => XML_SAVE_OPTIONS).strip, headers: {'Content-Type' => "application/json; charset=utf-8", "Z-Track-Id" => z_track_id}, :timeout => 120)
1383
-
1384
- output_xml = Nokogiri::XML(response_query.body)
1385
- result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
1386
- status_code = response_query.code if response_query
1387
-
1388
- raise_errors(type: :SOAP, body: output_xml, response: response_query) if result.blank? || result == "Failed"
1389
- # raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if result.blank? || result == "Failed"
1390
- end
1391
-
1392
- file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
1393
- export_file = get_file(:url => self.fileURL(file_id))
1394
- export_file_path = export_file.path
1395
- Rails.logger.debug("=====> Export path #{export_file.path}")
1396
-
1397
- if extract && zip
1398
- require "zip"
1399
- new_path = export_file_path.partition('.zip').first
1400
- zipped = Zip::File.open(export_file_path)
1401
- file_handle = zipped.entries.first
1402
- file_handle.extract(new_path)
1403
- File.delete(export_file_path)
1404
- return new_path
1405
- else
1406
- return export_file_path
1407
- end
1408
- rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
1409
- if !(tries -= 1).zero?
1410
- Rails.logger.info("Export call failed - Trace ID: #{z_track_id}")
1411
- self.new_session
1412
- retry
1413
- else
1414
- raise ex
1415
- end
1416
-
1417
- rescue ZuoraAPI::Exceptions::ZuoraUnexpectedError => ex
1418
- if !(tries -= 1).zero?
1419
- Rails.logger.info("Trace ID: #{z_track_id} UnexpectedError, will retry after 10 seconds")
1420
- sleep 10
1421
- retry
1422
- else
1423
- raise ex
1424
- end
1425
-
1426
- rescue *ZUORA_API_ERRORS => ex
1427
- raise ex
1428
-
1429
- rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
1430
- if !(tries -= 1).zero?
1431
- Rails.logger.info("Trace ID: #{z_track_id} Timed out will retry after 5 seconds")
1432
- sleep 5
1433
- retry
1434
- else
1435
- raise ex
1436
- end
1473
+ result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
1474
+ end
1437
1475
 
1438
- rescue Errno::ECONNRESET => ex
1439
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
1440
- retry
1441
- else
1442
- raise ex
1443
- end
1476
+ file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
1477
+ export_file = get_file(:url => self.fileURL(file_id))
1478
+ export_file_path = export_file.path
1479
+
1480
+ if extract && zip
1481
+ require "zip"
1482
+ new_path = export_file_path.partition('.zip').first
1483
+ zipped = Zip::File.open(export_file_path)
1484
+ file_handle = zipped.entries.first
1485
+ file_handle.extract(new_path)
1486
+ File.delete(export_file_path)
1487
+ return new_path
1488
+ else
1489
+ return export_file_path
1490
+ end
1491
+ rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
1492
+ if !(tries -= 1).zero?
1493
+ Rails.logger.info("Export call failed - Trace ID: #{z_track_id}")
1494
+ self.new_session
1495
+ retry
1496
+ end
1497
+ raise ex
1444
1498
 
1445
- rescue ZuoraAPI::Exceptions::BadEntityError => ex
1446
- raise ex
1499
+ rescue ZuoraAPI::Exceptions::ZuoraUnexpectedError => ex
1500
+ if !(tries -= 1).zero?
1501
+ Rails.logger.info("Trace ID: #{z_track_id} UnexpectedError, will retry after 10 seconds")
1502
+ sleep(self.timeout_sleep)
1503
+ retry
1447
1504
  end
1505
+ raise ex
1506
+ rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
1507
+ if !(tries -= 1).zero?
1508
+ Rails.logger.info("Trace ID: #{z_track_id} Timed out will retry after 5 seconds")
1509
+ sleep(self.timeout_sleep)
1510
+ retry
1511
+ end
1512
+ raise ex
1448
1513
  end
1449
1514
 
1450
1515
  def query(query, parse = false)
1451
- output_xml, input_xml = self.soap_call({:debug => false, :timeout_retry => true}) do |xml|
1516
+ output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true) do |xml|
1452
1517
  xml['ns1'].query do
1453
1518
  xml['ns1'].queryString query
1454
1519
  end