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