zuora_api 1.6.3a → 1.6.3b

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
- SHA1:
3
- metadata.gz: c292b68d4516c064e818be8fab8dede9b4991f2a
4
- data.tar.gz: 766d2919def5cc16c08ffd787ac5e02efc2102cf
2
+ SHA256:
3
+ metadata.gz: 7a5f3d85642928cb6296595c0d644bb39a4a91543d08536ce8cd500cfb848b60
4
+ data.tar.gz: bd2b2670f4997b05ddd9ad8f0be6be3c5376b5b373d68d695491d9d7a22249fe
5
5
  SHA512:
6
- metadata.gz: be8d9fb020d087c989174466c52a0f9bd7c9188896a9a2f5dd6df84faba550c45950bdd9f1ad0179e5ae4fde8020b1aa6ea7f2aa7232820aecdc344cf0da0c24
7
- data.tar.gz: 7987debce7355e853c3979b334ee83c27e8a5ccac83b9f2d3985fa703748759e36a6686967771d434bda6d006fd57222af197a846168a35af13cd12b72a0bcc1
6
+ metadata.gz: 59356375be3c396d05309b29859ece9d961312ad774abeb5f0138f89a29e004371cce349315ec53b8bf16230dd679e106bae18f629a9cadd0522d2c44dfcc096
7
+ data.tar.gz: e2b094fe4d063f7719a2f428290111e20e7e505a709522e71b885f662459534e2eb914c80e1e3d4635f9dc266cabf0d35fcae0b25495a50e4dc56dc8be85734e
data/.gitlab-ci.yml CHANGED
@@ -1,5 +1,4 @@
1
-
2
- image: ruby:2.3.1
1
+ image: ruby:2.6
3
2
  stages:
4
3
  - setup
5
4
  - test
@@ -41,8 +40,14 @@ rubygems-deploy:
41
40
  stage: deploy
42
41
  allow_failure: false
43
42
  script:
43
+ - echo "deb http://ftp.us.debian.org/debian testing main contrib non-free" >> /etc/apt/sources.list
44
+ - apt-get update
45
+ - apt-get install -y git
46
+ - apt-get clean all
47
+ - gem install dpl
44
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
45
- - if [[ "master" == $CI_BUILD_REF_SLUG ]];then export VERSION=`git describe --match "[0-9]*\.[0-9]*\.[0-9]*" --abbrev=0 --tags HEAD`; fi
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
+ - echo $VERSION
46
51
  - sed -i "s/0.0.1/$VERSION/" /Connect/zuora-gem/lib/zuora_api/version.rb
47
52
  - git add /Connect/zuora-gem/lib/zuora_api/version.rb
48
53
  - git config --global user.email "connect@zuora.com"
@@ -53,7 +58,6 @@ rubygems-deploy:
53
58
  - version=$(rake install | grep -o 'pkg/zuora_api-.*gem')
54
59
  - curl -u $USERNAME:$PASSWORD https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
55
60
  - gem push $version
56
- - dpl --provider=rubygems --api-key=$API_KEY --gem=zuora_connect
57
61
  only:
58
62
  - master
59
- - staging
63
+ - staging
data/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ ## [1.6.28] - 2018-3-12
5
+ ### Added
6
+ - Way to avoid force encoding for filedownload
7
+
8
+ ## [1.6.22] - 2019-01-03
9
+ ### Changed
10
+ - get_identity method - supports ZSession auth now
11
+ - updated rspecs accordingly
12
+
13
+ ## [1.6.18] - 2018-12-06
14
+ ### Added
15
+ - zconnect_provider attribute accessor for identifying ZConnect cookies
16
+ - Methods for Hallway integration:
17
+ - get_identity
18
+ - get_full_nav
19
+ - set_nav
20
+ - refresh_nav
21
+ - get_oauth_client
22
+
23
+ ### Changed
24
+ - The way environment and region are set
25
+
26
+
@@ -42,7 +42,7 @@ module InsightsAPI
42
42
  if status['status']== "COMPLETE"
43
43
  signedUrl = status['signedUrl']
44
44
  return status
45
- elsif status['status'] == "FAILED"
45
+ elsif status['status'] == "FAILED" || status['status'] == "ERROR"
46
46
  return status
47
47
  else
48
48
  sleep(60)
@@ -86,6 +86,21 @@ module ZuoraAPI
86
86
  end
87
87
  end
88
88
 
89
+ class ZuoraAPITemporaryError < Error
90
+ attr_reader :code, :response
91
+ attr_writer :default_message
92
+
93
+ def initialize(message = nil,response=nil, code =nil)
94
+ @code = code
95
+ @message = message
96
+ @response = response
97
+ @default_message = "There is a temporary error with zuora system."
98
+ end
99
+
100
+ def to_s
101
+ @message || @default_message
102
+ end
103
+ end
89
104
 
90
105
  class ZuoraAPIAuthenticationTypeError < Error
91
106
  attr_reader :code, :response
@@ -4,22 +4,203 @@ require "uri"
4
4
 
5
5
  module ZuoraAPI
6
6
  class Login
7
- ENVIRONMENTS = [SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown' ]
8
- REGIONS = [EU = 'EU', US = 'US' ]
9
- XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
10
- attr_accessor :region, :url, :wsdl_number, :current_session, :environment, :status, :errors, :current_error, :user_info, :tenant_id, :tenant_name, :entity_id, :timeout_sleep
7
+ ENVIRONMENTS = [SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
8
+ REGIONS = [EU = 'EU', US = 'US', NA = 'NA' ]
9
+ MIN_Endpoint = '91.0'
10
+ XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
11
+ CONNECTION_EXCEPTIONS = [Net::OpenTimeout, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, SocketError]
12
+ CONNECTION_READ_EXCEPTIONS = [Net::ReadTimeout, Errno::ECONNRESET, Errno::EPIPE]
13
+
14
+ attr_accessor :region, :url, :wsdl_number, :current_session, :environment, :status, :errors, :current_error, :user_info, :tenant_id, :tenant_name, :entity_id, :timeout_sleep, :hostname, :zconnect_provider
11
15
 
12
16
  def initialize(url: nil, entity_id: nil, session: nil, status: nil, **keyword_args)
13
- @url = url
14
- @entity_id = entity_id
17
+ raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
18
+ # raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
19
+ @url = url.gsub(/(\d{2}\.\d)$/, MIN_Endpoint)
20
+ @hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
21
+ @entity_id = get_entity_id(entity_id: entity_id)
15
22
  @errors = Hash.new
16
23
  @current_session = session
17
24
  @status = status.blank? ? "Active" : status
18
25
  @user_info = Hash.new
26
+ self.update_region
19
27
  self.update_environment
28
+ self.update_zconnect_provider
20
29
  @timeout_sleep = 5
21
30
  end
22
31
 
32
+ def get_identity(cookies)
33
+ zsession = cookies["ZSession"]
34
+ zconnect_accesstoken = get_zconnect_accesstoken(cookies)
35
+ begin
36
+ if !zsession.blank?
37
+ response = HTTParty.get("https://#{self.hostname}/apps/v1/identity", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
38
+ output_json = JSON.parse(response.body)
39
+ elsif !zconnect_accesstoken.blank?
40
+ code = zconnect_accesstoken.split("#!").last
41
+ encrypted_token, tenant_id = Base64.decode64(code).split(":")
42
+ begin
43
+ body = {'token' => encrypted_token}.to_json
44
+ rescue => ex
45
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Invalid ZConnect Cookie", {}, 400)
46
+ end
47
+ response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/identity", :body => body, :headers => { 'Content-Type' => 'application/json' })
48
+ output_json = JSON.parse(response.body)
49
+ else
50
+ if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
51
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}", {}, 400)
52
+ else
53
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present", {}, 400)
54
+ end
55
+ end
56
+ rescue JSON::ParserError => ex
57
+ output_json = {}
58
+ end
59
+ raise_errors(type: :JSON, body: output_json, response: response)
60
+ return output_json
61
+ end
62
+
63
+ def get_full_nav(cookies)
64
+ zsession = cookies["ZSession"]
65
+ zconnect_accesstoken = get_zconnect_accesstoken(cookies)
66
+ begin
67
+ if !zsession.blank?
68
+ response = HTTParty.get("https://#{self.hostname}/apps/v1/navigation", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
69
+ output_json = JSON.parse(response.body)
70
+ elsif !zconnect_accesstoken.blank?
71
+ response = HTTParty.get("https://#{self.hostname}/apps/zconnectsession/navigation", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}",'Content-Type' => 'application/json'})
72
+ output_json = JSON.parse(response.body)
73
+ else
74
+ if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
75
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}", {}, 400)
76
+ else
77
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present", {}, 400)
78
+ end
79
+ end
80
+ rescue JSON::ParserError => ex
81
+ output_json = {}
82
+ end
83
+ raise_errors(type: :JSON, body: output_json, response: response)
84
+ return output_json
85
+ end
86
+
87
+ def set_nav(state, cookies)
88
+ zsession = cookies["ZSession"]
89
+ zconnect_accesstoken = get_zconnect_accesstoken(cookies)
90
+ begin
91
+ if !zsession.blank?
92
+ response = HTTParty.put("https://#{self.hostname}/apps/v1/preference/navigation", :body => state.to_json, :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
93
+ output_json = JSON.parse(response.body)
94
+ elsif !zconnect_accesstoken.blank?
95
+ response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/navigationstate", :body => state.to_json, :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
96
+ output_json = JSON.parse(response.body)
97
+ else
98
+ if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
99
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}", {}, 400)
100
+ else
101
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present", {}, 400)
102
+ end
103
+ end
104
+ rescue JSON::ParserError => ex
105
+ output_json = {}
106
+ end
107
+ raise_errors(type: :JSON, body: output_json, response: response)
108
+ return output_json
109
+ end
110
+
111
+ def refresh_nav(cookies)
112
+ zsession = cookies["ZSession"]
113
+ zconnect_accesstoken = get_zconnect_accesstoken(cookies)
114
+ begin
115
+ if !zsession.blank?
116
+ response = HTTParty.post("https://#{self.hostname}/apps/v1/navigation/fetch", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
117
+ output_json = JSON.parse(response.body)
118
+ elsif !zconnect_accesstoken.blank?
119
+ response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/refresh-navbarcache", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
120
+ output_json = JSON.parse(response.body)
121
+ else
122
+ if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
123
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}", {}, 400)
124
+ else
125
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present", {}, 400)
126
+ end
127
+ end
128
+ rescue JSON::ParserError => ex
129
+ output_json = {}
130
+ end
131
+ raise_errors(type: :JSON, body: output_json, response: response)
132
+ return output_json
133
+ end
134
+
135
+ def get_zconnect_accesstoken(cookies)
136
+ accesstoken = nil
137
+ self.update_zconnect_provider
138
+ if !cookies[self.zconnect_provider].nil? && !cookies[self.zconnect_provider].empty?
139
+ accesstoken = cookies[self.zconnect_provider]
140
+ end
141
+ return accesstoken
142
+ end
143
+
144
+ def reporting_url(path)
145
+ map = {"US" => {"Sandbox" => "https://zconnectsandbox.zuora.com/api/rest/v1/",
146
+ "Production" => "https://zconnect.zuora.com/api/rest/v1/",
147
+ "Services"=> ""},
148
+ "EU" => {"Sandbox" => "https://zconnect.sandbox.eu.zuora.com/api/rest/v1/",
149
+ "Production" => "https://zconnect.eu.zuora.com/api/rest/v1/",
150
+ "Services"=> ""},
151
+ "NA" => {"Sandbox" => "https://zconnect.sandbox.na.zuora.com/api/rest/v1/",
152
+ "Production" => "https://zconnect.na.zuora.com/api/rest/v1/",
153
+ "Services"=> ""}
154
+ }
155
+ return map[zuora_client.region][zuora_client.environment].insert(-1, path)
156
+ end
157
+
158
+ # There are two ways to call this method. The first way is best.
159
+ # 1. Pass in cookies and optionally custom_authorities, name, and description
160
+ # 2. Pass in user_id, entity_ids, client_id, client_secret, and optionally custom_authorities, name, and description
161
+ # https://intranet.zuora.com/confluence/display/Sunburst/Create+an+OAuth+Client+through+API+Gateway#CreateanOAuthClientthroughAPIGateway-ZSession
162
+ def get_oauth_client (custom_authorities = [], info_name: "No Name", info_desc: "This client was created without a description.", user_id: nil, entity_ids: nil, client_id: nil, client_secret: nil, new_client_id: nil, new_client_secret: nil, cookies: nil)
163
+ authorization = ""
164
+ new_client_id = SecureRandom.uuid if new_client_id.blank?
165
+ new_client_secret = SecureRandom.hex(10) if new_client_secret.blank?
166
+
167
+ if !cookies.nil?
168
+ authorization = cookies["ZSession"]
169
+ authorization = "ZSession-a3N2w #{authorization}"
170
+ if entity_ids.blank? && cookies["ZuoraCurrentEntity"].present?
171
+ entity_ids = Array(cookies["ZuoraCurrentEntity"].unpack("a8a4a4a4a12").join('-'))
172
+ else
173
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Zuora Entity ID not provided", {}, 400)
174
+ end
175
+ if user_id.blank? && cookies["Zuora-User-Id"].present?
176
+ user_id = cookies["Zuora-User-Id"]
177
+ else
178
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Zuora User ID not provided", {}, 400)
179
+ end
180
+ elsif !client_id.nil? && !client_secret.nil?
181
+ bearer_response = HTTParty.post("https://#{self.hostname}/oauth/token", :headers => {'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json'}, :body => {'client_id' => client_id, 'client_secret' => URI::encode(client_secret), 'grant_type' => 'client_credentials'})
182
+ bearer_hash = JSON.parse(bearer_response.body)
183
+ bearer_token = bearer_hash["access_token"]
184
+ authorization = "Bearer #{bearer_token}"
185
+ end
186
+
187
+ if !authorization.blank? && !user_id.blank? && !entity_ids.blank?
188
+ endpoint = self.rest_endpoint("genesis/clients")
189
+ oauth_response = HTTParty.post(endpoint, :headers => {'authorization' => authorization, 'Content-Type' => 'application/json'}, :body => {'clientId' => new_client_id, 'clientSecret' => new_client_secret, 'userId' => user_id, 'entityIds' => entity_ids, 'customAuthorities' => custom_authorities, 'additionalInformation' => {'description' => info_desc, 'name' => info_name}}.to_json)
190
+ output_json = JSON.parse(oauth_response.body)
191
+ if oauth_response.code == 201
192
+ output_json["clientSecret"] = new_client_secret
193
+ return output_json
194
+ elsif oauth_response.code == 401 && !oauth_response.message.blank?
195
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(output_json["message"], {}, oauth_response.code)
196
+ else
197
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(output_json["error"], {}, oauth_response.code)
198
+ end
199
+ else
200
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Insufficient credentials provided", {}, 400)
201
+ end
202
+ end
203
+
23
204
  def self.environments
24
205
  %w(Sandbox Production Services Performance Staging)
25
206
  end
@@ -32,7 +213,8 @@ module ZuoraAPI
32
213
  return {"US" => {"Sandbox" => "https://apisandbox.zuora.com/apps/services/a/",
33
214
  "Production" => "https://www.zuora.com/apps/services/a/",
34
215
  "Performance" => "https://pt1.zuora.com/apps/services/a/",
35
- "Services" => "https://services347.zuora.com/apps/services/a/"},
216
+ "Services" => "https://services347.zuora.com/apps/services/a/",
217
+ "Staging" => "https://staging2.zuora.com/apps/services/a/"},
36
218
  "EU" => {"Sandbox" => "https://sandbox.eu.zuora.com/apps/services/a/",
37
219
  "Production" => "https://eu.zuora.com/apps/services/a/",
38
220
  "Performance" => "https://pt1.eu.zuora.com/apps/services/a/",
@@ -44,43 +226,119 @@ module ZuoraAPI
44
226
  }
45
227
  end
46
228
 
229
+ def get_entity_id(entity_id: nil)
230
+ if entity_id.present?
231
+ entity_match = /[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$/.match(entity_id)
232
+ if entity_match.blank?
233
+ raise "Entity length is wrong." if entity_id.length != 32
234
+ part_one, part_two, part_three, part_four, part_five = [entity_id[0..7], entity_id[8..11], entity_id[12..15], entity_id[16..19], entity_id[20..31]]
235
+ entity_id = "#{part_one}-#{part_two}-#{part_three}-#{part_four}-#{part_five}"
236
+ end
237
+ end
238
+ return entity_id
239
+ end
240
+
241
+ def update_region
242
+ if !self.hostname.blank?
243
+ if /(?<=\.|\/|^)(eu)(?=\.|\/|$)/ === self.hostname
244
+ self.region = "EU"
245
+ elsif /(?<=\.|\/|^)(na)(?=\.|\/|$)/ === self.hostname
246
+ self.region = "NA"
247
+ else
248
+ self.region = "US"
249
+ end
250
+ else # This will never happen
251
+ # raise "Can't update region because URL is blank"
252
+ self.region = "Unknown"
253
+ end
254
+ end
255
+
47
256
  def update_environment
48
257
  if !self.url.blank?
49
- env_path = self.url.split('https://').last.split('.zuora.com').first
50
- self.region = self.url.include?("eu.") ? "EU" : self.url.include?("na.") ? "NA" : "US"
51
- if env_path == 'apisandbox' || self.url.include?('sandbox')
258
+ if /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/ === self.hostname
52
259
  self.environment = 'Sandbox'
53
- elsif env_path == 'www' || env_path == 'api' || self.url.include?('tls10.zuora.com') || self.url.include?('origin-www.zuora.com') || self.url.include?('zforsf.zuora.com') || self.url.include?('https://zuora.com') || self.url.include?('eu.zuora.com') || self.url.include?('https://na.zuora.com')
54
- self.environment = 'Production'
55
- elsif env_path.include?('service') || env_path.include?('ep-edge')
260
+ elsif /(?<=\.|\/|^)(service|services[\d]*|ep-edge)(?=\.|\/|$)/ === self.hostname
56
261
  self.environment = 'Services'
57
- elsif env_path.include?('pt')
262
+ elsif /(?<=\.|\/|-|^)(pt[\d]*)(?=\.|\/|-|$)/ === self.hostname
58
263
  self.environment = 'Performance'
59
- elsif env_path.include?('staging2') || env_path.include?('staging1')
60
- self.region = 'US'
264
+ elsif /(?<=\.|\/|^)(staging1|staging2|stg)(?=\.|\/|$)/ === self.hostname
61
265
  self.environment = 'Staging'
266
+ elsif is_prod_env
267
+ self.environment = 'Production'
62
268
  else
63
269
  self.environment = 'Unknown'
64
270
  end
271
+ else # this will never happen
272
+ raise "Can't determine environment from blank URL"
273
+ end
274
+ end
275
+
276
+ def is_prod_env
277
+ is_prod = false
278
+ www_or_api = /(?<=\.|\/|^)(www|api)(?=\.|\/|$)/ === self.hostname
279
+ host_prefix_match = /(^|tls10\.|origin-www\.|zforsf\.|eu\.|na\.)(zuora\.com)/ === self.hostname
280
+ if www_or_api || host_prefix_match
281
+ is_prod = true
65
282
  end
283
+ return is_prod
284
+ end
285
+
286
+ def update_zconnect_provider
287
+ region = update_region
288
+ environment = update_environment
289
+ mappings = {"US" => {"Sandbox" => "ZConnectSbx", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev", "Services" => "ZConnectQA", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Staging" => "ZConnectQA"},
290
+ "NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectQANA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
291
+ "EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectQAEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU"},
292
+ "Unknown" => {"Unknown" => "Unknown"}}
293
+ self.zconnect_provider = mappings[region][environment]
294
+ # raise "Can't find ZConnect Provider for #{region} region and #{environment} environment" if self.zconnect_provider.nil?
66
295
  end
67
296
 
68
297
  def aqua_endpoint(url="")
69
- return "#{self.url.split("/apps").first}/apps/api/".concat(url)
298
+ match = /.*(\/apps\/)/.match(self.url)
299
+ if !match.nil?
300
+ url_slash_apps_slash = match[0]
301
+ else
302
+ raise "self.url has no /apps in it"
303
+ end
304
+ return "#{url_slash_apps_slash}api/#{url}"
70
305
  end
71
306
 
72
307
  def rest_endpoint(url="")
73
- if self.environment == 'Sandbox'
74
- return self.region == "US" ? "https://rest.apisandbox.zuora.com/v1/".concat(url) : self.region == "EU" ? "https://rest.sandbox.eu.zuora.com/v1/".concat(url) : "https://rest.sandbox.na.zuora.com/v1/".concat(url)
75
- elsif self.environment == 'Production'
76
- return self.region == "US" ? "https://rest.zuora.com/v1/".concat(url) : self.region == "EU" ? "https://rest.eu.zuora.com/v1/".concat(url) : "https://rest.na.zuora.com/v1/".concat(url)
77
- elsif self.environment == 'Services'
78
- return self.url.split('/')[0..2].join('/').concat('/apps/v1/').concat(url)
79
- elsif self.environment == 'Performance'
80
- return self.url.split('/')[0..2].join('/').concat('/apps/v1/').concat(url)
81
- else self.environment == 'Unknown'
82
- return url
308
+ update_environment
309
+ endpoint = url
310
+
311
+ case self.environment
312
+ when 'Sandbox'
313
+ case self.region
314
+ when 'US'
315
+ endpoint = "https://rest.apisandbox.zuora.com/v1/".concat(url)
316
+ when 'EU'
317
+ endpoint = "https://rest.sandbox.eu.zuora.com/v1/".concat(url)
318
+ when 'NA'
319
+ endpoint = "https://rest.sandbox.na.zuora.com/v1/".concat(url)
320
+ end
321
+ when 'Production'
322
+ case self.region
323
+ when 'US'
324
+ endpoint = "https://rest.zuora.com/v1/".concat(url)
325
+ when 'EU'
326
+ endpoint = "https://rest.eu.zuora.com/v1/".concat(url)
327
+ when 'NA'
328
+ endpoint = "https://rest.na.zuora.com/v1/".concat(url)
329
+ end
330
+ when 'Performance'
331
+ endpoint = "https://rest.pt1.zuora.com/v1/".concat(url)
332
+ when 'Services'
333
+ https = /https:\/\/|http:\/\//.match(self.url)[0]
334
+ host = self.hostname
335
+ endpoint = "#{https}#{host}/apps/v1/#{url}"
336
+ when 'Staging'
337
+ endpoint = "https://rest-staging2.zuora.com/".concat(url)
338
+ when 'Unknown'
339
+ raise "Environment unknown, returning passed in parameter unaltered"
83
340
  end
341
+ return endpoint
84
342
  end
85
343
 
86
344
  def fileURL(url="")
@@ -91,15 +349,14 @@ module ZuoraAPI
91
349
  return self.wsdl_number > 68 ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
92
350
  end
93
351
 
94
- def new_session(auth_type: :basic, debug: false)
352
+ def new_session(auth_type: :basic, debug: false)
95
353
  end
96
354
 
97
355
  def get_session(prefix: false, auth_type: :basic)
98
- Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}")
356
+ Rails.logger.debug("Get session for #{auth_type} - #{self.class.to_s}") if Rails.env.to_s == 'development'
99
357
  case auth_type
100
358
  when :basic
101
359
  if self.current_session.blank?
102
- Rails.logger.debug("Create new session")
103
360
  case self.class.to_s
104
361
  when 'ZuoraAPI::Oauth'
105
362
  if self.bearer_token.blank? || self.oauth_expired?
@@ -184,7 +441,15 @@ module ZuoraAPI
184
441
  else
185
442
  return output_xml, input_xml, response
186
443
  end
187
- rescue Net::OpenTimeout, Errno::ECONNRESET, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, SocketError => ex
444
+ rescue *CONNECTION_EXCEPTIONS => ex
445
+ if !(tries -= 1).zero?
446
+ Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
447
+ sleep(self.timeout_sleep)
448
+ retry
449
+ else
450
+ raise ex
451
+ end
452
+ rescue *CONNECTION_READ_EXCEPTIONS => ex
188
453
  if !(tries -= 1).zero? && timeout_retry
189
454
  Rails.logger.info("SOAP Call - #{ex.class} Timed out will retry after 5 seconds")
190
455
  sleep(self.timeout_sleep)
@@ -219,8 +484,7 @@ module ZuoraAPI
219
484
  error = []
220
485
  success = []
221
486
  body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').each_with_index do |call, object_index|
222
-
223
- if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false'
487
+ if call.xpath('./ns1:Success', 'ns1' =>'http://api.zuora.com/').text == 'false' && call.xpath('./ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
224
488
  message = "#{call.xpath('./*/ns1:Code', 'ns1' =>'http://api.zuora.com/').text}::#{call.xpath('./*/ns1:Message', 'ns1' =>'http://api.zuora.com/').text}"
225
489
  error.push(message)
226
490
  else
@@ -232,22 +496,28 @@ module ZuoraAPI
232
496
  #By default response if not passed in for SOAP as all SOAP is 200
233
497
  if error.present?
234
498
  if error.class == String
235
- if error == "INVALID_SESSION"
499
+ case error
500
+ when "INVALID_SESSION"
236
501
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("#{error}::#{message}", body, response.code )
237
- end
238
- if error == "REQUEST_EXCEEDED_LIMIT"
502
+ when "REQUEST_EXCEEDED_LIMIT"
239
503
  raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("#{error}::#{message}", body, response.code)
240
- end
241
- if error == "LOCK_COMPETITION"
504
+ when "LOCK_COMPETITION"
242
505
  raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", body, response.code)
243
- end
244
- if error.present?
245
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", body, response.code)
506
+ when "BATCH_FAIL_ERROR"
507
+ if message.include?("optimistic locking failed")
508
+ raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("#{error}::#{message}", body, response.code)
509
+ else
510
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", body, response.code)
511
+ end
512
+ when "TEMPORARY_ERROR"
513
+ raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new("#{error}::#{message}", body, response.code)
514
+ else
515
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{error}::#{message}", body, response.code) if error.present?
246
516
  end
247
517
  elsif error.class == Array
248
518
  if error[0].include?("LOCK_COMPETITION") && error.count == 1
249
519
  raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new(error.group_by {|v| v}.map {|k,v| "(#{v.size}x) - #{k == "::" ? 'UNKNOWN::No error provided' : k}"}.join(', '), body, response.code)
250
- else
520
+ else
251
521
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(error.group_by {|v| v}.map {|k,v| "(#{v.size}x) - #{k == "::" ? 'UNKNOWN::No error provided' : k}"}.join(', '), body, response.code, error, success)
252
522
  end
253
523
  end
@@ -258,6 +528,7 @@ module ZuoraAPI
258
528
  end
259
529
 
260
530
  when :JSON
531
+ body = body.dig("results").present? ? body["results"] : body if body.class == Hash
261
532
  if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
262
533
  messages_array = (body["reasons"] || []).map {|error| error['message']}.compact
263
534
  codes_array = (body["reasons"] || []).map {|error| error['code']}.compact
@@ -266,6 +537,10 @@ module ZuoraAPI
266
537
  raise ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError.new()
267
538
  end
268
539
 
540
+ if body['errorMessage']
541
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['errorMessage'],body,response.code)
542
+ end
543
+
269
544
  if body.dig("reasons").nil? ? false : body.dig("reasons")[0].dig("code") == 90000020
270
545
  raise ZuoraAPI::Exceptions::BadEntityError.new("#{messages_array.join(', ')}", body, response.code)
271
546
  end
@@ -324,11 +599,18 @@ module ZuoraAPI
324
599
  end
325
600
  end
326
601
 
327
- #Zuora REST Actions error (Create, Update, Delete)
602
+ #Zuora REST Actions error (Create, Update, Delete, Amend)
328
603
  if body.class == Array
329
604
  all_errors = body.select {|obj| !obj['Success'] || !obj['success'] }.map {|obj| obj['Errors'] || obj['errors'] }.compact
330
605
  all_success = body.select {|obj| obj['Success'] || obj['success']}.compact
331
606
 
607
+ if all_success.blank? && all_errors.present?
608
+ error_codes = all_errors.flatten.group_by {|error| error['Code']}.keys.uniq
609
+ if error_codes.size == 1 && error_codes[0] == "LOCK_COMPETITION"
610
+ raise ZuoraAPI::Exceptions::ZuoraAPILockCompetition.new("Retry Lock Competition", body, response.code)
611
+ end
612
+ end
613
+
332
614
  if all_errors.size > 0
333
615
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new("#{all_errors.flatten.group_by {|error| error['Message']}.keys.uniq.join(' ')}", body, response.code, all_errors, all_success )
334
616
  end
@@ -388,9 +670,11 @@ module ZuoraAPI
388
670
 
389
671
  base = self.url.include?(".com") ? self.url.split(".com")[0].concat(".com") : self.url.split(".eu")[0].concat(".eu")
390
672
  url = object ? "#{base}/apps/api/describe/#{object}" : "#{base}/apps/api/describe/"
391
- headers = !self.entity_id.blank? ? {"entityId" => self.entity_id, 'Content-Type' => "text/xml; charset=utf-8"} : {'Content-Type' => "text/xml; charset=utf-8"}
673
+ 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"}
392
674
  response = HTTParty.get(url, headers: {"Authorization" => self.get_session(prefix: true, auth_type: :basic)}.merge(headers), :timeout => 120)
393
675
 
676
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error.present? ? self.current_error : 'Describe call 401' ) if response.code == 401
677
+
394
678
  output_xml = Nokogiri::XML(response.body)
395
679
  des_hash = Hash.new
396
680
  if object == nil
@@ -420,7 +704,7 @@ module ZuoraAPI
420
704
  end
421
705
  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
422
706
  end
423
- rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, SocketError => ex
707
+ rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS) => ex
424
708
  if !(tries -= 1).zero?
425
709
  Rails.logger.info("Describe - #{ex.class} Timed out will retry after 5 seconds")
426
710
  sleep(self.timeout_sleep)
@@ -428,17 +712,30 @@ module ZuoraAPI
428
712
  else
429
713
  raise ex
430
714
  end
715
+ rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
716
+ if !(tries -= 1).zero?
717
+ Rails.logger.info("Session expired. Starting new session.")
718
+ self.new_session
719
+ retry
720
+ else
721
+ raise ex
722
+ end
431
723
  rescue => ex
432
724
  raise ex
433
725
  else
434
726
  return des_hash
435
727
  end
436
728
 
437
- def rest_call(method: :get, body: nil,headers: {}, url: rest_endpoint("catalog/products?pageSize=4"), debug: false, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError, ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition], z_session: true, session_type: :basic, timeout_retry: false, timeout: 120, **keyword_args)
729
+ def rest_call(method: :get, body: nil, headers: {}, url: rest_endpoint("catalog/products?pageSize=4"), debug: false, errors: [ZuoraAPI::Exceptions::ZuoraAPISessionError, ZuoraAPI::Exceptions::ZuoraAPIError, ZuoraAPI::Exceptions::ZuoraAPIRequestLimit, ZuoraAPI::Exceptions::ZuoraAPILockCompetition], z_session: true, session_type: :basic, timeout_retry: false, timeout: 120, **keyword_args)
438
730
  tries ||= 2
439
- headers["entityId"] = self.entity_id if !self.entity_id.blank?
731
+
732
+ if self.entity_id.present?
733
+ headers["Zuora-Entity-Ids"] = self.entity_id if headers.dig("Zuora-Entity-Ids").nil?
734
+ headers.delete_if { |key, value| ["entityId", "entityName"].include?(key.to_s) }
735
+ end
736
+
440
737
  raise "Method not supported, supported methods include: :get, :post, :put, :delete, :patch, :head, :options" if ![:get, :post, :put, :delete, :patch, :head, :options].include?(method)
441
-
738
+
442
739
  authentication_headers = z_session ? {"Authorization" => self.get_session(prefix: true, auth_type: session_type) } : {}
443
740
  headers = {'Content-Type' => "application/json; charset=utf-8"}.merge(headers).merge(authentication_headers)
444
741
 
@@ -455,7 +752,7 @@ module ZuoraAPI
455
752
  rescue ZuoraAPI::Exceptions::ZuoraAPIAuthenticationTypeError => ex
456
753
  if self.class.to_s == 'ZuoraAPI::Oauth'
457
754
  self.rest_call(method: method.to_sym, url: url, debug: debug, errors: errors, z_session: z_session, session_type: :bearer, timeout_retry: timeout_retry, timeout: timeout)
458
- else
755
+ else
459
756
  Rails.logger.debug("Rest Call - Session Bad Auth type")
460
757
  raise ex
461
758
  end
@@ -479,7 +776,15 @@ module ZuoraAPI
479
776
  end
480
777
  rescue ZuoraAPI::Exceptions::BadEntityError => ex
481
778
  raise ex
482
- rescue Net::OpenTimeout, Errno::ECONNRESET, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, SocketError => ex
779
+ rescue *CONNECTION_EXCEPTIONS => ex
780
+ if !(tries -= 1).zero?
781
+ Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
782
+ sleep(self.timeout_sleep)
783
+ retry
784
+ else
785
+ raise ex
786
+ end
787
+ rescue *CONNECTION_READ_EXCEPTIONS => ex
483
788
  if !(tries -= 1).zero? && timeout_retry
484
789
  Rails.logger.info("Rest Call - #{ex.class} Timed out will retry after 5 seconds")
485
790
  sleep(self.timeout_sleep)
@@ -539,7 +844,7 @@ module ZuoraAPI
539
844
  return products, catalog_map
540
845
  end
541
846
 
542
- def get_file(url: nil, headers: {}, count: 3, z_session: true, tempfile: true, file_path: defined?(Rails.root.join('tmp')) ? Rails.root.join('tmp') : Pathname.new(Dir.pwd), timeout_retries: 2, timeout: 120, **execute_params)
847
+ def get_file(url: nil, headers: {}, count: 3, 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: 2, timeout: 120, session_type: :basic, force_encoding: true, **execute_params)
543
848
  raise "file_path must be of class Pathname" if file_path.class != Pathname
544
849
 
545
850
  #Make sure directory exists
@@ -552,100 +857,126 @@ module ZuoraAPI
552
857
  http = Net::HTTP.new(uri.host, uri.port)
553
858
  http.read_timeout = timeout #Seconds
554
859
  http.use_ssl = true if uri.scheme.downcase == 'https'
555
- headers = headers.merge({"Authorization" => self.get_session(prefix: true)}) if z_session
860
+ if z_session
861
+ headers = headers.merge({"Authorization" => self.get_session(prefix: true)})
862
+ headers = headers.merge({"Zuora-Entity-Ids" => self.entity_id}) if !self.entity_id.blank?
863
+ end
556
864
 
557
865
  response_save = nil
558
- http.request_get(uri.path, headers) do |response|
559
- response_save = response
560
- status_code = response.code if response
561
-
562
- case response
563
- when Net::HTTPNotFound
564
- Rails.logger.fatal("404 - Not Found")
565
- raise response
566
-
567
- when Net::HTTPUnauthorized
568
- raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error) if count <= 0
569
- Rails.logger.fatal("Unauthorized: Retry")
570
- self.new_session
571
- return get_file(:url => url, :headers => headers, :count => count - 1, :z_session => z_session, :tempfile => tempfile, :file_path => file_path, :timeout_retries => timeout_retries, :timeout => timeout)
572
-
573
- when Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, SocketError
574
- Rails.logger.fatal("#{response.class} timeout - retry")
575
- return get_file(:url => url, :headers => headers, :count => count, :z_session => z_session, :tempfile => tempfile, :file_path => file_path, :timeout_retries => timeout_retries - 1, :timeout => timeout)
576
-
577
- when Net::HTTPClientError
578
- Rails.logger.fatal("Login: #{self.username} Export")
579
- raise response
580
-
581
- when Net::HTTPOK
582
- headers = {}
583
- response.each_header do |k,v|
584
- headers[k] = v
585
- end
586
- Rails.logger.debug("Headers: #{headers.to_s}")
587
-
588
- size, export_progress = [0, 0]
589
- encoding, type, full_filename = [nil, nil, nil]
590
- if response.header["Content-Disposition"].present?
591
- case response.header["Content-Disposition"]
592
- when /.*; filename\*=.*/
593
- full_filename = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[2].strip
594
- encoding = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[1].strip
595
- when /.*; filename=/
596
- full_filename = /.*; filename=(.*)/.match(response.header["Content-Disposition"])[1].strip
866
+ begin
867
+ http.request_get(uri.request_uri, headers) do |response|
868
+ response_save = response
869
+ status_code = response.code if response
870
+
871
+ case response
872
+ when Net::HTTPNotFound
873
+ Rails.logger.fatal("404 - Not Found")
874
+ raise
875
+
876
+ when Net::HTTPUnauthorized
877
+ if count <= 0
878
+ if z_session
879
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
880
+ else
881
+ raise
882
+ end
597
883
  end
598
- file_ending = ".#{full_filename.partition(".").last}"
599
- end
600
- #If user supplied a filename use it, else default to content header filename, else default to uri pattern
601
- filename = full_filename.present? ? full_filename.split(file_ending).first : File.basename(uri.path).rpartition('.').first
602
-
603
- if response.header["Content-Type"].present?
604
- case response.header["Content-Type"]
605
- when /.*;charset=.*/
606
- type = /(.*);charset=(.*)/.match(response.header["Content-Type"])[1]
607
- encoding = /(.*);charset=(.*)/.match(response.header["Content-Type"])[2]
608
- else
609
- type = response.header["Content-Type"]
610
- encoding ||= 'UTF-8'
884
+ Rails.logger.fatal("Unauthorized: Retry")
885
+ self.new_session if z_session
886
+ return get_file(:url => url, :headers => headers, :count => count - 1, :z_session => z_session, :tempfile => tempfile, :file_path => file_path, :timeout_retries => timeout_retries, :timeout => timeout, :force_encoding => force_encoding)
887
+
888
+ when Net::HTTPClientError
889
+ raise
890
+
891
+ when Net::HTTPOK
892
+ headers = {}
893
+ response.each_header do |k,v|
894
+ headers[k] = v
895
+ end
896
+ Rails.logger.debug("Headers: #{headers.to_s}")
897
+ if output_file_name.present?
898
+ file_ending ||= output_file_name.end_with?(".csv.zip") ? ".csv.zip" : File.extname(output_file_name)
899
+ filename ||= File.basename(output_file_name, file_ending)
611
900
  end
612
- end
613
- Rails.logger.info("File: #{filename}#{file_ending} #{encoding} #{type}")
614
901
 
615
- if response.header["Content-Length"].present?
616
- export_size = response.header["Content-Length"].to_i
617
- elsif response.header["ContentLength"].present?
618
- export_size = response.header["ContentLength"].to_i
619
- end
902
+ size, export_progress = [0, 0]
903
+ encoding, type, full_filename = [nil, nil, nil]
904
+ if response.header["Content-Disposition"].present?
905
+ case response.header["Content-Disposition"]
906
+ when /.*; filename\*=.*/
907
+ full_filename ||= /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[2].strip
908
+ encoding = /.*; filename\*=(.*)''(.*)/.match(response.header["Content-Disposition"])[1].strip
909
+ when /.*; filename=/
910
+ full_filename ||= /.*; filename=(.*)/.match(response.header["Content-Disposition"])[1].strip
911
+ else
912
+ raise "Can't parse Content-Disposition header: #{response.header["Content-Disposition"]}"
913
+ end
914
+ file_ending ||= full_filename.end_with?(".csv.zip") ? ".csv.zip" : File.extname(full_filename)
915
+ filename ||= File.basename(full_filename, file_ending)
916
+ end
620
917
 
621
- file_handle = nil
622
- timestamp = Time.now.to_i
623
- if tempfile
624
- require 'tempfile'
625
- file_handle = ::Tempfile.new(["#{filename}_#{timestamp}", "#{file_ending}"], file_path)
626
- file_handle.binmode
627
- else
628
- file_handle = File.new(file_path.join("#{filename}_#{timestamp}#{file_ending}"), "w+")
918
+ #If user supplied a filename use it, else default to content header filename, else default to uri pattern
919
+ file_ending ||= uri.path.end_with?(".csv.zip") ? ".csv.zip" : File.extname(uri.path)
920
+ filename ||= File.basename(uri.path, file_ending)
921
+
922
+ if response.header["Content-Type"].present?
923
+ case response.header["Content-Type"]
924
+ when /.*;charset=.*/
925
+ type = /(.*);charset=(.*)/.match(response.header["Content-Type"])[1]
926
+ encoding = /(.*);charset=(.*)/.match(response.header["Content-Type"])[2]
927
+ else
928
+ type = response.header["Content-Type"]
929
+ encoding ||= 'UTF-8'
930
+ end
931
+ end
932
+
933
+ if response.header["Content-Length"].present?
934
+ export_size = response.header["Content-Length"].to_i
935
+ elsif response.header["ContentLength"].present?
936
+ export_size = response.header["ContentLength"].to_i
937
+ end
938
+
939
+ Rails.logger.info("File: #{filename}#{file_ending} #{encoding} #{type} #{export_size}")
940
+
941
+ file_prefix = add_timestamp ? "#{filename}_#{Time.now.to_i}" : filename
942
+ if tempfile
943
+ require 'tempfile'
944
+ file_handle = ::Tempfile.new([file_prefix, "#{file_ending}"], file_path)
945
+ else
946
+ file_handle = File.new(file_path.join("#{file_prefix}#{file_ending}"), "w+")
947
+ end
629
948
  file_handle.binmode
630
- end
631
949
 
632
- response.read_body do |chunk|
633
- file_handle << chunk.force_encoding(encoding)
950
+ response.read_body do |chunk|
951
+ if force_encoding
952
+ file_handle << chunk.force_encoding(encoding)
953
+ else
954
+ file_handle << chunk
955
+ end
634
956
 
635
- if defined?(export_size) && export_size != 0 && export_size.class == Integer
636
- size += chunk.size
637
- new_progress = (size * 100) / export_size
638
- unless new_progress == export_progress
639
- Rails.logger.debug("Login: #{self.username} Export Downloading %s (%3d%%)" % [filename, new_progress])
957
+ if defined?(export_size) && export_size != 0 && export_size.class == Integer
958
+ size += chunk.size
959
+ new_progress = (size * 100) / export_size
960
+ unless new_progress == export_progress
961
+ Rails.logger.debug("Login: Export Downloading %s (%3d%%)" % [filename, new_progress])
962
+ end
963
+ export_progress = new_progress
640
964
  end
641
- export_progress = new_progress
642
965
  end
643
- end
644
966
 
645
- file_handle.close
646
- Rails.logger.debug("Filepath: #{file_handle.path} Size: #{File.size(file_handle.path).to_f/1000000} mb")
967
+ file_handle.close
968
+ Rails.logger.debug("Filepath: #{file_handle.path} Size: #{File.size(file_handle.path).to_f/1000000} mb")
647
969
 
648
- return file_handle
970
+ return file_handle
971
+ end
972
+ end
973
+ rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS).concat([Net::HTTPBadResponse]) => e
974
+ sleep(5)
975
+ if (timeout_retries -= 1) >= 0
976
+ Rails.logger.warn("Download Failed: #{e.class} : #{e.message}")
977
+ retry
978
+ else
979
+ raise
649
980
  end
650
981
  end
651
982
  rescue Exception => e
@@ -656,8 +987,6 @@ module ZuoraAPI
656
987
  end
657
988
 
658
989
  def getDataSourceExport(query, extract: true, encrypted: false, zip: true)
659
- Rails.logger.debug("Build export")
660
- Rails.logger.debug("#{query}")
661
990
  request = Nokogiri::XML::Builder.new do |xml|
662
991
  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
663
992
  xml['SOAP-ENV'].Header do
@@ -680,7 +1009,7 @@ module ZuoraAPI
680
1009
  end
681
1010
 
682
1011
  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)
683
-
1012
+
684
1013
  output_xml = Nokogiri::XML(response_query.body)
685
1014
  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"
686
1015
  id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
@@ -704,7 +1033,7 @@ module ZuoraAPI
704
1033
  while result != "Completed"
705
1034
  sleep 3
706
1035
  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)
707
-
1036
+
708
1037
  output_xml = Nokogiri::XML(response_query.body)
709
1038
  result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
710
1039
  status_code = response_query.code if response_query
@@ -712,7 +1041,6 @@ module ZuoraAPI
712
1041
  end
713
1042
 
714
1043
  file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
715
- Rails.logger.debug('=====> Export finished')
716
1044
  export_file = get_file(:url => self.fileURL(file_id))
717
1045
  export_file_path = export_file.path
718
1046
  Rails.logger.debug("=====> Export path #{export_file.path}")
@@ -731,7 +1059,6 @@ module ZuoraAPI
731
1059
  end
732
1060
 
733
1061
  def query(query, parse = false)
734
- Rails.logger.debug("Querying Zuora for #{query}")
735
1062
  output_xml, input_xml = self.soap_call({:debug => false, :timeout_retry => true}) do |xml|
736
1063
  xml['ns1'].query do
737
1064
  xml['ns1'].queryString query
@@ -756,7 +1083,7 @@ module ZuoraAPI
756
1083
  response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
757
1084
  http.request req
758
1085
  end
759
-
1086
+
760
1087
  Rails.logger.debug("Response #{response.code} #{response.message}: #{response.body}")
761
1088
 
762
1089
  result = JSON.parse(response.body)
@@ -27,11 +27,11 @@ module ZuoraAPI
27
27
 
28
28
  input_xml = Nokogiri::XML(request.to_xml(:save_with => XML_SAVE_OPTIONS).strip)
29
29
  input_xml.xpath('//ns1:session', 'ns1' =>'http://api.zuora.com/').children.remove
30
- Rails.logger.debug('Connect') {"Request SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}"} if debug
30
+ Rails.logger.debug('Connect') {"Request Code: #{@response_query.code} SOAP XML: #{input_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}"} if debug
31
31
 
32
32
  @response_query = HTTParty.post(self.url,:body => request.to_xml(:save_with => XML_SAVE_OPTIONS).strip, :headers => {'Content-Type' => "text/xml; charset=utf-8"}, :timeout => 10)
33
33
  @output_xml = Nokogiri::XML(@response_query.body)
34
- Rails.logger.debug('Connect') {"Response SOAP XML: #{@output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}"} if debug
34
+ Rails.logger.debug('Connect') {"Response Code: #{@response_query.code} SOAP XML: #{@output_xml.to_xml(:save_with => XML_SAVE_OPTIONS).strip}"} if debug
35
35
 
36
36
  if !@response_query.success?
37
37
  self.current_session = nil
@@ -70,6 +70,9 @@ module ZuoraAPI
70
70
  self.status = 'Locked'
71
71
  self.current_error = 'Locked user login, please wait or navigate to Zuora to unlock user'
72
72
  self.errors[:username] = self.current_error
73
+ elsif self.current_error.include?('Entity not exist:')
74
+ self.status = 'Entity Missing'
75
+ self.errors[:base] = self.current_error
73
76
  else
74
77
  self.status = 'Unknown'
75
78
  self.current_error = @output_xml.xpath('//faultstring').text if self.current_error.blank?
@@ -85,10 +88,11 @@ module ZuoraAPI
85
88
  #Username & password combo
86
89
  retrieved_session = @output_xml.xpath('//ns1:Session', 'ns1' =>'http://api.zuora.com/').text
87
90
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new("No session found for api call.") if retrieved_session.blank?
91
+ self.status = 'Active'
88
92
  self.current_session = retrieved_session
89
93
  end
90
94
  return self.status
91
- rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
95
+ rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS) => ex
92
96
  if !(tries -= 1).zero?
93
97
  Rails.logger.info {"#{ex.class} Timed out will retry after 5 seconds"}
94
98
  sleep(self.timeout_sleep)
@@ -98,6 +102,12 @@ module ZuoraAPI
98
102
  self.status = 'Timeout'
99
103
  return self.status
100
104
  end
105
+ rescue EOFError
106
+ if self.url.match?(/.*services\d{1,}.zuora.com*/)
107
+ self.current_error = "Services tenant '#{self.url.scan(/.*\/\/(services\d{1,}).zuora.com*/).last.first}' is no longer available."
108
+ self.status = 'Not Available'
109
+ return self.status
110
+ end
101
111
  end
102
112
  end
103
113
  end
@@ -24,9 +24,14 @@ module ZuoraAPI
24
24
  return self.status
25
25
  end
26
26
 
27
+ def get_active_bearer_token
28
+ self.get_bearer_token if self.oauth_expired?
29
+ return self.bearer_token
30
+ end
31
+
27
32
  def get_z_session(debug: false)
28
33
  tries ||= 2
29
- headers = self.entity_id.present? ? {:entityId => self.entity_id } : {}
34
+ headers = self.entity_id.present? ? {"Zuora-Entity-Ids" => self.entity_id } : {}
30
35
  output_json, response = self.rest_call(:url => self.rest_endpoint("connections"), :session_type => :bearer, :headers => headers)
31
36
  self.current_session = response.headers.to_h['set-cookie'][0].split(';')[0].split('=',2)[1].gsub('%3D', '=')
32
37
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
@@ -47,7 +52,7 @@ module ZuoraAPI
47
52
  else
48
53
  return [output_json, response]
49
54
  end
50
- rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
55
+ rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS) => ex
51
56
  if !(tries -= 1).zero?
52
57
  Rails.logger.info {"#{ex.class} Timed out will retry after 5 seconds"}
53
58
  sleep(self.timeout_sleep)
@@ -87,7 +92,7 @@ module ZuoraAPI
87
92
  else
88
93
  return [output_json, response]
89
94
  end
90
- rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
95
+ rescue *(CONNECTION_EXCEPTIONS).concat(CONNECTION_READ_EXCEPTIONS) => ex
91
96
  if !(tries -= 1).zero?
92
97
  Rails.logger.info {"#{ex.class} Timed out will retry after 5 seconds"}
93
98
  sleep(self.timeout_sleep)
@@ -1,3 +1,3 @@
1
1
  module ZuoraAPI
2
- VERSION = "1.6.3a"
2
+ VERSION = "1.6.3b"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zuora_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3a
4
+ version: 1.6.3b
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zuora Strategic Solutions Group
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-14 00:00:00.000000000 Z
11
+ date: 2019-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -153,6 +153,7 @@ files:
153
153
  - ".gitlab-ci.yml"
154
154
  - ".rspec"
155
155
  - ".travis.yml"
156
+ - CHANGELOG.md
156
157
  - Gemfile
157
158
  - Gemfile.lock
158
159
  - README.md
@@ -185,8 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
186
  - !ruby/object:Gem::Version
186
187
  version: 1.3.1
187
188
  requirements: []
188
- rubyforge_project:
189
- rubygems_version: 2.6.8
189
+ rubygems_version: 3.0.3
190
190
  signing_key:
191
191
  specification_version: 4
192
192
  summary: Gem that provides easy integration to Zuora