zuora_api 1.7.06 → 1.7.7f
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 -0
- data/.gitlab-ci.yml +19 -19
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -1
- data/README.md +1 -1
- data/gemfiles/Gemfile-rails.5.0.x +5 -0
- data/gemfiles/Gemfile-rails.5.1.x +5 -0
- data/gemfiles/Gemfile-rails.5.2.x +5 -0
- data/gemfiles/Gemfile-rails.6.0.x +5 -0
- data/lib/insights_api/login.rb +5 -6
- data/lib/zuora_api/exceptions.rb +118 -29
- data/lib/zuora_api/login.rb +720 -339
- data/lib/zuora_api/logins/basic.rb +11 -93
- data/lib/zuora_api/logins/oauth.rb +38 -76
- data/lib/zuora_api/version.rb +1 -1
- data/zuora_api.gemspec +4 -3
- metadata +30 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 804cba875550e86bca336f5ca155978460b16957f9269f3d677f0e823027d87c
|
|
4
|
+
data.tar.gz: 62f53c8d42f6dfbf8de530d5bf9a90f941eb8369c82fde27afff841445ca8c23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9cc2ccd120c5bc4ca6af6b1048239d49c6b4a714c604d996ec08e83a25a2769e357b49c70c43f3c8c6bfdf631dcb7de7ff249b137e197746c5cdda4f6ef7d188
|
|
7
|
+
data.tar.gz: 42761bc535e218762be87e37d32b5a0fcd43ecd7230a9b88b83d0749b0764b675355398aee3ecf10ac7bcdcdf0e2bf9e01bb602e59d65d63c8028840462274e1
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
CHANGED
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
image: ruby:2.
|
|
1
|
+
image: ruby:2.7
|
|
2
2
|
stages:
|
|
3
|
-
- setup
|
|
4
3
|
- test
|
|
5
4
|
- deploy
|
|
6
5
|
|
|
7
|
-
setup:
|
|
8
|
-
stage: setup
|
|
9
|
-
allow_failure: true
|
|
10
|
-
cache:
|
|
11
|
-
key: gems
|
|
12
|
-
paths:
|
|
13
|
-
- vendor/bundle
|
|
14
|
-
script:
|
|
15
|
-
- apt-get update -qy
|
|
16
|
-
- apt-get install -y nodejs
|
|
17
|
-
- bundle install
|
|
18
|
-
|
|
19
6
|
rubocop-testing:
|
|
20
7
|
stage: test
|
|
21
8
|
allow_failure: true
|
|
@@ -30,11 +17,24 @@ security-testing:
|
|
|
30
17
|
- gem install brakeman
|
|
31
18
|
- brakeman
|
|
32
19
|
|
|
33
|
-
|
|
20
|
+
ruby:test:
|
|
34
21
|
stage: test
|
|
22
|
+
cache:
|
|
23
|
+
key: ruby:$RUBY_VERSION-rails:$RAILS_VERSION
|
|
24
|
+
paths:
|
|
25
|
+
- vendor/ruby
|
|
26
|
+
parallel:
|
|
27
|
+
matrix:
|
|
28
|
+
- RUBY_VERSION: "2.7"
|
|
29
|
+
RAILS_VERSION: ["5.0", "5.1", "5.2", "6.0"]
|
|
30
|
+
before_script:
|
|
31
|
+
- bundle config set path 'vendor/ruby'
|
|
32
|
+
- bundle config --global gemfile "gemfiles/Gemfile-rails.$RAILS_VERSION.x"
|
|
33
|
+
- bundle install
|
|
35
34
|
script:
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- bundle exec rails -v
|
|
36
|
+
- bundle exec rspec
|
|
37
|
+
coverage: '/\(\d+.\d+\%\) covered/'
|
|
38
38
|
|
|
39
39
|
rubygems-deploy:
|
|
40
40
|
stage: deploy
|
|
@@ -48,8 +48,8 @@ rubygems-deploy:
|
|
|
48
48
|
- if [[ "staging" == $CI_BUILD_REF_SLUG ]];then export VERSION=`git describe --match "[0-9]*\.[0-9]*\.[0-9]*[a-z]" --abbrev=0 --tags HEAD`; fi
|
|
49
49
|
- if [[ "master" == $CI_BUILD_REF_SLUG ]];then export VERSION=`git describe --exclude "[0-9]*\.[0-9]*\.[0-9]*[a-z]" --abbrev=0 --tags HEAD`; fi
|
|
50
50
|
- echo $VERSION
|
|
51
|
-
- sed -i "s/0.0.1/$VERSION/"
|
|
52
|
-
- git add
|
|
51
|
+
- sed -i "s/0.0.1/$VERSION/" lib/zuora_api/version.rb
|
|
52
|
+
- git add lib/zuora_api/version.rb
|
|
53
53
|
- git config --global user.email "connect@zuora.com"
|
|
54
54
|
- git config --global user.name "Connect Automation"
|
|
55
55
|
- git commit -m "Automated Version Update $VERSION"
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
|
3
3
|
|
|
4
|
+
## [1.7.07] - 2018-9-10
|
|
5
|
+
### Changed
|
|
6
|
+
- Cookie name for service endpoint integration
|
|
7
|
+
|
|
8
|
+
## [1.7.06] - 2018-8-25
|
|
9
|
+
### Added
|
|
10
|
+
- Retry for 502/503
|
|
11
|
+
- Standard exception for 504
|
|
12
|
+
|
|
13
|
+
## [1.7.05] - 2018-8-25
|
|
14
|
+
### Added
|
|
15
|
+
- Added support for oauth token forbidden
|
|
16
|
+
|
|
17
|
+
## [1.7.02] - 2018-8-23
|
|
18
|
+
### Added
|
|
19
|
+
- Added mulit part support for rest call
|
|
20
|
+
|
|
4
21
|
## [1.7.01] - 2018-8-06
|
|
5
22
|
### Changed
|
|
6
23
|
- Changed library used to determine host
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Zuora Gem
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/zuora_api) [](https://badge.fury.io/rb/zuora_api) [](https://gitlab.0.ecc.auw2.zuora/extension-products/shared-libraries/zuora-gem/commits/master)
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
Add this line to your application's Gemfile:
|
data/lib/insights_api/login.rb
CHANGED
|
@@ -190,12 +190,11 @@ module InsightsAPI
|
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
rescue => ex
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
end
|
|
193
|
+
raise ex if tries.zero?
|
|
194
|
+
|
|
195
|
+
tries -= 1
|
|
196
|
+
sleep 3
|
|
197
|
+
retry
|
|
199
198
|
else
|
|
200
199
|
return temp_file
|
|
201
200
|
end
|
data/lib/zuora_api/exceptions.rb
CHANGED
|
@@ -1,14 +1,38 @@
|
|
|
1
1
|
module ZuoraAPI
|
|
2
2
|
module Exceptions
|
|
3
|
-
class Error < StandardError;
|
|
3
|
+
class Error < StandardError;
|
|
4
|
+
def parse_message(message)
|
|
5
|
+
case message
|
|
6
|
+
when /^Payment status should be Processed. Invalid payment is P-\d*./
|
|
7
|
+
@message = "Payment status should be Processed."
|
|
8
|
+
when /^Adjustment cannot be created for invoice(.*) with a zero balance./
|
|
9
|
+
@message = "Adjustment cannot be created for invoice with a zero balance."
|
|
10
|
+
when /^The balance of all the invoice items and tax items is 0. No write-off is needed for the invoice .*./
|
|
11
|
+
@message = "The balance of all the invoice items and tax items is 0. No write-off is needed for the invoice."
|
|
12
|
+
when /^Json input does not match schema. Error(s): string ".*" is too long .*/
|
|
13
|
+
@message = "Json input does not match schema. Error(s): String is too long."
|
|
14
|
+
when /^Query failed \(#[\d\w_]*\): line [0-9]+:[0-9]+: (.*)$/
|
|
15
|
+
@message = "Query failed: #{$1}"
|
|
16
|
+
when /^Query failed \(#[\d\w_]*\): (.*)$/
|
|
17
|
+
@message = "Query failed: #{$1}"
|
|
18
|
+
when /^Could not find [\w\d]{32}.$/
|
|
19
|
+
@message = "Could not find object."
|
|
20
|
+
when /^Subscription [\w\d]{32} is in expired status. It is not supported to generate billing documents for expired subscriptions./
|
|
21
|
+
@message = "Subscription is in expired status. It is not supported to generate billing documents for expired subscriptions."
|
|
22
|
+
else
|
|
23
|
+
@message = message
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
class FileDownloadError < StandardError; end
|
|
4
28
|
class AuthorizationNotPerformed < Error; end
|
|
5
29
|
class ZuoraAPISessionError < Error
|
|
6
30
|
attr_reader :code, :response
|
|
7
31
|
attr_writer :default_message
|
|
8
32
|
|
|
9
|
-
def initialize(message = nil,response=nil)
|
|
10
|
-
@code = response.
|
|
11
|
-
@message = message
|
|
33
|
+
def initialize(message = nil,response=nil, errors = [], successes = [])
|
|
34
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
35
|
+
@message = parse_message(message)
|
|
12
36
|
@response = response
|
|
13
37
|
@default_message = "Error with Zuora Session."
|
|
14
38
|
end
|
|
@@ -16,6 +40,19 @@ module ZuoraAPI
|
|
|
16
40
|
def to_s
|
|
17
41
|
@message || @default_message
|
|
18
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
|
|
19
56
|
end
|
|
20
57
|
|
|
21
58
|
class BadEntityError < Error
|
|
@@ -23,8 +60,8 @@ module ZuoraAPI
|
|
|
23
60
|
attr_writer :default_message
|
|
24
61
|
|
|
25
62
|
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
26
|
-
@code = response.
|
|
27
|
-
@message = message
|
|
63
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
64
|
+
@message = parse_message(message)
|
|
28
65
|
@response = response
|
|
29
66
|
@default_message = "Error with Zuora Entity"
|
|
30
67
|
@errors = errors
|
|
@@ -41,8 +78,8 @@ module ZuoraAPI
|
|
|
41
78
|
attr_writer :default_message
|
|
42
79
|
|
|
43
80
|
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
44
|
-
@code = response.
|
|
45
|
-
@message = message
|
|
81
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
82
|
+
@message = parse_message(message)
|
|
46
83
|
@response = response
|
|
47
84
|
@default_message = "Error communicating with Zuora."
|
|
48
85
|
@errors = errors
|
|
@@ -54,13 +91,31 @@ module ZuoraAPI
|
|
|
54
91
|
end
|
|
55
92
|
end
|
|
56
93
|
|
|
94
|
+
class ZuoraAPIInternalServerError < Error
|
|
95
|
+
attr_reader :code, :response, :errors, :successes
|
|
96
|
+
attr_writer :default_message
|
|
97
|
+
|
|
98
|
+
def initialize(message = nil,response = nil, errors = [], successes = [], *args)
|
|
99
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
100
|
+
@message = parse_message(message)
|
|
101
|
+
@response = response
|
|
102
|
+
@default_message = "Zuora Internal Server Error."
|
|
103
|
+
@errors = errors
|
|
104
|
+
@successes = successes
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_s
|
|
108
|
+
@message || @default_message
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
57
112
|
class ZuoraAPIRequestLimit < Error
|
|
58
113
|
attr_reader :code, :response
|
|
59
114
|
attr_writer :default_message
|
|
60
115
|
|
|
61
|
-
def initialize(message = nil,response=nil,
|
|
62
|
-
@code = response.
|
|
63
|
-
@message = message
|
|
116
|
+
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
117
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
118
|
+
@message = parse_message(message)
|
|
64
119
|
@response = response
|
|
65
120
|
@default_message = "Your request limit has been exceeded for zuora."
|
|
66
121
|
end
|
|
@@ -70,13 +125,29 @@ module ZuoraAPI
|
|
|
70
125
|
end
|
|
71
126
|
end
|
|
72
127
|
|
|
128
|
+
class ZuoraAPIUnkownError < Error
|
|
129
|
+
attr_reader :code, :response
|
|
130
|
+
attr_writer :default_message
|
|
131
|
+
|
|
132
|
+
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
133
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
134
|
+
@message = parse_message(message)
|
|
135
|
+
@response = response
|
|
136
|
+
@default_message = "An unkown error occured. Workflow is not responsible. Please contact Support."
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def to_s
|
|
140
|
+
@message || @default_message
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
73
144
|
class ZuoraAPILockCompetition < Error
|
|
74
145
|
attr_reader :code, :response
|
|
75
146
|
attr_writer :default_message
|
|
76
147
|
|
|
77
|
-
def initialize(message = nil,response=nil,
|
|
78
|
-
@code = response.
|
|
79
|
-
@message = message
|
|
148
|
+
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
149
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
150
|
+
@message = parse_message(message)
|
|
80
151
|
@response = response
|
|
81
152
|
@default_message = "Operation failed due to lock competition. Please retry"
|
|
82
153
|
end
|
|
@@ -90,9 +161,9 @@ module ZuoraAPI
|
|
|
90
161
|
attr_reader :code, :response
|
|
91
162
|
attr_writer :default_message
|
|
92
163
|
|
|
93
|
-
def initialize(message = nil,response=nil,
|
|
94
|
-
@code = response.
|
|
95
|
-
@message = message
|
|
164
|
+
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
165
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
166
|
+
@message = parse_message(message)
|
|
96
167
|
@response = response
|
|
97
168
|
@default_message = "Operation failed due to lock competition. Please retry"
|
|
98
169
|
end
|
|
@@ -102,15 +173,32 @@ module ZuoraAPI
|
|
|
102
173
|
end
|
|
103
174
|
end
|
|
104
175
|
|
|
105
|
-
class
|
|
176
|
+
class ZuoraUnexpectedError < Error
|
|
106
177
|
attr_reader :code, :response
|
|
107
178
|
attr_writer :default_message
|
|
108
179
|
|
|
109
|
-
def initialize(message = nil,response=nil, *args)
|
|
110
|
-
@code = response.
|
|
111
|
-
@message = message
|
|
180
|
+
def initialize(message = nil, response=nil, errors = [], successes = [], *args)
|
|
181
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
182
|
+
@message = parse_message(message)
|
|
183
|
+
@response = response
|
|
184
|
+
@default_message = "An unexpected error occurred"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def to_s
|
|
188
|
+
@message || @default_message
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
class ZuoraAPITemporaryError < Error
|
|
193
|
+
attr_reader :code, :response, :errors
|
|
194
|
+
attr_writer :default_message
|
|
195
|
+
|
|
196
|
+
def initialize(message = nil, response = nil, errors = [], successes = [], *args)
|
|
197
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
198
|
+
@message = parse_message(message)
|
|
112
199
|
@response = response
|
|
113
200
|
@default_message = "There is a temporary error with zuora system."
|
|
201
|
+
@errors = errors
|
|
114
202
|
end
|
|
115
203
|
|
|
116
204
|
def to_s
|
|
@@ -122,9 +210,9 @@ module ZuoraAPI
|
|
|
122
210
|
attr_reader :code, :response
|
|
123
211
|
attr_writer :default_message
|
|
124
212
|
|
|
125
|
-
def initialize(message = nil,response=nil,
|
|
126
|
-
@code = response.
|
|
127
|
-
@message = message
|
|
213
|
+
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
214
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
215
|
+
@message = parse_message(message)
|
|
128
216
|
@response = response
|
|
129
217
|
@default_message = "Authentication type is not supported by this Login"
|
|
130
218
|
end
|
|
@@ -138,8 +226,8 @@ module ZuoraAPI
|
|
|
138
226
|
attr_reader :code, :response
|
|
139
227
|
attr_writer :default_message
|
|
140
228
|
|
|
141
|
-
def initialize(message = nil,response=nil,
|
|
142
|
-
@code = response.
|
|
229
|
+
def initialize(message = nil,response=nil, errors = [], successes = [], *args)
|
|
230
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
143
231
|
@message = message
|
|
144
232
|
@response = response
|
|
145
233
|
@default_message = "Authentication type is not supported by this Login"
|
|
@@ -151,13 +239,14 @@ module ZuoraAPI
|
|
|
151
239
|
end
|
|
152
240
|
|
|
153
241
|
class ZuoraAPIReadTimeout < Net::ReadTimeout
|
|
154
|
-
attr_reader :code, :response
|
|
242
|
+
attr_reader :code, :response, :request
|
|
155
243
|
attr_writer :default_message
|
|
156
244
|
|
|
157
|
-
def initialize(message = nil,response=nil,
|
|
158
|
-
@code = response.
|
|
245
|
+
def initialize(message = nil, response = nil, request = nil, errors = [], successes = [], *args)
|
|
246
|
+
@code = response.class.to_s == "HTTParty::Response" ? response.code : nil
|
|
159
247
|
@message = message
|
|
160
248
|
@response = response
|
|
249
|
+
@request = request
|
|
161
250
|
@default_message = "Authentication type is not supported by this Login"
|
|
162
251
|
end
|
|
163
252
|
|
data/lib/zuora_api/login.rb
CHANGED
|
@@ -5,9 +5,9 @@ require 'zuora_api/exceptions'
|
|
|
5
5
|
|
|
6
6
|
module ZuoraAPI
|
|
7
7
|
class Login
|
|
8
|
-
ENVIRONMENTS = [SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
|
|
8
|
+
ENVIRONMENTS = [TEST = 'Test', SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
|
|
9
9
|
REGIONS = [EU = 'EU', US = 'US', NA = 'NA' ]
|
|
10
|
-
|
|
10
|
+
MIN_Endpoints = {'Test': '105.0', 'Sandbox': '105.0', 'Production': '105.0', 'Performance': '105.0', 'Services': '96.0', 'Unknown': '96.0', 'Staging': '105.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 = [
|
|
@@ -16,7 +16,8 @@ module ZuoraAPI
|
|
|
16
16
|
Errno::ECONNREFUSED,
|
|
17
17
|
SocketError,
|
|
18
18
|
Errno::EHOSTUNREACH,
|
|
19
|
-
Errno::EADDRNOTAVAIL
|
|
19
|
+
Errno::EADDRNOTAVAIL,
|
|
20
|
+
Errno::ETIMEDOUT,
|
|
20
21
|
].freeze
|
|
21
22
|
|
|
22
23
|
CONNECTION_READ_EXCEPTIONS = [
|
|
@@ -30,7 +31,17 @@ module ZuoraAPI
|
|
|
30
31
|
ZuoraAPI::Exceptions::ZuoraAPIRequestLimit,
|
|
31
32
|
ZuoraAPI::Exceptions::ZuoraAPILockCompetition,
|
|
32
33
|
ZuoraAPI::Exceptions::ZuoraAPITemporaryError,
|
|
33
|
-
ZuoraAPI::Exceptions::ZuoraDataIntegrity
|
|
34
|
+
ZuoraAPI::Exceptions::ZuoraDataIntegrity,
|
|
35
|
+
ZuoraAPI::Exceptions::ZuoraAPIInternalServerError,
|
|
36
|
+
ZuoraAPI::Exceptions::ZuoraUnexpectedError,
|
|
37
|
+
ZuoraAPI::Exceptions::ZuoraAPIUnkownError
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
ZUORA_SERVER_ERRORS = [
|
|
41
|
+
ZuoraAPI::Exceptions::ZuoraAPIInternalServerError,
|
|
42
|
+
ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout,
|
|
43
|
+
ZuoraAPI::Exceptions::ZuoraAPIReadTimeout,
|
|
44
|
+
ZuoraAPI::Exceptions::ZuoraUnexpectedError
|
|
34
45
|
].freeze
|
|
35
46
|
|
|
36
47
|
attr_accessor :region, :url, :wsdl_number, :current_session, :bearer_token, :oauth_session_expires_at, :environment, :status, :errors, :current_error, :user_info, :tenant_id, :tenant_name, :entity_id, :timeout_sleep, :hostname, :zconnect_provider
|
|
@@ -39,10 +50,12 @@ module ZuoraAPI
|
|
|
39
50
|
raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
|
|
40
51
|
# raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
|
|
41
52
|
self.hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
self.url =
|
|
53
|
+
self.update_environment
|
|
54
|
+
min_endpoint = MIN_Endpoints[self.environment.to_sym]
|
|
55
|
+
if !/apps\/services\/a\/\d+\.\d$/.match(url.strip)
|
|
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)
|
|
46
59
|
else
|
|
47
60
|
self.url = url
|
|
48
61
|
end
|
|
@@ -54,34 +67,18 @@ module ZuoraAPI
|
|
|
54
67
|
self.status = status.blank? ? "Active" : status
|
|
55
68
|
self.user_info = Hash.new
|
|
56
69
|
self.update_region
|
|
57
|
-
self.update_environment
|
|
58
70
|
self.update_zconnect_provider
|
|
59
71
|
@timeout_sleep = 5
|
|
60
72
|
end
|
|
61
73
|
|
|
62
74
|
def get_identity(cookies)
|
|
63
75
|
zsession = cookies["ZSession"]
|
|
64
|
-
zconnect_accesstoken = get_zconnect_accesstoken(cookies)
|
|
65
76
|
begin
|
|
66
77
|
if !zsession.blank?
|
|
67
78
|
response = HTTParty.get("https://#{self.hostname}/apps/v1/identity", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
|
|
68
79
|
output_json = JSON.parse(response.body)
|
|
69
|
-
elsif zconnect_accesstoken.present?
|
|
70
|
-
begin
|
|
71
|
-
code = zconnect_accesstoken.split("#!").last
|
|
72
|
-
encrypted_token, tenant_id = Base64.decode64(code).split(":")
|
|
73
|
-
body = {'token' => encrypted_token}.to_json
|
|
74
|
-
rescue => ex
|
|
75
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Invalid ZConnect Cookie")
|
|
76
|
-
end
|
|
77
|
-
response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/identity", :body => body, :headers => { 'Content-Type' => 'application/json' })
|
|
78
|
-
output_json = JSON.parse(response.body)
|
|
79
80
|
else
|
|
80
|
-
|
|
81
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
|
|
82
|
-
else
|
|
83
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
84
|
-
end
|
|
81
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
85
82
|
end
|
|
86
83
|
rescue JSON::ParserError => ex
|
|
87
84
|
output_json = {}
|
|
@@ -92,20 +89,12 @@ module ZuoraAPI
|
|
|
92
89
|
|
|
93
90
|
def get_full_nav(cookies)
|
|
94
91
|
zsession = cookies["ZSession"]
|
|
95
|
-
zconnect_accesstoken = get_zconnect_accesstoken(cookies)
|
|
96
92
|
begin
|
|
97
93
|
if zsession.present?
|
|
98
94
|
response = HTTParty.get("https://#{self.hostname}/apps/v1/navigation", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
|
|
99
95
|
output_json = JSON.parse(response.body)
|
|
100
|
-
elsif zconnect_accesstoken.present?
|
|
101
|
-
response = HTTParty.get("https://#{self.hostname}/apps/zconnectsession/navigation", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}",'Content-Type' => 'application/json'})
|
|
102
|
-
output_json = JSON.parse(response.body)
|
|
103
96
|
else
|
|
104
|
-
|
|
105
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
|
|
106
|
-
else
|
|
107
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
108
|
-
end
|
|
97
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
109
98
|
end
|
|
110
99
|
rescue JSON::ParserError => ex
|
|
111
100
|
output_json = {}
|
|
@@ -116,20 +105,12 @@ module ZuoraAPI
|
|
|
116
105
|
|
|
117
106
|
def set_nav(state, cookies)
|
|
118
107
|
zsession = cookies["ZSession"]
|
|
119
|
-
zconnect_accesstoken = get_zconnect_accesstoken(cookies)
|
|
120
108
|
begin
|
|
121
109
|
if !zsession.blank?
|
|
122
110
|
response = HTTParty.put("https://#{self.hostname}/apps/v1/preference/navigation", :body => state.to_json, :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
|
|
123
111
|
output_json = JSON.parse(response.body)
|
|
124
|
-
elsif !zconnect_accesstoken.blank?
|
|
125
|
-
response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/navigationstate", :body => state.to_json, :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
|
|
126
|
-
output_json = JSON.parse(response.body)
|
|
127
112
|
else
|
|
128
|
-
|
|
129
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
|
|
130
|
-
else
|
|
131
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
132
|
-
end
|
|
113
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
133
114
|
end
|
|
134
115
|
rescue JSON::ParserError => ex
|
|
135
116
|
output_json = {}
|
|
@@ -140,20 +121,12 @@ module ZuoraAPI
|
|
|
140
121
|
|
|
141
122
|
def refresh_nav(cookies)
|
|
142
123
|
zsession = cookies["ZSession"]
|
|
143
|
-
zconnect_accesstoken = get_zconnect_accesstoken(cookies)
|
|
144
124
|
begin
|
|
145
125
|
if !zsession.blank?
|
|
146
126
|
response = HTTParty.post("https://#{self.hostname}/apps/v1/navigation/fetch", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
|
|
147
127
|
output_json = JSON.parse(response.body)
|
|
148
|
-
elsif !zconnect_accesstoken.blank?
|
|
149
|
-
response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/refresh-navbarcache", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
|
|
150
|
-
output_json = JSON.parse(response.body)
|
|
151
128
|
else
|
|
152
|
-
|
|
153
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
|
|
154
|
-
else
|
|
155
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
156
|
-
end
|
|
129
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
|
|
157
130
|
end
|
|
158
131
|
rescue JSON::ParserError => ex
|
|
159
132
|
output_json = {}
|
|
@@ -162,27 +135,21 @@ module ZuoraAPI
|
|
|
162
135
|
return output_json
|
|
163
136
|
end
|
|
164
137
|
|
|
165
|
-
def get_zconnect_accesstoken(cookies)
|
|
166
|
-
accesstoken = nil
|
|
167
|
-
self.update_zconnect_provider
|
|
168
|
-
if !cookies[self.zconnect_provider].nil? && !cookies[self.zconnect_provider].empty?
|
|
169
|
-
accesstoken = cookies[self.zconnect_provider]
|
|
170
|
-
end
|
|
171
|
-
return accesstoken
|
|
172
|
-
end
|
|
173
|
-
|
|
174
138
|
def reporting_url(path)
|
|
175
139
|
map = {"US" => {"Sandbox" => "https://zconnectsandbox.zuora.com/api/rest/v1/",
|
|
176
140
|
"Production" => "https://zconnect.zuora.com/api/rest/v1/",
|
|
177
|
-
"
|
|
141
|
+
"Test" => "https://reporting-sbx.zan.0001.sbx.auw2.zuora.com/api/rest/v1/",
|
|
142
|
+
"Staging" => "https://reporting-stg11.zan.svc.auw2.zuora.com/api/rest/v1/",
|
|
143
|
+
"Performance" => "https://zconnectpt1.zuora.com/api/rest/v1/",
|
|
144
|
+
"Services" => "https://reporting-svc08.svc.auw2.zuora.com/api/rest/v1/"},
|
|
178
145
|
"EU" => {"Sandbox" => "https://zconnect.sandbox.eu.zuora.com/api/rest/v1/",
|
|
179
146
|
"Production" => "https://zconnect.eu.zuora.com/api/rest/v1/",
|
|
180
|
-
"Services"=> ""},
|
|
147
|
+
"Services"=> "https://reporting-sbx0000.sbx.aec1.zuora.com/api/rest/v1/"},
|
|
181
148
|
"NA" => {"Sandbox" => "https://zconnect.sandbox.na.zuora.com/api/rest/v1/",
|
|
182
149
|
"Production" => "https://zconnect.na.zuora.com/api/rest/v1/",
|
|
183
150
|
"Services"=> ""}
|
|
184
151
|
}
|
|
185
|
-
return map[
|
|
152
|
+
return map[self.region][self.environment].insert(-1, path)
|
|
186
153
|
end
|
|
187
154
|
|
|
188
155
|
# There are two ways to call this method. The first way is best.
|
|
@@ -232,7 +199,7 @@ module ZuoraAPI
|
|
|
232
199
|
end
|
|
233
200
|
|
|
234
201
|
def self.environments
|
|
235
|
-
%w(Sandbox Production Services Performance Staging)
|
|
202
|
+
%w(Sandbox Production Services Performance Staging Test)
|
|
236
203
|
end
|
|
237
204
|
|
|
238
205
|
def self.regions
|
|
@@ -244,11 +211,13 @@ module ZuoraAPI
|
|
|
244
211
|
"Production" => "https://www.zuora.com/apps/services/a/",
|
|
245
212
|
"Performance" => "https://pt1.zuora.com/apps/services/a/",
|
|
246
213
|
"Services" => "https://services347.zuora.com/apps/services/a/",
|
|
247
|
-
"Staging" => "https://staging2.zuora.com/apps/services/a/"
|
|
214
|
+
"Staging" => "https://staging2.zuora.com/apps/services/a/",
|
|
215
|
+
"Test" => "https://test.zuora.com/apps/services/a/"},
|
|
248
216
|
"EU" => {"Sandbox" => "https://sandbox.eu.zuora.com/apps/services/a/",
|
|
249
217
|
"Production" => "https://eu.zuora.com/apps/services/a/",
|
|
250
218
|
"Performance" => "https://pt1.eu.zuora.com/apps/services/a/",
|
|
251
|
-
"Services" => "https://services347.eu.zuora.com/apps/services/a/"
|
|
219
|
+
"Services" => "https://services347.eu.zuora.com/apps/services/a/",
|
|
220
|
+
"Test" => "https://test.eu.zuora.com/apps/services/a/"},
|
|
252
221
|
"NA" => {"Sandbox" => "https://sandbox.na.zuora.com/apps/services/a/",
|
|
253
222
|
"Production" => "https://na.zuora.com/apps/services/a/",
|
|
254
223
|
"Performance" => "https://pt1.na.zuora.com/apps/services/a/",
|
|
@@ -284,16 +253,19 @@ module ZuoraAPI
|
|
|
284
253
|
end
|
|
285
254
|
|
|
286
255
|
def update_environment
|
|
287
|
-
if !self.
|
|
288
|
-
|
|
256
|
+
if !self.hostname.blank?
|
|
257
|
+
case self.hostname
|
|
258
|
+
when /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/
|
|
289
259
|
self.environment = 'Sandbox'
|
|
290
|
-
|
|
260
|
+
when /(?<=\.|\/|^)(service[\d]*|services[\d]*|ep-edge)(?=\.|\/|$)/
|
|
291
261
|
self.environment = 'Services'
|
|
292
|
-
|
|
262
|
+
when /(?<=\.|\/|-|^)(pt[\d]*)(?=\.|\/|-|$)/
|
|
293
263
|
self.environment = 'Performance'
|
|
294
|
-
|
|
264
|
+
when /(?<=\.|\/|^)(staging1|staging2|stg)(?=\.|\/|$)/
|
|
295
265
|
self.environment = 'Staging'
|
|
296
|
-
|
|
266
|
+
when /(?<=\.|\/|^)(test)(?=\.|\/|$)/
|
|
267
|
+
self.environment = 'Test'
|
|
268
|
+
when /(?<=\.|\/|^)(www|api)(?=\.|\/|$)/, /(^|tls10\.|origin-www\.|zforsf\.|eu\.|na\.)(zuora\.com)/
|
|
297
269
|
self.environment = 'Production'
|
|
298
270
|
else
|
|
299
271
|
self.environment = 'Unknown'
|
|
@@ -303,25 +275,14 @@ module ZuoraAPI
|
|
|
303
275
|
end
|
|
304
276
|
end
|
|
305
277
|
|
|
306
|
-
def is_prod_env
|
|
307
|
-
is_prod = false
|
|
308
|
-
www_or_api = /(?<=\.|\/|^)(www|api)(?=\.|\/|$)/ === self.hostname
|
|
309
|
-
host_prefix_match = /(^|tls10\.|origin-www\.|zforsf\.|eu\.|na\.)(zuora\.com)/ === self.hostname
|
|
310
|
-
if www_or_api || host_prefix_match
|
|
311
|
-
is_prod = true
|
|
312
|
-
end
|
|
313
|
-
return is_prod
|
|
314
|
-
end
|
|
315
|
-
|
|
316
278
|
def update_zconnect_provider
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
mappings = {"US" => {"Sandbox" => "ZConnectSbx",
|
|
320
|
-
"NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "
|
|
321
|
-
"EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "
|
|
279
|
+
update_region if self.region.blank?
|
|
280
|
+
update_environment if self.environment.blank?
|
|
281
|
+
mappings = {"US" => {"Sandbox" => "ZConnectSbx", "Services" => "ZConnectSvcUS", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Test" => "ZConnectTest", "Staging" => "ZConnectQA", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev"},
|
|
282
|
+
"NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectSvcNA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
|
|
283
|
+
"EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectSvcEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU", "Test" => "ZConnectTest"},
|
|
322
284
|
"Unknown" => {"Unknown" => "Unknown"}}
|
|
323
|
-
self.zconnect_provider = mappings[region][environment]
|
|
324
|
-
# raise "Can't find ZConnect Provider for #{region} region and #{environment} environment" if self.zconnect_provider.nil?
|
|
285
|
+
self.zconnect_provider = mappings[self.region][self.environment]
|
|
325
286
|
end
|
|
326
287
|
|
|
327
288
|
def aqua_endpoint(url="")
|
|
@@ -334,45 +295,35 @@ module ZuoraAPI
|
|
|
334
295
|
return "#{url_slash_apps_slash}api/#{url}"
|
|
335
296
|
end
|
|
336
297
|
|
|
337
|
-
def rest_endpoint(url="")
|
|
298
|
+
def rest_endpoint(url="", domain=true, prefix='/v1/')
|
|
338
299
|
update_environment
|
|
339
300
|
endpoint = url
|
|
340
|
-
|
|
301
|
+
url_postfix = {"US" => ".", "EU" => ".eu.", "NA" => ".na."}[self.region]
|
|
302
|
+
|
|
341
303
|
case self.environment
|
|
304
|
+
when 'Test'
|
|
305
|
+
endpoint = "https://rest.test#{url_postfix}zuora.com"
|
|
342
306
|
when 'Sandbox'
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
endpoint = "https://rest.apisandbox.zuora.com/v1/".concat(url)
|
|
346
|
-
when 'EU'
|
|
347
|
-
endpoint = "https://rest.sandbox.eu.zuora.com/v1/".concat(url)
|
|
348
|
-
when 'NA'
|
|
349
|
-
endpoint = "https://rest.sandbox.na.zuora.com/v1/".concat(url)
|
|
350
|
-
end
|
|
307
|
+
endpoint = "https://rest.sandbox#{url_postfix}zuora.com"
|
|
308
|
+
endpoint = "https://rest.apisandbox.zuora.com" if self.region == "US"
|
|
351
309
|
when 'Production'
|
|
352
|
-
|
|
353
|
-
when 'US'
|
|
354
|
-
endpoint = "https://rest.zuora.com/v1/".concat(url)
|
|
355
|
-
when 'EU'
|
|
356
|
-
endpoint = "https://rest.eu.zuora.com/v1/".concat(url)
|
|
357
|
-
when 'NA'
|
|
358
|
-
endpoint = "https://rest.na.zuora.com/v1/".concat(url)
|
|
359
|
-
end
|
|
310
|
+
endpoint = "https://rest#{url_postfix}zuora.com"
|
|
360
311
|
when 'Performance'
|
|
361
|
-
endpoint = "https://rest.pt1.zuora.com
|
|
312
|
+
endpoint = "https://rest.pt1.zuora.com"
|
|
362
313
|
when 'Services'
|
|
363
314
|
https = /https:\/\/|http:\/\//.match(self.url)[0]
|
|
364
315
|
host = self.hostname
|
|
365
|
-
endpoint = "#{https}rest#{host}
|
|
316
|
+
endpoint = "#{https}rest#{host}"
|
|
366
317
|
when 'Staging'
|
|
367
|
-
endpoint = "https://rest-staging2.zuora.com
|
|
318
|
+
endpoint = "https://rest-staging2.zuora.com"
|
|
368
319
|
when 'Unknown'
|
|
369
320
|
raise "Environment unknown, returning passed in parameter unaltered"
|
|
370
321
|
end
|
|
371
|
-
return endpoint
|
|
322
|
+
return domain ? endpoint.concat(prefix).concat(url) : prefix.concat(url)
|
|
372
323
|
end
|
|
373
324
|
|
|
374
|
-
def rest_domain
|
|
375
|
-
return URI(
|
|
325
|
+
def rest_domain(endpoint: self.rest_endpoint)
|
|
326
|
+
return URI(endpoint).host
|
|
376
327
|
end
|
|
377
328
|
|
|
378
329
|
def fileURL(url="")
|
|
@@ -383,43 +334,89 @@ module ZuoraAPI
|
|
|
383
334
|
return self.wsdl_number > 68 ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
|
|
384
335
|
end
|
|
385
336
|
|
|
386
|
-
def new_session(auth_type: :basic, debug: false)
|
|
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
|
+
|
|
387
370
|
end
|
|
388
371
|
|
|
389
|
-
def get_session(prefix: false, auth_type: :basic)
|
|
390
|
-
Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}") if Rails.env.to_s == 'development'
|
|
372
|
+
def get_session(prefix: false, auth_type: :basic, zuora_track_id: nil)
|
|
391
373
|
case auth_type
|
|
392
374
|
when :basic
|
|
393
375
|
if self.current_session.blank?
|
|
394
376
|
case self.class.to_s
|
|
395
377
|
when 'ZuoraAPI::Oauth'
|
|
396
378
|
if self.bearer_token.blank? || self.oauth_expired?
|
|
397
|
-
self.new_session(auth_type: :bearer)
|
|
379
|
+
self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
|
|
398
380
|
end
|
|
399
|
-
self.get_z_session
|
|
381
|
+
self.get_z_session(zuora_track_id: zuora_track_id)
|
|
400
382
|
when 'ZuoraAPI::Basic'
|
|
401
|
-
self.new_session(auth_type: :basic)
|
|
383
|
+
self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
|
|
402
384
|
else
|
|
403
|
-
|
|
385
|
+
self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
|
|
404
386
|
end
|
|
405
387
|
end
|
|
406
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
|
|
407
388
|
return prefix ? "ZSession #{self.current_session}" : self.current_session.to_s
|
|
408
389
|
when :bearer
|
|
409
390
|
case self.class.to_s
|
|
410
391
|
when 'ZuoraAPI::Oauth'
|
|
411
392
|
if self.bearer_token.blank? || self.oauth_expired?
|
|
412
|
-
self.new_session(auth_type: :bearer)
|
|
393
|
+
self.new_session(auth_type: :bearer, zuora_track_id: zuora_track_id)
|
|
413
394
|
end
|
|
414
395
|
when 'ZuoraAPI::Basic'
|
|
415
396
|
raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Basic Login, does not support Authentication of Type: #{auth_type}")
|
|
397
|
+
else
|
|
398
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Unknown Login, does not support Authentication of Type: #{auth_type}")
|
|
416
399
|
end
|
|
417
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if self.status != 'Active'
|
|
418
400
|
return prefix ? "Bearer #{self.bearer_token}" : self.bearer_token.to_s
|
|
419
401
|
end
|
|
420
402
|
end
|
|
421
403
|
|
|
422
|
-
def soap_call(
|
|
404
|
+
def soap_call(
|
|
405
|
+
ns1: 'ns1',
|
|
406
|
+
ns2: 'ns2',
|
|
407
|
+
batch_size: nil,
|
|
408
|
+
headers: {},
|
|
409
|
+
single_transaction: false,
|
|
410
|
+
debug: false,
|
|
411
|
+
zuora_track_id: nil,
|
|
412
|
+
errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS),
|
|
413
|
+
z_session: true,
|
|
414
|
+
timeout_retry: false,
|
|
415
|
+
timeout: 130,
|
|
416
|
+
timeout_sleep_interval: self.timeout_sleep,
|
|
417
|
+
output_exception_messages: true,
|
|
418
|
+
skip_session: false,
|
|
419
|
+
**keyword_args)
|
|
423
420
|
tries ||= 2
|
|
424
421
|
xml = Nokogiri::XML::Builder.new do |xml|
|
|
425
422
|
xml['SOAP-ENV'].Envelope('xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
|
|
@@ -428,8 +425,10 @@ module ZuoraAPI
|
|
|
428
425
|
'xmlns:api' => "http://api.zuora.com/",
|
|
429
426
|
"xmlns:#{ns1}" => "http://api.zuora.com/") do
|
|
430
427
|
xml['SOAP-ENV'].Header do
|
|
431
|
-
|
|
432
|
-
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
|
|
433
432
|
end
|
|
434
433
|
if single_transaction
|
|
435
434
|
xml["#{ns1}"].CallOptions do
|
|
@@ -447,96 +446,212 @@ module ZuoraAPI
|
|
|
447
446
|
end
|
|
448
447
|
end
|
|
449
448
|
end
|
|
450
|
-
|
|
451
449
|
input_xml = Nokogiri::XML(xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip)
|
|
452
450
|
input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
|
|
453
451
|
Rails.logger.debug("Request SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
|
|
454
452
|
|
|
455
|
-
|
|
453
|
+
headers.merge!({ 'Content-Type' => "text/xml; charset=utf-8", 'Accept' => 'text/xml'})
|
|
454
|
+
headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
|
|
455
|
+
|
|
456
|
+
request = HTTParty::Request.new(
|
|
457
|
+
Net::HTTP::Post,
|
|
458
|
+
self.url,
|
|
459
|
+
body: xml.doc.to_xml(:save_with => XML_SAVE_OPTIONS).strip,
|
|
460
|
+
headers: headers,
|
|
461
|
+
timeout: timeout,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
response = request.perform
|
|
465
|
+
|
|
456
466
|
output_xml = Nokogiri::XML(response.body)
|
|
457
467
|
Rails.logger.debug("Response SOAP XML: #{output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}") if debug
|
|
458
468
|
|
|
459
469
|
raise_errors(type: :SOAP, body: output_xml, response: response)
|
|
470
|
+
|
|
471
|
+
return output_xml, input_xml, response
|
|
472
|
+
|
|
460
473
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
|
461
|
-
if
|
|
474
|
+
raise if skip_session
|
|
475
|
+
if !tries.zero? && z_session
|
|
476
|
+
tries -= 1
|
|
462
477
|
Rails.logger.debug("SOAP Call - Session Invalid")
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
else
|
|
469
|
-
return output_xml, input_xml, response
|
|
478
|
+
|
|
479
|
+
begin
|
|
480
|
+
self.new_session(auth_type: :basic, zuora_track_id: zuora_track_id)
|
|
481
|
+
rescue *ZUORA_API_ERRORS => ex
|
|
482
|
+
return output_xml, input_xml, ex.response
|
|
470
483
|
end
|
|
484
|
+
|
|
485
|
+
retry
|
|
471
486
|
end
|
|
487
|
+
|
|
488
|
+
raise ex if errors.include?(ex.class)
|
|
489
|
+
|
|
490
|
+
return output_xml, input_xml, response
|
|
491
|
+
|
|
472
492
|
rescue *ZUORA_API_ERRORS => ex
|
|
473
|
-
if errors.include?(ex.class)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
493
|
+
raise ex if errors.include?(ex.class)
|
|
494
|
+
|
|
495
|
+
response = ex.response unless response
|
|
496
|
+
return output_xml, input_xml, response
|
|
497
|
+
|
|
478
498
|
rescue *CONNECTION_EXCEPTIONS => ex
|
|
479
|
-
if !
|
|
480
|
-
|
|
481
|
-
|
|
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)
|
|
482
503
|
retry
|
|
483
|
-
else
|
|
484
|
-
raise ex
|
|
485
504
|
end
|
|
505
|
+
|
|
506
|
+
self.log(location: "SOAP Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
|
507
|
+
raise ex
|
|
508
|
+
|
|
486
509
|
rescue *CONNECTION_READ_EXCEPTIONS => ex
|
|
487
|
-
if !
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
510
|
+
if !tries.zero?
|
|
511
|
+
tries -= 1
|
|
512
|
+
self.log(location: "SOAP Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
|
513
|
+
if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
|
|
514
|
+
retry
|
|
515
|
+
elsif timeout_retry
|
|
516
|
+
sleep(timeout_sleep_interval)
|
|
517
|
+
retry
|
|
518
|
+
end
|
|
493
519
|
end
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
520
|
+
|
|
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)
|
|
523
|
+
raise ex
|
|
524
|
+
|
|
525
|
+
rescue => ex
|
|
526
|
+
raise ex
|
|
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', 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)
|
|
497
549
|
else
|
|
498
|
-
|
|
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 ex.class == ZuoraAPI::Exceptions::ZuoraAPIRequestLimit
|
|
557
|
+
args.merge!({
|
|
558
|
+
zuora_trace_id: ex.response.headers["zuora-request-id"],
|
|
559
|
+
zuora_track_id: ex.response.request.options[:headers]["Zuora-Track-Id"]
|
|
560
|
+
})
|
|
561
|
+
elsif defined?(ex.response) && ex.response.present?
|
|
562
|
+
args.merge!({
|
|
563
|
+
request: {
|
|
564
|
+
path: ex.response.request.path.to_s,
|
|
565
|
+
method: ex.response.request.http_method.to_s.split("Net::HTTP::").last.upcase,
|
|
566
|
+
params: ex.response.request.raw_body.to_s,
|
|
567
|
+
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,
|
|
568
|
+
},
|
|
569
|
+
response: {
|
|
570
|
+
status: ex.response.code,
|
|
571
|
+
params: ex.response.body.to_s,
|
|
572
|
+
headers: ex.response.headers.to_s,
|
|
573
|
+
},
|
|
574
|
+
zuora_trace_id: ex.response.headers["zuora-request-id"],
|
|
575
|
+
zuora_track_id: ex.response.request.options[:headers]["Zuora-Track-Id"],
|
|
576
|
+
})
|
|
577
|
+
elsif defined?(ex.request) && ex.request.present?
|
|
578
|
+
args.merge!({
|
|
579
|
+
request: {
|
|
580
|
+
path: ex.request.path.to_s,
|
|
581
|
+
method: ex.request.http_method.to_s.split("Net::HTTP::").last.upcase,
|
|
582
|
+
params: ex.request.options[:body],
|
|
583
|
+
headers: ex.request.options[:headers].map{|k,v| [k.to_s, k.to_s.downcase.strip == "authorization" ? "VALUE FILTERED" : v]}.to_h.to_s
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
args.merge!({
|
|
587
|
+
zuora_track_id: ex.request.options[:headers]["Zuora-Track-Id"]
|
|
588
|
+
}) if ex.request.options[:headers]["Zuora-Track-Id"].present?
|
|
499
589
|
end
|
|
500
590
|
rescue => ex
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return
|
|
591
|
+
Rails.logger.error("Failed to create exception arguments", ex, args)
|
|
592
|
+
ensure
|
|
593
|
+
return args
|
|
504
594
|
end
|
|
505
595
|
|
|
506
596
|
def raise_errors(type: :SOAP, body: nil, response: nil)
|
|
597
|
+
request_uri, request_path, match_string = "", "", ""
|
|
598
|
+
if response.class.to_s == "HTTP::Message"
|
|
599
|
+
request_uri = response.http_header.request_uri.to_s
|
|
600
|
+
request_path = response.http_header.request_uri.path
|
|
601
|
+
match_string = "#{response.http_header.request_method}::#{response.code}::#{request_uri}"
|
|
602
|
+
else
|
|
603
|
+
request = response.request
|
|
604
|
+
request_uri = response.request.uri
|
|
605
|
+
request_path = request.path.path
|
|
606
|
+
match_string = "#{request.http_method.to_s.split("Net::HTTP::").last.upcase}::#{response.code}::#{request_path}"
|
|
607
|
+
end
|
|
608
|
+
|
|
507
609
|
if [502,503].include?(response.code)
|
|
508
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from
|
|
610
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from 'https://#{rest_domain(endpoint: request_uri)}'", response)
|
|
509
611
|
end
|
|
510
612
|
|
|
511
|
-
|
|
512
|
-
|
|
613
|
+
# Check failure response code
|
|
614
|
+
case response.code
|
|
615
|
+
when 504
|
|
616
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from 'https://#{rest_domain(endpoint: request_uri)}'", response)
|
|
617
|
+
when 429
|
|
618
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("The total number of concurrent requests has exceeded the limit allowed by the system. Please resubmit your request later.", response)
|
|
619
|
+
when 401
|
|
620
|
+
|
|
621
|
+
else
|
|
622
|
+
if body.class == Hash
|
|
623
|
+
case request_path
|
|
624
|
+
when /^\/v1\/connections$/
|
|
625
|
+
response_headers = response.headers.to_h
|
|
626
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Missing cookies for authentication call", response) if response_headers['set-cookie'].blank?
|
|
627
|
+
z_session_cookie = response_headers.fetch('set-cookie', []).select{|x| x.match(/^ZSession=.*/) }.first
|
|
628
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Missing ZSession cookie for authentication call", response) if z_session_cookie.blank?
|
|
629
|
+
end
|
|
630
|
+
end
|
|
513
631
|
end
|
|
514
632
|
|
|
515
633
|
case type
|
|
516
634
|
when :SOAP
|
|
517
|
-
error
|
|
518
|
-
message = body.xpath('//fns:FaultMessage', 'fns' =>'http://fault.api.zuora.com/').text
|
|
519
|
-
|
|
520
|
-
if error.blank? || message.blank?
|
|
521
|
-
error = body.xpath('//faultcode').text
|
|
522
|
-
message = body.xpath('//faultstring').text
|
|
523
|
-
end
|
|
635
|
+
error, success, message = get_soap_error_and_message(body)
|
|
524
636
|
|
|
525
|
-
if
|
|
526
|
-
|
|
527
|
-
message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
|
|
637
|
+
if body.xpath('//fns:LoginFault', 'fns' =>'http://fault.api.zuora.com/').present?
|
|
638
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(message, response)
|
|
528
639
|
end
|
|
529
640
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
641
|
+
if body.xpath('//ns1:queryResponse', 'ns1' => 'http://api.zuora.com/').present? &&
|
|
642
|
+
body.xpath(
|
|
643
|
+
'//ns1:records[@xsi:type="ns2:Export"]',
|
|
644
|
+
'ns1' => 'http://api.zuora.com/', 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
|
|
645
|
+
).present?
|
|
646
|
+
result = body.xpath('//ns2:Status', 'ns2' => 'http://object.api.zuora.com/').text
|
|
647
|
+
if result == 'Failed'
|
|
648
|
+
message = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
|
|
649
|
+
error = 'FATAL_ERROR'
|
|
650
|
+
if message.present?
|
|
651
|
+
identifier, new_message = message.scan(/^([\w\d]{16})\: (.*)/).first
|
|
652
|
+
error, message = ['UNEXPECTED_ERROR', new_message] if new_message.present?
|
|
538
653
|
else
|
|
539
|
-
|
|
654
|
+
message = 'Export failed due to unknown reason. Consult api logs.'
|
|
540
655
|
end
|
|
541
656
|
end
|
|
542
657
|
end
|
|
@@ -544,50 +659,79 @@ module ZuoraAPI
|
|
|
544
659
|
#By default response if not passed in for SOAP as all SOAP is 200
|
|
545
660
|
if error.present?
|
|
546
661
|
if error.class == String
|
|
547
|
-
|
|
548
|
-
when "INVALID_SESSION"
|
|
549
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{error}::#{message}", response)
|
|
550
|
-
when "REQUEST_EXCEEDED_LIMIT"
|
|
551
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{error}::#{message}", response)
|
|
552
|
-
when "LOCK_COMPETITION"
|
|
553
|
-
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", response)
|
|
554
|
-
when "BATCH_FAIL_ERROR"
|
|
555
|
-
if message.include?("optimistic locking failed")
|
|
556
|
-
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", response)
|
|
557
|
-
else
|
|
558
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response)
|
|
559
|
-
end
|
|
560
|
-
when "TEMPORARY_ERROR"
|
|
561
|
-
raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new("#{error}::#{message}", response)
|
|
562
|
-
when "INVALID_VALUE"
|
|
563
|
-
if message.include?("data integrity violation")
|
|
564
|
-
raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response)
|
|
565
|
-
else
|
|
566
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response)
|
|
567
|
-
end
|
|
568
|
-
else
|
|
569
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", response) if error.present?
|
|
570
|
-
end
|
|
662
|
+
raise_errors_helper(error: error, message: message, response: response)
|
|
571
663
|
elsif error.class == Array
|
|
572
|
-
if error
|
|
573
|
-
|
|
664
|
+
if error.uniq.size == 1
|
|
665
|
+
err, msg = error[0].split('::')
|
|
666
|
+
raise_errors_helper(error: err, message: msg, response: response, errors: error, success: success)
|
|
574
667
|
else
|
|
575
668
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(error.group_by {|v| v}.map {|k,v| "(#{v.size}x) - #{k == "::" ? 'UNKNOWN::No error provided' : k}"}.join(', '), response, error, success)
|
|
576
669
|
end
|
|
577
670
|
end
|
|
578
671
|
end
|
|
672
|
+
|
|
673
|
+
self.errors_via_content_type(response: response, type: :xml)
|
|
579
674
|
|
|
580
|
-
|
|
581
|
-
|
|
675
|
+
when :JSON
|
|
676
|
+
case request_path
|
|
677
|
+
when /^\/query\/jobs.*/ #DataQuery Paths
|
|
678
|
+
return if body.class != Hash
|
|
679
|
+
case match_string
|
|
680
|
+
when /^GET::200::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job, Capture of the id is present if needed in future error responses.
|
|
681
|
+
if body.dig('data', "errorCode") == "LINK_10000005"
|
|
682
|
+
raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body.dig('data', "errorMessage"), response)
|
|
683
|
+
elsif (body.dig('data', "errorMessage").present? || body.dig('data', "queryStatus") == "failed")
|
|
684
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('data', "errorMessage"), response)
|
|
685
|
+
end
|
|
686
|
+
when /^GET::404::\/query\/jobs\/([a-zA-Z0-9\-_]+)$/ #Get DQ job not found, capture of the id is present if needed in future error responses.
|
|
687
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('message'), response) if body.dig('message').present?
|
|
688
|
+
when /^POST::400::\/query\/jobs$/ #Create DQ job
|
|
689
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body.dig('message'), response) if body.dig('message').present?
|
|
690
|
+
end
|
|
691
|
+
when /^\/api\/rest\/v1\/reports.*/ #Reporting Paths
|
|
692
|
+
reporting_message = response.parsed_response.dig("ZanResponse", "response", "message") || body.dig("message")
|
|
693
|
+
if reporting_message&.include?("com.zuora.rest.RestUsageException: The user does not have permissions for this API.")
|
|
694
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(reporting_message, response)
|
|
695
|
+
elsif reporting_message&.include?("500 Internal Server Error")
|
|
696
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Internal Server Error. The Reporting API is down. Contact Support.", response)
|
|
697
|
+
end
|
|
698
|
+
case match_string
|
|
699
|
+
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.
|
|
700
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(reporting_message, response) if reporting_message.present?
|
|
701
|
+
end
|
|
702
|
+
when /\/objects\/batch\//
|
|
703
|
+
if body['code'].present? && /61$/.match(body['code'].to_s).present? # if last 2 digits of code are 61
|
|
704
|
+
raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body['message'], nil, body['details'])
|
|
705
|
+
end
|
|
706
|
+
when /^\/api\/v1\/payment_plans.*/
|
|
707
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['error'], response) if body['error']
|
|
582
708
|
end
|
|
583
709
|
|
|
584
|
-
when :JSON
|
|
585
710
|
body = body.dig("results").present? ? body["results"] : body if body.class == Hash
|
|
586
711
|
if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
codes_array =
|
|
590
|
-
|
|
712
|
+
reason_keys = %w(reasons errors)
|
|
713
|
+
message_keys = %w(message title)
|
|
714
|
+
messages_array, codes_array = [[],[]]
|
|
715
|
+
reason_keys.each do |rsn_key|
|
|
716
|
+
message_keys.each do |msg_key|
|
|
717
|
+
messages_array = body.fetch(rsn_key, []).map {|error| error[msg_key]}.compact
|
|
718
|
+
break if messages_array.present?
|
|
719
|
+
end
|
|
720
|
+
codes_array = body.fetch(rsn_key, []).map {|error| error['code']}.compact
|
|
721
|
+
break if messages_array.present? && codes_array.present?
|
|
722
|
+
end
|
|
723
|
+
if body.dig('error').class == Hash
|
|
724
|
+
messages_array = messages_array.push(body.dig("error", 'message')).compact
|
|
725
|
+
codes_array = codes_array.push(body.dig("error", 'code')).compact
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
if body['message'] == 'request exceeded limit'
|
|
729
|
+
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)
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
if (body.dig('message') || '').downcase.include?('unexpected error') && response.code != 500
|
|
733
|
+
raise ZuoraAPI::Exceptions::ZuoraUnexpectedError.new(body['message'], response)
|
|
734
|
+
end
|
|
591
735
|
|
|
592
736
|
if body['message'] == "No bearer token" && response.code == 400
|
|
593
737
|
raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new("Authentication type is not supported by this Login", response)
|
|
@@ -602,16 +746,29 @@ module ZuoraAPI
|
|
|
602
746
|
end
|
|
603
747
|
|
|
604
748
|
#Oauth Tokens - User deactivated
|
|
605
|
-
if body['
|
|
606
|
-
|
|
749
|
+
if body['path'] == '/oauth/token'
|
|
750
|
+
if body['status'] == 403 && response.code == 403
|
|
751
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Forbidden", response)
|
|
752
|
+
elsif body['status'] == 400 && response.code == 400 && body['message'].include?("Invalid client id")
|
|
753
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Invalid Oauth Client Id", response)
|
|
754
|
+
end
|
|
607
755
|
end
|
|
608
756
|
|
|
609
757
|
if body['error'] == 'Unauthorized' && body['status'] == 401
|
|
610
|
-
|
|
758
|
+
if body['message'].present?
|
|
759
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(body['message'], response)
|
|
760
|
+
else
|
|
761
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
|
|
762
|
+
end
|
|
611
763
|
end
|
|
612
764
|
#Authentication failed
|
|
613
765
|
if (codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(11) || response.code == 401) && !codes_array.include?(422)
|
|
614
|
-
|
|
766
|
+
new_message = messages_array.join(', ')
|
|
767
|
+
if new_message.present?
|
|
768
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(new_message, response)
|
|
769
|
+
else
|
|
770
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(body['message'], response)
|
|
771
|
+
end
|
|
615
772
|
end
|
|
616
773
|
|
|
617
774
|
#Zuora REST Create Amendment error #Authentication failed
|
|
@@ -619,16 +776,30 @@ module ZuoraAPI
|
|
|
619
776
|
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body['faultstring']}", response)
|
|
620
777
|
end
|
|
621
778
|
|
|
779
|
+
#Locking contention
|
|
780
|
+
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(50)
|
|
781
|
+
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{messages_array.join(', ')}", response)
|
|
782
|
+
end
|
|
783
|
+
#Internal Server Error
|
|
784
|
+
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(60)
|
|
785
|
+
if messages_array.uniq.size == 1
|
|
786
|
+
if messages_array.first.match(/^Transaction declined.*|^There is an invoice pending tax.*|^The Zuora GetTax call to Avalara.*|^The tax calculation call to Zuora Connect returned the following error: Status Code: 4.*/)
|
|
787
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(messages_array.first, response)
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("#{messages_array.join(', ')}", response)
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
#Retryiable Service Error
|
|
794
|
+
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(61)
|
|
795
|
+
raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new("#{messages_array.join(', ')}", response)
|
|
796
|
+
end
|
|
797
|
+
|
|
622
798
|
#Request exceeded limit
|
|
623
799
|
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(70)
|
|
624
800
|
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{messages_array.join(', ')}", response)
|
|
625
801
|
end
|
|
626
802
|
|
|
627
|
-
#Locking contention
|
|
628
|
-
if codes_array.map{|code| code.to_s.slice(6,7).to_i}.include?(50)
|
|
629
|
-
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{messages_array.join(', ')}", response)
|
|
630
|
-
end
|
|
631
|
-
|
|
632
803
|
#All Errors catch
|
|
633
804
|
if codes_array.size > 0
|
|
634
805
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{messages_array.join(', ')}", response)
|
|
@@ -636,26 +807,20 @@ module ZuoraAPI
|
|
|
636
807
|
|
|
637
808
|
#Zuora REST Query Errors
|
|
638
809
|
if body["faultcode"].present?
|
|
639
|
-
|
|
640
|
-
when "fns:MALFORMED_QUERY"
|
|
641
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
|
|
642
|
-
when "fns:REQUEST_EXCEEDED_LIMIT"
|
|
643
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
|
|
644
|
-
when "fns:LOCK_COMPETITION"
|
|
645
|
-
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
|
|
646
|
-
when "INVALID_SESSION"
|
|
647
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
|
|
648
|
-
else
|
|
649
|
-
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{body["faultcode"]}::#{body["faultstring"]}", response)
|
|
650
|
-
end
|
|
810
|
+
raise_errors_helper(error: body["faultcode"], message: body["faultstring"], response: response)
|
|
651
811
|
end
|
|
652
812
|
|
|
653
813
|
if body["Errors"].present? || body["errors"].present?
|
|
654
|
-
|
|
655
|
-
(
|
|
656
|
-
(
|
|
657
|
-
|
|
658
|
-
|
|
814
|
+
codes, messages = [[],[]]
|
|
815
|
+
body.fetch("Errors", []).each { |obj| messages.push(obj["Message"]); messages.push(obj["title"]); codes.push(obj["Code"]); codes.push(obj["code"]) }
|
|
816
|
+
body.fetch("errors", []).each { |obj| messages.push(obj["Message"]); messages.push(obj["title"]); codes.push(obj["Code"]); codes.push(obj["code"]) }
|
|
817
|
+
codes, messages = [codes.uniq.compact, messages.uniq.compact]
|
|
818
|
+
if codes.size > 0
|
|
819
|
+
if codes.size == 1
|
|
820
|
+
raise_errors_helper(error: codes.first, message: messages.first, response: response, errors: messages)
|
|
821
|
+
else
|
|
822
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{messages.join(", ")}", response, messages)
|
|
823
|
+
end
|
|
659
824
|
end
|
|
660
825
|
end
|
|
661
826
|
end
|
|
@@ -673,7 +838,7 @@ module ZuoraAPI
|
|
|
673
838
|
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("Retry Lock Competition", response)
|
|
674
839
|
elsif error_messages.first.include?("data integrity violation")
|
|
675
840
|
raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response)
|
|
676
|
-
end
|
|
841
|
+
end
|
|
677
842
|
end
|
|
678
843
|
end
|
|
679
844
|
|
|
@@ -682,15 +847,140 @@ module ZuoraAPI
|
|
|
682
847
|
end
|
|
683
848
|
end
|
|
684
849
|
|
|
850
|
+
if body.class == Hash && body['message'].present?
|
|
851
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(body['message'], response) if response.code == 500
|
|
852
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['message'], response) if ![200,201].include?(response.code)
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
self.errors_via_content_type(response: response, type: :json)
|
|
856
|
+
|
|
685
857
|
#All other errors
|
|
686
|
-
if ![200,201].include?(response.code)
|
|
687
|
-
|
|
688
|
-
|
|
858
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(response.body, response) if ![200,201].include?(response.code)
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
def errors_via_content_type(response: nil, type: :xml)
|
|
863
|
+
response_content_types = response.headers.transform_keys(&:downcase).fetch('content-type', []).first || ""
|
|
864
|
+
|
|
865
|
+
if response_content_types.include?('application/json') && type != :json
|
|
866
|
+
output_json = JSON.parse(response.body)
|
|
867
|
+
self.raise_errors(type: :JSON, body: output_json, response: response)
|
|
868
|
+
|
|
869
|
+
elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml') || response_content_types.include?('application/soap+xml')) and type != :xml
|
|
870
|
+
output_xml = Nokogiri::XML(response.body)
|
|
871
|
+
self.raise_errors(type: :SOAP, body: output_xml, response: response)
|
|
872
|
+
|
|
873
|
+
elsif response_content_types.include?('text/html')
|
|
874
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Akamai Error", response) if response.headers.fetch('server', '') == 'AkamaiGHost'
|
|
875
|
+
|
|
876
|
+
parse_body = Nokogiri::HTML(response.body)
|
|
877
|
+
error_title = parse_body.xpath('//h2').text
|
|
878
|
+
error_title = parse_body.xpath('//h1').text if error_title.blank?
|
|
879
|
+
error_message = parse_body.xpath('//p').text
|
|
880
|
+
|
|
881
|
+
error_message = error_title if error_message.blank?
|
|
882
|
+
|
|
883
|
+
if error_title.present?
|
|
884
|
+
case error_title
|
|
885
|
+
when /Service Unavailable/
|
|
886
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new(error_message, response)
|
|
887
|
+
when /Client sent a bad request./, /Bad Request/, /403 Forbidden/
|
|
888
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
|
|
889
|
+
when /414 Request-URI Too Large/
|
|
890
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Request URL is too long", response)
|
|
891
|
+
else
|
|
892
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(error_message, response)
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Http response body is missing", response) if response.body.blank?
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(response.body, response) if response.code == 500
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def get_soap_error_and_message(body)
|
|
904
|
+
error = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
|
|
905
|
+
message = body.xpath('//fns:FaultMessage', 'fns' =>'http://fault.api.zuora.com/').text
|
|
906
|
+
|
|
907
|
+
if error.blank? || message.blank?
|
|
908
|
+
error = body.xpath('//faultcode').text
|
|
909
|
+
message = body.xpath('//faultstring').text
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
if error.blank? || message.blank?
|
|
913
|
+
error = body.xpath('//ns1:Code', 'ns1' =>'http://api.zuora.com/').text
|
|
914
|
+
message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
if error.blank? || message.blank?
|
|
918
|
+
error = body.xpath('//soapenv:Value', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
|
|
919
|
+
message = body.xpath('//soapenv:Text', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
#Update/Create/Delete Calls with multiple requests and responses
|
|
923
|
+
if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
|
|
924
|
+
error = []
|
|
925
|
+
success = []
|
|
926
|
+
body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').each_with_index do |call, object_index|
|
|
927
|
+
if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false' && call.xpath('./ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
|
|
928
|
+
message = "#{call.xpath('./*/ns1:Code', 'ns1' =>'http://api.zuora.com/').text}::#{call.xpath('./*/ns1:Message', 'ns1' =>'http://api.zuora.com/').text}"
|
|
929
|
+
error.push(message)
|
|
689
930
|
else
|
|
690
|
-
|
|
931
|
+
success.push(call.xpath('./ns1:Id', 'ns1' =>'http://api.zuora.com/').text)
|
|
691
932
|
end
|
|
692
933
|
end
|
|
693
934
|
end
|
|
935
|
+
return error, success, message
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
def raise_errors_helper(error: nil, message: nil, response: nil, errors: [], success: [])
|
|
939
|
+
case error
|
|
940
|
+
when /.*INVALID_SESSION/
|
|
941
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(message, response, errors, success)
|
|
942
|
+
when /.*REQUEST_EXCEEDED_LIMIT/
|
|
943
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new(message, response, errors, success)
|
|
944
|
+
when /.*LOCK_COMPETITION/
|
|
945
|
+
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(message, response, errors, success)
|
|
946
|
+
when /.*BATCH_FAIL_ERROR/
|
|
947
|
+
if message.include?("optimistic locking failed") || message.include?("Operation failed due to a lock competition, please retry later.")
|
|
948
|
+
raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(message, response, errors, success)
|
|
949
|
+
elsif message.include?("org.hibernate.exception.ConstraintViolationException")
|
|
950
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
|
|
951
|
+
end
|
|
952
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
|
953
|
+
when /.*TEMPORARY_ERROR/, /.*TRANSACTION_TIMEOUT/
|
|
954
|
+
raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(message, response, errors, success)
|
|
955
|
+
when /.*INVALID_VALUE/
|
|
956
|
+
if message.include?("data integrity violation")
|
|
957
|
+
raise ZuoraAPI::Exceptions::ZuoraDataIntegrity.new("Data Integrity Violation", response, errors), success
|
|
958
|
+
end
|
|
959
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
|
960
|
+
when /.*UNKNOWN_ERROR/
|
|
961
|
+
if /payment\/refund|Credit Balance Adjustment|Payment Gateway|ARSettlement permission/.match(message).nil?
|
|
962
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
|
|
963
|
+
end
|
|
964
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
|
965
|
+
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/
|
|
966
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
|
967
|
+
when /.*UNEXPECTED_ERROR/
|
|
968
|
+
raise ZuoraAPI::Exceptions::ZuoraUnexpectedError.new(message, response, errors, success)
|
|
969
|
+
when /.*soapenv:Server.*/
|
|
970
|
+
if /^Invalid value.*for type.*|^Id is invalid|^date string can not be less than 19 charactors$/.match(message).present?
|
|
971
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
|
972
|
+
elsif /^unknown$|^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
|
|
973
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
|
|
974
|
+
end
|
|
975
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
|
|
976
|
+
when /soapenv:Receiver/
|
|
977
|
+
if /^com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character.*$/.match(message).present?
|
|
978
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
|
|
979
|
+
end
|
|
980
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
|
|
981
|
+
else
|
|
982
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Z:#{error}::#{message}", response, errors, success)
|
|
983
|
+
end
|
|
694
984
|
end
|
|
695
985
|
|
|
696
986
|
def aqua_query(queryName: '', query: '', version: '1.2', jobName: 'Aqua',partner: '', project: '')
|
|
@@ -737,7 +1027,7 @@ module ZuoraAPI
|
|
|
737
1027
|
base = self.url.include?(".com") ? self.url.split(".com")[0].concat(".com") : self.url.split(".eu")[0].concat(".eu")
|
|
738
1028
|
url = object ? "#{base}/apps/api/describe/#{object}" : "#{base}/apps/api/describe/"
|
|
739
1029
|
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"}
|
|
740
|
-
response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic)}.merge(headers), :timeout =>
|
|
1030
|
+
response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic)}.merge(headers), :timeout => 130)
|
|
741
1031
|
|
|
742
1032
|
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error.present? ? self.current_error : 'Describe call 401', response) if response.code == 401
|
|
743
1033
|
|
|
@@ -770,27 +1060,31 @@ module ZuoraAPI
|
|
|
770
1060
|
end
|
|
771
1061
|
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
|
|
772
1062
|
end
|
|
1063
|
+
|
|
1064
|
+
return des_hash
|
|
773
1065
|
rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
|
|
774
|
-
if !
|
|
775
|
-
|
|
1066
|
+
if !tries.zero?
|
|
1067
|
+
tries -= 1
|
|
1068
|
+
self.log(location: "Describe", exception: ex, message: "Timed out will retry after #{self.timeout_sleep} seconds", level: :debug)
|
|
776
1069
|
sleep(self.timeout_sleep)
|
|
777
1070
|
retry
|
|
778
|
-
else
|
|
779
|
-
raise ex
|
|
780
1071
|
end
|
|
1072
|
+
|
|
1073
|
+
self.log(location: "Describe", exception: ex, message: "Timed out", level: :error) if log_errors
|
|
1074
|
+
raise ex
|
|
1075
|
+
|
|
781
1076
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
|
782
|
-
if !
|
|
1077
|
+
if !tries.zero? && self.status == 'Active'
|
|
1078
|
+
tries -= 1
|
|
783
1079
|
Rails.logger.debug("Describe session expired. Starting new session.")
|
|
784
1080
|
self.new_session
|
|
785
1081
|
retry
|
|
786
|
-
else
|
|
787
|
-
Rails.logger.error("Describe session expired. Starting new session.") if log_errors
|
|
788
|
-
raise ex
|
|
789
1082
|
end
|
|
1083
|
+
|
|
1084
|
+
Rails.logger.error("Describe session expired. Starting new session.") if log_errors
|
|
1085
|
+
raise ex
|
|
790
1086
|
rescue => ex
|
|
791
1087
|
raise ex
|
|
792
|
-
else
|
|
793
|
-
return des_hash
|
|
794
1088
|
end
|
|
795
1089
|
|
|
796
1090
|
def rest_call(
|
|
@@ -803,93 +1097,124 @@ module ZuoraAPI
|
|
|
803
1097
|
z_session: true,
|
|
804
1098
|
session_type: :basic,
|
|
805
1099
|
timeout_retry: false,
|
|
806
|
-
timeout:
|
|
1100
|
+
timeout: 130,
|
|
1101
|
+
timeout_sleep_interval: self.timeout_sleep,
|
|
807
1102
|
multipart: false,
|
|
808
|
-
|
|
1103
|
+
stream_body: false,
|
|
1104
|
+
output_exception_messages: true,
|
|
1105
|
+
zuora_track_id: nil,
|
|
1106
|
+
**keyword_args,
|
|
1107
|
+
&block
|
|
809
1108
|
)
|
|
810
1109
|
tries ||= 2
|
|
811
1110
|
|
|
812
|
-
if self.entity_id.present?
|
|
813
|
-
headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
|
|
814
|
-
headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
|
|
815
|
-
end
|
|
816
|
-
|
|
817
1111
|
raise "Method not supported, supported methods include: :get, :post, :put, :delete, :patch, :head, :options" if ![:get, :post, :put, :delete, :patch, :head, :options].include?(method)
|
|
818
1112
|
|
|
819
|
-
authentication_headers =
|
|
820
|
-
|
|
1113
|
+
authentication_headers = {}
|
|
1114
|
+
if z_session
|
|
1115
|
+
authentication_headers = {"Authorization" => self.get_session(prefix: true, auth_type: session_type, zuora_track_id: zuora_track_id) }
|
|
1116
|
+
if self.entity_id.present?
|
|
1117
|
+
authentication_headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
|
|
1118
|
+
authentication_headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
|
|
821
1122
|
|
|
822
|
-
|
|
823
|
-
"Net::HTTP::#{method.to_s.capitalize}".constantize,
|
|
824
|
-
url,
|
|
825
|
-
body: body,
|
|
826
|
-
headers: modified_headers,
|
|
827
|
-
timeout: timeout,
|
|
828
|
-
multipart: multipart,
|
|
829
|
-
).perform
|
|
1123
|
+
modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
|
|
830
1124
|
|
|
831
|
-
Rails.logger.debug("Response Code: #{response.code}") if debug
|
|
832
1125
|
begin
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1126
|
+
request = HTTParty::Request.new(
|
|
1127
|
+
"Net::HTTP::#{method.to_s.capitalize}".constantize,
|
|
1128
|
+
url,
|
|
1129
|
+
body: body,
|
|
1130
|
+
headers: modified_headers,
|
|
1131
|
+
timeout: timeout,
|
|
1132
|
+
multipart: multipart,
|
|
1133
|
+
stream_body: stream_body
|
|
1134
|
+
)
|
|
1135
|
+
|
|
1136
|
+
response = request.perform(&block)
|
|
1137
|
+
|
|
1138
|
+
Rails.logger.debug("Response Code: #{response.code}") if debug
|
|
1139
|
+
begin
|
|
1140
|
+
output_json = JSON.parse(response.body)
|
|
1141
|
+
rescue JSON::ParserError => ex
|
|
1142
|
+
output_json = {}
|
|
1143
|
+
end
|
|
1144
|
+
Rails.logger.debug("Response JSON: #{output_json}") if debug && output_json.present?
|
|
1145
|
+
|
|
1146
|
+
raise_errors(type: :JSON, body: output_json, response: response)
|
|
1147
|
+
rescue => ex
|
|
1148
|
+
reset_files(body) if multipart
|
|
1149
|
+
raise
|
|
836
1150
|
end
|
|
837
|
-
Rails.logger.debug("Response JSON: #{output_json}") if debug && output_json.present?
|
|
838
1151
|
|
|
839
|
-
|
|
1152
|
+
return [output_json, response]
|
|
840
1153
|
rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
|
|
841
1154
|
if self.class.to_s == 'ZuoraAPI::Oauth' && ex.message.include?("Authentication type is not supported by this Login")
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
Rails.logger.debug("Rest Call - Session Bad Auth type")
|
|
845
|
-
raise ex
|
|
1155
|
+
session_type = :bearer
|
|
1156
|
+
retry
|
|
846
1157
|
end
|
|
1158
|
+
Rails.logger.debug("Rest Call - Session Bad Auth type")
|
|
1159
|
+
raise ex
|
|
1160
|
+
|
|
847
1161
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
|
848
|
-
if !
|
|
1162
|
+
if !tries.zero? && z_session
|
|
1163
|
+
tries -= 1
|
|
849
1164
|
Rails.logger.debug("Rest Call - Session Invalid #{session_type}")
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
else
|
|
856
|
-
return [output_json, response]
|
|
1165
|
+
|
|
1166
|
+
begin
|
|
1167
|
+
self.new_session(auth_type: session_type)
|
|
1168
|
+
rescue *ZUORA_API_ERRORS => ex
|
|
1169
|
+
return [output_json, ex.response]
|
|
857
1170
|
end
|
|
1171
|
+
|
|
1172
|
+
retry
|
|
858
1173
|
end
|
|
1174
|
+
|
|
1175
|
+
raise ex if errors.include?(ex.class)
|
|
1176
|
+
return [output_json, response]
|
|
1177
|
+
|
|
859
1178
|
rescue *ZUORA_API_ERRORS => ex
|
|
860
|
-
if errors.include?(ex.class)
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1179
|
+
raise ex if errors.include?(ex.class)
|
|
1180
|
+
|
|
1181
|
+
response = ex.response unless response
|
|
1182
|
+
return [output_json, response]
|
|
1183
|
+
|
|
865
1184
|
rescue ZuoraAPI::Exceptions::BadEntityError => ex
|
|
866
1185
|
raise ex
|
|
867
1186
|
rescue *CONNECTION_EXCEPTIONS => ex
|
|
868
|
-
if !
|
|
869
|
-
|
|
870
|
-
|
|
1187
|
+
if !tries.zero?
|
|
1188
|
+
tries -= 1
|
|
1189
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
|
1190
|
+
sleep(timeout_sleep_interval)
|
|
871
1191
|
retry
|
|
872
|
-
else
|
|
873
|
-
raise ex
|
|
874
1192
|
end
|
|
1193
|
+
|
|
1194
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
|
1195
|
+
raise ex
|
|
1196
|
+
|
|
875
1197
|
rescue *CONNECTION_READ_EXCEPTIONS => ex
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
retry
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
else
|
|
887
|
-
raise ex
|
|
1198
|
+
|
|
1199
|
+
if !tries.zero?
|
|
1200
|
+
tries -= 1
|
|
1201
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out will retry after #{timeout_sleep_interval} seconds", level: :debug)
|
|
1202
|
+
if ex.is_a?(Errno::ECONNRESET) && ex.message.include?('SSL_connect')
|
|
1203
|
+
retry
|
|
1204
|
+
elsif timeout_retry
|
|
1205
|
+
sleep(timeout_sleep_interval)
|
|
1206
|
+
retry
|
|
1207
|
+
end
|
|
888
1208
|
end
|
|
1209
|
+
|
|
1210
|
+
self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
|
|
1211
|
+
ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.instance_of?(Net::ReadTimeout)
|
|
1212
|
+
raise ex
|
|
1213
|
+
|
|
889
1214
|
rescue => ex
|
|
890
1215
|
raise ex
|
|
891
|
-
|
|
892
|
-
|
|
1216
|
+
ensure
|
|
1217
|
+
self.error_logger(ex) if defined?(ex) && Rails.logger.class.to_s == "Ougai::Logger"
|
|
893
1218
|
end
|
|
894
1219
|
|
|
895
1220
|
def update_create_tenant
|
|
@@ -911,8 +1236,9 @@ module ZuoraAPI
|
|
|
911
1236
|
while !response["nextPage"].blank?
|
|
912
1237
|
url = self.rest_endpoint(response["nextPage"].split('/v1/').last)
|
|
913
1238
|
Rails.logger.debug("Fetch Catalog URL #{url}")
|
|
914
|
-
output_json, response = self.rest_call(:
|
|
915
|
-
|
|
1239
|
+
output_json, response = self.rest_call(debug: false, url: url, timeout_retry: true)
|
|
1240
|
+
|
|
1241
|
+
if !/(true|t|yes|y|1)$/.match(output_json['success'].to_s) || output_json['success'].class != TrueClass
|
|
916
1242
|
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}", response)
|
|
917
1243
|
end
|
|
918
1244
|
output_json["products"].each do |product|
|
|
@@ -938,9 +1264,11 @@ module ZuoraAPI
|
|
|
938
1264
|
return products, catalog_map
|
|
939
1265
|
end
|
|
940
1266
|
|
|
941
|
-
def get_file(url: nil, headers: {}, z_session: true, tempfile: true, output_file_name: nil, add_timestamp: true, file_path: defined?(Rails.root.join('tmp')) ? Rails.root.join('tmp') : Pathname.new(Dir.pwd), timeout_retries: 3, timeout:
|
|
1267
|
+
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)
|
|
942
1268
|
raise "file_path must be of class Pathname" if file_path.class != Pathname
|
|
943
1269
|
|
|
1270
|
+
retry_count ||= timeout_retries
|
|
1271
|
+
|
|
944
1272
|
#Make sure directory exists
|
|
945
1273
|
require 'fileutils'
|
|
946
1274
|
FileUtils.mkdir_p(file_path) unless File.exists?(file_path)
|
|
@@ -955,8 +1283,9 @@ module ZuoraAPI
|
|
|
955
1283
|
headers = headers.merge({"Zuora-Entity-Ids" => self.entity_id}) if !self.entity_id.blank?
|
|
956
1284
|
end
|
|
957
1285
|
|
|
1286
|
+
headers['Zuora-Track-Id'] = zuora_track_id if zuora_track_id.present?
|
|
1287
|
+
|
|
958
1288
|
response_save = nil
|
|
959
|
-
retry_count ||= timeout_retries
|
|
960
1289
|
http.request_get(uri.request_uri, headers) do |response|
|
|
961
1290
|
response_save = response
|
|
962
1291
|
status_code = response.code if response
|
|
@@ -1040,36 +1369,42 @@ module ZuoraAPI
|
|
|
1040
1369
|
return file_handle
|
|
1041
1370
|
when Net::HTTPUnauthorized
|
|
1042
1371
|
if z_session
|
|
1043
|
-
|
|
1372
|
+
unless (retry_count -= 1).zero?
|
|
1044
1373
|
self.new_session
|
|
1045
|
-
raise
|
|
1046
|
-
else
|
|
1047
|
-
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
|
|
1374
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError, 'Retrying'
|
|
1048
1375
|
end
|
|
1376
|
+
raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
|
|
1377
|
+
end
|
|
1378
|
+
raise
|
|
1379
|
+
when Net::HTTPNotFound
|
|
1380
|
+
if url.include?(self.fileURL)
|
|
1381
|
+
raise ZuoraAPI::Exceptions::FileDownloadError.new(
|
|
1382
|
+
"The current tenant does not have a file with id '#{url.split('/').last}'"
|
|
1383
|
+
)
|
|
1049
1384
|
else
|
|
1050
|
-
raise
|
|
1385
|
+
raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
|
|
1051
1386
|
end
|
|
1052
1387
|
else
|
|
1053
|
-
raise
|
|
1388
|
+
raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
|
|
1054
1389
|
end
|
|
1055
1390
|
end
|
|
1056
|
-
|
|
1057
|
-
rescue => ex
|
|
1391
|
+
|
|
1392
|
+
rescue => ex
|
|
1058
1393
|
sleep(5)
|
|
1059
1394
|
if (retry_count -= 1) >= 0
|
|
1060
1395
|
retry
|
|
1061
|
-
else
|
|
1062
|
-
Rails.logger.error("File Download Failed")
|
|
1063
|
-
raise
|
|
1064
1396
|
end
|
|
1397
|
+
Rails.logger.error("File Download Failed")
|
|
1398
|
+
raise
|
|
1065
1399
|
end
|
|
1066
1400
|
|
|
1067
|
-
def getDataSourceExport(query, extract: true, encrypted: false, zip: true)
|
|
1401
|
+
def getDataSourceExport(query, extract: true, encrypted: false, zip: true, z_track_id: "")
|
|
1402
|
+
tries ||= 3
|
|
1068
1403
|
request = Nokogiri::XML::Builder.new do |xml|
|
|
1069
1404
|
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
|
|
1070
1405
|
xml['SOAP-ENV'].Header do
|
|
1071
1406
|
xml['ns1'].SessionHeader do
|
|
1072
|
-
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic)
|
|
1407
|
+
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
|
|
1073
1408
|
end
|
|
1074
1409
|
end
|
|
1075
1410
|
xml['SOAP-ENV'].Body do
|
|
@@ -1086,17 +1421,20 @@ module ZuoraAPI
|
|
|
1086
1421
|
end
|
|
1087
1422
|
end
|
|
1088
1423
|
|
|
1089
|
-
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 =>
|
|
1424
|
+
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)
|
|
1090
1425
|
|
|
1091
1426
|
output_xml = Nokogiri::XML(response_query.body)
|
|
1092
|
-
|
|
1427
|
+
raise_errors(type: :SOAP, body: output_xml, response: response_query) if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
|
|
1428
|
+
|
|
1429
|
+
# raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
|
|
1430
|
+
|
|
1093
1431
|
id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
|
|
1094
1432
|
|
|
1095
1433
|
confirmRequest = Nokogiri::XML::Builder.new do |xml|
|
|
1096
1434
|
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
|
|
1097
1435
|
xml['SOAP-ENV'].Header do
|
|
1098
1436
|
xml['ns1'].SessionHeader do
|
|
1099
|
-
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic)
|
|
1437
|
+
xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
|
|
1100
1438
|
end
|
|
1101
1439
|
end
|
|
1102
1440
|
xml['SOAP-ENV'].Body do
|
|
@@ -1110,12 +1448,14 @@ module ZuoraAPI
|
|
|
1110
1448
|
|
|
1111
1449
|
while result != "Completed"
|
|
1112
1450
|
sleep 3
|
|
1113
|
-
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 =>
|
|
1451
|
+
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)
|
|
1114
1452
|
|
|
1115
1453
|
output_xml = Nokogiri::XML(response_query.body)
|
|
1116
1454
|
result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
|
|
1117
1455
|
status_code = response_query.code if response_query
|
|
1118
|
-
|
|
1456
|
+
|
|
1457
|
+
raise_errors(type: :SOAP, body: output_xml, response: response_query) if result.blank? || result == "Failed"
|
|
1458
|
+
# raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if result.blank? || result == "Failed"
|
|
1119
1459
|
end
|
|
1120
1460
|
|
|
1121
1461
|
file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
|
|
@@ -1134,10 +1474,39 @@ module ZuoraAPI
|
|
|
1134
1474
|
else
|
|
1135
1475
|
return export_file_path
|
|
1136
1476
|
end
|
|
1477
|
+
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
|
1478
|
+
if !(tries -= 1).zero?
|
|
1479
|
+
Rails.logger.info("Export call failed - Trace ID: #{z_track_id}")
|
|
1480
|
+
self.new_session
|
|
1481
|
+
retry
|
|
1482
|
+
end
|
|
1483
|
+
raise ex
|
|
1484
|
+
|
|
1485
|
+
rescue ZuoraAPI::Exceptions::ZuoraUnexpectedError => ex
|
|
1486
|
+
if !(tries -= 1).zero?
|
|
1487
|
+
Rails.logger.info("Trace ID: #{z_track_id} UnexpectedError, will retry after 10 seconds")
|
|
1488
|
+
sleep 10
|
|
1489
|
+
retry
|
|
1490
|
+
end
|
|
1491
|
+
raise ex
|
|
1492
|
+
|
|
1493
|
+
rescue *ZUORA_API_ERRORS => ex
|
|
1494
|
+
raise ex
|
|
1495
|
+
|
|
1496
|
+
rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
|
|
1497
|
+
if !(tries -= 1).zero?
|
|
1498
|
+
Rails.logger.info("Trace ID: #{z_track_id} Timed out will retry after 5 seconds")
|
|
1499
|
+
sleep 5
|
|
1500
|
+
retry
|
|
1501
|
+
end
|
|
1502
|
+
raise ex
|
|
1503
|
+
|
|
1504
|
+
rescue ZuoraAPI::Exceptions::BadEntityError => ex
|
|
1505
|
+
raise ex
|
|
1137
1506
|
end
|
|
1138
1507
|
|
|
1139
1508
|
def query(query, parse = false)
|
|
1140
|
-
output_xml, input_xml = self.soap_call(
|
|
1509
|
+
output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true) do |xml|
|
|
1141
1510
|
xml['ns1'].query do
|
|
1142
1511
|
xml['ns1'].queryString query
|
|
1143
1512
|
end
|
|
@@ -1199,5 +1568,17 @@ module ZuoraAPI
|
|
|
1199
1568
|
end
|
|
1200
1569
|
return "failure"
|
|
1201
1570
|
end
|
|
1571
|
+
|
|
1572
|
+
def reset_files(body)
|
|
1573
|
+
return unless body.is_a? Hash
|
|
1574
|
+
|
|
1575
|
+
body.transform_values! do |v|
|
|
1576
|
+
if v.is_a?(File)
|
|
1577
|
+
v.reopen(v.path)
|
|
1578
|
+
else
|
|
1579
|
+
v
|
|
1580
|
+
end
|
|
1581
|
+
end
|
|
1582
|
+
end
|
|
1202
1583
|
end
|
|
1203
1584
|
end
|