zuora_api 1.7.66 → 1.11.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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