zuora_api 1.7.03 → 1.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fe1f740bb001aa14dbd92c6d4e40f12d7444bc93f6b7629453687bffc725a8a
4
- data.tar.gz: 7cde2faa394443a5d243311fd3c2e289104910d23fe069d64ccf0031e51c9d94
3
+ metadata.gz: 1215bc1af56e355b1e454f84b465e380280e1915c978b37003e9bb01bdcd73e4
4
+ data.tar.gz: e702c8c5d31fc49aabf4ea71a27ff409deb6bc304603335602a4fa2e80ad5f82
5
5
  SHA512:
6
- metadata.gz: a2c5dc988d2329b255f2d315ad9c3e0c9ce7a827185e055382b7a274162a6be979c6cf92afa9cfccf5267a1d4632398dc4f4434e06d140c3f79b2b0fb6dcfaaa
7
- data.tar.gz: 2355124490081c1c1c7b4f144516e20e23b95104e2fa015d12eb0ca6e2d69acace7edee8f499dd0263f2edebd28720655711d5bdc81b72c1479322d00b1661db
6
+ metadata.gz: bff03abeeb11116c1d75fdef3299182dde6452a5da95113f8a124afcbfa065f3c06ba04496bec6008d1e2b793c4a454fcd3471d2879f8390714178ab2e09ee65
7
+ data.tar.gz: 0fcbaab89256770c7f3fe616839ba402bac33122c474ac6ac16068ae67badb959bace310af9a12171b41ff2a064050f0fa44c7c09262eae3d707151da092abcc
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .idea
@@ -48,8 +48,8 @@ rubygems-deploy:
48
48
  - if [[ "staging" == $CI_BUILD_REF_SLUG ]];then export VERSION=`git describe --match "[0-9]*\.[0-9]*\.[0-9]*[a-z]" --abbrev=0 --tags HEAD`; fi
49
49
  - if [[ "master" == $CI_BUILD_REF_SLUG ]];then export VERSION=`git describe --exclude "[0-9]*\.[0-9]*\.[0-9]*[a-z]" --abbrev=0 --tags HEAD`; fi
50
50
  - echo $VERSION
51
- - sed -i "s/0.0.1/$VERSION/" /Connect/zuora-gem/lib/zuora_api/version.rb
52
- - git add /Connect/zuora-gem/lib/zuora_api/version.rb
51
+ - sed -i "s/0.0.1/$VERSION/" lib/zuora_api/version.rb
52
+ - git add lib/zuora_api/version.rb
53
53
  - git config --global user.email "connect@zuora.com"
54
54
  - git config --global user.name "Connect Automation"
55
55
  - git commit -m "Automated Version Update $VERSION"
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [1.7.07] - 2018-9-10
5
+ ### Changed
6
+ - Cookie name for service endpoint integration
7
+
8
+ ## [1.7.06] - 2018-8-25
9
+ ### Added
10
+ - Retry for 502/503
11
+ - Standard exception for 504
12
+
13
+ ## [1.7.05] - 2018-8-25
14
+ ### Added
15
+ - Added support for oauth token forbidden
16
+
17
+ ## [1.7.02] - 2018-8-23
18
+ ### Added
19
+ - Added mulit part support for rest call
20
+
4
21
  ## [1.7.01] - 2018-8-06
5
22
  ### Changed
6
23
  - Changed library used to determine host
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Zuora Gem
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/zuora_api.svg)](https://badge.fury.io/rb/zuora_api) [![coverage report](https://gitlab.zuora.com/Connect/zuora-gem/badges/master/coverage.svg)](https://gitlab.zuora.com/Connect/zuora-gem/commits/master)
3
+ [![Gem Version](https://badge.fury.io/rb/zuora_api.svg)](https://badge.fury.io/rb/zuora_api) [![coverage report](https://gitlab.0.ecc.auw2.zuora/extension-products/shared-libraries/zuora-gem/badges/master/coverage.svg)](https://gitlab.0.ecc.auw2.zuora/extension-products/shared-libraries/zuora-gem/commits/master)
4
4
 
5
5
  ## Installation
6
6
  Add this line to your application's Gemfile:
@@ -190,12 +190,11 @@ module InsightsAPI
190
190
  end
191
191
 
192
192
  rescue => ex
193
- if !(tries -= 1).zero?
194
- sleep 3
195
- retry
196
- else
197
- raise ex
198
- end
193
+ raise ex if tries.zero?
194
+
195
+ tries -= 1
196
+ sleep 3
197
+ retry
199
198
  else
200
199
  return temp_file
201
200
  end
@@ -1,14 +1,38 @@
1
1
  module ZuoraAPI
2
2
  module Exceptions
3
- class Error < StandardError; end
3
+ class Error < StandardError;
4
+ def parse_message(message)
5
+ case message
6
+ when /^Payment status should be Processed. Invalid payment is P-\d*./
7
+ @message = "Payment status should be Processed."
8
+ when /^Adjustment cannot be created for invoice(.*) with a zero balance./
9
+ @message = "Adjustment cannot be created for invoice with a zero balance."
10
+ when /^The balance of all the invoice items and tax items is 0. No write-off is needed for the invoice .*./
11
+ @message = "The balance of all the invoice items and tax items is 0. No write-off is needed for the invoice."
12
+ when /^Json input does not match schema. Error(s): string ".*" is too long .*/
13
+ @message = "Json input does not match schema. Error(s): String is too long."
14
+ when /^Query failed \(#[\d\w_]*\): line [0-9]+:[0-9]+: (.*)$/
15
+ @message = "Query failed: #{$1}"
16
+ when /^Query failed \(#[\d\w_]*\): (.*)$/
17
+ @message = "Query failed: #{$1}"
18
+ when /^Could not find [\w\d]{32}.$/
19
+ @message = "Could not find object."
20
+ when /^Subscription [\w\d]{32} is in expired status. It is not supported to generate billing documents for expired subscriptions./
21
+ @message = "Subscription is in expired status. It is not supported to generate billing documents for expired subscriptions."
22
+ else
23
+ @message = message
24
+ end
25
+ end
26
+ end
27
+ class FileDownloadError < StandardError; end
4
28
  class AuthorizationNotPerformed < Error; end
5
29
  class ZuoraAPISessionError < Error
6
30
  attr_reader :code, :response
7
31
  attr_writer :default_message
8
32
 
9
- def initialize(message = nil,response=nil)
10
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
11
- @message = message
33
+ def initialize(message = nil,response=nil, errors = [], successes = [])
34
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
35
+ @message = parse_message(message)
12
36
  @response = response
13
37
  @default_message = "Error with Zuora Session."
14
38
  end
@@ -23,8 +47,8 @@ module ZuoraAPI
23
47
  attr_writer :default_message
24
48
 
25
49
  def initialize(message = nil,response=nil, errors = [], successes = [], *args)
26
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
27
- @message = message
50
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
51
+ @message = parse_message(message)
28
52
  @response = response
29
53
  @default_message = "Error with Zuora Entity"
30
54
  @errors = errors
@@ -41,8 +65,8 @@ module ZuoraAPI
41
65
  attr_writer :default_message
42
66
 
43
67
  def initialize(message = nil,response=nil, errors = [], successes = [], *args)
44
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
45
- @message = message
68
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
69
+ @message = parse_message(message)
46
70
  @response = response
47
71
  @default_message = "Error communicating with Zuora."
48
72
  @errors = errors
@@ -54,13 +78,31 @@ module ZuoraAPI
54
78
  end
55
79
  end
56
80
 
81
+ class ZuoraAPIInternalServerError < Error
82
+ attr_reader :code, :response, :errors, :successes
83
+ attr_writer :default_message
84
+
85
+ def initialize(message = nil,response = nil, errors = [], successes = [], *args)
86
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
87
+ @message = parse_message(message)
88
+ @response = response
89
+ @default_message = "Zuora Internal Server Error."
90
+ @errors = errors
91
+ @successes = successes
92
+ end
93
+
94
+ def to_s
95
+ @message || @default_message
96
+ end
97
+ end
98
+
57
99
  class ZuoraAPIRequestLimit < Error
58
100
  attr_reader :code, :response
59
101
  attr_writer :default_message
60
102
 
61
- def initialize(message = nil,response=nil, *args)
62
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
63
- @message = message
103
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
104
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
105
+ @message = parse_message(message)
64
106
  @response = response
65
107
  @default_message = "Your request limit has been exceeded for zuora."
66
108
  end
@@ -70,13 +112,29 @@ module ZuoraAPI
70
112
  end
71
113
  end
72
114
 
115
+ class ZuoraAPIUnkownError < Error
116
+ attr_reader :code, :response
117
+ attr_writer :default_message
118
+
119
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
120
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
121
+ @message = parse_message(message)
122
+ @response = response
123
+ @default_message = "An unkown error occured. Workflow is not responsible. Please contact Support."
124
+ end
125
+
126
+ def to_s
127
+ @message || @default_message
128
+ end
129
+ end
130
+
73
131
  class ZuoraAPILockCompetition < Error
74
132
  attr_reader :code, :response
75
133
  attr_writer :default_message
76
134
 
77
- def initialize(message = nil,response=nil, *args)
78
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
79
- @message = message
135
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
136
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
137
+ @message = parse_message(message)
80
138
  @response = response
81
139
  @default_message = "Operation failed due to lock competition. Please retry"
82
140
  end
@@ -90,9 +148,9 @@ module ZuoraAPI
90
148
  attr_reader :code, :response
91
149
  attr_writer :default_message
92
150
 
93
- def initialize(message = nil,response=nil, *args)
94
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
95
- @message = message
151
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
152
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
153
+ @message = parse_message(message)
96
154
  @response = response
97
155
  @default_message = "Operation failed due to lock competition. Please retry"
98
156
  end
@@ -102,13 +160,29 @@ module ZuoraAPI
102
160
  end
103
161
  end
104
162
 
163
+ class ZuoraUnexpectedError < Error
164
+ attr_reader :code, :response
165
+ attr_writer :default_message
166
+
167
+ def initialize(message = nil, response=nil, errors = [], successes = [], *args)
168
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
169
+ @message = parse_message(message)
170
+ @response = response
171
+ @default_message = "An unexpected error occurred"
172
+ end
173
+
174
+ def to_s
175
+ @message || @default_message
176
+ end
177
+ end
178
+
105
179
  class ZuoraAPITemporaryError < Error
106
180
  attr_reader :code, :response
107
181
  attr_writer :default_message
108
182
 
109
- def initialize(message = nil,response=nil, *args)
110
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
111
- @message = message
183
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
184
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
185
+ @message = parse_message(message)
112
186
  @response = response
113
187
  @default_message = "There is a temporary error with zuora system."
114
188
  end
@@ -122,10 +196,43 @@ module ZuoraAPI
122
196
  attr_reader :code, :response
123
197
  attr_writer :default_message
124
198
 
125
- def initialize(message = nil,response=nil, *args)
126
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
199
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
200
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
201
+ @message = parse_message(message)
202
+ @response = response
203
+ @default_message = "Authentication type is not supported by this Login"
204
+ end
205
+
206
+ def to_s
207
+ @message || @default_message
208
+ end
209
+ end
210
+
211
+ class ZuoraAPIConnectionTimeout < Net::OpenTimeout
212
+ attr_reader :code, :response
213
+ attr_writer :default_message
214
+
215
+ def initialize(message = nil,response=nil, errors = [], successes = [], *args)
216
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
217
+ @message = message
218
+ @response = response
219
+ @default_message = "Authentication type is not supported by this Login"
220
+ end
221
+
222
+ def to_s
223
+ @message || @default_message
224
+ end
225
+ end
226
+
227
+ class ZuoraAPIReadTimeout < Net::ReadTimeout
228
+ attr_reader :code, :response, :request
229
+ attr_writer :default_message
230
+
231
+ def initialize(message = nil, response = nil, request = nil, errors = [], successes = [], *args)
232
+ @code = response.class.to_s == "HTTParty::Response" ? response.code : nil
127
233
  @message = message
128
234
  @response = response
235
+ @request = request
129
236
  @default_message = "Authentication type is not supported by this Login"
130
237
  end
131
238
 
@@ -5,13 +5,44 @@ require 'zuora_api/exceptions'
5
5
 
6
6
  module ZuoraAPI
7
7
  class Login
8
- ENVIRONMENTS = [SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
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
10
  MIN_Endpoint = '96.0'
11
11
  XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
12
- CONNECTION_EXCEPTIONS = [Net::OpenTimeout, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, SocketError, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL]
13
- CONNECTION_READ_EXCEPTIONS = [Net::ReadTimeout, Errno::ECONNRESET, Errno::EPIPE]
14
- ZUORA_API_ERRORS = [ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition, ZuoraAPI::Exceptions::ZuoraAPITemporaryError, ZuoraAPI::Exceptions::ZuoraDataIntegrity]
12
+
13
+ CONNECTION_EXCEPTIONS = [
14
+ Net::OpenTimeout,
15
+ OpenSSL::SSL::SSLError,
16
+ Errno::ECONNREFUSED,
17
+ SocketError,
18
+ Errno::EHOSTUNREACH,
19
+ Errno::EADDRNOTAVAIL,
20
+ Errno::ETIMEDOUT,
21
+ ].freeze
22
+
23
+ CONNECTION_READ_EXCEPTIONS = [
24
+ Net::ReadTimeout,
25
+ Errno::ECONNRESET,
26
+ Errno::EPIPE
27
+ ].freeze
28
+
29
+ ZUORA_API_ERRORS = [
30
+ ZuoraAPI::Exceptions::ZuoraAPIError,
31
+ ZuoraAPI::Exceptions::ZuoraAPIRequestLimit,
32
+ ZuoraAPI::Exceptions::ZuoraAPILockCompetition,
33
+ ZuoraAPI::Exceptions::ZuoraAPITemporaryError,
34
+ ZuoraAPI::Exceptions::ZuoraDataIntegrity,
35
+ ZuoraAPI::Exceptions::ZuoraAPIInternalServerError,
36
+ ZuoraAPI::Exceptions::ZuoraUnexpectedError,
37
+ ZuoraAPI::Exceptions::ZuoraAPIUnkownError
38
+ ].freeze
39
+
40
+ ZUORA_SERVER_ERRORS = [
41
+ ZuoraAPI::Exceptions::ZuoraAPIInternalServerError,
42
+ ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout,
43
+ ZuoraAPI::Exceptions::ZuoraAPIReadTimeout,
44
+ ZuoraAPI::Exceptions::ZuoraUnexpectedError
45
+ ].freeze
15
46
 
16
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
17
48
 
@@ -19,10 +50,10 @@ module ZuoraAPI
19
50
  raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
20
51
  # raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
21
52
  self.hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
22
- if !/apps\/services\/a\/\d{2}\.\d$/.match(url.strip)
53
+ if !/apps\/services\/a\/\d+\.\d$/.match(url.strip)
23
54
  self.url = "https://#{hostname}/apps/services/a/#{MIN_Endpoint}"
24
- elsif MIN_Endpoint.to_f > url.scan(/(\d{2}\.\d)$/).dig(0,0).to_f
25
- self.url = url.gsub(/(\d{2}\.\d)$/, 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)
26
57
  else
27
58
  self.url = url
28
59
  end
@@ -41,27 +72,12 @@ module ZuoraAPI
41
72
 
42
73
  def get_identity(cookies)
43
74
  zsession = cookies["ZSession"]
44
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
45
75
  begin
46
76
  if !zsession.blank?
47
77
  response = HTTParty.get("https://#{self.hostname}/apps/v1/identity", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
48
78
  output_json = JSON.parse(response.body)
49
- elsif zconnect_accesstoken.present?
50
- begin
51
- code = zconnect_accesstoken.split("#!").last
52
- encrypted_token, tenant_id = Base64.decode64(code).split(":")
53
- body = {'token' => encrypted_token}.to_json
54
- rescue => ex
55
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Invalid ZConnect Cookie")
56
- end
57
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/identity", :body => body, :headers => { 'Content-Type' => 'application/json' })
58
- output_json = JSON.parse(response.body)
59
79
  else
60
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
61
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
62
- else
63
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
64
- end
80
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
65
81
  end
66
82
  rescue JSON::ParserError => ex
67
83
  output_json = {}
@@ -72,20 +88,12 @@ module ZuoraAPI
72
88
 
73
89
  def get_full_nav(cookies)
74
90
  zsession = cookies["ZSession"]
75
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
76
91
  begin
77
92
  if zsession.present?
78
93
  response = HTTParty.get("https://#{self.hostname}/apps/v1/navigation", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
79
94
  output_json = JSON.parse(response.body)
80
- elsif zconnect_accesstoken.present?
81
- response = HTTParty.get("https://#{self.hostname}/apps/zconnectsession/navigation", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}",'Content-Type' => 'application/json'})
82
- output_json = JSON.parse(response.body)
83
95
  else
84
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
85
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
86
- else
87
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
88
- end
96
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
89
97
  end
90
98
  rescue JSON::ParserError => ex
91
99
  output_json = {}
@@ -96,20 +104,12 @@ module ZuoraAPI
96
104
 
97
105
  def set_nav(state, cookies)
98
106
  zsession = cookies["ZSession"]
99
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
100
107
  begin
101
108
  if !zsession.blank?
102
109
  response = HTTParty.put("https://#{self.hostname}/apps/v1/preference/navigation", :body => state.to_json, :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
103
110
  output_json = JSON.parse(response.body)
104
- elsif !zconnect_accesstoken.blank?
105
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/navigationstate", :body => state.to_json, :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
106
- output_json = JSON.parse(response.body)
107
111
  else
108
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
109
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
110
- else
111
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
112
- end
112
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
113
113
  end
114
114
  rescue JSON::ParserError => ex
115
115
  output_json = {}
@@ -120,20 +120,12 @@ module ZuoraAPI
120
120
 
121
121
  def refresh_nav(cookies)
122
122
  zsession = cookies["ZSession"]
123
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
124
123
  begin
125
124
  if !zsession.blank?
126
125
  response = HTTParty.post("https://#{self.hostname}/apps/v1/navigation/fetch", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
127
126
  output_json = JSON.parse(response.body)
128
- elsif !zconnect_accesstoken.blank?
129
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/refresh-navbarcache", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
130
- output_json = JSON.parse(response.body)
131
127
  else
132
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
133
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
134
- else
135
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
136
- end
128
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
137
129
  end
138
130
  rescue JSON::ParserError => ex
139
131
  output_json = {}
@@ -142,27 +134,21 @@ module ZuoraAPI
142
134
  return output_json
143
135
  end
144
136
 
145
- def get_zconnect_accesstoken(cookies)
146
- accesstoken = nil
147
- self.update_zconnect_provider
148
- if !cookies[self.zconnect_provider].nil? && !cookies[self.zconnect_provider].empty?
149
- accesstoken = cookies[self.zconnect_provider]
150
- end
151
- return accesstoken
152
- end
153
-
154
137
  def reporting_url(path)
155
138
  map = {"US" => {"Sandbox" => "https://zconnectsandbox.zuora.com/api/rest/v1/",
156
139
  "Production" => "https://zconnect.zuora.com/api/rest/v1/",
157
- "Services"=> ""},
140
+ "Test" => "https://reporting-sbx.zan.0001.sbx.auw2.zuora.com/api/rest/v1/",
141
+ "Staging" => "https://reporting-stg11.zan.svc.auw2.zuora.com/api/rest/v1/",
142
+ "Performance" => "https://zconnectpt1.zuora.com/api/rest/v1/",
143
+ "Services" => "https://reporting-svc08.svc.auw2.zuora.com/api/rest/v1/"},
158
144
  "EU" => {"Sandbox" => "https://zconnect.sandbox.eu.zuora.com/api/rest/v1/",
159
145
  "Production" => "https://zconnect.eu.zuora.com/api/rest/v1/",
160
- "Services"=> ""},
146
+ "Services"=> "https://reporting-sbx0000.sbx.aec1.zuora.com/api/rest/v1/"},
161
147
  "NA" => {"Sandbox" => "https://zconnect.sandbox.na.zuora.com/api/rest/v1/",
162
148
  "Production" => "https://zconnect.na.zuora.com/api/rest/v1/",
163
149
  "Services"=> ""}
164
150
  }
165
- return map[zuora_client.region][zuora_client.environment].insert(-1, path)
151
+ return map[self.region][self.environment].insert(-1, path)
166
152
  end
167
153
 
168
154
  # There are two ways to call this method. The first way is best.
@@ -212,7 +198,7 @@ module ZuoraAPI
212
198
  end
213
199
 
214
200
  def self.environments
215
- %w(Sandbox Production Services Performance Staging)
201
+ %w(Sandbox Production Services Performance Staging Test)
216
202
  end
217
203
 
218
204
  def self.regions
@@ -224,11 +210,13 @@ module ZuoraAPI
224
210
  "Production" => "https://www.zuora.com/apps/services/a/",
225
211
  "Performance" => "https://pt1.zuora.com/apps/services/a/",
226
212
  "Services" => "https://services347.zuora.com/apps/services/a/",
227
- "Staging" => "https://staging2.zuora.com/apps/services/a/"},
213
+ "Staging" => "https://staging2.zuora.com/apps/services/a/",
214
+ "Test" => "https://test.zuora.com/apps/services/a/"},
228
215
  "EU" => {"Sandbox" => "https://sandbox.eu.zuora.com/apps/services/a/",
229
216
  "Production" => "https://eu.zuora.com/apps/services/a/",
230
217
  "Performance" => "https://pt1.eu.zuora.com/apps/services/a/",
231
- "Services" => "https://services347.eu.zuora.com/apps/services/a/"},
218
+ "Services" => "https://services347.eu.zuora.com/apps/services/a/",
219
+ "Test" => "https://test.eu.zuora.com/apps/services/a/"},
232
220
  "NA" => {"Sandbox" => "https://sandbox.na.zuora.com/apps/services/a/",
233
221
  "Production" => "https://na.zuora.com/apps/services/a/",
234
222
  "Performance" => "https://pt1.na.zuora.com/apps/services/a/",
@@ -265,15 +253,18 @@ module ZuoraAPI
265
253
 
266
254
  def update_environment
267
255
  if !self.url.blank?
268
- if /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/ === self.hostname
256
+ case self.hostname
257
+ when /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/
269
258
  self.environment = 'Sandbox'
270
- elsif /(?<=\.|\/|^)(service[\d]*|services[\d]*|ep-edge)(?=\.|\/|$)/ === self.hostname
259
+ when /(?<=\.|\/|^)(service[\d]*|services[\d]*|ep-edge)(?=\.|\/|$)/
271
260
  self.environment = 'Services'
272
- elsif /(?<=\.|\/|-|^)(pt[\d]*)(?=\.|\/|-|$)/ === self.hostname
261
+ when /(?<=\.|\/|-|^)(pt[\d]*)(?=\.|\/|-|$)/
273
262
  self.environment = 'Performance'
274
- elsif /(?<=\.|\/|^)(staging1|staging2|stg)(?=\.|\/|$)/ === self.hostname
263
+ when /(?<=\.|\/|^)(staging1|staging2|stg)(?=\.|\/|$)/
275
264
  self.environment = 'Staging'
276
- elsif is_prod_env
265
+ when /(?<=\.|\/|^)(test)(?=\.|\/|$)/
266
+ self.environment = 'Test'
267
+ when /(?<=\.|\/|^)(www|api)(?=\.|\/|$)/, /(^|tls10\.|origin-www\.|zforsf\.|eu\.|na\.)(zuora\.com)/
277
268
  self.environment = 'Production'
278
269
  else
279
270
  self.environment = 'Unknown'
@@ -283,25 +274,14 @@ module ZuoraAPI
283
274
  end
284
275
  end
285
276
 
286
- def is_prod_env
287
- is_prod = false
288
- www_or_api = /(?<=\.|\/|^)(www|api)(?=\.|\/|$)/ === self.hostname
289
- host_prefix_match = /(^|tls10\.|origin-www\.|zforsf\.|eu\.|na\.)(zuora\.com)/ === self.hostname
290
- if www_or_api || host_prefix_match
291
- is_prod = true
292
- end
293
- return is_prod
294
- end
295
-
296
277
  def update_zconnect_provider
297
278
  region = update_region
298
279
  environment = update_environment
299
- mappings = {"US" => {"Sandbox" => "ZConnectSbx", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev", "Services" => "ZConnectQA", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Staging" => "ZConnectQA"},
300
- "NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectQANA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
301
- "EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectQAEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU"},
280
+ mappings = {"US" => {"Sandbox" => "ZConnectSbx", "Services" => "ZConnectSvcUS", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Test" => "ZConnectTest", "Staging" => "ZConnectQA", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev"},
281
+ "NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectSvcNA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
282
+ "EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectSvcEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU", "Test" => "ZConnectTest"},
302
283
  "Unknown" => {"Unknown" => "Unknown"}}
303
284
  self.zconnect_provider = mappings[region][environment]
304
- # raise "Can't find ZConnect Provider for #{region} region and #{environment} environment" if self.zconnect_provider.nil?
305
285
  end
306
286
 
307
287
  def aqua_endpoint(url="")
@@ -314,45 +294,35 @@ module ZuoraAPI
314
294
  return "#{url_slash_apps_slash}api/#{url}"
315
295
  end
316
296
 
317
- def rest_endpoint(url="")
297
+ def rest_endpoint(url="", domain=true, prefix='/v1/')
318
298
  update_environment
319
299
  endpoint = url
320
-
300
+ url_postfix = {"US" => ".", "EU" => ".eu.", "NA" => ".na."}[self.region]
301
+
321
302
  case self.environment
303
+ when 'Test'
304
+ endpoint = "https://rest.test#{url_postfix}zuora.com"
322
305
  when 'Sandbox'
323
- case self.region
324
- when 'US'
325
- endpoint = "https://rest.apisandbox.zuora.com/v1/".concat(url)
326
- when 'EU'
327
- endpoint = "https://rest.sandbox.eu.zuora.com/v1/".concat(url)
328
- when 'NA'
329
- endpoint = "https://rest.sandbox.na.zuora.com/v1/".concat(url)
330
- end
306
+ endpoint = "https://rest.sandbox#{url_postfix}zuora.com"
307
+ endpoint = "https://rest.apisandbox.zuora.com" if self.region == "US"
331
308
  when 'Production'
332
- case self.region
333
- when 'US'
334
- endpoint = "https://rest.zuora.com/v1/".concat(url)
335
- when 'EU'
336
- endpoint = "https://rest.eu.zuora.com/v1/".concat(url)
337
- when 'NA'
338
- endpoint = "https://rest.na.zuora.com/v1/".concat(url)
339
- end
309
+ endpoint = "https://rest#{url_postfix}zuora.com"
340
310
  when 'Performance'
341
- endpoint = "https://rest.pt1.zuora.com/v1/".concat(url)
311
+ endpoint = "https://rest.pt1.zuora.com"
342
312
  when 'Services'
343
313
  https = /https:\/\/|http:\/\//.match(self.url)[0]
344
314
  host = self.hostname
345
- endpoint = "#{https}#{host}/apps/v1/#{url}"
315
+ endpoint = "#{https}rest#{host}"
346
316
  when 'Staging'
347
- endpoint = "https://rest-staging2.zuora.com/v1/".concat(url)
317
+ endpoint = "https://rest-staging2.zuora.com"
348
318
  when 'Unknown'
349
319
  raise "Environment unknown, returning passed in parameter unaltered"
350
320
  end
351
- return endpoint
321
+ return domain ? endpoint.concat(prefix).concat(url) : prefix.concat(url)
352
322
  end
353
323
 
354
324
  def rest_domain
355
- return URI(self.rest_endpoint).host
325
+ return URI(self.rest_endpoint).host
356
326
  end
357
327
 
358
328
  def fileURL(url="")
@@ -363,10 +333,10 @@ module ZuoraAPI
363
333
  return self.wsdl_number > 68 ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
364
334
  end
365
335
 
366
- def new_session(auth_type: :basic, debug: false)
336
+ def new_session(auth_type: :basic, debug: false, zuora_track_id: nil)
367
337
  end
368
338
 
369
- def get_session(prefix: false, auth_type: :basic)
339
+ def get_session(prefix: false, auth_type: :basic, zuora_track_id: nil)
370
340
  Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}") if Rails.env.to_s == 'development'
371
341
  case auth_type
372
342
  when :basic
@@ -374,13 +344,13 @@ module ZuoraAPI
374
344
  case self.class.to_s
375
345
  when 'ZuoraAPI::Oauth'
376
346
  if self.bearer_token.blank? || self.oauth_expired?
377
- self.new_session(auth_type: :bearer)
347
+ self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
378
348
  end
379
- self.get_z_session if self.status == 'Active'
349
+ self.get_z_session(zuora_track_id: zuora_track_id) if self.status == 'Active'
380
350
  when 'ZuoraAPI::Basic'
381
- self.new_session(auth_type: :basic)
351
+ self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
382
352
  else
383
- raise "No Zuora Login Specified"
353
+ self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
384
354
  end
385
355
  end
386
356
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
@@ -389,17 +359,33 @@ module ZuoraAPI
389
359
  case self.class.to_s
390
360
  when 'ZuoraAPI::Oauth'
391
361
  if self.bearer_token.blank? || self.oauth_expired?
392
- self.new_session(auth_type: :bearer)
362
+ self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
393
363
  end
394
364
  when 'ZuoraAPI::Basic'
395
365
  raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Basic Login, does not support Authentication of Type: #{auth_type}")
366
+ else
367
+ raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Unknown Login, does not support Authentication of Type: #{auth_type}")
396
368
  end
369
+
397
370
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
398
371
  return prefix ? "Bearer #{self.bearer_token}" : self.bearer_token.to_s
399
372
  end
400
373
  end
401
374
 
402
- def soap_call(ns1: 'ns1', ns2: 'ns2', batch_size: nil, single_transaction: false, debug: false, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS), z_session: true, timeout_retry: false, timeout: 120,**keyword_args)
375
+ def soap_call(
376
+ ns1: 'ns1',
377
+ ns2: 'ns2',
378
+ batch_size: nil,
379
+ single_transaction: false,
380
+ debug: false,
381
+ zuora_track_id: nil,
382
+ errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS),
383
+ z_session: true,
384
+ timeout_retry: false,
385
+ timeout: 120,
386
+ timeout_sleep_interval: self.timeout_sleep,
387
+ output_exception_messages: true,
388
+ **keyword_args)
403
389
  tries ||= 2
404
390
  xml = Nokogiri::XML::Builder.new do |xml|
405
391
  xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
@@ -409,7 +395,7 @@ module ZuoraAPI
409
395
  "xmlns:#{ns1}" => "http://api.zuora.com/") do
410
396
  xml['SOAP-ENV'].Header do
411
397
  xml["#{ns1}"].SessionHeader do
412
- xml["#{ns1}"].session self.get_session(prefix: false, auth_type: :basic)
398
+ xml["#{ns1}"].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: zuora_track_id)
413
399
  end
414
400
  if single_transaction
415
401
  xml["#{ns1}"].CallOptions do
@@ -432,15 +418,34 @@ module ZuoraAPI
432
418
  input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
433
419
  Rails.logger.debug("Request SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
434
420
 
435
- response = HTTParty.post(self.url,:body => xml.doc.to_xml(:save_with => XML_SAVE_OPTIONS).strip, :headers => {'Content-Type' => "text/xml; charset=utf-8"}, :timeout => timeout)
421
+ headers = { 'Content-Type' => "text/xml; charset=utf-8", 'Accept' => 'text/xml'}
422
+ headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
423
+
424
+ request = HTTParty::Request.new(
425
+ Net::HTTP::Post,
426
+ self.url,
427
+ body: xml.doc.to_xml(:save_with => XML_SAVE_OPTIONS).strip,
428
+ headers: headers,
429
+ timeout: timeout,
430
+ )
431
+
432
+ response = request.perform
433
+
436
434
  output_xml = Nokogiri::XML(response.body)
437
435
  Rails.logger.debug("Response SOAP XML: #{output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
438
436
 
439
437
  raise_errors(type: :SOAP, body: output_xml, response: response)
440
438
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
441
- if !(tries -= 1).zero? && z_session
439
+ if !tries.zero? && z_session
440
+ tries -= 1
442
441
  Rails.logger.debug("SOAP Call - Session Invalid")
443
- self.new_session(auth_type: :basic)
442
+
443
+ begin
444
+ self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
445
+ rescue *ZUORA_API_ERRORS => ex
446
+ return output_xml, input_xml, ex.response
447
+ end
448
+
444
449
  retry
445
450
  else
446
451
  if errors.include?(ex.class)
@@ -453,30 +458,45 @@ module ZuoraAPI
453
458
  if errors.include?(ex.class)
454
459
  raise ex
455
460
  else
461
+ response = ex.response unless response
456
462
  return output_xml, input_xml, response
457
463
  end
458
464
  rescue *CONNECTION_EXCEPTIONS => ex
459
- if !(tries -= 1).zero?
460
- Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
461
- sleep(self.timeout_sleep)
462
- retry
463
- else
464
- raise ex
465
+ if tries.zero?
466
+ if output_exception_messages
467
+ if Rails.logger.class.to_s == "Ougai::Logger"
468
+ Rails.logger.error("SOAP Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
469
+ else
470
+ Rails.logger.error("SOAP Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
471
+ end
472
+ end
473
+ raise ex
465
474
  end
475
+
476
+ tries -= 1
477
+ sleep(timeout_sleep_interval)
478
+ retry
466
479
  rescue *CONNECTION_READ_EXCEPTIONS => ex
467
- if !(tries -= 1).zero? && timeout_retry
468
- Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
469
- sleep(self.timeout_sleep)
470
- retry
471
- else
472
- raise ex
480
+ if !tries.zero?
481
+ tries -= 1
482
+
483
+ if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
484
+ retry
485
+ elsif timeout_retry
486
+ sleep(timeout_sleep_interval)
487
+ retry
488
+ end
473
489
  end
474
- rescue Errno::ECONNRESET => ex
475
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
476
- retry
477
- else
478
- raise ex
490
+
491
+ if output_exception_messages
492
+ if Rails.logger.class.to_s == "Ougai::Logger"
493
+ Rails.logger.error("SOAP Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
494
+ else
495
+ Rails.logger.error("SOAP Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
496
+ end
479
497
  end
498
+ raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
499
+ raise ex
480
500
  rescue => ex
481
501
  raise ex
482
502
  else
@@ -484,31 +504,60 @@ module ZuoraAPI
484
504
  end
485
505
 
486
506
  def raise_errors(type: :SOAP, body: nil, response: nil)
487
- case type
488
- when :SOAP
489
- error = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
490
- message = body.xpath('//fns:FaultMessage', 'fns' =>'http://fault.api.zuora.com/').text
507
+ request_uri, request_path, match_string = "", "", ""
508
+ if response.class.to_s == "HTTP::Message"
509
+ request_uri = response.http_header.request_uri.to_s
510
+ request_path = response.http_header.request_uri.path
511
+ match_string = "#{response.http_header.request_method}::#{response.code}::#{request_uri}"
512
+ else
513
+ request = response.request
514
+ request_uri = response.request.uri
515
+ request_path = request.path.path
516
+ match_string = "#{request.http_method.to_s.split("Net::HTTP::").last.upcase}::#{response.code}::#{request_path}"
517
+ end
491
518
 
492
- if error.blank? || message.blank?
493
- error = body.xpath('//faultcode').text
494
- message = body.xpath('//faultstring').text
495
- end
519
+ if [502,503].include?(response.code)
520
+ raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from #{request_uri}", response)
521
+ end
496
522
 
497
- if error.blank? || message.blank?
498
- error = body.xpath('//ns1:Code', 'ns1' =>'http://api.zuora.com/').text
499
- message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
523
+ # Check failure response code
524
+ case response.code
525
+ when 504
526
+ raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from #{request_uri}", response)
527
+ when 429
528
+ 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)
529
+ when 401
530
+
531
+ else
532
+ if body.class == Hash
533
+ case request_path
534
+ when /^\/v1\/connections$/
535
+ response_headers = response.headers.to_h
536
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Missing cookies for authentication call", response) if response_headers['set-cookie'].blank?
537
+ z_session_cookie = response_headers.fetch('set-cookie', []).select{|x| x.match(/^ZSession=.*/) }.first
538
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Missing ZSession cookie for authentication call", response) if z_session_cookie.blank?
539
+ end
500
540
  end
541
+ end
501
542
 
502
- #Update/Create/Delete Calls with multiple requests and responses
503
- if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
504
- error = []
505
- success = []
506
- body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').each_with_index do |call, object_index|
507
- if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false' && call.xpath('./ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
508
- message = "#{call.xpath('./*/ns1:Code', 'ns1' =>'http://api.zuora.com/').text}::#{call.xpath('./*/ns1:Message', 'ns1' =>'http://api.zuora.com/').text}"
509
- error.push(message)
543
+ case type
544
+ when :SOAP
545
+ error, success, message = get_soap_error_and_message(body)
546
+
547
+ if body.xpath('//ns1:queryResponse', 'ns1' => 'http://api.zuora.com/').present? &&
548
+ body.xpath(
549
+ '//ns1:records[@xsi:type="ns2:Export"]',
550
+ 'ns1' => 'http://api.zuora.com/', 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
551
+ ).present?
552
+ result = body.xpath('//ns2:Status', 'ns2' => 'http://object.api.zuora.com/').text
553
+ if result == 'Failed'
554
+ reason = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
555
+ if reason.present?
556
+ message = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
557
+ 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'
510
558
  else
511
- success.push(call.xpath('./ns1:Id', 'ns1' =>'http://api.zuora.com/').text)
559
+ error = 'FATAL_ERROR'
560
+ message = 'Export failed due to unknown reason. Consult api logs.'
512
561
  end
513
562
  end
514
563
  end
@@ -516,44 +565,48 @@ module ZuoraAPI
516
565
  #By default response if not passed in for SOAP as all SOAP is 200
517
566
  if error.present?
518
567
  if error.class == String
519
- case error
520
- when "INVALID_SESSION"
521
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{error}::#{message}", response)
522
- when "REQUEST_EXCEEDED_LIMIT"
523
- raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{error}::#{message}", response)
524
- when "LOCK_COMPETITION"
525
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", response)
526
- when "BATCH_FAIL_ERROR"
527
- if message.include?("optimistic locking failed")
528
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", response)
529
- else
530
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response)
531
- end
532
- when "TEMPORARY_ERROR"
533
- raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new("#{error}::#{message}", response)
534
- when "INVALID_VALUE"
535
- if message.include?("data integrity violation")
536
- raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response)
537
- else
538
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response)
539
- end
540
- else
541
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response) if error.present?
542
- end
568
+ raise_errors_helper(error: error, message: message, response: response)
543
569
  elsif error.class == Array
544
- if error[0].include?("LOCK_COMPETITION") && error.count == 1
545
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(error.group_by {|v| v}.map {|k,v| "(#{v.size}x) - #{k == "::" ? 'UNKNOWN::No error provided' : k}"}.join(', '), response)
570
+ if error.uniq.size == 1
571
+ err, msg = error[0].split('::')
572
+ raise_errors_helper(error: err, message: msg, response: response, errors: error, success: success)
546
573
  else
547
574
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(error.group_by {|v| v}.map {|k,v| "(#{v.size}x) - #{k == "::" ? 'UNKNOWN::No error provided' : k}"}.join(', '), response, error, success)
548
575
  end
549
576
  end
550
577
  end
578
+
579
+ self.errors_via_content_type(response: response, type: :xml)
551
580
 
552
- if response.code == 429
553
- 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)
581
+ when :JSON
582
+ case request_path
583
+ when /^\/query\/jobs.*/ #DataQuery Paths
584
+ return if body.class != Hash
585
+ case match_string
586
+ when /^GET::200::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job, Capture of the id is present if needed in future error responses.
587
+ if body.dig('data', "errorCode") == "LINK_10000005"
588
+ raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body.dig('data', "errorMessage"), response)
589
+ elsif (body.dig('data', "errorMessage").present? || body.dig('data', "queryStatus") == "failed")
590
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('data', "errorMessage"), response)
591
+ end
592
+ 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.
593
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('message'), response) if body.dig('message').present?
594
+ when /^POST::400::\/query\/jobs$/ #Create DQ job
595
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('message'), response) if body.dig('message').present?
596
+ end
597
+ when /^\/api\/rest\/v1\/reports.*/ #Reporting Paths
598
+ reporting_message = response.parsed_response.dig("ZanResponse", "response", "message") || body.dig("message")
599
+ if reporting_message&.include?("com.zuora.rest.RestUsageException: The user does not have permissions for this API.")
600
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(reporting_message, response)
601
+ elsif reporting_message&.include?("500 Internal Server Error")
602
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Internal Server Error. The Reporting API is down. Contact Support.", response)
603
+ end
604
+ case match_string
605
+ 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.
606
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(reporting_message, response) if reporting_message.present?
607
+ end
554
608
  end
555
609
 
556
- when :JSON
557
610
  body = body.dig("results").present? ? body["results"] : body if body.class == Hash
558
611
  if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
559
612
  messages_array = body.fetch("reasons", []).map {|error| error['message']}.compact
@@ -561,6 +614,14 @@ module ZuoraAPI
561
614
  codes_array = body.fetch("reasons", []).map {|error| error['code']}.compact
562
615
  codes_array = codes_array.push(body.dig("error", 'code')).compact if body.dig('error').class == Hash
563
616
 
617
+ if body['message'] == 'request exceeded limit'
618
+ 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)
619
+ end
620
+
621
+ if (body.dig('message') || '').downcase.include?('unexpected error') && response.code != 500
622
+ raise ZuoraAPI::Exceptions::ZuoraUnexpectedError.new(body['message'], response)
623
+ end
624
+
564
625
  if body['message'] == "No bearer token" && response.code == 400
565
626
  raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Authentication type is not supported by this Login", response)
566
627
  end
@@ -574,8 +635,12 @@ module ZuoraAPI
574
635
  end
575
636
 
576
637
  #Oauth Tokens - User deactivated
577
- if body['message'] == 'Forbidden' && body['status'] == 403 && response.code == 403
578
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Forbidden", response)
638
+ if body['path'] == '/oauth/token'
639
+ if body['status'] == 403 && response.code == 403
640
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Forbidden", response)
641
+ elsif body['status'] == 400 && response.code == 400 && body['message'].include?("Invalid client id")
642
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Invalid Oauth Client Id", response)
643
+ end
579
644
  end
580
645
 
581
646
  if body['error'] == 'Unauthorized' && body['status'] == 401
@@ -583,7 +648,12 @@ module ZuoraAPI
583
648
  end
584
649
  #Authentication failed
585
650
  if (codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(11) || response.code == 401) && !codes_array.include?(422)
586
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
651
+ new_message = messages_array.join(', ')
652
+ if new_message.present?
653
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(new_message, response)
654
+ else
655
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(body['message'], response)
656
+ end
587
657
  end
588
658
 
589
659
  #Zuora REST Create Amendment error #Authentication failed
@@ -591,16 +661,30 @@ module ZuoraAPI
591
661
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body['faultstring']}", response)
592
662
  end
593
663
 
664
+ #Locking contention
665
+ if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(50)
666
+ raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{messages_array.join(', ')}", response)
667
+ end
668
+ #Internal Server Error
669
+ if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(60)
670
+ if messages_array.uniq.size == 1
671
+ 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.*/)
672
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(messages_array.first, response)
673
+ end
674
+ end
675
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("#{messages_array.join(', ')}", response)
676
+ end
677
+
678
+ #Retryiable Service Error
679
+ if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(61)
680
+ raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new("#{messages_array.join(', ')}", response)
681
+ end
682
+
594
683
  #Request exceeded limit
595
684
  if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(70)
596
685
  raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{messages_array.join(', ')}", response)
597
686
  end
598
687
 
599
- #Locking contention
600
- if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(50)
601
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{messages_array.join(', ')}", response)
602
- end
603
-
604
688
  #All Errors catch
605
689
  if codes_array.size > 0
606
690
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{messages_array.join(', ')}", response)
@@ -608,26 +692,20 @@ module ZuoraAPI
608
692
 
609
693
  #Zuora REST Query Errors
610
694
  if body["faultcode"].present?
611
- case body["faultcode"]
612
- when "fns:MALFORMED_QUERY"
613
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
614
- when "fns:REQUEST_EXCEEDED_LIMIT"
615
- raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
616
- when "fns:LOCK_COMPETITION"
617
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
618
- when "INVALID_SESSION"
619
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
620
- else
621
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
622
- end
695
+ raise_errors_helper(error: body["faultcode"], message: body["faultstring"], response: response)
623
696
  end
624
697
 
625
698
  if body["Errors"].present? || body["errors"].present?
626
- errors = []
627
- (body["Errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
628
- (body["errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
629
- if errors.size > 0
630
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{errors.join(", ")}", response, errors)
699
+ codes, messages = [[],[]]
700
+ body.fetch("Errors", []).each { |obj| messages.push(obj["Message"]); messages.push(obj["title"]); codes.push(obj["Code"]); codes.push(obj["code"]) }
701
+ body.fetch("errors", []).each { |obj| messages.push(obj["Message"]); messages.push(obj["title"]); codes.push(obj["Code"]); codes.push(obj["code"]) }
702
+ codes, messages = [codes.uniq.compact, messages.uniq.compact]
703
+ if codes.size > 0
704
+ if codes.size == 1
705
+ raise_errors_helper(error: codes.first, message: messages.first, response: response, errors: messages)
706
+ else
707
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{messages.join(", ")}", response, messages)
708
+ end
631
709
  end
632
710
  end
633
711
  end
@@ -645,7 +723,7 @@ module ZuoraAPI
645
723
  raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("Retry Lock Competition", response)
646
724
  elsif error_messages.first.include?("data integrity violation")
647
725
  raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response)
648
- end
726
+ end
649
727
  end
650
728
  end
651
729
 
@@ -654,15 +732,128 @@ module ZuoraAPI
654
732
  end
655
733
  end
656
734
 
735
+ if body.class == Hash && body['message'].present?
736
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(body['message'], response) if response.code == 500
737
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['message'], response) if ![200,201].include?(response.code)
738
+ end
739
+
740
+ self.errors_via_content_type(response: response, type: :json)
741
+
657
742
  #All other errors
658
- if ![200,201].include?(response.code)
659
- if response.code == 429
660
- 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)
743
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(response.body, response) if ![200,201].include?(response.code)
744
+ end
745
+ end
746
+
747
+ def errors_via_content_type(response: nil, type: :xml)
748
+ response_content_types = response.headers.transform_keys(&:downcase).fetch('content-type', []).first || ""
749
+
750
+ if response_content_types.include?('application/json') && type != :json
751
+ output_json = JSON.parse(response.body)
752
+ self.raise_errors(type: :JSON, body: output_json, response: response)
753
+
754
+ elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml')) and type != :xml
755
+ output_xml = Nokogiri::XML(response.body)
756
+ self.raise_errors(type: :SOAP, body: output_xml, response: response)
757
+
758
+ elsif response_content_types.include?('text/html')
759
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Akamai Error", response) if response.headers.fetch('server', '') == 'AkamaiGHost'
760
+
761
+ parse_body = Nokogiri::HTML(response.body)
762
+ error_title = parse_body.xpath('//h2').text
763
+ error_title = parse_body.xpath('//h1').text if error_title.blank?
764
+ error_message = parse_body.xpath('//p').text
765
+
766
+ error_message = error_title if error_message.blank?
767
+
768
+ if error_title.present?
769
+ case error_title
770
+ when /Service Unavailable/
771
+ raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new(error_message, response)
772
+ when /Client sent a bad request./, /Bad Request/, /403 Forbidden/
773
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
774
+ else
775
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
776
+ end
777
+ end
778
+
779
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Http response body is missing", response) if response.body.blank?
780
+ end
781
+
782
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(response.body, response) if response.code == 500
783
+ end
784
+
785
+
786
+ def get_soap_error_and_message(body)
787
+ error = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
788
+ message = body.xpath('//fns:FaultMessage', 'fns' =>'http://fault.api.zuora.com/').text
789
+
790
+ if error.blank? || message.blank?
791
+ error = body.xpath('//faultcode').text
792
+ message = body.xpath('//faultstring').text
793
+ end
794
+
795
+ if error.blank? || message.blank?
796
+ error = body.xpath('//ns1:Code', 'ns1' =>'http://api.zuora.com/').text
797
+ message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
798
+ end
799
+
800
+ #Update/Create/Delete Calls with multiple requests and responses
801
+ if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
802
+ error = []
803
+ success = []
804
+ body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').each_with_index do |call, object_index|
805
+ if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false' && call.xpath('./ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
806
+ message = "#{call.xpath('./*/ns1:Code', 'ns1' =>'http://api.zuora.com/').text}::#{call.xpath('./*/ns1:Message', 'ns1' =>'http://api.zuora.com/').text}"
807
+ error.push(message)
661
808
  else
662
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{response.message}", response)
809
+ success.push(call.xpath('./ns1:Id', 'ns1' =>'http://api.zuora.com/').text)
663
810
  end
664
811
  end
665
812
  end
813
+ return error, success, message
814
+ end
815
+
816
+ def raise_errors_helper(error: nil, message: nil, response: nil, errors: [], success: [])
817
+ case error
818
+ when /.*INVALID_SESSION/
819
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(message, response, errors, success)
820
+ when /.*REQUEST_EXCEEDED_LIMIT/
821
+ raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new(message, response, errors, success)
822
+ when /.*LOCK_COMPETITION/
823
+ raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(message, response, errors, success)
824
+ when /.*BATCH_FAIL_ERROR/
825
+ if message.include?("optimistic locking failed") || message.include?("Operation failed due to a lock competition, please retry later.")
826
+ raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(message, response, errors, success)
827
+ elsif message.include?("org.hibernate.exception.ConstraintViolationException")
828
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
829
+ end
830
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
831
+ when /.*TEMPORARY_ERROR/, /.*TRANSACTION_TIMEOUT/
832
+ raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(message, response, errors, success)
833
+ when /.*INVALID_VALUE/
834
+ if message.include?("data integrity violation")
835
+ raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response, errors), success
836
+ end
837
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
838
+ when /.*UNKNOWN_ERROR/
839
+ if /payment\/refund|Credit Balance Adjustment|Payment Gateway|ARSettlement permission/.match(message).nil?
840
+ raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
841
+ end
842
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
843
+ 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/
844
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
845
+ when /.*UNEXPECTED_ERROR/
846
+ raise ZuoraAPI::Exceptions::ZuoraUnexpectedError.new(message, response, errors, success)
847
+ when /.*soapenv:Server.*/
848
+ if /^Invalid value.*for type.*|^Id is invalid|^date string can not be less than 19 charactors$/.match(message).present?
849
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
850
+ elsif /^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
851
+ raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
852
+ end
853
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
854
+ else
855
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Z:#{error}::#{message}", response, errors, success)
856
+ end
666
857
  end
667
858
 
668
859
  def aqua_query(queryName: '', query: '', version: '1.2', jobName: 'Aqua',partner: '', project: '')
@@ -742,16 +933,24 @@ module ZuoraAPI
742
933
  end
743
934
  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
744
935
  end
745
- rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS) => ex
746
- if !(tries -= 1).zero?
747
- Rails.logger.info("Describe - #{ex.class} Timed out will retry after 5 seconds")
748
- sleep(self.timeout_sleep)
749
- retry
750
- else
751
- raise ex
936
+ rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
937
+ if tries.zero?
938
+ if log_errors
939
+ if Rails.logger.class.to_s == "Ougai::Logger"
940
+ Rails.logger.error("Describe - Timed out will retry after #{self.timeout_sleep} seconds", ex)
941
+ else
942
+ Rails.logger.error("Describe - #{ex.class} Timed out will retry after #{self.timeout_sleep} seconds")
943
+ end
944
+ end
945
+ raise ex
752
946
  end
947
+
948
+ tries -= 1
949
+ sleep(self.timeout_sleep)
950
+ retry
753
951
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
754
- if !(tries -= 1).zero? && self.status == 'Active'
952
+ if !tries.zero? && self.status == 'Active'
953
+ tries -= 1
755
954
  Rails.logger.debug("Describe session expired. Starting new session.")
756
955
  self.new_session
757
956
  retry
@@ -776,50 +975,73 @@ module ZuoraAPI
776
975
  session_type: :basic,
777
976
  timeout_retry: false,
778
977
  timeout: 120,
978
+ timeout_sleep_interval: self.timeout_sleep,
779
979
  multipart: false,
780
- **keyword_args
980
+ stream_body: false,
981
+ output_exception_messages: true,
982
+ **keyword_args,
983
+ &block
781
984
  )
782
985
  tries ||= 2
783
986
 
784
- if self.entity_id.present?
785
- headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
786
- headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
787
- end
788
-
789
987
  raise "Method not supported, supported methods include: :get, :post, :put, :delete, :patch, :head, :options" if ![:get, :post, :put, :delete, :patch, :head, :options].include?(method)
790
988
 
791
- authentication_headers = z_session ? {"Authorization" => self.get_session(prefix: true, auth_type: session_type) } : {}
792
- modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
989
+ authentication_headers = {}
990
+ if z_session
991
+ authentication_headers = {"Authorization" => self.get_session(prefix: true, auth_type: session_type) }
992
+ if self.entity_id.present?
993
+ authentication_headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
994
+ authentication_headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
995
+ end
996
+ end
793
997
 
794
- response = HTTParty::Request.new(
795
- "Net::HTTP::#{method.to_s.capitalize}".constantize,
796
- url,
797
- body: body,
798
- headers: modified_headers,
799
- timeout: timeout,
800
- multipart: multipart,
801
- ).perform
998
+ modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
802
999
 
803
- Rails.logger.debug("Response Code: #{response.code}") if debug
804
1000
  begin
805
- output_json = JSON.parse(response.body)
806
- rescue JSON::ParserError => ex
807
- output_json = {}
808
- end
809
- Rails.logger.debug("Response JSON: #{output_json}") if debug && output_json.present?
1001
+ request = HTTParty::Request.new(
1002
+ "Net::HTTP::#{method.to_s.capitalize}".constantize,
1003
+ url,
1004
+ body: body,
1005
+ headers: modified_headers,
1006
+ timeout: timeout,
1007
+ multipart: multipart,
1008
+ stream_body: stream_body
1009
+ )
1010
+
1011
+ response = request.perform(&block)
1012
+
1013
+ Rails.logger.debug("Response Code: #{response.code}") if debug
1014
+ begin
1015
+ output_json = JSON.parse(response.body)
1016
+ rescue JSON::ParserError => ex
1017
+ output_json = {}
1018
+ end
1019
+ Rails.logger.debug("Response JSON: #{output_json}") if debug && output_json.present?
810
1020
 
811
- raise_errors(type: :JSON, body: output_json, response: response)
1021
+ raise_errors(type: :JSON, body: output_json, response: response)
1022
+ rescue
1023
+ reset_files(body) if multipart
1024
+ raise
1025
+ end
812
1026
  rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
813
1027
  if self.class.to_s == 'ZuoraAPI::Oauth' && ex.message.include?("Authentication type is not supported by this Login")
814
- self.rest_call(method: method.to_sym, url: url, body: body, debug: debug, errors: errors, z_session: z_session, session_type: :bearer, timeout_retry: timeout_retry, timeout: timeout)
1028
+ session_type = :bearer
1029
+ retry
815
1030
  else
816
1031
  Rails.logger.debug("Rest Call - Session Bad Auth type")
817
1032
  raise ex
818
1033
  end
819
1034
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
820
- if !(tries -= 1).zero? && z_session
1035
+ if !tries.zero? && z_session
1036
+ tries -= 1
821
1037
  Rails.logger.debug("Rest Call - Session Invalid #{session_type}")
822
- self.new_session(auth_type: session_type)
1038
+
1039
+ begin
1040
+ self.new_session(auth_type: session_type)
1041
+ rescue *ZUORA_API_ERRORS => ex
1042
+ return [output_json, ex.response]
1043
+ end
1044
+
823
1045
  retry
824
1046
  else
825
1047
  if errors.include?(ex.class)
@@ -832,32 +1054,46 @@ module ZuoraAPI
832
1054
  if errors.include?(ex.class)
833
1055
  raise ex
834
1056
  else
1057
+ response = ex.response unless response
835
1058
  return [output_json, response]
836
1059
  end
837
1060
  rescue ZuoraAPI::Exceptions::BadEntityError => ex
838
1061
  raise ex
839
1062
  rescue *CONNECTION_EXCEPTIONS => ex
840
- if !(tries -= 1).zero?
841
- Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
842
- sleep(self.timeout_sleep)
843
- retry
844
- else
845
- raise ex
1063
+ if tries.zero?
1064
+ if output_exception_messages
1065
+ if Rails.logger.class.to_s == "Ougai::Logger"
1066
+ Rails.logger.error("Rest Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
1067
+ else
1068
+ Rails.logger.error("Rest Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
1069
+ end
1070
+ end
1071
+ raise ex
846
1072
  end
1073
+
1074
+ tries -= 1
1075
+ sleep(timeout_sleep_interval)
1076
+ retry
847
1077
  rescue *CONNECTION_READ_EXCEPTIONS => ex
848
- if !(tries -= 1).zero? && timeout_retry
849
- Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
850
- sleep(self.timeout_sleep)
851
- retry
852
- else
853
- raise ex
1078
+ if !tries.zero?
1079
+ tries -= 1
1080
+ if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
1081
+ retry
1082
+ elsif timeout_retry
1083
+ sleep(timeout_sleep_interval)
1084
+ retry
1085
+ end
854
1086
  end
855
- rescue Errno::ECONNRESET => ex
856
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
857
- retry
858
- else
859
- raise ex
1087
+
1088
+ if output_exception_messages
1089
+ if Rails.logger.class.to_s == "Ougai::Logger"
1090
+ Rails.logger.error("Rest Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
1091
+ else
1092
+ Rails.logger.error("Rest Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
1093
+ end
860
1094
  end
1095
+ raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
1096
+ raise ex
861
1097
  rescue => ex
862
1098
  raise ex
863
1099
  else
@@ -910,9 +1146,11 @@ module ZuoraAPI
910
1146
  return products, catalog_map
911
1147
  end
912
1148
 
913
- def get_file(url: nil, headers: {}, z_session: true, tempfile: true, output_file_name: 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)
1149
+ 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)
914
1150
  raise "file_path must be of class Pathname" if file_path.class != Pathname
915
1151
 
1152
+ retry_count ||= timeout_retries
1153
+
916
1154
  #Make sure directory exists
917
1155
  require 'fileutils'
918
1156
  FileUtils.mkdir_p(file_path) unless File.exists?(file_path)
@@ -927,8 +1165,9 @@ module ZuoraAPI
927
1165
  headers = headers.merge({"Zuora-Entity-Ids" => self.entity_id}) if !self.entity_id.blank?
928
1166
  end
929
1167
 
1168
+ headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
1169
+
930
1170
  response_save = nil
931
- retry_count ||= timeout_retries
932
1171
  http.request_get(uri.request_uri, headers) do |response|
933
1172
  response_save = response
934
1173
  status_code = response.code if response
@@ -1022,11 +1261,11 @@ module ZuoraAPI
1022
1261
  raise
1023
1262
  end
1024
1263
  else
1025
- raise StandardError.new("File Download Failed #{response.class}")
1264
+ raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
1026
1265
  end
1027
1266
  end
1028
-
1029
- rescue => ex
1267
+
1268
+ rescue => ex
1030
1269
  sleep(5)
1031
1270
  if (retry_count -= 1) >= 0
1032
1271
  retry
@@ -1036,75 +1275,122 @@ module ZuoraAPI
1036
1275
  end
1037
1276
  end
1038
1277
 
1039
- def getDataSourceExport(query, extract: true, encrypted: false, zip: true)
1040
- request = Nokogiri::XML::Builder.new do |xml|
1041
- 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
1042
- xml['SOAP-ENV'].Header do
1043
- xml['ns1'].SessionHeader do
1044
- xml['ns1'].session self.get_session(prefix: false, auth_type: :basic)
1278
+ def getDataSourceExport(query, extract: true, encrypted: false, zip: true, z_track_id: "")
1279
+ begin
1280
+ tries ||= 3
1281
+ request = Nokogiri::XML::Builder.new do |xml|
1282
+ 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
1283
+ xml['SOAP-ENV'].Header do
1284
+ xml['ns1'].SessionHeader do
1285
+ xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
1286
+ end
1045
1287
  end
1046
- end
1047
- xml['SOAP-ENV'].Body do
1048
- xml['ns1'].create do
1049
- xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1050
- xml['ns2'].Format 'csv'
1051
- xml['ns2'].Zip zip
1052
- xml['ns2'].Name 'googman'
1053
- xml['ns2'].Query query
1054
- xml['ns2'].Encrypted encrypted
1288
+ xml['SOAP-ENV'].Body do
1289
+ xml['ns1'].create do
1290
+ xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1291
+ xml['ns2'].Format 'csv'
1292
+ xml['ns2'].Zip zip
1293
+ xml['ns2'].Name 'googman'
1294
+ xml['ns2'].Query query
1295
+ xml['ns2'].Encrypted encrypted
1296
+ end
1055
1297
  end
1056
1298
  end
1057
1299
  end
1058
1300
  end
1059
- end
1060
1301
 
1061
- response_query = HTTParty.post(self.url, body: request.to_xml(:save_with => XML_SAVE_OPTIONS).strip, headers: {'Content-Type' => "application/json; charset=utf-8"}, :timeout => 120)
1302
+ 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)
1062
1303
 
1063
- output_xml = Nokogiri::XML(response_query.body)
1064
- raise 'Export Creation Unsuccessful : ' + output_xml.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1065
- id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
1304
+ output_xml = Nokogiri::XML(response_query.body)
1305
+ raise_errors(type: :SOAP, body: output_xml, response: response_query) if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1066
1306
 
1067
- confirmRequest = Nokogiri::XML::Builder.new do |xml|
1068
- 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
1069
- xml['SOAP-ENV'].Header do
1070
- xml['ns1'].SessionHeader do
1071
- xml['ns1'].session self.get_session(prefix: false, auth_type: :basic)
1307
+ # raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1308
+
1309
+ id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
1310
+
1311
+ confirmRequest = Nokogiri::XML::Builder.new do |xml|
1312
+ 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
1313
+ xml['SOAP-ENV'].Header do
1314
+ xml['ns1'].SessionHeader do
1315
+ xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
1316
+ end
1072
1317
  end
1073
- end
1074
- xml['SOAP-ENV'].Body do
1075
- xml['ns1'].query do
1076
- xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
1318
+ xml['SOAP-ENV'].Body do
1319
+ xml['ns1'].query do
1320
+ xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
1321
+ end
1077
1322
  end
1078
1323
  end
1079
1324
  end
1080
- end
1081
- result = 'Waiting'
1325
+ result = 'Waiting'
1082
1326
 
1083
- while result != "Completed"
1084
- sleep 3
1085
- response_query = HTTParty.post(self.url, body: confirmRequest.to_xml(:save_with => XML_SAVE_OPTIONS).strip, headers: {'Content-Type' => "application/json; charset=utf-8"}, :timeout => 120)
1327
+ while result != "Completed"
1328
+ sleep 3
1329
+ 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)
1086
1330
 
1087
- output_xml = Nokogiri::XML(response_query.body)
1088
- result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
1089
- status_code = response_query.code if response_query
1090
- raise "Export Creation Unsuccessful : #{output_xml.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text}" if result.blank? || result == "Failed"
1091
- end
1331
+ output_xml = Nokogiri::XML(response_query.body)
1332
+ result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
1333
+ status_code = response_query.code if response_query
1092
1334
 
1093
- file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
1094
- export_file = get_file(:url => self.fileURL(file_id))
1095
- export_file_path = export_file.path
1096
- Rails.logger.debug("=====> Export path #{export_file.path}")
1097
-
1098
- if extract && zip
1099
- require "zip"
1100
- new_path = export_file_path.partition('.zip').first
1101
- zipped = Zip::File.open(export_file_path)
1102
- file_handle = zipped.entries.first
1103
- file_handle.extract(new_path)
1104
- File.delete(export_file_path)
1105
- return new_path
1106
- else
1107
- return export_file_path
1335
+ raise_errors(type: :SOAP, body: output_xml, response: response_query) if result.blank? || result == "Failed"
1336
+ # raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if result.blank? || result == "Failed"
1337
+ end
1338
+
1339
+ file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
1340
+ export_file = get_file(:url => self.fileURL(file_id))
1341
+ export_file_path = export_file.path
1342
+ Rails.logger.debug("=====> Export path #{export_file.path}")
1343
+
1344
+ if extract && zip
1345
+ require "zip"
1346
+ new_path = export_file_path.partition('.zip').first
1347
+ zipped = Zip::File.open(export_file_path)
1348
+ file_handle = zipped.entries.first
1349
+ file_handle.extract(new_path)
1350
+ File.delete(export_file_path)
1351
+ return new_path
1352
+ else
1353
+ return export_file_path
1354
+ end
1355
+ rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
1356
+ if !(tries -= 1).zero?
1357
+ Rails.logger.info("Export call failed - Trace ID: #{z_track_id}")
1358
+ self.new_session
1359
+ retry
1360
+ else
1361
+ raise ex
1362
+ end
1363
+
1364
+ rescue ZuoraAPI::Exceptions::ZuoraUnexpectedError => ex
1365
+ if !(tries -= 1).zero?
1366
+ Rails.logger.info("Trace ID: #{z_track_id} UnexpectedError, will retry after 10 seconds")
1367
+ sleep 10
1368
+ retry
1369
+ else
1370
+ raise ex
1371
+ end
1372
+
1373
+ rescue *ZUORA_API_ERRORS => ex
1374
+ raise ex
1375
+
1376
+ rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
1377
+ if !(tries -= 1).zero?
1378
+ Rails.logger.info("Trace ID: #{z_track_id} Timed out will retry after 5 seconds")
1379
+ sleep 5
1380
+ retry
1381
+ else
1382
+ raise ex
1383
+ end
1384
+
1385
+ rescue Errno::ECONNRESET => ex
1386
+ if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
1387
+ retry
1388
+ else
1389
+ raise ex
1390
+ end
1391
+
1392
+ rescue ZuoraAPI::Exceptions::BadEntityError => ex
1393
+ raise ex
1108
1394
  end
1109
1395
  end
1110
1396
 
@@ -1171,5 +1457,17 @@ module ZuoraAPI
1171
1457
  end
1172
1458
  return "failure"
1173
1459
  end
1460
+
1461
+ def reset_files(body)
1462
+ return unless body.is_a? Hash
1463
+
1464
+ body.transform_values! do |v|
1465
+ if v.is_a?(File)
1466
+ v.reopen(v.path)
1467
+ else
1468
+ v
1469
+ end
1470
+ end
1471
+ end
1174
1472
  end
1175
1473
  end