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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f857a810633aad60e80293f6400b9fa8fab38bff15537589e4ffe0ef2aca808d
4
- data.tar.gz: d4512de5de2f7eb888dc32ca7d283fee4f366ad7a96c8dc619bd8ebd83b8f55d
3
+ metadata.gz: 804cba875550e86bca336f5ca155978460b16957f9269f3d677f0e823027d87c
4
+ data.tar.gz: 62f53c8d42f6dfbf8de530d5bf9a90f941eb8369c82fde27afff841445ca8c23
5
5
  SHA512:
6
- metadata.gz: 5d2a72796a2b442627768876ac6954b10aff3be6ea61b11375f4bd2d305b970c5887d26dbf0962b8876bb7575cfcf9a3c49e56d0a8ae0d6fe71ed4078fe04bd6
7
- data.tar.gz: c957560ae8e70276ff8c9fd6d2e9777af145d837bbb93ee1114f36ec7946ab59033820113b93872c4b42855be40c86e04a979255f839908facc9fb4ea4faa350
6
+ metadata.gz: 9cc2ccd120c5bc4ca6af6b1048239d49c6b4a714c604d996ec08e83a25a2769e357b49c70c43f3c8c6bfdf631dcb7de7ff249b137e197746c5cdda4f6ef7d188
7
+ data.tar.gz: 42761bc535e218762be87e37d32b5a0fcd43ecd7230a9b88b83d0749b0764b675355398aee3ecf10ac7bcdcdf0e2bf9e01bb602e59d65d63c8028840462274e1
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .idea
11
+ .DS_Store
@@ -1,21 +1,8 @@
1
- image: ruby:2.6
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
- rspec-testing:
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
- - bundle install
37
- - rspec
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/" /Connect/zuora-gem/lib/zuora_api/version.rb
52
- - git add /Connect/zuora-gem/lib/zuora_api/version.rb
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"
@@ -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
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
-
2
+ ruby "2.7.1"
3
3
  # Specify your gem's dependencies in zuora.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Zuora Gem
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/zuora_api.svg)](https://badge.fury.io/rb/zuora_api) [![coverage report](https://gitlab.zuora.com/Connect/zuora-gem/badges/master/coverage.svg)](https://gitlab.zuora.com/Connect/zuora-gem/commits/master)
3
+ [![Gem Version](https://badge.fury.io/rb/zuora_api.svg)](https://badge.fury.io/rb/zuora_api) [![coverage report](https://gitlab.0.ecc.auw2.zuora/extension-products/shared-libraries/zuora-gem/badges/master/coverage.svg)](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:
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "rails", "~> 5.0.0"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "rails", "~> 5.1.0"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "rails", "~> 5.2.0"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "rails", "~> 6.0.0"
@@ -190,12 +190,11 @@ module InsightsAPI
190
190
  end
191
191
 
192
192
  rescue => ex
193
- if !(tries -= 1).zero?
194
- sleep 3
195
- retry
196
- else
197
- raise ex
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
@@ -1,14 +1,38 @@
1
1
  module ZuoraAPI
2
2
  module Exceptions
3
- class Error < StandardError; end
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.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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, *args)
62
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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, *args)
78
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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, *args)
94
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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 ZuoraAPITemporaryError < Error
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.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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, *args)
126
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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, *args)
142
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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, *args)
158
- @code = response.present? && response.class.to_s == "HTTParty::Response" ? response.code : nil
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
 
@@ -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
- MIN_Endpoint = '96.0'
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
- if !/apps\/services\/a\/\d{2}\.\d$/.match(url.strip)
43
- self.url = "https://#{hostname}/apps/services/a/#{MIN_Endpoint}"
44
- elsif MIN_Endpoint.to_f > url.scan(/(\d{2}\.\d)$/).dig(0,0).to_f
45
- self.url = url.gsub(/(\d{2}\.\d)$/, MIN_Endpoint)
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
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
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
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
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
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
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
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
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
- "Services"=> ""},
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[zuora_client.region][zuora_client.environment].insert(-1, path)
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.url.blank?
288
- if /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/ === self.hostname
256
+ if !self.hostname.blank?
257
+ case self.hostname
258
+ when /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/
289
259
  self.environment = 'Sandbox'
290
- elsif /(?<=\.|\/|^)(service[\d]*|services[\d]*|ep-edge)(?=\.|\/|$)/ === self.hostname
260
+ when /(?<=\.|\/|^)(service[\d]*|services[\d]*|ep-edge)(?=\.|\/|$)/
291
261
  self.environment = 'Services'
292
- elsif /(?<=\.|\/|-|^)(pt[\d]*)(?=\.|\/|-|$)/ === self.hostname
262
+ when /(?<=\.|\/|-|^)(pt[\d]*)(?=\.|\/|-|$)/
293
263
  self.environment = 'Performance'
294
- elsif /(?<=\.|\/|^)(staging1|staging2|stg)(?=\.|\/|$)/ === self.hostname
264
+ when /(?<=\.|\/|^)(staging1|staging2|stg)(?=\.|\/|$)/
295
265
  self.environment = 'Staging'
296
- elsif is_prod_env
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
- region = update_region
318
- environment = update_environment
319
- mappings = {"US" => {"Sandbox" => "ZConnectSbx", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev", "Services" => "ZConnectQA", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Staging" => "ZConnectQA"},
320
- "NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectQANA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
321
- "EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectQAEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU"},
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
- case self.region
344
- when 'US'
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
- case self.region
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/v1/".concat(url)
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}/v1/#{url}"
316
+ endpoint = "#{https}rest#{host}"
366
317
  when 'Staging'
367
- endpoint = "https://rest-staging2.zuora.com/v1/".concat(url)
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(self.rest_endpoint).host
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 if self.status == 'Active'
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
- raise "No Zuora Login Specified"
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(ns1: 'ns1', ns2: 'ns2', batch_size: nil, single_transaction: false, debug: false, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError].concat(ZUORA_API_ERRORS), z_session: true, timeout_retry: false, timeout: 120,**keyword_args)
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
- xml["#{ns1}"].SessionHeader do
432
- xml["#{ns1}"].session self.get_session(prefix: false, auth_type: :basic)
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
- response = HTTParty.post(self.url,:body => xml.doc.to_xml(:save_with => XML_SAVE_OPTIONS).strip, :headers => {'Content-Type' => "text/xml; charset=utf-8"}, :timeout => timeout)
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 !(tries -= 1).zero? && z_session
474
+ raise if skip_session
475
+ if !tries.zero? && z_session
476
+ tries -= 1
462
477
  Rails.logger.debug("SOAP Call - Session Invalid")
463
- self.new_session(auth_type: :basic)
464
- retry
465
- else
466
- if errors.include?(ex.class)
467
- raise ex
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
- raise ex
475
- else
476
- return output_xml, input_xml, response
477
- end
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 !(tries -= 1).zero?
480
- Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
481
- sleep(self.timeout_sleep)
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 !(tries -= 1).zero? && timeout_retry
488
- Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
489
- sleep(self.timeout_sleep)
490
- retry
491
- else
492
- raise ex
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
- rescue Errno::ECONNRESET => ex
495
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
496
- retry
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
- raise ex
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
- raise ex
502
- else
503
- return output_xml, input_xml, response
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 downstream host", response)
610
+ raise ZuoraAPI::Exceptions::ZuoraAPIConnectionTimeout.new("Received #{response.code} from 'https://#{rest_domain(endpoint: request_uri)}'", response)
509
611
  end
510
612
 
511
- if response.code == 504
512
- raise ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received 504 from downstream host", response)
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 = body.xpath('//fns:FaultCode', 'fns' =>'http://fault.api.zuora.com/').text
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 error.blank? || message.blank?
526
- error = body.xpath('//ns1:Code', 'ns1' =>'http://api.zuora.com/').text
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
- #Update/Create/Delete Calls with multiple requests and responses
531
- if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
532
- error = []
533
- success = []
534
- body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').each_with_index do |call, object_index|
535
- if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false' && call.xpath('./ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
536
- message = "#{call.xpath('./*/ns1:Code', 'ns1' =>'http://api.zuora.com/').text}::#{call.xpath('./*/ns1:Message', 'ns1' =>'http://api.zuora.com/').text}"
537
- error.push(message)
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
- success.push(call.xpath('./ns1:Id', 'ns1' =>'http://api.zuora.com/').text)
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
- case error
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[0].include?("LOCK_COMPETITION") && error.count == 1
573
- raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(error.group_by {|v| v}.map {|k,v| "(#{v.size}x) - #{k == "::" ? 'UNKNOWN::No error provided' : k}"}.join(', '), response)
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
- if response.code == 429
581
- 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)
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
- messages_array = body.fetch("reasons", []).map {|error| error['message']}.compact
588
- messages_array = messages_array.push(body.dig("error", 'message')).compact if body.dig('error').class == Hash
589
- codes_array = body.fetch("reasons", []).map {|error| error['code']}.compact
590
- codes_array = codes_array.push(body.dig("error", 'code')).compact if body.dig('error').class == Hash
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['reason'] == 'Forbidden' && body['status'] == 403 && response.code == 403
606
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("Forbidden", response)
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
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
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
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{messages_array.join(', ')}", response)
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
- case body["faultcode"]
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
- errors = []
655
- (body["Errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
656
- (body["errors"] || []).select { |obj| errors.push(obj["Message"]) }.compact
657
- if errors.size > 0
658
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{errors.join(", ")}", response, errors)
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
- if response.code == 429
688
- 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)
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
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{response.message}", response)
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 => 120)
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 !(tries -= 1).zero?
775
- Rails.logger.info("Describe - #{ex.class} Timed out will retry after 5 seconds")
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 !(tries -= 1).zero? && self.status == 'Active'
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: 120,
1100
+ timeout: 130,
1101
+ timeout_sleep_interval: self.timeout_sleep,
807
1102
  multipart: false,
808
- **keyword_args
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 = z_session ? {"Authorization" => self.get_session(prefix: true, auth_type: session_type) } : {}
820
- modified_headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(authentication_headers).merge(headers)
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
- response = HTTParty::Request.new(
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
- output_json = JSON.parse(response.body)
834
- rescue JSON::ParserError => ex
835
- output_json = {}
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
- raise_errors(type: :JSON, body: output_json, response: response)
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
- self.rest_call(method: method.to_sym, url: url, body: body, debug: debug, errors: errors, z_session: z_session, session_type: :bearer, timeout_retry: timeout_retry, timeout: timeout)
843
- else
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 !(tries -= 1).zero? && z_session
1162
+ if !tries.zero? && z_session
1163
+ tries -= 1
849
1164
  Rails.logger.debug("Rest Call - Session Invalid #{session_type}")
850
- self.new_session(auth_type: session_type)
851
- retry
852
- else
853
- if errors.include?(ex.class)
854
- raise ex
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
- raise ex
862
- else
863
- return [output_json, response]
864
- end
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 !(tries -= 1).zero?
869
- Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
870
- sleep(self.timeout_sleep)
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
- if !(tries -= 1).zero? && timeout_retry
877
- Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
878
- sleep(self.timeout_sleep)
879
- retry
880
- else
881
- raise ex
882
- end
883
- rescue Errno::ECONNRESET => ex
884
- if !(tries -= 1).zero? && ex.message.include?('SSL_connect')
885
- retry
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
- else
892
- return [output_json, response]
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(:debug => false, :url => url, :errors => [ZuoraAPI::Exceptions::ZuoraAPISessionError], :timeout_retry => true )
915
- if !output_json['success'] =~ (/(true|t|yes|y|1)$/i) || output_json['success'].class != TrueClass
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: 120, session_type: :basic, **execute_params)
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
- if !(retry_count -= 1).zero?
1372
+ unless (retry_count -= 1).zero?
1044
1373
  self.new_session
1045
- raise response.class
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 StandardError.new("File Download Failed #{response.class}")
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 => 120)
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
- raise 'Export Creation Unsuccessful : ' + output_xml.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
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 => 120)
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
- raise "Export Creation Unsuccessful : #{output_xml.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text}" if result.blank? || result == "Failed"
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({:debug => false, :timeout_retry => true}) do |xml|
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