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