zuora_api_oauth_alpha 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 +7 -0
- data/Rakefile +6 -0
- data/lib/insights_api/login.rb +221 -0
- data/lib/zuora_api_oauth_alpha/exceptions.rb +86 -0
- data/lib/zuora_api_oauth_alpha/login.rb +707 -0
- data/lib/zuora_api_oauth_alpha/logins/basic.rb +139 -0
- data/lib/zuora_api_oauth_alpha/logins/oauth.rb +171 -0
- data/lib/zuora_api_oauth_alpha/version.rb +3 -0
- data/lib/zuora_api_oauth_alpha.rb +10 -0
- metadata +185 -0
@@ -0,0 +1,707 @@
|
|
1
|
+
require "httparty"
|
2
|
+
require "nokogiri"
|
3
|
+
|
4
|
+
module ZuoraAPI
|
5
|
+
class Login
|
6
|
+
ENVIRONMENTS = [SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown' ]
|
7
|
+
REGIONS = [EU = 'EU', US = 'US' ]
|
8
|
+
attr_accessor :username, :password, :region,:url, :wsdl_number, :current_session, :environment, :status, :errors, :current_error, :user_info, :tenant_id, :tenant_name, :entity_id, :timeout_sleep, :authentication_type
|
9
|
+
|
10
|
+
def initialize(username: nil, password: nil, url: nil, entity_id: nil, session: nil, status: nil, authentication_type: "Basic", **keyword_args)
|
11
|
+
@authentication_type = authentication_type
|
12
|
+
@username = username
|
13
|
+
@password = password
|
14
|
+
@url = url
|
15
|
+
@entity_id = entity_id
|
16
|
+
@current_session = session
|
17
|
+
@errors = Hash.new
|
18
|
+
@status = status.blank? ? "Active" : status
|
19
|
+
@user_info = Hash.new
|
20
|
+
self.update_environment
|
21
|
+
@timeout_sleep = 5
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.environments
|
25
|
+
%w(Sandbox Production Services Performance)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.regions
|
29
|
+
%w(US EU NA)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.endpoints
|
33
|
+
return {"US" => {"Sandbox" => "https://apisandbox.zuora.com/apps/services/a/",
|
34
|
+
"Production" => "https://www.zuora.com/apps/services/a/",
|
35
|
+
"Performance" => "https://pt1.zuora.com/apps/services/a/",
|
36
|
+
"Services" => "https://services347.zuora.com/apps/services/a/"},
|
37
|
+
"EU" => {"Sandbox" => "https://sandbox.eu.zuora.com/apps/services/a/",
|
38
|
+
"Production" => "https://eu.zuora.com/apps/services/a/",
|
39
|
+
"Performance" => "https://pt1.eu.zuora.com/apps/services/a/",
|
40
|
+
"Services" => "https://services347.eu.zuora.com/apps/services/a/"},
|
41
|
+
"NA" => {"Sandbox" => "https://sandbox.na.zuora.com/apps/services/a/",
|
42
|
+
"Production" => "https://na.zuora.com/apps/services/a/",
|
43
|
+
"Performance" => "https://pt1.na.zuora.com/apps/services/a/",
|
44
|
+
"Services" => "https://services347.na.zuora.com/apps/services/a/"}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def update_environment
|
50
|
+
if !self.url.blank?
|
51
|
+
env_path = self.url.split('https://').last.split('.zuora.com').first
|
52
|
+
self.region = self.url.include?("eu.") ? "EU" : self.url.include?("na.") ? "NA" : "US"
|
53
|
+
if env_path == 'apisandbox' || self.url.include?('sandbox')
|
54
|
+
self.environment = 'Sandbox'
|
55
|
+
elsif env_path == 'www' || env_path == 'api' || self.url.include?('tls10.zuora.com') || self.url.include?('origin-www.zuora.com') || self.url.include?('zforsf.zuora.com') || self.url.include?('https://zuora.com') || self.url.include?('eu.zuora.com') || self.url.include?('https://na.zuora.com')
|
56
|
+
self.environment = 'Production'
|
57
|
+
elsif env_path.include?('service') || env_path.include?('ep-edge')
|
58
|
+
self.environment = 'Services'
|
59
|
+
elsif env_path.include?('pt')
|
60
|
+
self.environment = 'Performance'
|
61
|
+
elsif env_path.include?('app-0')
|
62
|
+
self.environment = 'Staging'
|
63
|
+
else
|
64
|
+
self.environment = 'Unknown'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def aqua_endpoint(url="")
|
70
|
+
return "#{self.url.split("/apps").first}/apps/api/".concat(url)
|
71
|
+
end
|
72
|
+
|
73
|
+
def rest_endpoint(url="")
|
74
|
+
if self.environment == 'Sandbox'
|
75
|
+
return self.region == "US" ? "https://rest.apisandbox.zuora.com/v1/".concat(url) : self.region == "EU" ? "https://rest.sandbox.eu.zuora.com/v1/".concat(url) : "https://rest.sandbox.na.zuora.com/v1/".concat(url)
|
76
|
+
elsif self.environment == 'Production'
|
77
|
+
return self.region == "US" ? "https://rest.zuora.com/v1/".concat(url) : self.region == "EU" ? "https://rest.eu.zuora.com/v1/".concat(url) : "https://rest.na.zuora.com/v1/".concat(url)
|
78
|
+
elsif self.environment == 'Services'
|
79
|
+
return self.url.split('/')[0..2].join('/').concat('/apps/v1/').concat(url)
|
80
|
+
elsif self.environment == 'Performance'
|
81
|
+
return self.url.split('/')[0..2].join('/').concat('/apps/v1/').concat(url)
|
82
|
+
else self.environment == 'Unknown'
|
83
|
+
return url
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def fileURL(url="")
|
88
|
+
return self.url.split(".com").first.concat(".com/apps/api/file/").concat(url)
|
89
|
+
end
|
90
|
+
|
91
|
+
def dateFormat
|
92
|
+
return self.wsdl_number > 68 ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
|
93
|
+
end
|
94
|
+
|
95
|
+
def new_session
|
96
|
+
raise "Method not defined"
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_session(prefix = false, auth_type = :basic)
|
100
|
+
Rails.logger.debug("Create new session")
|
101
|
+
self.current_session = self.new_session(auth_type) if self.check_session(auth_type)
|
102
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
|
103
|
+
return self.authentication_prefix(auth_type) + self.current_session.to_s if prefix
|
104
|
+
return self.current_session.to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
def soap_call(ns1: 'ns1', ns2: 'ns2', batch_size: nil, single_transaction: false, debug: true, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError, ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition], z_session: true, timeout_retry: false, timeout: 120,**keyword_args)
|
108
|
+
tries ||= 2
|
109
|
+
xml = Nokogiri::XML::Builder.new do |xml|
|
110
|
+
xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
|
111
|
+
"xmlns:#{ns2}" => "http://object.api.zuora.com/",
|
112
|
+
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
|
113
|
+
'xmlns:api' => "http://api.zuora.com/",
|
114
|
+
"xmlns:#{ns1}" => "http://api.zuora.com/") do
|
115
|
+
xml['SOAP-ENV'].Header do
|
116
|
+
xml["#{ns1}"].SessionHeader do
|
117
|
+
xml["#{ns1}"].session self.get_session(false, :basic)
|
118
|
+
end
|
119
|
+
if single_transaction
|
120
|
+
xml["#{ns1}"].CallOptions do
|
121
|
+
xml.useSingleTransaction single_transaction
|
122
|
+
end
|
123
|
+
end
|
124
|
+
if batch_size
|
125
|
+
xml["#{ns1}"].QueryOptions do
|
126
|
+
xml.batchSize batch_size
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
xml['SOAP-ENV'].Body do
|
131
|
+
yield xml, keyword_args
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
input_xml = Nokogiri::XML(xml.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION).strip)
|
137
|
+
input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
|
138
|
+
Rails.logger.debug('Connect') {"Request SOAP XML: #{input_xml.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION).strip}"} if debug
|
139
|
+
response = HTTParty.post(self.url,:body => xml.doc.to_xml, :headers => {'Content-Type' => "text/xml; charset=utf-8"}, :timeout => timeout)
|
140
|
+
output_xml = Nokogiri::XML(response.body)
|
141
|
+
Rails.logger.debug('Connect') {"Response SOAP XML: #{output_xml.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION).strip}"} if debug
|
142
|
+
|
143
|
+
raise_errors(type: :SOAP, body: output_xml, response: response)
|
144
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
145
|
+
if !(tries -= 1).zero? && z_session
|
146
|
+
Rails.logger.debug {"Session Invalid"}
|
147
|
+
self.new_session
|
148
|
+
retry
|
149
|
+
else
|
150
|
+
if errors.include?(ex.class)
|
151
|
+
raise ex
|
152
|
+
else
|
153
|
+
return [output_xml, input_xml]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition => ex
|
157
|
+
if errors.include?(ex.class)
|
158
|
+
raise ex
|
159
|
+
else
|
160
|
+
return [output_xml, input_xml]
|
161
|
+
end
|
162
|
+
rescue Net::OpenTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
|
163
|
+
if !(tries -= 1).zero? && timeout_retry
|
164
|
+
Rails.logger.info {"#{ex.class} Timed out will retry after 5 seconds"}
|
165
|
+
sleep(self.timeout_sleep)
|
166
|
+
retry
|
167
|
+
else
|
168
|
+
raise ex
|
169
|
+
end
|
170
|
+
rescue => ex
|
171
|
+
raise ex
|
172
|
+
else
|
173
|
+
return [output_xml, input_xml]
|
174
|
+
end
|
175
|
+
|
176
|
+
def raise_errors(type: :SOAP, body: nil, response: nil)
|
177
|
+
case type
|
178
|
+
when :SOAP
|
179
|
+
|
180
|
+
error = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
|
181
|
+
message = body.xpath('//fns:FaultMessage', 'fns' =>'http://fault.api.zuora.com/').text
|
182
|
+
|
183
|
+
if error.blank? || message.blank?
|
184
|
+
error = body.xpath('//ns1:Code', 'ns1' =>'http://api.zuora.com/').text
|
185
|
+
message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
|
186
|
+
end
|
187
|
+
if error.blank? || message.blank?
|
188
|
+
error = body.xpath('//faultcode').text
|
189
|
+
message = body.xpath('//faultstring').text
|
190
|
+
end
|
191
|
+
|
192
|
+
if error.present?
|
193
|
+
if error == "INVALID_SESSION"
|
194
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{error}::#{message}")
|
195
|
+
end
|
196
|
+
if error == "REQUEST_EXCEEDED_LIMIT"
|
197
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{error}::#{message}")
|
198
|
+
end
|
199
|
+
if error == "LOCK_COMPETITION"
|
200
|
+
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}")
|
201
|
+
end
|
202
|
+
if error.present?
|
203
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
when :JSON
|
208
|
+
if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
|
209
|
+
messages_array = (body["reasons"] || []).map {|error| error['message']}.compact
|
210
|
+
codes_array = (body["reasons"] || []).map {|error| error['code']}.compact
|
211
|
+
|
212
|
+
#Authentication failed
|
213
|
+
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(11) || response.code == 401
|
214
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}")
|
215
|
+
end
|
216
|
+
|
217
|
+
#Zuora REST Create Amendment error #Authentication failed
|
218
|
+
if body["faultcode"].present? && body["faultcode"] == "fns:INVALID_SESSION"
|
219
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body['faultstring']}")
|
220
|
+
end
|
221
|
+
|
222
|
+
#Request exceeded limit
|
223
|
+
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(70)
|
224
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{messages_array.join(', ')}")
|
225
|
+
end
|
226
|
+
|
227
|
+
#Locking contention
|
228
|
+
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(50)
|
229
|
+
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{messages_array.join(', ')}")
|
230
|
+
end
|
231
|
+
|
232
|
+
#All Errors catch
|
233
|
+
if codes_array.size > 0
|
234
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{messages_array.join(', ')}")
|
235
|
+
end
|
236
|
+
|
237
|
+
#Zuora REST Query Errors
|
238
|
+
if body["faultcode"].present?
|
239
|
+
case body["faultcode"]
|
240
|
+
when "fns:MALFORMED_QUERY"
|
241
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}")
|
242
|
+
when "fns:REQUEST_EXCEEDED_LIMIT"
|
243
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{body["faultcode"]}::#{body["faultstring"]}")
|
244
|
+
when "fns:LOCK_COMPETITION"
|
245
|
+
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{body["faultcode"]}::#{body["faultstring"]}")
|
246
|
+
when "INVALID_SESSION"
|
247
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body["faultcode"]}::#{body["faultstring"]}")
|
248
|
+
else
|
249
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
if body["Errors"].present? || body["errors"].present?
|
254
|
+
errors = []
|
255
|
+
(body["Errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
|
256
|
+
(body["errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
|
257
|
+
if errors.size > 0
|
258
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{errors.join(", ")}", nil, nil, errors)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
#Zuora REST Actions error (Create, Update, Delete)
|
264
|
+
if body.class == Array
|
265
|
+
all_errors = body.select {|obj| !obj['Success'] || !obj['success'] }.map {|obj| obj['Errors'] || obj['errors'] }.compact
|
266
|
+
all_success = body.select {|obj| obj['Success'] || obj['success']}.compact
|
267
|
+
|
268
|
+
if all_errors.size > 0
|
269
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{all_errors.flatten.group_by {|error| error['Message']}.keys.uniq.join(' ')}", nil, nil, all_errors, all_success )
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
#All other errors
|
274
|
+
if response.code != 200
|
275
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{response.message}")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def aqua_query(queryName: '', query: '', version: '1.2', jobName: 'Aqua',partner: '', project: '')
|
281
|
+
params = {
|
282
|
+
"format" => 'csv',
|
283
|
+
"version" => version,
|
284
|
+
"name" => jobName,
|
285
|
+
"encrypted" => 'none',
|
286
|
+
"useQueryLabels" => 'true',
|
287
|
+
"partner" => partner,
|
288
|
+
"project" => project,
|
289
|
+
"queries" => [{
|
290
|
+
"name" => queryName,
|
291
|
+
"query" => query,
|
292
|
+
"type" => 'zoqlexport'
|
293
|
+
}]
|
294
|
+
}
|
295
|
+
response = self.rest_call(method: :post, body: params.to_json, url: self.aqua_endpoint("batch-query/"))
|
296
|
+
if(response[0]["id"].nil?)
|
297
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error in AQuA Process. #{response}")
|
298
|
+
end
|
299
|
+
return getFileById(id: response[0]["id"])
|
300
|
+
end
|
301
|
+
|
302
|
+
def getFileById(id: "2c92c0f85e7f88ff015e86b8f8f4517f")
|
303
|
+
response = nil
|
304
|
+
result = "new"
|
305
|
+
while result != "completed" do
|
306
|
+
sleep(2)#sleep 2 seconds
|
307
|
+
response, fullResponse = self.rest_call(method: :get, body: {}, url: self.aqua_endpoint("batch-query/jobs/#{id}"))
|
308
|
+
result = response["batches"][0]["status"]
|
309
|
+
if result == "error"
|
310
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Aqua Error: #{response}")
|
311
|
+
break
|
312
|
+
end
|
313
|
+
end
|
314
|
+
fileId = response["batches"][0]["fileId"]
|
315
|
+
return self.get_file(url: self.aqua_endpoint("file/#{fileId}"))
|
316
|
+
end
|
317
|
+
|
318
|
+
def describe_call(object = nil)
|
319
|
+
tries ||= 2
|
320
|
+
base = self.url.include?(".com") ? self.url.split(".com")[0].concat(".com") : self.url.split(".eu")[0].concat(".eu")
|
321
|
+
url = object ? "#{base}/apps/api/describe/#{object}" : "#{base}/apps/api/describe/"
|
322
|
+
headers = !self.entity_id.blank? ? {"entityId" => self.entity_id, 'Content-Type' => "text/xml; charset=utf-8"} : {'Content-Type' => "text/xml; charset=utf-8"}
|
323
|
+
|
324
|
+
response = HTTParty.get(url, headers: {"Authorization" => self.get_session(true)}.merge(headers), :timeout => 120)
|
325
|
+
|
326
|
+
output_xml = Nokogiri::XML(response.body)
|
327
|
+
des_hash = Hash.new
|
328
|
+
if object == nil
|
329
|
+
output_xml.xpath("//object").each do |object|
|
330
|
+
temp = {:label => object.xpath(".//label").text, :url => object.attributes["href"].value }
|
331
|
+
des_hash[object.xpath(".//name").text] = temp
|
332
|
+
end
|
333
|
+
else
|
334
|
+
output_xml.xpath("//field").each do |object|
|
335
|
+
temp = {:label => object.xpath(".//label").text,:selectable => object.xpath(".//selectable").text,
|
336
|
+
:createable => object.xpath(".//label").text == "ID" ? "false" : object.xpath(".//createable").text,
|
337
|
+
:filterable => object.xpath(".//filterable").text,
|
338
|
+
:updateable => object.xpath(".//label").text == "ID" ? "false" : object.xpath(".//updateable").text,
|
339
|
+
:custom => object.xpath(".//custom").text,:maxlength => object.xpath(".//maxlength").text,
|
340
|
+
:required => object.xpath(".//required").text,
|
341
|
+
:type => object.xpath(".//type").text,
|
342
|
+
:context => object.xpath(".//context").collect{ |x| x.text } }
|
343
|
+
temp[:options] = object.xpath(".//option").collect{ |x| x.text } if object.xpath(".//option").size > 0
|
344
|
+
des_hash[object.xpath(".//name").text.to_sym] = temp
|
345
|
+
des_hash[:fieldsToNull] = {:label => "FieldsToNull",:selectable => "false",
|
346
|
+
:createable => "false",:filterable => "false",
|
347
|
+
:updateable => "true",:custom => "false",
|
348
|
+
:required => "false",:type => "picklist",
|
349
|
+
:maxlength => "" ,:context => ["soap"],
|
350
|
+
:options => des_hash.map {|k,v| k if v[:updateable] == "true" && v[:required] == "false"}.compact.uniq }
|
351
|
+
|
352
|
+
end
|
353
|
+
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
|
354
|
+
end
|
355
|
+
rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
|
356
|
+
if !(tries -= 1).zero?
|
357
|
+
Rails.logger.info {"#{ex.class} Timed out will retry after 5 seconds"}
|
358
|
+
sleep(self.timeout_sleep)
|
359
|
+
retry
|
360
|
+
else
|
361
|
+
raise ex
|
362
|
+
end
|
363
|
+
rescue => ex
|
364
|
+
raise ex
|
365
|
+
else
|
366
|
+
return des_hash
|
367
|
+
end
|
368
|
+
|
369
|
+
def rest_call(method: :get, body: {},headers: {}, url: rest_endpoint("catalog/products?pageSize=4"), debug: true, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError, ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition], z_session: true, oauth_only: false, timeout_retry: false, timeout: 120, **keyword_args)
|
370
|
+
tries ||= 2
|
371
|
+
headers["entityId"] = self.entity_id if !self.entity_id.blank?
|
372
|
+
raise "Method not supported, supported methods include: :get, :post, :put, :delete, :patch, :head, :options" if ![:get, :post, :put, :delete, :patch, :head, :options].include?(method)
|
373
|
+
|
374
|
+
response = HTTParty::Request.new("Net::HTTP::#{method.to_s.capitalize}".constantize, url, body: body, headers: {'Content-Type' => "application/json; charset=utf-8"}.merge(z_session ? {"Authorization" => "#{self.get_session(true, oauth_only ? :bearer : nil)}" } : {}).merge(headers), timeout: timeout).perform
|
375
|
+
Rails.logger.debug('Connect') {"Response Code: #{response.code}" } if debug
|
376
|
+
begin
|
377
|
+
output_json = JSON.parse(response.body)
|
378
|
+
rescue JSON::ParserError => ex
|
379
|
+
output_json = {}
|
380
|
+
end
|
381
|
+
Rails.logger.debug('Connect') {"Response JSON: #{output_json}"} if debug && output_json.present?
|
382
|
+
|
383
|
+
raise_errors(type: :JSON, body: output_json, response: response)
|
384
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
385
|
+
if !(tries -= 1).zero? && z_session
|
386
|
+
Rails.logger.debug {"Session Invalid"}
|
387
|
+
self.new_session
|
388
|
+
retry
|
389
|
+
else
|
390
|
+
if errors.include?(ex.class)
|
391
|
+
raise ex
|
392
|
+
else
|
393
|
+
return [output_json, response]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition => ex
|
397
|
+
if errors.include?(ex.class)
|
398
|
+
raise ex
|
399
|
+
else
|
400
|
+
return [output_json, response]
|
401
|
+
end
|
402
|
+
rescue Net::OpenTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
|
403
|
+
if !(tries -= 1).zero? && timeout_retry
|
404
|
+
Rails.logger.info {"#{ex.class} Timed out will retry after 5 seconds"}
|
405
|
+
sleep(self.timeout_sleep)
|
406
|
+
retry
|
407
|
+
else
|
408
|
+
raise ex
|
409
|
+
end
|
410
|
+
rescue => ex
|
411
|
+
raise ex
|
412
|
+
else
|
413
|
+
return [output_json, response]
|
414
|
+
end
|
415
|
+
|
416
|
+
def update_create_tenant
|
417
|
+
Rails.logger.debug("Update and/or Create Tenant")
|
418
|
+
output_xml, input_xml = soap_call() do |xml|
|
419
|
+
xml['api'].getUserInfo
|
420
|
+
end
|
421
|
+
user_info = output_xml.xpath('//ns1:getUserInfoResponse', 'ns1' =>'http://api.zuora.com/')
|
422
|
+
output_hash = Hash[user_info.children.map {|x| [x.name.to_sym, x.text] }]
|
423
|
+
self.user_info = output_hash
|
424
|
+
self.user_info['entities'] = self.rest_call(:url => self.rest_endpoint("user-access/user-profile/#{self.user_info['UserId']}/accessible-entities"))['entities']
|
425
|
+
self.tenant_name = output_hash[:TenantName]
|
426
|
+
self.tenant_id = output_hash[:TenantId]
|
427
|
+
return self
|
428
|
+
end
|
429
|
+
|
430
|
+
def get_catalog(page_size: 40)
|
431
|
+
products, catalog_map, response = [{}, {}, {'nextPage' => self.rest_endpoint("catalog/products?pageSize=#{page_size}") }]
|
432
|
+
while !response["nextPage"].blank?
|
433
|
+
url = self.rest_endpoint(response["nextPage"].split('/v1/').last)
|
434
|
+
Rails.logger.debug("Fetch Catalog URL #{url}")
|
435
|
+
output_json, response = self.rest_call(:debug => false, :url => url, :errors => [ZuoraAPI::Exceptions::ZuoraAPISessionError], :timeout_retry => true )
|
436
|
+
if !output_json['success'] =~ (/(true|t|yes|y|1)$/i) || output_json['success'].class != TrueClass
|
437
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}")
|
438
|
+
end
|
439
|
+
output_json["products"].each do |product|
|
440
|
+
catalog_map[product["id"]] = {"productId" => product["id"]}
|
441
|
+
rateplans = {}
|
442
|
+
|
443
|
+
product["productRatePlans"].each do |rateplan|
|
444
|
+
catalog_map[rateplan["id"]] = {"productId" => product["id"], "productRatePlanId" => rateplan["id"]}
|
445
|
+
charges = {}
|
446
|
+
|
447
|
+
rateplan["productRatePlanCharges"].each do |charge|
|
448
|
+
catalog_map[charge["id"]] = {"productId" => product["id"], "productRatePlanId" => rateplan["id"], "productRatePlanChargeId" => charge["id"]}
|
449
|
+
charges[charge["id"]] = charge.merge({"productId" => product["id"], "productName" => product["name"], "productRatePlanId" => rateplan["id"], "productRatePlanName" => rateplan["name"] })
|
450
|
+
end
|
451
|
+
|
452
|
+
rateplan["productRatePlanCharges"] = charges
|
453
|
+
rateplans[rateplan["id"]] = rateplan.merge({"productId" => product["id"], "productName" => product["name"]})
|
454
|
+
end
|
455
|
+
product["productRatePlans"] = rateplans
|
456
|
+
products[product['id']] = product
|
457
|
+
end
|
458
|
+
end
|
459
|
+
return products, catalog_map
|
460
|
+
end
|
461
|
+
|
462
|
+
def get_file(url: nil, headers: {}, count: 3, z_session: true, tempfile: true, file_path: defined?(Rails.root.join('tmp')) ? Rails.root.join('tmp') : Pathname.new(Dir.pwd), timeout_retries: 2, timeout: 120, **execute_params)
|
463
|
+
raise "file_path must be of class Pathname" if file_path.class != Pathname
|
464
|
+
|
465
|
+
#Make sure directory exists
|
466
|
+
require 'fileutils'
|
467
|
+
FileUtils.mkdir_p(file_path) unless File.exists?(file_path)
|
468
|
+
|
469
|
+
begin
|
470
|
+
uri = URI.parse(url)
|
471
|
+
|
472
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
473
|
+
http.read_timeout = timeout #Seconds
|
474
|
+
http.use_ssl = true if uri.scheme.downcase == 'https'
|
475
|
+
headers = headers.merge({"Authorization" => self.get_session(true)}) if z_session
|
476
|
+
|
477
|
+
response_save = nil
|
478
|
+
http.request_get(uri.path, headers) do |response|
|
479
|
+
response_save = response
|
480
|
+
case response
|
481
|
+
when Net::HTTPNotFound
|
482
|
+
Rails.logger.fatal("404 - Not Found")
|
483
|
+
raise response
|
484
|
+
|
485
|
+
when Net::HTTPUnauthorized
|
486
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if count <= 0
|
487
|
+
Rails.logger.fatal("Unauthorized: Retry")
|
488
|
+
self.new_session
|
489
|
+
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)
|
490
|
+
|
491
|
+
when Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError
|
492
|
+
Rails.logger.fatal("#{response.class} timeout - retry")
|
493
|
+
return get_file(:url => url, :headers => headers, :count => count, :z_session => z_session, :tempfile => tempfile, :file_path => file_path, :timeout_retries => timeout_retries - 1, :timeout => timeout)
|
494
|
+
|
495
|
+
when Net::HTTPClientError
|
496
|
+
Rails.logger.fatal("Login: #{self.username} Export")
|
497
|
+
raise response
|
498
|
+
|
499
|
+
when Net::HTTPOK
|
500
|
+
headers = {}
|
501
|
+
response.each_header do |k,v|
|
502
|
+
headers[k] = v
|
503
|
+
end
|
504
|
+
Rails.logger.debug("Headers: #{headers.to_s}")
|
505
|
+
|
506
|
+
size, export_progress = [0, 0]
|
507
|
+
encoding, type, full_filename = [nil, nil, nil]
|
508
|
+
if response.header["Content-Disposition"].present?
|
509
|
+
case response.header["Content-Disposition"]
|
510
|
+
when /.*; filename\*=.*/
|
511
|
+
full_filename = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[2].strip
|
512
|
+
encoding = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[1].strip
|
513
|
+
when /.*; filename=/
|
514
|
+
full_filename = /.*; filename=(.*)/.match(response.header["Content-Disposition"])[1].strip
|
515
|
+
end
|
516
|
+
file_ending = ".#{full_filename.partition(".").last}"
|
517
|
+
end
|
518
|
+
#If user supplied a filename use it, else default to content header filename, else default to uri pattern
|
519
|
+
filename = full_filename.present? ? full_filename.split(file_ending).first : File.basename(uri.path).rpartition('.').first
|
520
|
+
|
521
|
+
if response.header["Content-Type"].present?
|
522
|
+
case response.header["Content-Type"]
|
523
|
+
when /.*;charset=.*/
|
524
|
+
type = /(.*);charset=(.*)/.match(response.header["Content-Type"])[1]
|
525
|
+
encoding = /(.*);charset=(.*)/.match(response.header["Content-Type"])[2]
|
526
|
+
else
|
527
|
+
type = response.header["Content-Type"]
|
528
|
+
encoding ||= 'UTF-8'
|
529
|
+
end
|
530
|
+
end
|
531
|
+
Rails.logger.info("File: #{filename}#{file_ending} #{encoding} #{type}")
|
532
|
+
|
533
|
+
if response.header["Content-Length"].present?
|
534
|
+
export_size = response.header["Content-Length"].to_i
|
535
|
+
elsif response.header["ContentLength"].present?
|
536
|
+
export_size = response.header["ContentLength"].to_i
|
537
|
+
end
|
538
|
+
|
539
|
+
file_handle = nil
|
540
|
+
timestamp = Time.now.to_i
|
541
|
+
if tempfile
|
542
|
+
require 'tempfile'
|
543
|
+
file_handle = ::Tempfile.new(["#{filename}_#{timestamp}", "#{file_ending}"], file_path)
|
544
|
+
file_handle.binmode
|
545
|
+
else
|
546
|
+
file_handle = File.new(file_path.join("#{filename}_#{timestamp}#{file_ending}"), "w+")
|
547
|
+
file_handle.binmode
|
548
|
+
end
|
549
|
+
|
550
|
+
response.read_body do |chunk|
|
551
|
+
file_handle << chunk.force_encoding(encoding)
|
552
|
+
|
553
|
+
if defined?(export_size) && export_size != 0 && export_size.class == Integer
|
554
|
+
size += chunk.size
|
555
|
+
new_progress = (size * 100) / export_size
|
556
|
+
unless new_progress == export_progress
|
557
|
+
Rails.logger.debug("Login: #{self.username} Export Downloading %s (%3d%%)" % [filename, new_progress])
|
558
|
+
end
|
559
|
+
export_progress = new_progress
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
file_handle.close
|
564
|
+
Rails.logger.info("Filepath: #{file_handle.path} Size: #{File.size(file_handle.path).to_f/1000000} mb")
|
565
|
+
|
566
|
+
return file_handle
|
567
|
+
end
|
568
|
+
end
|
569
|
+
rescue Exception => e
|
570
|
+
Rails.logger.fatal('GetFile') {"Download Failed: #{response_save} - #{e.message}"}
|
571
|
+
Rails.logger.fatal('GetFile') {"Download Failed: #{e.backtrace.join("\n")}"}
|
572
|
+
raise
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
def getDataSourceExport(query, extract: true, encrypted: false, zip: true)
|
577
|
+
Rails.logger.info('Export') {"Build export"}
|
578
|
+
Rails.logger.debug('Export query') {"#{query}"}
|
579
|
+
request = Nokogiri::XML::Builder.new do |xml|
|
580
|
+
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
|
581
|
+
xml['SOAP-ENV'].Header do
|
582
|
+
xml['ns1'].SessionHeader do
|
583
|
+
xml['ns1'].session self.get_session(false, :basic)
|
584
|
+
end
|
585
|
+
end
|
586
|
+
xml['SOAP-ENV'].Body do
|
587
|
+
xml['ns1'].create do
|
588
|
+
xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
|
589
|
+
xml['ns2'].Format 'csv'
|
590
|
+
xml['ns2'].Zip zip
|
591
|
+
xml['ns2'].Name 'googman'
|
592
|
+
xml['ns2'].Query query
|
593
|
+
xml['ns2'].Encrypted encrypted
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
response_query = HTTParty.post(self.url, body: request.to_xml, headers: {'Content-Type' => "application/json; charset=utf-8"}, :timeout => 120)
|
600
|
+
output_xml = Nokogiri::XML(response_query.body)
|
601
|
+
|
602
|
+
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"
|
603
|
+
id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
|
604
|
+
|
605
|
+
confirmRequest = Nokogiri::XML::Builder.new do |xml|
|
606
|
+
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
|
607
|
+
xml['SOAP-ENV'].Header do
|
608
|
+
xml['ns1'].SessionHeader do
|
609
|
+
xml['ns1'].session self.get_session(false, :basic)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
xml['SOAP-ENV'].Body do
|
613
|
+
xml['ns1'].query do
|
614
|
+
xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
result = 'Waiting'
|
620
|
+
while result != "Completed"
|
621
|
+
response_query = HTTParty.post(self.url, body: confirmRequest.to_xml, headers: {'Content-Type' => "application/json; charset=utf-8"}, :timeout => 120)
|
622
|
+
output_xml = Nokogiri::XML(response_query.body)
|
623
|
+
result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
|
624
|
+
raise "Export Creation Unsuccessful : #{output_xml.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text}" if result.blank? || result == "Failed"
|
625
|
+
end
|
626
|
+
|
627
|
+
file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
|
628
|
+
Rails.logger.info('Export') {'=====> Export finished'}
|
629
|
+
export_file = get_file(:url => self.fileURL(file_id))
|
630
|
+
export_file_path = export_file.path
|
631
|
+
Rails.logger.info('Export') {"=====> Export path #{export_file.path}"}
|
632
|
+
|
633
|
+
if extract && zip
|
634
|
+
require "zip"
|
635
|
+
new_path = export_file_path.partition('.zip').first
|
636
|
+
zipped = Zip::File.open(export_file_path)
|
637
|
+
file_handle = zipped.entries.first
|
638
|
+
file_handle.extract(new_path)
|
639
|
+
File.delete(export_file_path)
|
640
|
+
return new_path
|
641
|
+
else
|
642
|
+
return export_file_path
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
def query(query, parse = false)
|
647
|
+
Rails.logger.debug('query') {"Querying Zuora for #{query}"}
|
648
|
+
output_xml, input_xml = self.soap_call({:debug => false, :timeout_retry => true}) do |xml|
|
649
|
+
xml['ns1'].query do
|
650
|
+
xml['ns1'].queryString query
|
651
|
+
end
|
652
|
+
end
|
653
|
+
if parse
|
654
|
+
return [] if output_xml.xpath('//ns1:size', 'ns1' =>'http://api.zuora.com/').text == '0'
|
655
|
+
data = output_xml.xpath('//ns1:records', 'ns1' =>'http://api.zuora.com/').map {|record| record.children.map {|element| [element.name, element.text]}.to_h}
|
656
|
+
return data
|
657
|
+
else
|
658
|
+
return output_xml
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
def createJournalRun(call)
|
663
|
+
url = rest_endpoint("/journal-runs")
|
664
|
+
uri = URI(url)
|
665
|
+
req = Net::HTTP::Post.new(uri,initheader = {'Content-Type' =>'application/json'})
|
666
|
+
req["Authorization"] = self.get_session(true)
|
667
|
+
req.body = call
|
668
|
+
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
|
669
|
+
http.request req
|
670
|
+
end
|
671
|
+
Rails.logger.debug('Journal Run') {"Response #{response.code} #{response.message}:
|
672
|
+
#{response.body}"}
|
673
|
+
|
674
|
+
result = JSON.parse(response.body)
|
675
|
+
if result["success"]
|
676
|
+
jrNumber = result["journalRunNumber"]
|
677
|
+
return jrNumber
|
678
|
+
else
|
679
|
+
message = result["reasons"][0]["message"]
|
680
|
+
Rails.logger.debug('Journal Run') {"Journal Run failed with message #{message}"}
|
681
|
+
return result
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
def checkJRStatus(jrNumber)
|
686
|
+
Rails.logger.info('Journal Run') {"Check for completion"}
|
687
|
+
url = rest_endpoint("/journal-runs/#{jrNumber}")
|
688
|
+
uri = URI(url)
|
689
|
+
req = Net::HTTP::Get.new(uri,initheader = {'Content-Type' =>'application/json'})
|
690
|
+
req["Authorization"] = self.get_session(true)
|
691
|
+
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
|
692
|
+
http.request req
|
693
|
+
end
|
694
|
+
result = JSON.parse(response.body)
|
695
|
+
if result["success"]
|
696
|
+
if !(result["status"].eql? "Completed")
|
697
|
+
sleep(20.seconds)
|
698
|
+
end
|
699
|
+
return result["status"]
|
700
|
+
else
|
701
|
+
message = result["reasons"][0]["message"]
|
702
|
+
Rails.logger.info('Journal Run') {"Checking status of journal run failed with message #{message}"}
|
703
|
+
end
|
704
|
+
return "failure"
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|