zuora_api 1.8.2 → 1.8.21
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/.gitignore +2 -1
- data/.gitlab-ci.yml +1 -1
- data/Gemfile +1 -1
- data/catalog-info.yaml +12 -0
- data/docs/index.md +147 -0
- data/lib/zuora_api/exceptions.rb +16 -2
- data/lib/zuora_api/login.rb +346 -240
- data/lib/zuora_api/logins/basic.rb +10 -101
- data/lib/zuora_api/logins/oauth.rb +24 -84
- data/lib/zuora_api/version.rb +1 -1
- data/zuora_api.gemspec +3 -2
- metadata +24 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44a650019155981d2075062e2d4de0d8feff5c5e89b7d3ba9e4540f81de803dc
|
4
|
+
data.tar.gz: 6db8be3cfaa2905300c7b3d91b7b7d4f8b5aa5eb8306d24116dac7f1035dd3b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 704fd52ba264db7e1937f6307d0c761d72f615495d15097b1e5ceb14bc31b3b07b8b71ef99b87d74c95706399a822008bf654e06e6e4f8e4e6637187ab993446
|
7
|
+
data.tar.gz: 0be344718465df6d0bbb69d7fca93263c9bcc299a20ad9a70498f9d56e09eb5b708d9d474984c42f183220eff62ba145b486fbae5be73202e7c7a210b740c9a5
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
CHANGED
data/Gemfile
CHANGED
data/catalog-info.yaml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
---
|
2
|
+
apiVersion: backstage.io/v1alpha1
|
3
|
+
kind: Component
|
4
|
+
metadata:
|
5
|
+
annotations:
|
6
|
+
backstage.io/techdocs-ref: "gitlab:https://gitlab.zeta.tools/extension-products/shared-libraries/zuora-gem.git"
|
7
|
+
description: "Zuora API Rails Gem"
|
8
|
+
name: Zuora-Gem
|
9
|
+
spec:
|
10
|
+
lifecycle: production
|
11
|
+
owner: connect@zuora.com
|
12
|
+
type: library
|
data/docs/index.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# Zuora Gem
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/zuora_api) [](https://gitlab.0.ecc.auw2.zuora/extension-products/shared-libraries/zuora-gem/commits/master)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
Add this line to your application's Gemfile:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
gem 'zuora_api'
|
10
|
+
```
|
11
|
+
Then execute `bundle install` in your terminal
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Zuora Login Object
|
16
|
+
In order to make API calls a Zuora Login object must be created
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
zuora_client = ZuoraAPI::Login.new(username: "username", password: "password", url: "url")
|
20
|
+
```
|
21
|
+
|
22
|
+
| Name | Type | Description | Example |
|
23
|
+
| ------------------- | ----------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
24
|
+
| username | `Attribute` | Username to the Zuora environment | `zuora_client.username = "username"` |
|
25
|
+
| password | `Attribute` | password to the Zuora environment | `zuora_client.password = "Password"` |
|
26
|
+
| url | `Attribute` | Endpoint to the Zuora tenant | `zuora_client.url = "www.zuora.com"` |
|
27
|
+
| wsdl_number | `Attribute` | WSDL number of the zuora login | `wsdl = zuora_client.wsdl_number` |
|
28
|
+
| status | `Attribute` | Status of the login | `zuora_client.status` |
|
29
|
+
| current_session | `Attribute` | Current session for the login | `zuora_client.current_session` |
|
30
|
+
| environment | `Attribute` | environment of the login | `zuora_client.environment` |
|
31
|
+
| errors | `Attribute` | Any errors that the login has based on the login call | `zuora_client.errors` |
|
32
|
+
| current_error | `Attribute` | Current error from the new_session call | `zuora_client.current_error` |
|
33
|
+
| user_info | `Attribute` | Information related to the login | `zuora_client.user_info` |
|
34
|
+
| tenant_id | `Attribute` | Tenant ID the login is associated to | `zuora_client.tenant_id` |
|
35
|
+
| tenant_name | `Attribute` | Tenant Name of tenant the login is associated to | `zuora_client.tenant_name` |
|
36
|
+
| entity_id | `Attribute` | Current entity the login session is associated to | `zuora_client.entity_id` |
|
37
|
+
| rest_call | `Method` | Executes a REST call | `zuora_client.rest_call()` |
|
38
|
+
| soap_call | `Method` | Executes a SOAP call | `output_xml, input_xml = zuora_client.soap_call() do `|xml, args|` xml['ns1'].query do xml['ns1'].queryString "select id, name from account" end end` |
|
39
|
+
| query | `Method` | Executes a query call | `zuora_client.query("select id, name from account")` |
|
40
|
+
| getDataSourceExport | `Method` | Pulls a data source export with the given query and returns the file location | `zuora_client.getDataSourceExport("select id, name from account")` |
|
41
|
+
| describe_call | `Method` | Performs the describe call against the Zuora tenant for all objects or a specific object | `response = zuora_client.describe_call("Account")` |
|
42
|
+
| createJournalRun | `Method` | Creates a Journal Run | `zuora_client.createJournalRun(call)` |
|
43
|
+
| checkJRStatus | `Method` | Checks the status of a journal run | `zuora_client.checkJRStatus(journal_run_id)` |
|
44
|
+
| update_environment | `Method` | Sets the login's environment based on the url | `zuora_client.update_environment` |
|
45
|
+
| aqua_endpoint | `Method` | Returns the AQuA endpoint for the login based off the environment | `zuora_client.aqua_endpoint` |
|
46
|
+
| rest_endpoint | `Method` | Returns the REST endpoint for the login based off the environment | `zuora_client.rest_endpoint` |
|
47
|
+
| fileURL | `Method` | Returns the URL for files | `zuora_client.fileURL` |
|
48
|
+
| dateFormat | `Method` | Returns the data format syntax based on the wsdl_number | `zuora_client.dateFormat` |
|
49
|
+
| new_session | `Method` | Create a new session | `zuora_client.new_session` |
|
50
|
+
| get_session | `Method` | Returns the current session | `zuora_client.get_session`|
|
51
|
+
|
52
|
+
## Rest Call
|
53
|
+
```ruby
|
54
|
+
zuora_client.rest_call(method: :get, body: {}, url: zuora_client.rest_endpoint("catalog/products?pageSize=4"))
|
55
|
+
```
|
56
|
+
|
57
|
+
### Soap Call
|
58
|
+
Returns both output and input XML
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
zuora_client.soap_call(ns1: 'ns1', ns2: 'ns2', batch_size: nil, single_transaction: false)
|
62
|
+
```
|
63
|
+
|
64
|
+
Example Call
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
output_xml, input_xml = zuora_client.soap_call() do |xml, args|
|
68
|
+
xml['ns1'].query do
|
69
|
+
xml['ns1'].queryString "select id, name from account"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
### Query
|
74
|
+
```ruby
|
75
|
+
zuora_client.query("select id from account")
|
76
|
+
```
|
77
|
+
### Data Export
|
78
|
+
Returns the file location of the data source export after downloading from Zuora
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
zuora_client.getDataSourceExport("select id from account")
|
82
|
+
```
|
83
|
+
|
84
|
+
| Name | Description | Default | Example |
|
85
|
+
| --------- | ---------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------- |
|
86
|
+
| query | The query to execute | `N/A` | `zuora_client.getDataSourceExport("select id from account")` |
|
87
|
+
| zip | Indicates if the data source export should be a zip | `true` | `zuora_client.getDataSourceExport("select id from account", zip: false)` |
|
88
|
+
| extract | Indicates if the data source export should be extracted if it is a zip | `true` | `zuora_client.getDataSourceExport("select id from account", extract: false)` |
|
89
|
+
| encrypted | Indicates if the data source export should be encrypted | `false` | `zuora_client.getDataSourceExport("select id from account", encrypted: true)` |
|
90
|
+
|
91
|
+
### Describe Call
|
92
|
+
This returns all available objects from the describe call as a hash. This response can be accessed by using response["Account"] to retrieve all related data about that object.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
response = zuora_client.describe_call("Account")
|
96
|
+
```
|
97
|
+
This returns all information and fields related to that object model as a hash.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
response = zuora_client.describe_call()
|
101
|
+
```
|
102
|
+
|
103
|
+
### Journal Run
|
104
|
+
```ruby
|
105
|
+
zuora_client.createJournalRun(call)
|
106
|
+
```
|
107
|
+
|
108
|
+
## Insights API
|
109
|
+
|
110
|
+
In order to make API calls a Zuora Login object must be created by running:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
insightsapi = InsightsAPI::Login.new(api_token: "api token", url: "Nw1.api.insights.zuora.com/api/")
|
114
|
+
```
|
115
|
+
|
116
|
+
Note that the login will default to the insights production url.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
Date format: "YYYY-MM-DDT00:00:00Z"
|
120
|
+
```
|
121
|
+
|
122
|
+
### Uploading Data into Insights
|
123
|
+
```ruby
|
124
|
+
insightsapi.upload_into_insights(dataSourceName, recordType, batchDate, filePath)
|
125
|
+
```
|
126
|
+
dataSourceName: What system the data is coming from.
|
127
|
+
recordType: The type of records ie: "EVENTS, ATTRIBUTES, and METRICS"
|
128
|
+
batachDate: The date the data applies to.
|
129
|
+
|
130
|
+
### Describing Insights Data
|
131
|
+
```ruby
|
132
|
+
insightsapi.describe(type: "ACCOUNT/USER", object: "ATTRIBUTES/EVENTS/SEGMENTS/METRICS")
|
133
|
+
```
|
134
|
+
Returns json payload describing attributes, events, metrics for each Account or User.
|
135
|
+
|
136
|
+
### Downloading Data from Insights
|
137
|
+
```ruby
|
138
|
+
insightsapi.data_export_insights(objecttype, segmentuuid, startDate: nil, endDate: nil, tries: 30)
|
139
|
+
```
|
140
|
+
```ruby
|
141
|
+
insightsapi.data_export_insights_file(objecttype, segmentuuid, startDate: nil, endDate: nil, tries: 30)
|
142
|
+
```
|
143
|
+
Both do the same thing except one returns a url(data_export_insights) to download the file yourself and the other returns an actual Ruby temporary file(data_export_insights_file).
|
144
|
+
|
145
|
+
objectype: "ACCOUNT/USER"
|
146
|
+
|
147
|
+
segmentuuid: A single or array of string or int of a segment uuid(s) that you get from the describe call. The csv holds a column with a bool that represents if that User or Account belongs to that segment.
|
data/lib/zuora_api/exceptions.rb
CHANGED
@@ -40,6 +40,19 @@ module ZuoraAPI
|
|
40
40
|
def to_s
|
41
41
|
@message || @default_message
|
42
42
|
end
|
43
|
+
|
44
|
+
def parse_message(message)
|
45
|
+
case message
|
46
|
+
when /^Invalid Oauth Client Id$/, /^Unable to generate token.$/
|
47
|
+
@message = "Invalid login, please check client ID and Client Secret or URL endpoint"
|
48
|
+
when /^Forbidden$/
|
49
|
+
@message = "The user associated to OAuth credential set has been deactivated."
|
50
|
+
when /^Invalid login. User name and password do not match.$/
|
51
|
+
@message = "Invalid login, please check username and password or URL endpoint"
|
52
|
+
else
|
53
|
+
@message = message
|
54
|
+
end
|
55
|
+
end
|
43
56
|
end
|
44
57
|
|
45
58
|
class BadEntityError < Error
|
@@ -177,14 +190,15 @@ module ZuoraAPI
|
|
177
190
|
end
|
178
191
|
|
179
192
|
class ZuoraAPITemporaryError < Error
|
180
|
-
attr_reader :code, :response
|
193
|
+
attr_reader :code, :response, :errors
|
181
194
|
attr_writer :default_message
|
182
195
|
|
183
|
-
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
196
|
+
def initialize(message = nil, response = nil, errors = [], successes = [], *args)
|
184
197
|
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
185
198
|
@message = parse_message(message)
|
186
199
|
@response = response
|
187
200
|
@default_message = "There is a temporary error with zuora system."
|
201
|
+
@errors = errors
|
188
202
|
end
|
189
203
|
|
190
204
|
def to_s
|
data/lib/zuora_api/login.rb
CHANGED
@@ -7,7 +7,7 @@ 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': '107.0', 'Sandbox': '107.0', 'Production': '107.0', 'Performance': '107.0', 'Services': '96.0', 'Unknown': '96.0', 'Staging': '107.0'}.freeze
|
11
11
|
XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
|
12
12
|
|
13
13
|
CONNECTION_EXCEPTIONS = [
|
@@ -39,8 +39,8 @@ module ZuoraAPI
|
|
39
39
|
|
40
40
|
ZUORA_SERVER_ERRORS = [
|
41
41
|
ZuoraAPI::Exceptions::ZuoraAPIInternalServerError,
|
42
|
-
|
43
|
-
|
42
|
+
ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout,
|
43
|
+
ZuoraAPI::Exceptions::ZuoraAPIReadTimeout,
|
44
44
|
ZuoraAPI::Exceptions::ZuoraUnexpectedError
|
45
45
|
].freeze
|
46
46
|
|
@@ -50,10 +50,12 @@ module ZuoraAPI
|
|
50
50
|
raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
|
51
51
|
# raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
|
52
52
|
self.hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
|
53
|
+
self.update_environment
|
54
|
+
min_endpoint = MIN_Endpoints[self.environment.to_sym]
|
53
55
|
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)$/,
|
56
|
+
self.url = "https://#{hostname}/apps/services/a/#{min_endpoint}"
|
57
|
+
elsif min_endpoint.to_f > url.scan(/(\d+\.\d)$/).dig(0,0).to_f
|
58
|
+
self.url = url.gsub(/(\d+\.\d)$/, min_endpoint)
|
57
59
|
else
|
58
60
|
self.url = url
|
59
61
|
end
|
@@ -65,7 +67,6 @@ module ZuoraAPI
|
|
65
67
|
self.status = status.blank? ? "Active" : status
|
66
68
|
self.user_info = Hash.new
|
67
69
|
self.update_region
|
68
|
-
self.update_environment
|
69
70
|
self.update_zconnect_provider
|
70
71
|
@timeout_sleep = 5
|
71
72
|
end
|
@@ -252,7 +253,7 @@ module ZuoraAPI
|
|
252
253
|
end
|
253
254
|
|
254
255
|
def update_environment
|
255
|
-
if !self.
|
256
|
+
if !self.hostname.blank?
|
256
257
|
case self.hostname
|
257
258
|
when /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/
|
258
259
|
self.environment = 'Sandbox'
|
@@ -275,13 +276,13 @@ module ZuoraAPI
|
|
275
276
|
end
|
276
277
|
|
277
278
|
def update_zconnect_provider
|
278
|
-
|
279
|
-
|
279
|
+
update_region if self.region.blank?
|
280
|
+
update_environment if self.environment.blank?
|
280
281
|
mappings = {"US" => {"Sandbox" => "ZConnectSbx", "Services" => "ZConnectSvcUS", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Test" => "ZConnectTest", "Staging" => "ZConnectQA", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev"},
|
281
282
|
"NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectSvcNA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
|
282
283
|
"EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectSvcEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU", "Test" => "ZConnectTest"},
|
283
284
|
"Unknown" => {"Unknown" => "Unknown"}}
|
284
|
-
self.zconnect_provider = mappings[region][environment]
|
285
|
+
self.zconnect_provider = mappings[self.region][self.environment]
|
285
286
|
end
|
286
287
|
|
287
288
|
def aqua_endpoint(url="")
|
@@ -321,8 +322,8 @@ module ZuoraAPI
|
|
321
322
|
return domain ? endpoint.concat(prefix).concat(url) : prefix.concat(url)
|
322
323
|
end
|
323
324
|
|
324
|
-
def rest_domain
|
325
|
-
return URI(
|
325
|
+
def rest_domain(endpoint: self.rest_endpoint)
|
326
|
+
return URI(endpoint).host
|
326
327
|
end
|
327
328
|
|
328
329
|
def fileURL(url="")
|
@@ -334,10 +335,41 @@ module ZuoraAPI
|
|
334
335
|
end
|
335
336
|
|
336
337
|
def new_session(auth_type: :basic, debug: false, zuora_track_id: nil)
|
338
|
+
retries ||= 2
|
339
|
+
yield
|
340
|
+
|
341
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
342
|
+
self.status = 'Invalid'
|
343
|
+
self.current_error = ex.message
|
344
|
+
raise
|
345
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPIError => ex
|
346
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(ex.message, ex.response)
|
347
|
+
|
348
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPIInternalServerError => ex
|
349
|
+
raise ex if retries.zero?
|
350
|
+
|
351
|
+
retries -= 1
|
352
|
+
sleep(self.timeout_sleep)
|
353
|
+
retry
|
354
|
+
|
355
|
+
rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
|
356
|
+
self.log(location: "BasicLogin", exception: ex, message: "Timed out", level: :error)
|
357
|
+
|
358
|
+
self.current_error = "Request timed out. Try again"
|
359
|
+
self.status = 'Timeout'
|
360
|
+
|
361
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
|
362
|
+
|
363
|
+
rescue EOFError
|
364
|
+
if self.url.match?(/.*services\d{1,}.zuora.com*/)
|
365
|
+
self.current_error = "Services tenant '#{self.url.scan(/.*\/\/(services\d{1,}).zuora.com*/).last.first}' is no longer available."
|
366
|
+
self.status = 'Not Available'
|
367
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
|
368
|
+
end
|
369
|
+
|
337
370
|
end
|
338
371
|
|
339
372
|
def get_session(prefix: false, auth_type: :basic, zuora_track_id: nil)
|
340
|
-
Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}") if Rails.env.to_s == 'development'
|
341
373
|
case auth_type
|
342
374
|
when :basic
|
343
375
|
if self.current_session.blank?
|
@@ -346,14 +378,13 @@ module ZuoraAPI
|
|
346
378
|
if self.bearer_token.blank? || self.oauth_expired?
|
347
379
|
self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
|
348
380
|
end
|
349
|
-
self.get_z_session(zuora_track_id: zuora_track_id)
|
381
|
+
self.get_z_session(zuora_track_id: zuora_track_id)
|
350
382
|
when 'ZuoraAPI::Basic'
|
351
383
|
self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
|
352
384
|
else
|
353
385
|
self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
|
354
386
|
end
|
355
387
|
end
|
356
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
|
357
388
|
return prefix ? "ZSession #{self.current_session}" : self.current_session.to_s
|
358
389
|
when :bearer
|
359
390
|
case self.class.to_s
|
@@ -366,8 +397,6 @@ module ZuoraAPI
|
|
366
397
|
else
|
367
398
|
raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Unknown Login, does not support Authentication of Type: #{auth_type}")
|
368
399
|
end
|
369
|
-
|
370
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
|
371
400
|
return prefix ? "Bearer #{self.bearer_token}" : self.bearer_token.to_s
|
372
401
|
end
|
373
402
|
end
|
@@ -375,16 +404,18 @@ module ZuoraAPI
|
|
375
404
|
def soap_call(
|
376
405
|
ns1: 'ns1',
|
377
406
|
ns2: 'ns2',
|
378
|
-
batch_size: nil,
|
407
|
+
batch_size: nil,
|
408
|
+
headers: {},
|
379
409
|
single_transaction: false,
|
380
410
|
debug: false,
|
381
411
|
zuora_track_id: nil,
|
382
412
|
errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS),
|
383
413
|
z_session: true,
|
384
414
|
timeout_retry: false,
|
385
|
-
timeout:
|
415
|
+
timeout: 130,
|
386
416
|
timeout_sleep_interval: self.timeout_sleep,
|
387
417
|
output_exception_messages: true,
|
418
|
+
skip_session: false,
|
388
419
|
**keyword_args)
|
389
420
|
tries ||= 2
|
390
421
|
xml = Nokogiri::XML::Builder.new do |xml|
|
@@ -394,8 +425,10 @@ module ZuoraAPI
|
|
394
425
|
'xmlns:api' => "http://api.zuora.com/",
|
395
426
|
"xmlns:#{ns1}" => "http://api.zuora.com/") do
|
396
427
|
xml['SOAP-ENV'].Header do
|
397
|
-
|
398
|
-
xml["#{ns1}"].
|
428
|
+
if !skip_session
|
429
|
+
xml["#{ns1}"].SessionHeader do
|
430
|
+
xml["#{ns1}"].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: zuora_track_id)
|
431
|
+
end
|
399
432
|
end
|
400
433
|
if single_transaction
|
401
434
|
xml["#{ns1}"].CallOptions do
|
@@ -413,12 +446,11 @@ module ZuoraAPI
|
|
413
446
|
end
|
414
447
|
end
|
415
448
|
end
|
416
|
-
|
417
449
|
input_xml = Nokogiri::XML(xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip)
|
418
450
|
input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
|
419
451
|
Rails.logger.debug("Request SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
|
420
452
|
|
421
|
-
headers
|
453
|
+
headers.merge!({ 'Content-Type' => "text/xml; charset=utf-8", 'Accept' => 'text/xml'})
|
422
454
|
headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
|
423
455
|
|
424
456
|
request = HTTParty::Request.new(
|
@@ -435,7 +467,11 @@ module ZuoraAPI
|
|
435
467
|
Rails.logger.debug("Response SOAP XML: #{output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
|
436
468
|
|
437
469
|
raise_errors(type: :SOAP, body: output_xml, response: response)
|
470
|
+
|
471
|
+
return output_xml, input_xml, response
|
472
|
+
|
438
473
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
474
|
+
raise if skip_session
|
439
475
|
if !tries.zero? && z_session
|
440
476
|
tries -= 1
|
441
477
|
Rails.logger.debug("SOAP Call - Session Invalid")
|
@@ -447,39 +483,33 @@ module ZuoraAPI
|
|
447
483
|
end
|
448
484
|
|
449
485
|
retry
|
450
|
-
else
|
451
|
-
if errors.include?(ex.class)
|
452
|
-
raise ex
|
453
|
-
else
|
454
|
-
return output_xml, input_xml, response
|
455
|
-
end
|
456
486
|
end
|
487
|
+
|
488
|
+
raise ex if errors.include?(ex.class)
|
489
|
+
|
490
|
+
return output_xml, input_xml, response
|
491
|
+
|
457
492
|
rescue *ZUORA_API_ERRORS => ex
|
458
|
-
if errors.include?(ex.class)
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
end
|
493
|
+
raise ex if errors.include?(ex.class)
|
494
|
+
|
495
|
+
response = ex.response unless response
|
496
|
+
return output_xml, input_xml, response
|
497
|
+
|
464
498
|
rescue *CONNECTION_EXCEPTIONS => ex
|
465
|
-
if tries.zero?
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
Rails.logger.error("SOAP Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
|
471
|
-
end
|
472
|
-
end
|
473
|
-
raise ex
|
499
|
+
if !tries.zero?
|
500
|
+
tries -= 1
|
501
|
+
self.log(location: "SOAP Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
502
|
+
sleep(timeout_sleep_interval)
|
503
|
+
retry
|
474
504
|
end
|
475
505
|
|
476
|
-
|
477
|
-
|
478
|
-
|
506
|
+
self.log(location: "SOAP Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
507
|
+
raise ex
|
508
|
+
|
479
509
|
rescue *CONNECTION_READ_EXCEPTIONS => ex
|
480
510
|
if !tries.zero?
|
481
511
|
tries -= 1
|
482
|
-
|
512
|
+
self.log(location: "SOAP Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
483
513
|
if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
|
484
514
|
retry
|
485
515
|
elsif timeout_retry
|
@@ -488,19 +518,74 @@ module ZuoraAPI
|
|
488
518
|
end
|
489
519
|
end
|
490
520
|
|
491
|
-
if output_exception_messages
|
492
|
-
|
493
|
-
Rails.logger.error("SOAP Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
|
494
|
-
else
|
495
|
-
Rails.logger.error("SOAP Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
|
496
|
-
end
|
497
|
-
end
|
498
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
|
521
|
+
self.log(location: "SOAP Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
522
|
+
ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.instance_of?(Net::ReadTimeout)
|
499
523
|
raise ex
|
524
|
+
|
500
525
|
rescue => ex
|
501
526
|
raise ex
|
502
|
-
|
503
|
-
|
527
|
+
ensure
|
528
|
+
self.error_logger(ex) if defined?(ex) && Rails.logger.class.to_s == "Ougai::Logger"
|
529
|
+
end
|
530
|
+
|
531
|
+
def error_logger(ex)
|
532
|
+
exception_args = Rails.logger.with_fields.merge(self.exception_args(ex))
|
533
|
+
case ex
|
534
|
+
when ZuoraAPI::Exceptions::ZuoraAPIUnkownError, ZuoraAPI::Exceptions::ZuoraDataIntegrity
|
535
|
+
Rails.logger.error('Zuora Unknown/Integrity Error', ex, exception_args)
|
536
|
+
when ZuoraAPI::Exceptions::ZuoraAPIRequestLimit
|
537
|
+
Rails.logger.info('Zuora APILimit Reached', ex, exception_args)
|
538
|
+
when *(ZuoraAPI::Login::ZUORA_API_ERRORS-ZuoraAPI::Login::ZUORA_SERVER_ERRORS)
|
539
|
+
#Rails.logger.debug('Zuora API Error', ex, self.exception_args(ex))
|
540
|
+
when *ZuoraAPI::Login::ZUORA_SERVER_ERRORS
|
541
|
+
Rails.logger.error('Zuora Server Error', ex, exception_args)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def log(location: "Rest Call", exception: nil, message: "Timed out will retry after #{self.timeout_sleep} seconds", level: :info )
|
546
|
+
level = :debug if ![:debug, :info, :warn, :error, :fatal].include?(level)
|
547
|
+
if Rails.logger.class.to_s == "Ougai::Logger"
|
548
|
+
Rails.logger.send(level.to_sym, "#{location} - #{message}", exception)
|
549
|
+
else
|
550
|
+
Rails.logger.send(level.to_sym, "#{location} - #{exception.class} #{message}")
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
def exception_args(ex)
|
555
|
+
args = {}
|
556
|
+
if defined?(ex.response) && ex.response.present?
|
557
|
+
args.merge!({
|
558
|
+
request: {
|
559
|
+
path: ex.response.request.path.to_s,
|
560
|
+
method: ex.response.request.http_method.to_s.split("Net::HTTP::").last.upcase,
|
561
|
+
params: ex.response.request.raw_body.to_s,
|
562
|
+
headers: ex.response.request.options[:headers].map{|k,v| [k.to_s, k.to_s.downcase.strip == "authorization" ? "VALUE FILTERED" : v]}.to_h.to_s,
|
563
|
+
},
|
564
|
+
response: {
|
565
|
+
status: ex.response.code,
|
566
|
+
params: ex.response.body.to_s,
|
567
|
+
headers: ex.response.headers.to_s,
|
568
|
+
},
|
569
|
+
zuora_trace_id: ex.response.headers["zuora-request-id"],
|
570
|
+
zuora_track_id: ex.response.request.options[:headers]["Zuora-Track-Id"],
|
571
|
+
})
|
572
|
+
elsif defined?(ex.request) && ex.request.present?
|
573
|
+
args.merge!({
|
574
|
+
request: {
|
575
|
+
path: ex.request.path.to_s,
|
576
|
+
method: ex.request.http_method.to_s.split("Net::HTTP::").last.upcase,
|
577
|
+
params: ex.request.options[:body],
|
578
|
+
headers: ex.request.options[:headers].map{|k,v| [k.to_s, k.to_s.downcase.strip == "authorization" ? "VALUE FILTERED" : v]}.to_h.to_s
|
579
|
+
}
|
580
|
+
})
|
581
|
+
args.merge!({
|
582
|
+
zuora_track_id: ex.request.options[:headers]["Zuora-Track-Id"]
|
583
|
+
}) if ex.request.options[:headers]["Zuora-Track-Id"].present?
|
584
|
+
end
|
585
|
+
rescue => ex
|
586
|
+
Rails.logger.error("Failed to create exception arguments", ex, args)
|
587
|
+
ensure
|
588
|
+
return args
|
504
589
|
end
|
505
590
|
|
506
591
|
def raise_errors(type: :SOAP, body: nil, response: nil)
|
@@ -517,13 +602,13 @@ module ZuoraAPI
|
|
517
602
|
end
|
518
603
|
|
519
604
|
if [502,503].include?(response.code)
|
520
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from
|
605
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from 'https://#{rest_domain(endpoint: request_uri)}'", response)
|
521
606
|
end
|
522
607
|
|
523
608
|
# Check failure response code
|
524
609
|
case response.code
|
525
610
|
when 504
|
526
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from
|
611
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from 'https://#{rest_domain(endpoint: request_uri)}'", response)
|
527
612
|
when 429
|
528
613
|
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("The total number of concurrent requests has exceeded the limit allowed by the system. Please resubmit your request later.", response)
|
529
614
|
when 401
|
@@ -544,6 +629,10 @@ module ZuoraAPI
|
|
544
629
|
when :SOAP
|
545
630
|
error, success, message = get_soap_error_and_message(body)
|
546
631
|
|
632
|
+
if body.xpath('//fns:LoginFault', 'fns' =>'http://fault.api.zuora.com/').present?
|
633
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(message, response)
|
634
|
+
end
|
635
|
+
|
547
636
|
if body.xpath('//ns1:queryResponse', 'ns1' => 'http://api.zuora.com/').present? &&
|
548
637
|
body.xpath(
|
549
638
|
'//ns1:records[@xsi:type="ns2:Export"]',
|
@@ -551,12 +640,12 @@ module ZuoraAPI
|
|
551
640
|
).present?
|
552
641
|
result = body.xpath('//ns2:Status', 'ns2' => 'http://object.api.zuora.com/').text
|
553
642
|
if result == 'Failed'
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
643
|
+
message = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
|
644
|
+
error = 'FATAL_ERROR'
|
645
|
+
if message.present?
|
646
|
+
identifier, new_message = message.scan(/^([\w\d]{16})\: (.*)/).first
|
647
|
+
error, message = ['UNEXPECTED_ERROR', new_message] if new_message.present?
|
558
648
|
else
|
559
|
-
error = 'FATAL_ERROR'
|
560
649
|
message = 'Export failed due to unknown reason. Consult api logs.'
|
561
650
|
end
|
562
651
|
end
|
@@ -605,14 +694,31 @@ module ZuoraAPI
|
|
605
694
|
when /^GET::400::\/api\/rest\/v1\/reports\/(reportlabels\/)?([a-zA-Z0-9\-_]+)\/report-details$/ # Get report, capture of the id is present if needed in future error responses.
|
606
695
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(reporting_message, response) if reporting_message.present?
|
607
696
|
end
|
697
|
+
when /\/objects\/batch\//
|
698
|
+
if body['code'].present? && /61$/.match(body['code'].to_s).present? # if last 2 digits of code are 61
|
699
|
+
raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body['message'], nil, body['details'])
|
700
|
+
end
|
701
|
+
when /^\/api\/v1\/payment_plans.*/
|
702
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['error'], response) if body['error']
|
608
703
|
end
|
609
704
|
|
610
705
|
body = body.dig("results").present? ? body["results"] : body if body.class == Hash
|
611
706
|
if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
|
612
|
-
|
613
|
-
|
614
|
-
codes_array =
|
615
|
-
|
707
|
+
reason_keys = %w(reasons errors)
|
708
|
+
message_keys = %w(message title)
|
709
|
+
messages_array, codes_array = [[],[]]
|
710
|
+
reason_keys.each do |rsn_key|
|
711
|
+
message_keys.each do |msg_key|
|
712
|
+
messages_array = body.fetch(rsn_key, []).map {|error| error[msg_key]}.compact
|
713
|
+
break if messages_array.present?
|
714
|
+
end
|
715
|
+
codes_array = body.fetch(rsn_key, []).map {|error| error['code']}.compact
|
716
|
+
break if messages_array.present? && codes_array.present?
|
717
|
+
end
|
718
|
+
if body.dig('error').class == Hash
|
719
|
+
messages_array = messages_array.push(body.dig("error", 'message')).compact
|
720
|
+
codes_array = codes_array.push(body.dig("error", 'code')).compact
|
721
|
+
end
|
616
722
|
|
617
723
|
if body['message'] == 'request exceeded limit'
|
618
724
|
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)
|
@@ -644,7 +750,11 @@ module ZuoraAPI
|
|
644
750
|
end
|
645
751
|
|
646
752
|
if body['error'] == 'Unauthorized' && body['status'] == 401
|
647
|
-
|
753
|
+
if body['message'].present?
|
754
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(body['message'], response)
|
755
|
+
else
|
756
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
|
757
|
+
end
|
648
758
|
end
|
649
759
|
#Authentication failed
|
650
760
|
if (codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(11) || response.code == 401) && !codes_array.include?(422)
|
@@ -751,7 +861,7 @@ module ZuoraAPI
|
|
751
861
|
output_json = JSON.parse(response.body)
|
752
862
|
self.raise_errors(type: :JSON, body: output_json, response: response)
|
753
863
|
|
754
|
-
elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml')) and type != :xml
|
864
|
+
elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml') || response_content_types.include?('application/soap+xml')) and type != :xml
|
755
865
|
output_xml = Nokogiri::XML(response.body)
|
756
866
|
self.raise_errors(type: :SOAP, body: output_xml, response: response)
|
757
867
|
|
@@ -771,6 +881,8 @@ module ZuoraAPI
|
|
771
881
|
raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new(error_message, response)
|
772
882
|
when /Client sent a bad request./, /Bad Request/, /403 Forbidden/
|
773
883
|
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
|
884
|
+
when /414 Request-URI Too Large/
|
885
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Request URL is too long", response)
|
774
886
|
else
|
775
887
|
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
|
776
888
|
end
|
@@ -797,6 +909,11 @@ module ZuoraAPI
|
|
797
909
|
message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
|
798
910
|
end
|
799
911
|
|
912
|
+
if error.blank? || message.blank?
|
913
|
+
error = body.xpath('//soapenv:Value', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
|
914
|
+
message = body.xpath('//soapenv:Text', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
|
915
|
+
end
|
916
|
+
|
800
917
|
#Update/Create/Delete Calls with multiple requests and responses
|
801
918
|
if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
|
802
919
|
error = []
|
@@ -840,17 +957,22 @@ module ZuoraAPI
|
|
840
957
|
raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
|
841
958
|
end
|
842
959
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
843
|
-
when /invalid/, /^DUPLICATE_VALUE/, /^REQUEST_REJECTED/, /INVALID_ID/, /MAX_RECORDS_EXCEEDED/, /INVALID_FIELD/, /MALFORMED_QUERY/, /NO_PERMISSION/, /PDF_QUERY_ERROR/, /MISSING_REQUIRED_VALUE/, /INVALID_TYPE/, /TRANSACTION_FAILED/, /API_DISABLED/, /CANNOT_DELETE/, /ACCOUNTING_PERIOD_CLOSED/
|
960
|
+
when /^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/
|
844
961
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
845
962
|
when /.*UNEXPECTED_ERROR/
|
846
963
|
raise ZuoraAPI::Exceptions::ZuoraUnexpectedError.new(message, response, errors, success)
|
847
964
|
when /.*soapenv:Server.*/
|
848
965
|
if /^Invalid value.*for type.*|^Id is invalid|^date string can not be less than 19 charactors$/.match(message).present?
|
849
966
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
850
|
-
elsif /^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
|
967
|
+
elsif /^unknown$|^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
|
851
968
|
raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
|
852
969
|
end
|
853
970
|
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
|
971
|
+
when /soapenv:Receiver/
|
972
|
+
if /^com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character.*$/.match(message).present?
|
973
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
974
|
+
end
|
975
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
|
854
976
|
else
|
855
977
|
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Z:#{error}::#{message}", response, errors, success)
|
856
978
|
end
|
@@ -900,7 +1022,7 @@ module ZuoraAPI
|
|
900
1022
|
base = self.url.include?(".com") ? self.url.split(".com")[0].concat(".com") : self.url.split(".eu")[0].concat(".eu")
|
901
1023
|
url = object ? "#{base}/apps/api/describe/#{object}" : "#{base}/apps/api/describe/"
|
902
1024
|
headers = self.entity_id.present? ? {"Zuora-Entity-Ids" => self.entity_id, 'Content-Type' => "text/xml; charset=utf-8"} : {'Content-Type' => "text/xml; charset=utf-8"}
|
903
|
-
response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic)}.merge(headers), :timeout =>
|
1025
|
+
response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic)}.merge(headers), :timeout => 130)
|
904
1026
|
|
905
1027
|
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error.present? ? self.current_error : 'Describe call 401', response) if response.code == 401
|
906
1028
|
|
@@ -933,35 +1055,31 @@ module ZuoraAPI
|
|
933
1055
|
end
|
934
1056
|
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
|
935
1057
|
end
|
1058
|
+
|
1059
|
+
return des_hash
|
936
1060
|
rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
|
937
|
-
if tries.zero?
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
Rails.logger.error("Describe - #{ex.class} Timed out will retry after #{self.timeout_sleep} seconds")
|
943
|
-
end
|
944
|
-
end
|
945
|
-
raise ex
|
1061
|
+
if !tries.zero?
|
1062
|
+
tries -= 1
|
1063
|
+
self.log(location: "Describe", exception: ex, message: "Timed out will retry after #{self.timeout_sleep} seconds", level: :debug)
|
1064
|
+
sleep(self.timeout_sleep)
|
1065
|
+
retry
|
946
1066
|
end
|
947
1067
|
|
948
|
-
|
949
|
-
|
950
|
-
|
1068
|
+
self.log(location: "Describe", exception: ex, message: "Timed out", level: :error) if log_errors
|
1069
|
+
raise ex
|
1070
|
+
|
951
1071
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
952
1072
|
if !tries.zero? && self.status == 'Active'
|
953
1073
|
tries -= 1
|
954
1074
|
Rails.logger.debug("Describe session expired. Starting new session.")
|
955
1075
|
self.new_session
|
956
1076
|
retry
|
957
|
-
else
|
958
|
-
Rails.logger.error("Describe session expired. Starting new session.") if log_errors
|
959
|
-
raise ex
|
960
1077
|
end
|
1078
|
+
|
1079
|
+
Rails.logger.error("Describe session expired. Starting new session.") if log_errors
|
1080
|
+
raise ex
|
961
1081
|
rescue => ex
|
962
1082
|
raise ex
|
963
|
-
else
|
964
|
-
return des_hash
|
965
1083
|
end
|
966
1084
|
|
967
1085
|
def rest_call(
|
@@ -974,11 +1092,12 @@ module ZuoraAPI
|
|
974
1092
|
z_session: true,
|
975
1093
|
session_type: :basic,
|
976
1094
|
timeout_retry: false,
|
977
|
-
timeout:
|
1095
|
+
timeout: 130,
|
978
1096
|
timeout_sleep_interval: self.timeout_sleep,
|
979
1097
|
multipart: false,
|
980
1098
|
stream_body: false,
|
981
1099
|
output_exception_messages: true,
|
1100
|
+
zuora_track_id: nil,
|
982
1101
|
**keyword_args,
|
983
1102
|
&block
|
984
1103
|
)
|
@@ -988,12 +1107,13 @@ module ZuoraAPI
|
|
988
1107
|
|
989
1108
|
authentication_headers = {}
|
990
1109
|
if z_session
|
991
|
-
authentication_headers = {"Authorization" => self.get_session(prefix: true, auth_type: session_type) }
|
1110
|
+
authentication_headers = {"Authorization" => self.get_session(prefix: true, auth_type: session_type, zuora_track_id: zuora_track_id) }
|
992
1111
|
if self.entity_id.present?
|
993
1112
|
authentication_headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
|
994
1113
|
authentication_headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
|
995
1114
|
end
|
996
1115
|
end
|
1116
|
+
headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
|
997
1117
|
|
998
1118
|
modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
|
999
1119
|
|
@@ -1019,18 +1139,20 @@ module ZuoraAPI
|
|
1019
1139
|
Rails.logger.debug("Response JSON: #{output_json}") if debug && output_json.present?
|
1020
1140
|
|
1021
1141
|
raise_errors(type: :JSON, body: output_json, response: response)
|
1022
|
-
rescue
|
1142
|
+
rescue => ex
|
1023
1143
|
reset_files(body) if multipart
|
1024
1144
|
raise
|
1025
1145
|
end
|
1146
|
+
|
1147
|
+
return [output_json, response]
|
1026
1148
|
rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
|
1027
1149
|
if self.class.to_s == 'ZuoraAPI::Oauth' && ex.message.include?("Authentication type is not supported by this Login")
|
1028
1150
|
session_type = :bearer
|
1029
1151
|
retry
|
1030
|
-
else
|
1031
|
-
Rails.logger.debug("Rest Call - Session Bad Auth type")
|
1032
|
-
raise ex
|
1033
1152
|
end
|
1153
|
+
Rails.logger.debug("Rest Call - Session Bad Auth type")
|
1154
|
+
raise ex
|
1155
|
+
|
1034
1156
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
1035
1157
|
if !tries.zero? && z_session
|
1036
1158
|
tries -= 1
|
@@ -1043,40 +1165,35 @@ module ZuoraAPI
|
|
1043
1165
|
end
|
1044
1166
|
|
1045
1167
|
retry
|
1046
|
-
else
|
1047
|
-
if errors.include?(ex.class)
|
1048
|
-
raise ex
|
1049
|
-
else
|
1050
|
-
return [output_json, response]
|
1051
|
-
end
|
1052
1168
|
end
|
1169
|
+
|
1170
|
+
raise ex if errors.include?(ex.class)
|
1171
|
+
return [output_json, response]
|
1172
|
+
|
1053
1173
|
rescue *ZUORA_API_ERRORS => ex
|
1054
|
-
if errors.include?(ex.class)
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
end
|
1174
|
+
raise ex if errors.include?(ex.class)
|
1175
|
+
|
1176
|
+
response = ex.response unless response
|
1177
|
+
return [output_json, response]
|
1178
|
+
|
1060
1179
|
rescue ZuoraAPI::Exceptions::BadEntityError => ex
|
1061
1180
|
raise ex
|
1062
1181
|
rescue *CONNECTION_EXCEPTIONS => ex
|
1063
|
-
if tries.zero?
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
Rails.logger.error("Rest Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
|
1069
|
-
end
|
1070
|
-
end
|
1071
|
-
raise ex
|
1182
|
+
if !tries.zero?
|
1183
|
+
tries -= 1
|
1184
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
1185
|
+
sleep(timeout_sleep_interval)
|
1186
|
+
retry
|
1072
1187
|
end
|
1073
1188
|
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1189
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
1190
|
+
raise ex
|
1191
|
+
|
1077
1192
|
rescue *CONNECTION_READ_EXCEPTIONS => ex
|
1193
|
+
|
1078
1194
|
if !tries.zero?
|
1079
1195
|
tries -= 1
|
1196
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
1080
1197
|
if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
|
1081
1198
|
retry
|
1082
1199
|
elsif timeout_retry
|
@@ -1084,20 +1201,15 @@ module ZuoraAPI
|
|
1084
1201
|
retry
|
1085
1202
|
end
|
1086
1203
|
end
|
1087
|
-
|
1088
|
-
if output_exception_messages
|
1089
|
-
|
1090
|
-
Rails.logger.error("Rest Call - Timed out will retry after #{timeout_sleep_interval} seconds", ex)
|
1091
|
-
else
|
1092
|
-
Rails.logger.error("Rest Call - #{ex.class} Timed out will retry after #{timeout_sleep_interval} seconds")
|
1093
|
-
end
|
1094
|
-
end
|
1095
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from #{url}", nil, request) if ex.instance_of?(Net::ReadTimeout)
|
1204
|
+
|
1205
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
1206
|
+
ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.instance_of?(Net::ReadTimeout)
|
1096
1207
|
raise ex
|
1208
|
+
|
1097
1209
|
rescue => ex
|
1098
1210
|
raise ex
|
1099
|
-
|
1100
|
-
|
1211
|
+
ensure
|
1212
|
+
self.error_logger(ex) if defined?(ex) && Rails.logger.class.to_s == "Ougai::Logger"
|
1101
1213
|
end
|
1102
1214
|
|
1103
1215
|
def update_create_tenant
|
@@ -1119,8 +1231,9 @@ module ZuoraAPI
|
|
1119
1231
|
while !response["nextPage"].blank?
|
1120
1232
|
url = self.rest_endpoint(response["nextPage"].split('/v1/').last)
|
1121
1233
|
Rails.logger.debug("Fetch Catalog URL #{url}")
|
1122
|
-
output_json, response = self.rest_call(:
|
1123
|
-
|
1234
|
+
output_json, response = self.rest_call(debug: false, url: url, timeout_retry: true)
|
1235
|
+
|
1236
|
+
if !/(true|t|yes|y|1)$/.match(output_json['success'].to_s) || output_json['success'].class != TrueClass
|
1124
1237
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}", response)
|
1125
1238
|
end
|
1126
1239
|
output_json["products"].each do |product|
|
@@ -1146,7 +1259,7 @@ module ZuoraAPI
|
|
1146
1259
|
return products, catalog_map
|
1147
1260
|
end
|
1148
1261
|
|
1149
|
-
def get_file(url: nil, headers: {}, z_session: true, tempfile: true, output_file_name: nil, zuora_track_id: nil, add_timestamp: true, file_path: defined?(Rails.root.join('tmp')) ? Rails.root.join('tmp') : Pathname.new(Dir.pwd), timeout_retries: 3, timeout:
|
1262
|
+
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)
|
1150
1263
|
raise "file_path must be of class Pathname" if file_path.class != Pathname
|
1151
1264
|
|
1152
1265
|
retry_count ||= timeout_retries
|
@@ -1251,14 +1364,20 @@ module ZuoraAPI
|
|
1251
1364
|
return file_handle
|
1252
1365
|
when Net::HTTPUnauthorized
|
1253
1366
|
if z_session
|
1254
|
-
|
1367
|
+
unless (retry_count -= 1).zero?
|
1255
1368
|
self.new_session
|
1256
|
-
raise
|
1257
|
-
else
|
1258
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
|
1369
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError, 'Retrying'
|
1259
1370
|
end
|
1371
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
|
1372
|
+
end
|
1373
|
+
raise
|
1374
|
+
when Net::HTTPNotFound
|
1375
|
+
if url.include?(self.fileURL)
|
1376
|
+
raise ZuoraAPI::Exceptions::FileDownloadError.new(
|
1377
|
+
"The current tenant does not have a file with id '#{url.split('/').last}'"
|
1378
|
+
)
|
1260
1379
|
else
|
1261
|
-
raise
|
1380
|
+
raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
|
1262
1381
|
end
|
1263
1382
|
else
|
1264
1383
|
raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
|
@@ -1269,133 +1388,120 @@ module ZuoraAPI
|
|
1269
1388
|
sleep(5)
|
1270
1389
|
if (retry_count -= 1) >= 0
|
1271
1390
|
retry
|
1272
|
-
else
|
1273
|
-
Rails.logger.error("File Download Failed")
|
1274
|
-
raise
|
1275
1391
|
end
|
1392
|
+
Rails.logger.error("File Download Failed")
|
1393
|
+
raise
|
1276
1394
|
end
|
1277
1395
|
|
1278
1396
|
def getDataSourceExport(query, extract: true, encrypted: false, zip: true, z_track_id: "")
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
xml['SOAP-ENV'].
|
1283
|
-
xml['
|
1284
|
-
xml['ns1'].
|
1285
|
-
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
|
1286
|
-
end
|
1397
|
+
tries ||= 3
|
1398
|
+
request = Nokogiri::XML::Builder.new do |xml|
|
1399
|
+
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
|
1400
|
+
xml['SOAP-ENV'].Header do
|
1401
|
+
xml['ns1'].SessionHeader do
|
1402
|
+
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
|
1287
1403
|
end
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1404
|
+
end
|
1405
|
+
xml['SOAP-ENV'].Body do
|
1406
|
+
xml['ns1'].create do
|
1407
|
+
xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
|
1408
|
+
xml['ns2'].Format 'csv'
|
1409
|
+
xml['ns2'].Zip zip
|
1410
|
+
xml['ns2'].Name 'googman'
|
1411
|
+
xml['ns2'].Query query
|
1412
|
+
xml['ns2'].Encrypted encrypted
|
1297
1413
|
end
|
1298
1414
|
end
|
1299
1415
|
end
|
1300
1416
|
end
|
1417
|
+
end
|
1301
1418
|
|
1302
|
-
|
1419
|
+
response_query = HTTParty.post(self.url, body: request.to_xml(:save_with => XML_SAVE_OPTIONS).strip, headers: {'Content-Type' => "application/json; charset=utf-8", "Z-Track-Id" => z_track_id}, :timeout => 130)
|
1303
1420
|
|
1304
|
-
|
1305
|
-
|
1421
|
+
output_xml = Nokogiri::XML(response_query.body)
|
1422
|
+
raise_errors(type: :SOAP, body: output_xml, response: response_query) if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
|
1306
1423
|
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1424
|
+
# raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
|
1425
|
+
|
1426
|
+
id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
|
1310
1427
|
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
end
|
1428
|
+
confirmRequest = Nokogiri::XML::Builder.new do |xml|
|
1429
|
+
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
|
1430
|
+
xml['SOAP-ENV'].Header do
|
1431
|
+
xml['ns1'].SessionHeader do
|
1432
|
+
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
|
1317
1433
|
end
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1434
|
+
end
|
1435
|
+
xml['SOAP-ENV'].Body do
|
1436
|
+
xml['ns1'].query do
|
1437
|
+
xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
|
1322
1438
|
end
|
1323
1439
|
end
|
1324
1440
|
end
|
1325
|
-
|
1326
|
-
|
1327
|
-
while result != "Completed"
|
1328
|
-
sleep 3
|
1329
|
-
response_query = HTTParty.post(self.url, body: confirmRequest.to_xml(:save_with => XML_SAVE_OPTIONS).strip, headers: {'Content-Type' => "application/json; charset=utf-8", "Z-Track-Id" => z_track_id}, :timeout => 120)
|
1441
|
+
end
|
1442
|
+
result = 'Waiting'
|
1330
1443
|
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1444
|
+
while result != "Completed"
|
1445
|
+
sleep 3
|
1446
|
+
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 => 130)
|
1334
1447
|
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
|
1340
|
-
export_file = get_file(:url => self.fileURL(file_id))
|
1341
|
-
export_file_path = export_file.path
|
1342
|
-
Rails.logger.debug("=====> Export path #{export_file.path}")
|
1343
|
-
|
1344
|
-
if extract && zip
|
1345
|
-
require "zip"
|
1346
|
-
new_path = export_file_path.partition('.zip').first
|
1347
|
-
zipped = Zip::File.open(export_file_path)
|
1348
|
-
file_handle = zipped.entries.first
|
1349
|
-
file_handle.extract(new_path)
|
1350
|
-
File.delete(export_file_path)
|
1351
|
-
return new_path
|
1352
|
-
else
|
1353
|
-
return export_file_path
|
1354
|
-
end
|
1355
|
-
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
1356
|
-
if !(tries -= 1).zero?
|
1357
|
-
Rails.logger.info("Export call failed - Trace ID: #{z_track_id}")
|
1358
|
-
self.new_session
|
1359
|
-
retry
|
1360
|
-
else
|
1361
|
-
raise ex
|
1362
|
-
end
|
1448
|
+
output_xml = Nokogiri::XML(response_query.body)
|
1449
|
+
result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
|
1450
|
+
status_code = response_query.code if response_query
|
1363
1451
|
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
sleep 10
|
1368
|
-
retry
|
1369
|
-
else
|
1370
|
-
raise ex
|
1371
|
-
end
|
1452
|
+
raise_errors(type: :SOAP, body: output_xml, response: response_query) if result.blank? || result == "Failed"
|
1453
|
+
# raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if result.blank? || result == "Failed"
|
1454
|
+
end
|
1372
1455
|
|
1373
|
-
|
1374
|
-
|
1456
|
+
file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
|
1457
|
+
export_file = get_file(:url => self.fileURL(file_id))
|
1458
|
+
export_file_path = export_file.path
|
1459
|
+
Rails.logger.debug("=====> Export path #{export_file.path}")
|
1460
|
+
|
1461
|
+
if extract && zip
|
1462
|
+
require "zip"
|
1463
|
+
new_path = export_file_path.partition('.zip').first
|
1464
|
+
zipped = Zip::File.open(export_file_path)
|
1465
|
+
file_handle = zipped.entries.first
|
1466
|
+
file_handle.extract(new_path)
|
1467
|
+
File.delete(export_file_path)
|
1468
|
+
return new_path
|
1469
|
+
else
|
1470
|
+
return export_file_path
|
1471
|
+
end
|
1472
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
1473
|
+
if !(tries -= 1).zero?
|
1474
|
+
Rails.logger.info("Export call failed - Trace ID: #{z_track_id}")
|
1475
|
+
self.new_session
|
1476
|
+
retry
|
1477
|
+
end
|
1478
|
+
raise ex
|
1375
1479
|
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
end
|
1480
|
+
rescue ZuoraAPI::Exceptions::ZuoraUnexpectedError => ex
|
1481
|
+
if !(tries -= 1).zero?
|
1482
|
+
Rails.logger.info("Trace ID: #{z_track_id} UnexpectedError, will retry after 10 seconds")
|
1483
|
+
sleep 10
|
1484
|
+
retry
|
1485
|
+
end
|
1486
|
+
raise ex
|
1384
1487
|
|
1385
|
-
|
1386
|
-
|
1387
|
-
retry
|
1388
|
-
else
|
1389
|
-
raise ex
|
1390
|
-
end
|
1488
|
+
rescue *ZUORA_API_ERRORS => ex
|
1489
|
+
raise ex
|
1391
1490
|
|
1392
|
-
|
1393
|
-
|
1491
|
+
rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
|
1492
|
+
if !(tries -= 1).zero?
|
1493
|
+
Rails.logger.info("Trace ID: #{z_track_id} Timed out will retry after 5 seconds")
|
1494
|
+
sleep 5
|
1495
|
+
retry
|
1394
1496
|
end
|
1497
|
+
raise ex
|
1498
|
+
|
1499
|
+
rescue ZuoraAPI::Exceptions::BadEntityError => ex
|
1500
|
+
raise ex
|
1395
1501
|
end
|
1396
1502
|
|
1397
1503
|
def query(query, parse = false)
|
1398
|
-
output_xml, input_xml = self.soap_call(
|
1504
|
+
output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true) do |xml|
|
1399
1505
|
xml['ns1'].query do
|
1400
1506
|
xml['ns1'].queryString query
|
1401
1507
|
end
|