zuora_api 1.7.00 → 1.8.00

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecec87283f238f546be12e3fadb16e9fabbee7aec1129d47401876359271f868
4
- data.tar.gz: 98d980959dcf672fad54bdc87fa89061f6aee4627eebb4ef89da8573c204419b
3
+ metadata.gz: e79d63055902d66670dad6abed776b5c02ccb422b886bad2e632624e43eb16ba
4
+ data.tar.gz: 8abaee55dad2d4c3636bcbd08159a26dc403a8041aecb7d2ce2f6d84bd9f3917
5
5
  SHA512:
6
- metadata.gz: 0f18782b6e2bc5d2433c4f0b632fe931205392db8b334edf524accec0e17821394a064f82cb45983d0a0ae7cbbc106b8ae8911b90c35651445e139bf94d261d7
7
- data.tar.gz: 56555f10ce5434a30a54d4cad556170987a033f1cbfef34dc0b246fd935878ae4e7bd989e4c7ba7f3f95a73d291c25983d39260b41a8c91a1dcb9a019e879eab
6
+ metadata.gz: 72165d5892a404b6e25c6f32e83e52065a23837eeb7f4d72a40f13e7c642c460362a3c4ed83b51760e5efac650c5eaffc4d4baa1b97b6c980818273af3fa9af1
7
+ data.tar.gz: 271b546b700d026e9ac12a22418eb90fc45a2a2b1bd66f872586a5552a4dfe703cb95f9f7dd78cb38960d4885a65e20acab0095ab0a2b5c5b3339295b129b995
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,28 @@
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
+
21
+ ## [1.7.01] - 2018-8-06
22
+ ### Changed
23
+ - Changed library used to determine host
24
+ - Added retry for 504 timouts in file download
25
+
4
26
  ## [1.7.00] - 2018-8-05
5
27
  ### Changed
6
28
  - Raise proper exception when oauth client is from deactivated user.
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,34 +134,28 @@ 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.
169
155
  # 1. Pass in cookies and optionally custom_authorities, name, and description
170
156
  # 2. Pass in user_id, entity_ids, client_id, client_secret, and optionally custom_authorities, name, and description
171
157
  # https://intranet.zuora.com/confluence/display/Sunburst/Create+an+OAuth+Client+through+API+Gateway#CreateanOAuthClientthroughAPIGateway-ZSession
172
- 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)
158
+ 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)
173
159
  authorization = ""
174
160
  new_client_id = SecureRandom.uuid if new_client_id.blank?
175
161
  new_client_secret = SecureRandom.hex(10) if new_client_secret.blank?
@@ -195,7 +181,7 @@ module ZuoraAPI
195
181
  end
196
182
 
197
183
  if !authorization.blank? && !user_id.blank? && !entity_ids.blank?
198
- endpoint = self.rest_endpoint("genesis/clients")
184
+ endpoint = chomp_v1_from_genesis_endpoint ? self.rest_endpoint.chomp("v1/").concat("genesis/clients") : self.rest_endpoint("genesis/clients")
199
185
  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)
200
186
  output_json = JSON.parse(oauth_response.body)
201
187
  if oauth_response.code == 201
@@ -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,46 +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}rest#{host}/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
- require 'addressable/uri'
356
- return ::Addressable::URI.parse(self.rest_endpoint).host
325
+ return URI(self.rest_endpoint).host
357
326
  end
358
327
 
359
328
  def fileURL(url="")
@@ -364,10 +333,10 @@ module ZuoraAPI
364
333
  return self.wsdl_number > 68 ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
365
334
  end
366
335
 
367
- def new_session(auth_type: :basic, debug: false)
336
+ def new_session(auth_type: :basic, debug: false, zuora_track_id: nil)
368
337
  end
369
338
 
370
- def get_session(prefix: false, auth_type: :basic)
339
+ def get_session(prefix: false, auth_type: :basic, zuora_track_id: nil)
371
340
  Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}") if Rails.env.to_s == 'development'
372
341
  case auth_type
373
342
  when :basic
@@ -375,13 +344,13 @@ module ZuoraAPI
375
344
  case self.class.to_s
376
345
  when 'ZuoraAPI::Oauth'
377
346
  if self.bearer_token.blank? || self.oauth_expired?
378
- self.new_session(auth_type: :bearer)
347
+ self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
379
348
  end
380
- self.get_z_session if self.status == 'Active'
349
+ self.get_z_session(zuora_track_id: zuora_track_id) if self.status == 'Active'
381
350
  when 'ZuoraAPI::Basic'
382
- self.new_session(auth_type: :basic)
351
+ self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
383
352
  else
384
- raise "No Zuora Login Specified"
353
+ self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
385
354
  end
386
355
  end
387
356
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
@@ -390,17 +359,33 @@ module ZuoraAPI
390
359
  case self.class.to_s
391
360
  when 'ZuoraAPI::Oauth'
392
361
  if self.bearer_token.blank? || self.oauth_expired?
393
- self.new_session(auth_type: :bearer)
362
+ self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
394
363
  end
395
364
  when 'ZuoraAPI::Basic'
396
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}")
397
368
  end
369
+
398
370
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
399
371
  return prefix ? "Bearer #{self.bearer_token}" : self.bearer_token.to_s
400
372
  end
401
373
  end
402
374
 
403
- 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)
404
389
  tries ||= 2
405
390
  xml = Nokogiri::XML::Builder.new do |xml|
406
391
  xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
@@ -410,7 +395,7 @@ module ZuoraAPI
410
395
  "xmlns:#{ns1}" => "http://api.zuora.com/") do
411
396
  xml['SOAP-ENV'].Header do
412
397
  xml["#{ns1}"].SessionHeader do
413
- 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)
414
399
  end
415
400
  if single_transaction
416
401
  xml["#{ns1}"].CallOptions do
@@ -433,15 +418,34 @@ module ZuoraAPI
433
418
  input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
434
419
  Rails.logger.debug("Request SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
435
420
 
436
- 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
+
437
434
  output_xml = Nokogiri::XML(response.body)
438
435
  Rails.logger.debug("Response SOAP XML: #{output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
439
436
 
440
437
  raise_errors(type: :SOAP, body: output_xml, response: response)
441
438
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
442
- if !(tries -= 1).zero? && z_session
439
+ if !tries.zero? && z_session
440
+ tries -= 1
443
441
  Rails.logger.debug("SOAP Call - Session Invalid")
444
- 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
+
445
449
  retry
446
450
  else
447
451
  if errors.include?(ex.class)
@@ -454,30 +458,45 @@ module ZuoraAPI
454
458
  if errors.include?(ex.class)
455
459
  raise ex
456
460
  else
461
+ response = ex.response unless response
457
462
  return output_xml, input_xml, response
458
463
  end
459
464
  rescue *CONNECTION_EXCEPTIONS => ex
460
- if !(tries -= 1).zero?
461
- Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
462
- sleep(self.timeout_sleep)
463
- retry
464
- else
465
- 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
466
474
  end
475
+
476
+ tries -= 1
477
+ sleep(timeout_sleep_interval)
478
+ retry
467
479
  rescue *CONNECTION_READ_EXCEPTIONS => ex
468
- if !(tries -= 1).zero? && timeout_retry
469
- Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
470
- sleep(self.timeout_sleep)
471
- retry
472
- else
473
- 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
474
489
  end
475
- rescue Errno::ECONNRESET => ex
476
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
477
- retry
478
- else
479
- 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
480
497
  end
498
+ raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
499
+ raise ex
481
500
  rescue => ex
482
501
  raise ex
483
502
  else
@@ -485,31 +504,60 @@ module ZuoraAPI
485
504
  end
486
505
 
487
506
  def raise_errors(type: :SOAP, body: nil, response: nil)
488
- case type
489
- when :SOAP
490
- error = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
491
- 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
492
518
 
493
- if error.blank? || message.blank?
494
- error = body.xpath('//faultcode').text
495
- message = body.xpath('//faultstring').text
496
- end
519
+ if [502,503].include?(response.code)
520
+ raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from #{request_uri}", response)
521
+ end
497
522
 
498
- if error.blank? || message.blank?
499
- error = body.xpath('//ns1:Code', 'ns1' =>'http://api.zuora.com/').text
500
- 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
501
540
  end
541
+ end
502
542
 
503
- #Update/Create/Delete Calls with multiple requests and responses
504
- if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
505
- error = []
506
- success = []
507
- body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').each_with_index do |call, object_index|
508
- if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false' && call.xpath('./ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
509
- message = "#{call.xpath('./*/ns1:Code', 'ns1' =>'http://api.zuora.com/').text}::#{call.xpath('./*/ns1:Message', 'ns1' =>'http://api.zuora.com/').text}"
510
- 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'
511
558
  else
512
- 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.'
513
561
  end
514
562
  end
515
563
  end
@@ -517,44 +565,48 @@ module ZuoraAPI
517
565
  #By default response if not passed in for SOAP as all SOAP is 200
518
566
  if error.present?
519
567
  if error.class == String
520
- case error
521
- when "INVALID_SESSION"
522
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{error}::#{message}", response)
523
- when "REQUEST_EXCEEDED_LIMIT"
524
- raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{error}::#{message}", response)
525
- when "LOCK_COMPETITION"
526
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", response)
527
- when "BATCH_FAIL_ERROR"
528
- if message.include?("optimistic locking failed")
529
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", response)
530
- else
531
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response)
532
- end
533
- when "TEMPORARY_ERROR"
534
- raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new("#{error}::#{message}", response)
535
- when "INVALID_VALUE"
536
- if message.include?("data integrity violation")
537
- raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response)
538
- else
539
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response)
540
- end
541
- else
542
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response) if error.present?
543
- end
568
+ raise_errors_helper(error: error, message: message, response: response)
544
569
  elsif error.class == Array
545
- if error[0].include?("LOCK_COMPETITION") && error.count == 1
546
- 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)
547
573
  else
548
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)
549
575
  end
550
576
  end
551
577
  end
578
+
579
+ self.errors_via_content_type(response: response, type: :xml)
552
580
 
553
- if response.code == 429
554
- 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
555
608
  end
556
609
 
557
- when :JSON
558
610
  body = body.dig("results").present? ? body["results"] : body if body.class == Hash
559
611
  if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
560
612
  messages_array = body.fetch("reasons", []).map {|error| error['message']}.compact
@@ -562,6 +614,14 @@ module ZuoraAPI
562
614
  codes_array = body.fetch("reasons", []).map {|error| error['code']}.compact
563
615
  codes_array = codes_array.push(body.dig("error", 'code')).compact if body.dig('error').class == Hash
564
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
+
565
625
  if body['message'] == "No bearer token" && response.code == 400
566
626
  raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Authentication type is not supported by this Login", response)
567
627
  end
@@ -575,8 +635,12 @@ module ZuoraAPI
575
635
  end
576
636
 
577
637
  #Oauth Tokens - User deactivated
578
- if body['message'] == 'Forbidden' && body['status'] == 403 && response.code == 403
579
- 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
580
644
  end
581
645
 
582
646
  if body['error'] == 'Unauthorized' && body['status'] == 401
@@ -584,7 +648,12 @@ module ZuoraAPI
584
648
  end
585
649
  #Authentication failed
586
650
  if (codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(11) || response.code == 401) && !codes_array.include?(422)
587
- 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
588
657
  end
589
658
 
590
659
  #Zuora REST Create Amendment error #Authentication failed
@@ -592,16 +661,30 @@ module ZuoraAPI
592
661
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body['faultstring']}", response)
593
662
  end
594
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
+
595
683
  #Request exceeded limit
596
684
  if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(70)
597
685
  raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{messages_array.join(', ')}", response)
598
686
  end
599
687
 
600
- #Locking contention
601
- if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(50)
602
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{messages_array.join(', ')}", response)
603
- end
604
-
605
688
  #All Errors catch
606
689
  if codes_array.size > 0
607
690
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{messages_array.join(', ')}", response)
@@ -609,26 +692,20 @@ module ZuoraAPI
609
692
 
610
693
  #Zuora REST Query Errors
611
694
  if body["faultcode"].present?
612
- case body["faultcode"]
613
- when "fns:MALFORMED_QUERY"
614
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
615
- when "fns:REQUEST_EXCEEDED_LIMIT"
616
- raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
617
- when "fns:LOCK_COMPETITION"
618
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
619
- when "INVALID_SESSION"
620
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
621
- else
622
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
623
- end
695
+ raise_errors_helper(error: body["faultcode"], message: body["faultstring"], response: response)
624
696
  end
625
697
 
626
698
  if body["Errors"].present? || body["errors"].present?
627
- errors = []
628
- (body["Errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
629
- (body["errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
630
- if errors.size > 0
631
- 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
632
709
  end
633
710
  end
634
711
  end
@@ -646,7 +723,7 @@ module ZuoraAPI
646
723
  raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("Retry Lock Competition", response)
647
724
  elsif error_messages.first.include?("data integrity violation")
648
725
  raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response)
649
- end
726
+ end
650
727
  end
651
728
  end
652
729
 
@@ -655,15 +732,128 @@ module ZuoraAPI
655
732
  end
656
733
  end
657
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
+
658
742
  #All other errors
659
- if ![200,201].include?(response.code)
660
- if response.code == 429
661
- 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)
662
808
  else
663
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{response.message}", response)
809
+ success.push(call.xpath('./ns1:Id', 'ns1' =>'http://api.zuora.com/').text)
664
810
  end
665
811
  end
666
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
667
857
  end
668
858
 
669
859
  def aqua_query(queryName: '', query: '', version: '1.2', jobName: 'Aqua',partner: '', project: '')
@@ -743,16 +933,24 @@ module ZuoraAPI
743
933
  end
744
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
745
935
  end
746
- rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS) => ex
747
- if !(tries -= 1).zero?
748
- Rails.logger.info("Describe - #{ex.class} Timed out will retry after 5 seconds")
749
- sleep(self.timeout_sleep)
750
- retry
751
- else
752
- 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
753
946
  end
947
+
948
+ tries -= 1
949
+ sleep(self.timeout_sleep)
950
+ retry
754
951
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
755
- if !(tries -= 1).zero? && self.status == 'Active'
952
+ if !tries.zero? && self.status == 'Active'
953
+ tries -= 1
756
954
  Rails.logger.debug("Describe session expired. Starting new session.")
757
955
  self.new_session
758
956
  retry
@@ -766,40 +964,84 @@ module ZuoraAPI
766
964
  return des_hash
767
965
  end
768
966
 
769
- def rest_call(method: :get, body: nil, headers: {}, url: rest_endpoint("catalog/products?pageSize=4"), debug: false, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS), z_session: true, session_type: :basic, timeout_retry: false, timeout: 120, **keyword_args)
967
+ def rest_call(
968
+ method: :get,
969
+ body: nil,
970
+ headers: {},
971
+ url: rest_endpoint("catalog/products?pageSize=4"),
972
+ debug: false,
973
+ errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS),
974
+ z_session: true,
975
+ session_type: :basic,
976
+ timeout_retry: false,
977
+ timeout: 120,
978
+ timeout_sleep_interval: self.timeout_sleep,
979
+ multipart: false,
980
+ stream_body: false,
981
+ output_exception_messages: true,
982
+ **keyword_args,
983
+ &block
984
+ )
770
985
  tries ||= 2
771
986
 
772
- if self.entity_id.present?
773
- headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
774
- headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
775
- end
776
-
777
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)
778
988
 
779
- authentication_headers = z_session ? {"Authorization" => self.get_session(prefix: true, auth_type: session_type) } : {}
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
997
+
780
998
  modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
781
999
 
782
- response = HTTParty::Request.new("Net::HTTP::#{method.to_s.capitalize}".constantize, url, body: body, headers: modified_headers, timeout: timeout).perform
783
- Rails.logger.debug("Response Code: #{response.code}") if debug
784
1000
  begin
785
- output_json = JSON.parse(response.body)
786
- rescue JSON::ParserError => ex
787
- output_json = {}
788
- end
789
- 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?
790
1020
 
791
- 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
792
1026
  rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
793
1027
  if self.class.to_s == 'ZuoraAPI::Oauth' && ex.message.include?("Authentication type is not supported by this Login")
794
- 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
795
1030
  else
796
1031
  Rails.logger.debug("Rest Call - Session Bad Auth type")
797
1032
  raise ex
798
1033
  end
799
1034
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
800
- if !(tries -= 1).zero? && z_session
1035
+ if !tries.zero? && z_session
1036
+ tries -= 1
801
1037
  Rails.logger.debug("Rest Call - Session Invalid #{session_type}")
802
- 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
+
803
1045
  retry
804
1046
  else
805
1047
  if errors.include?(ex.class)
@@ -812,32 +1054,46 @@ module ZuoraAPI
812
1054
  if errors.include?(ex.class)
813
1055
  raise ex
814
1056
  else
1057
+ response = ex.response unless response
815
1058
  return [output_json, response]
816
1059
  end
817
1060
  rescue ZuoraAPI::Exceptions::BadEntityError => ex
818
1061
  raise ex
819
1062
  rescue *CONNECTION_EXCEPTIONS => ex
820
- if !(tries -= 1).zero?
821
- Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
822
- sleep(self.timeout_sleep)
823
- retry
824
- else
825
- 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
826
1072
  end
1073
+
1074
+ tries -= 1
1075
+ sleep(timeout_sleep_interval)
1076
+ retry
827
1077
  rescue *CONNECTION_READ_EXCEPTIONS => ex
828
- if !(tries -= 1).zero? && timeout_retry
829
- Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
830
- sleep(self.timeout_sleep)
831
- retry
832
- else
833
- 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
834
1086
  end
835
- rescue Errno::ECONNRESET => ex
836
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
837
- retry
838
- else
839
- 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
840
1094
  end
1095
+ raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
1096
+ raise ex
841
1097
  rescue => ex
842
1098
  raise ex
843
1099
  else
@@ -890,210 +1146,251 @@ module ZuoraAPI
890
1146
  return products, catalog_map
891
1147
  end
892
1148
 
893
- def get_file(url: nil, headers: {}, count: 3, 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: 2, 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)
894
1150
  raise "file_path must be of class Pathname" if file_path.class != Pathname
895
1151
 
1152
+ retry_count ||= timeout_retries
1153
+
896
1154
  #Make sure directory exists
897
1155
  require 'fileutils'
898
1156
  FileUtils.mkdir_p(file_path) unless File.exists?(file_path)
899
1157
 
900
- begin
901
- status_code = nil
902
- uri = URI.parse(url)
903
- http = Net::HTTP.new(uri.host, uri.port)
904
- http.read_timeout = timeout #Seconds
905
- http.use_ssl = true if !uri.scheme.nil? && uri.scheme.downcase == 'https'
906
- if z_session
907
- headers = headers.merge({"Authorization" => self.get_session(prefix: true)})
908
- headers = headers.merge({"Zuora-Entity-Ids" => self.entity_id}) if !self.entity_id.blank?
909
- end
1158
+ status_code = nil
1159
+ uri = URI.parse(url)
1160
+ http = Net::HTTP.new(uri.host, uri.port)
1161
+ http.read_timeout = timeout #Seconds
1162
+ http.use_ssl = true if !uri.scheme.nil? && uri.scheme.downcase == 'https'
1163
+ if z_session
1164
+ headers = headers.merge({"Authorization" => self.get_session(prefix: true)})
1165
+ headers = headers.merge({"Zuora-Entity-Ids" => self.entity_id}) if !self.entity_id.blank?
1166
+ end
910
1167
 
911
- response_save = nil
912
- begin
913
- http.request_get(uri.request_uri, headers) do |response|
914
- response_save = response
915
- status_code = response.code if response
916
-
917
- case response
918
- when Net::HTTPNotFound
919
- raise
920
-
921
- when Net::HTTPUnauthorized
922
- if count <= 0
923
- if z_session
924
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
925
- else
926
- raise
927
- end
928
- end
929
- self.new_session if z_session
930
- return get_file(:url => url, :headers => headers, :count => count - 1, :z_session => z_session, :tempfile => tempfile, :file_path => file_path, :timeout_retries => timeout_retries, :timeout => timeout)
1168
+ headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
1169
+
1170
+ response_save = nil
1171
+ http.request_get(uri.request_uri, headers) do |response|
1172
+ response_save = response
1173
+ status_code = response.code if response
1174
+ case response
1175
+ when Net::HTTPOK
1176
+ headers = {}
1177
+ response.each_header do |k,v|
1178
+ headers[k] = v
1179
+ end
1180
+ Rails.logger.debug("Headers: #{headers.to_s}")
1181
+ if output_file_name.present?
1182
+ file_ending ||= output_file_name.end_with?(".csv.zip") ? ".csv.zip" : File.extname(output_file_name)
1183
+ filename ||= File.basename(output_file_name, file_ending)
1184
+ end
931
1185
 
932
- when Net::HTTPClientError
933
- raise
1186
+ size, export_progress = [0, 0]
1187
+ encoding, type, full_filename = [nil, nil, nil]
1188
+ if response.header["Content-Disposition"].present?
1189
+ case response.header["Content-Disposition"]
1190
+ when /.*; filename\*=.*/
1191
+ full_filename ||= /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[2].strip
1192
+ encoding = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[1].strip
1193
+ when /.*; filename=/
1194
+ full_filename ||= /.*; filename=(.*)/.match(response.header["Content-Disposition"])[1].strip
1195
+ else
1196
+ raise "Can't parse Content-Disposition header: #{response.header["Content-Disposition"]}"
1197
+ end
1198
+ file_ending ||= full_filename.end_with?(".csv.zip") ? ".csv.zip" : File.extname(full_filename)
1199
+ filename ||= File.basename(full_filename, file_ending)
1200
+ end
934
1201
 
935
- when Net::HTTPOK
936
- headers = {}
937
- response.each_header do |k,v|
938
- headers[k] = v
939
- end
940
- Rails.logger.debug("Headers: #{headers.to_s}")
941
- if output_file_name.present?
942
- file_ending ||= output_file_name.end_with?(".csv.zip") ? ".csv.zip" : File.extname(output_file_name)
943
- filename ||= File.basename(output_file_name, file_ending)
944
- end
1202
+ #If user supplied a filename use it, else default to content header filename, else default to uri pattern
1203
+ file_ending ||= uri.path.end_with?(".csv.zip") ? ".csv.zip" : File.extname(uri.path)
1204
+ filename ||= File.basename(uri.path, file_ending)
945
1205
 
946
- size, export_progress = [0, 0]
947
- encoding, type, full_filename = [nil, nil, nil]
948
- if response.header["Content-Disposition"].present?
949
- case response.header["Content-Disposition"]
950
- when /.*; filename\*=.*/
951
- full_filename ||= /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[2].strip
952
- encoding = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[1].strip
953
- when /.*; filename=/
954
- full_filename ||= /.*; filename=(.*)/.match(response.header["Content-Disposition"])[1].strip
955
- else
956
- raise "Can't parse Content-Disposition header: #{response.header["Content-Disposition"]}"
957
- end
958
- file_ending ||= full_filename.end_with?(".csv.zip") ? ".csv.zip" : File.extname(full_filename)
959
- filename ||= File.basename(full_filename, file_ending)
960
- end
1206
+ if response.header["Content-Type"].present?
1207
+ case response.header["Content-Type"]
1208
+ when /.*;charset=.*/
1209
+ type = /(.*);charset=(.*)/.match(response.header["Content-Type"])[1]
1210
+ encoding = /(.*);charset=(.*)/.match(response.header["Content-Type"])[2]
1211
+ else
1212
+ type = response.header["Content-Type"]
1213
+ encoding ||= 'UTF-8'
1214
+ end
1215
+ end
961
1216
 
962
- #If user supplied a filename use it, else default to content header filename, else default to uri pattern
963
- file_ending ||= uri.path.end_with?(".csv.zip") ? ".csv.zip" : File.extname(uri.path)
964
- filename ||= File.basename(uri.path, file_ending)
965
-
966
- if response.header["Content-Type"].present?
967
- case response.header["Content-Type"]
968
- when /.*;charset=.*/
969
- type = /(.*);charset=(.*)/.match(response.header["Content-Type"])[1]
970
- encoding = /(.*);charset=(.*)/.match(response.header["Content-Type"])[2]
971
- else
972
- type = response.header["Content-Type"]
973
- encoding ||= 'UTF-8'
974
- end
975
- end
1217
+ if response.header["Content-Length"].present?
1218
+ export_size = response.header["Content-Length"].to_i
1219
+ elsif response.header["ContentLength"].present?
1220
+ export_size = response.header["ContentLength"].to_i
1221
+ end
976
1222
 
977
- if response.header["Content-Length"].present?
978
- export_size = response.header["Content-Length"].to_i
979
- elsif response.header["ContentLength"].present?
980
- export_size = response.header["ContentLength"].to_i
981
- end
1223
+ Rails.logger.info("File: #{filename}#{file_ending} #{encoding} #{type} #{export_size}")
982
1224
 
983
- Rails.logger.info("File: #{filename}#{file_ending} #{encoding} #{type} #{export_size}")
1225
+ file_prefix = add_timestamp ? "#{filename}_#{Time.now.to_i}" : filename
1226
+ if tempfile
1227
+ require 'tempfile'
1228
+ file_handle = ::Tempfile.new([file_prefix, "#{file_ending}"], file_path)
1229
+ else
1230
+ file_handle = File.new(file_path.join("#{file_prefix}#{file_ending}"), "w+")
1231
+ end
1232
+ file_handle.binmode
984
1233
 
985
- file_prefix = add_timestamp ? "#{filename}_#{Time.now.to_i}" : filename
986
- if tempfile
987
- require 'tempfile'
988
- file_handle = ::Tempfile.new([file_prefix, "#{file_ending}"], file_path)
989
- else
990
- file_handle = File.new(file_path.join("#{file_prefix}#{file_ending}"), "w+")
991
- end
992
- file_handle.binmode
993
-
994
- response.read_body do |chunk|
995
- file_handle << chunk
996
-
997
- if defined?(export_size) && export_size != 0 && export_size.class == Integer
998
- size += chunk.size
999
- new_progress = (size * 100) / export_size
1000
- unless new_progress == export_progress
1001
- Rails.logger.debug("Login: Export Downloading %s (%3d%%)" % [filename, new_progress])
1002
- end
1003
- export_progress = new_progress
1004
- end
1234
+ response.read_body do |chunk|
1235
+ file_handle << chunk
1236
+
1237
+ if defined?(export_size) && export_size != 0 && export_size.class == Integer
1238
+ size += chunk.size
1239
+ new_progress = (size * 100) / export_size
1240
+ unless new_progress == export_progress
1241
+ Rails.logger.debug("Login: Export Downloading %s (%3d%%)" % [filename, new_progress])
1005
1242
  end
1243
+ export_progress = new_progress
1244
+ end
1245
+ end
1006
1246
 
1007
- file_handle.close
1008
- Rails.logger.debug("Filepath: #{file_handle.path} Size: #{File.size(file_handle.path).to_f/1000000} mb")
1247
+ file_handle.close
1248
+ Rails.logger.debug("Filepath: #{file_handle.path} Size: #{File.size(file_handle.path).to_f/1000000} mb")
1009
1249
 
1010
- return file_handle
1250
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Downloaded file is not a file: #{file_handle.class}") if !["Tempfile", "File"].include?(file_handle.class.to_s)
1251
+ return file_handle
1252
+ when Net::HTTPUnauthorized
1253
+ if z_session
1254
+ if !(retry_count -= 1).zero?
1255
+ self.new_session
1256
+ raise response.class
1257
+ else
1258
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
1011
1259
  end
1012
- end
1013
- rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS).concat([Net::HTTPBadResponse]) => e
1014
- sleep(5)
1015
- if (timeout_retries -= 1) >= 0
1016
- Rails.logger.warn("Download Failed: #{e.class} : #{e.message}")
1017
- retry
1018
1260
  else
1019
1261
  raise
1020
1262
  end
1263
+ else
1264
+ raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
1021
1265
  end
1022
- rescue => ex
1023
- Rails.logger.fatal(ex)
1266
+ end
1267
+
1268
+ rescue => ex
1269
+ sleep(5)
1270
+ if (retry_count -= 1) >= 0
1271
+ retry
1272
+ else
1273
+ Rails.logger.error("File Download Failed")
1024
1274
  raise
1025
1275
  end
1026
1276
  end
1027
1277
 
1028
- def getDataSourceExport(query, extract: true, encrypted: false, zip: true)
1029
- request = Nokogiri::XML::Builder.new do |xml|
1030
- 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
1031
- xml['SOAP-ENV'].Header do
1032
- xml['ns1'].SessionHeader do
1033
- 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
1034
1287
  end
1035
- end
1036
- xml['SOAP-ENV'].Body do
1037
- xml['ns1'].create do
1038
- xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1039
- xml['ns2'].Format 'csv'
1040
- xml['ns2'].Zip zip
1041
- xml['ns2'].Name 'googman'
1042
- xml['ns2'].Query query
1043
- 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
1044
1297
  end
1045
1298
  end
1046
1299
  end
1047
1300
  end
1048
- end
1049
1301
 
1050
- 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)
1051
1303
 
1052
- output_xml = Nokogiri::XML(response_query.body)
1053
- 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"
1054
- 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"
1055
1306
 
1056
- confirmRequest = Nokogiri::XML::Builder.new do |xml|
1057
- 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
1058
- xml['SOAP-ENV'].Header do
1059
- xml['ns1'].SessionHeader do
1060
- 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
1061
1317
  end
1062
- end
1063
- xml['SOAP-ENV'].Body do
1064
- xml['ns1'].query do
1065
- 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
1066
1322
  end
1067
1323
  end
1068
1324
  end
1069
- end
1070
- result = 'Waiting'
1325
+ result = 'Waiting'
1071
1326
 
1072
- while result != "Completed"
1073
- sleep 3
1074
- 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)
1075
1330
 
1076
- output_xml = Nokogiri::XML(response_query.body)
1077
- result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
1078
- status_code = response_query.code if response_query
1079
- raise "Export Creation Unsuccessful : #{output_xml.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text}" if result.blank? || result == "Failed"
1080
- 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
1081
1334
 
1082
- file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
1083
- export_file = get_file(:url => self.fileURL(file_id))
1084
- export_file_path = export_file.path
1085
- Rails.logger.debug("=====> Export path #{export_file.path}")
1086
-
1087
- if extract && zip
1088
- require "zip"
1089
- new_path = export_file_path.partition('.zip').first
1090
- zipped = Zip::File.open(export_file_path)
1091
- file_handle = zipped.entries.first
1092
- file_handle.extract(new_path)
1093
- File.delete(export_file_path)
1094
- return new_path
1095
- else
1096
- 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
1097
1394
  end
1098
1395
  end
1099
1396
 
@@ -1160,5 +1457,17 @@ module ZuoraAPI
1160
1457
  end
1161
1458
  return "failure"
1162
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
1163
1472
  end
1164
1473
  end