zuora_api 1.7.66k → 1.7.81

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7fa1187b824462a04a69aedfa13b20c67727e30a8fccef9a62d7c5b5dde77224
4
- data.tar.gz: ace9bbb9317ac71fecbff5af4fd24509f9698fcd33db71b3330342a4bca8d1aa
3
+ metadata.gz: e6c33a8033a33f0b2104721db4a09233874c8ee61df45a9d1167ec7301b5f9bf
4
+ data.tar.gz: f1fc6c8c62f7c7e054d84775cab9c283cb36bfde243e57cbe3f7f4eed5f80525
5
5
  SHA512:
6
- metadata.gz: 9ac138aec48b305c7d3c972f4b533b6e4ab7ff21e937dc9c79992a23b8480542b7bc25449f90365e59509b239e3fa7bad89248b4273337cb8e9d53d6c782ff8b
7
- data.tar.gz: 037f86064a5ea3434da2d5256d3652c36567036955ca7ebd1b56f50d86593415f615181d23c322b820506ece425f16b531a551073c6aa1517d4b8b7ee6005336
6
+ metadata.gz: 2894edcd05d8d4f5a94a725c705050d5ed63d582caebc721e91184adb6853daa036d5c13f942849ef665726c2005dd440212e1dde106cf643ea7fe6c5593b695
7
+ data.tar.gz: 4f933ca4535657b0f30feecc70412052cca33c99b1327a919d6c7f9966b16ce9663775c54af18c8665ad83838e8eafd16434253d7190fde8d3a7c6ff0a876758
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Zuora, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -145,3 +145,13 @@ Both do the same thing except one returns a url(data_export_insights) to downloa
145
145
  objectype: "ACCOUNT/USER"
146
146
 
147
147
  segmentuuid: A single or array of string or int of a segment uuid(s) that you get from the describe call. The csv holds a column with a bool that represents if that User or Account belongs to that segment.
148
+
149
+ ### License Information
150
+ IN THE EVENT YOU ARE AN EXISTING ZUORA CUSTOMER, USE OF THIS SOFTWARE IS GOVERNED
BY THE MIT LICENSE SET FORTH BELOW AND NOT THE MASTER SUBSCRIPTION AGREEMENT OR OTHER COMMERCIAL AGREEMENT ENTERED INTO BETWEEN YOU AND ZUORA (“AGREEMENT”). FOR THE AVOIDANCE OF DOUBT, ZUORA’S OBLIGATIONS WITH RESPECT TO TECHNICAL SUPPORT, UPTIME, INDEMNIFICATION, AND SECURITY SET FORTH IN THE AGREEMENT DO NOT APPLY TO THE USE OF THIS SOFTWARE.
151
+
152
+ Copyright 2021 Zuora, Inc.
153
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
154
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
155
+
156
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
157
+
@@ -238,7 +238,7 @@ module ZuoraAPI
238
238
  end
239
239
  end
240
240
 
241
- class ZuoraAPIReadTimeout < Net::ReadTimeout
241
+ class ZuoraAPIReadTimeout < Timeout::Error
242
242
  attr_reader :code, :response, :request
243
243
  attr_writer :default_message
244
244
 
@@ -7,7 +7,7 @@ module ZuoraAPI
7
7
  class Login
8
8
  ENVIRONMENTS = [TEST = 'Test', SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
9
9
  REGIONS = [EU = 'EU', US = 'US', NA = 'NA' ]
10
- MIN_Endpoint = '96.0'
10
+ MIN_Endpoints = {'Test': '107.0', 'Sandbox': '107.0', 'Production': '107.0', 'Performance': '107.0', 'Services': '96.0', 'Unknown': '96.0', 'Staging': '107.0'}.freeze
11
11
  XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
12
12
 
13
13
  CONNECTION_EXCEPTIONS = [
@@ -21,7 +21,7 @@ module ZuoraAPI
21
21
  ].freeze
22
22
 
23
23
  CONNECTION_READ_EXCEPTIONS = [
24
- Net::ReadTimeout,
24
+ Timeout::Error,
25
25
  Errno::ECONNRESET,
26
26
  Errno::EPIPE
27
27
  ].freeze
@@ -50,10 +50,12 @@ module ZuoraAPI
50
50
  raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
51
51
  # raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
52
52
  self.hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
53
+ self.update_environment
54
+ min_endpoint = MIN_Endpoints[self.environment.to_sym]
53
55
  if !/apps\/services\/a\/\d+\.\d$/.match(url.strip)
54
- self.url = "https://#{hostname}/apps/services/a/#{MIN_Endpoint}"
55
- elsif MIN_Endpoint.to_f > url.scan(/(\d+\.\d)$/).dig(0,0).to_f
56
- self.url = url.gsub(/(\d+\.\d)$/, MIN_Endpoint)
56
+ self.url = "https://#{hostname}/apps/services/a/#{min_endpoint}"
57
+ elsif min_endpoint.to_f > url.scan(/(\d+\.\d)$/).dig(0,0).to_f
58
+ self.url = url.gsub(/(\d+\.\d)$/, min_endpoint)
57
59
  else
58
60
  self.url = url
59
61
  end
@@ -65,36 +67,18 @@ module ZuoraAPI
65
67
  self.status = status.blank? ? "Active" : status
66
68
  self.user_info = Hash.new
67
69
  self.update_region
68
- self.update_environment
69
70
  self.update_zconnect_provider
70
71
  @timeout_sleep = 5
71
72
  end
72
73
 
73
74
  def get_identity(cookies)
74
75
  zsession = cookies["ZSession"]
75
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
76
76
  begin
77
77
  if !zsession.blank?
78
78
  response = HTTParty.get("https://#{self.hostname}/apps/v1/identity", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
79
79
  output_json = JSON.parse(response.body)
80
- elsif zconnect_accesstoken.present?
81
- begin
82
- code = zconnect_accesstoken.split("#!").last
83
- encrypted_token, tenant_id = Base64.decode64(code).split(":")
84
- body = {'token' => encrypted_token}.to_json
85
- rescue => ex
86
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Invalid ZConnect Cookie")
87
- end
88
- Rails.logger.info("Using ZConnect cookie in get_identity method")
89
-
90
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/identity", :body => body, :headers => { 'Content-Type' => 'application/json' })
91
- output_json = JSON.parse(response.body)
92
80
  else
93
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
94
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
95
- else
96
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
97
- end
81
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
98
82
  end
99
83
  rescue JSON::ParserError => ex
100
84
  output_json = {}
@@ -105,21 +89,12 @@ module ZuoraAPI
105
89
 
106
90
  def get_full_nav(cookies)
107
91
  zsession = cookies["ZSession"]
108
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
109
92
  begin
110
93
  if zsession.present?
111
94
  response = HTTParty.get("https://#{self.hostname}/apps/v1/navigation", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
112
95
  output_json = JSON.parse(response.body)
113
- elsif zconnect_accesstoken.present?
114
- Rails.logger.info("Using ZConnect cookie in get_full_nav method")
115
- response = HTTParty.get("https://#{self.hostname}/apps/zconnectsession/navigation", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}",'Content-Type' => 'application/json'})
116
- output_json = JSON.parse(response.body)
117
96
  else
118
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
119
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
120
- else
121
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
122
- end
97
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
123
98
  end
124
99
  rescue JSON::ParserError => ex
125
100
  output_json = {}
@@ -130,21 +105,12 @@ module ZuoraAPI
130
105
 
131
106
  def set_nav(state, cookies)
132
107
  zsession = cookies["ZSession"]
133
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
134
108
  begin
135
109
  if !zsession.blank?
136
110
  response = HTTParty.put("https://#{self.hostname}/apps/v1/preference/navigation", :body => state.to_json, :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
137
111
  output_json = JSON.parse(response.body)
138
- elsif !zconnect_accesstoken.blank?
139
- Rails.logger.info("Using ZConnect cookie in set_nav method")
140
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/navigationstate", :body => state.to_json, :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
141
- output_json = JSON.parse(response.body)
142
112
  else
143
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
144
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
145
- else
146
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
147
- end
113
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
148
114
  end
149
115
  rescue JSON::ParserError => ex
150
116
  output_json = {}
@@ -155,21 +121,12 @@ module ZuoraAPI
155
121
 
156
122
  def refresh_nav(cookies)
157
123
  zsession = cookies["ZSession"]
158
- zconnect_accesstoken = get_zconnect_accesstoken(cookies)
159
124
  begin
160
125
  if !zsession.blank?
161
126
  response = HTTParty.post("https://#{self.hostname}/apps/v1/navigation/fetch", :headers => {'Cookie' => "ZSession=#{zsession}", 'Content-Type' => 'application/json'})
162
127
  output_json = JSON.parse(response.body)
163
- elsif !zconnect_accesstoken.blank?
164
- Rails.logger.info("Using ZConnect cookie in refresh_nav method")
165
- response = HTTParty.post("https://#{self.hostname}/apps/zconnectsession/refresh-navbarcache", :headers => {'Cookie' => "#{self.zconnect_provider}=#{zconnect_accesstoken}", 'Content-Type' => 'application/json'})
166
- output_json = JSON.parse(response.body)
167
128
  else
168
- if zconnect_accesstoken.blank? && cookies.keys.any? { |x| x.include? "ZConnect"}
169
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZConnect cookie present matching #{self.hostname}")
170
- else
171
- raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
172
- end
129
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("No ZSession cookie present")
173
130
  end
174
131
  rescue JSON::ParserError => ex
175
132
  output_json = {}
@@ -178,15 +135,6 @@ module ZuoraAPI
178
135
  return output_json
179
136
  end
180
137
 
181
- def get_zconnect_accesstoken(cookies)
182
- accesstoken = nil
183
- self.update_zconnect_provider
184
- if !cookies[self.zconnect_provider].nil? && !cookies[self.zconnect_provider].empty?
185
- accesstoken = cookies[self.zconnect_provider]
186
- end
187
- return accesstoken
188
- end
189
-
190
138
  def reporting_url(path)
191
139
  map = {"US" => {"Sandbox" => "https://zconnectsandbox.zuora.com/api/rest/v1/",
192
140
  "Production" => "https://zconnect.zuora.com/api/rest/v1/",
@@ -208,7 +156,7 @@ module ZuoraAPI
208
156
  # 1. Pass in cookies and optionally custom_authorities, name, and description
209
157
  # 2. Pass in user_id, entity_ids, client_id, client_secret, and optionally custom_authorities, name, and description
210
158
  # https://intranet.zuora.com/confluence/display/Sunburst/Create+an+OAuth+Client+through+API+Gateway#CreateanOAuthClientthroughAPIGateway-ZSession
211
- 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)
159
+ 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, chomp_v1_from_genesis_endpoint: false, use_api_generated_client_secret: false)
212
160
  authorization = ""
213
161
  new_client_id = SecureRandom.uuid if new_client_id.blank?
214
162
  new_client_secret = SecureRandom.hex(10) if new_client_secret.blank?
@@ -234,11 +182,11 @@ module ZuoraAPI
234
182
  end
235
183
 
236
184
  if !authorization.blank? && !user_id.blank? && !entity_ids.blank?
237
- endpoint = self.rest_endpoint("genesis/clients")
185
+ endpoint = chomp_v1_from_genesis_endpoint ? self.rest_endpoint.chomp("v1/").concat("genesis/clients") : self.rest_endpoint("genesis/clients")
238
186
  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)
239
187
  output_json = JSON.parse(oauth_response.body)
240
188
  if oauth_response.code == 201
241
- output_json["clientSecret"] = new_client_secret
189
+ output_json["clientSecret"] = new_client_secret if !use_api_generated_client_secret
242
190
  return output_json
243
191
  elsif oauth_response.code == 401 && !oauth_response.message.blank?
244
192
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(output_json["message"], oauth_response)
@@ -305,7 +253,7 @@ module ZuoraAPI
305
253
  end
306
254
 
307
255
  def update_environment
308
- if !self.url.blank?
256
+ if !self.hostname.blank?
309
257
  case self.hostname
310
258
  when /(?<=\.|\/|-|^)(apisandbox|sandbox)(?=\.|\/|-|$)/
311
259
  self.environment = 'Sandbox'
@@ -328,13 +276,13 @@ module ZuoraAPI
328
276
  end
329
277
 
330
278
  def update_zconnect_provider
331
- region = update_region
332
- environment = update_environment
279
+ update_region if self.region.blank?
280
+ update_environment if self.environment.blank?
333
281
  mappings = {"US" => {"Sandbox" => "ZConnectSbx", "Services" => "ZConnectSvcUS", "Production" => "ZConnectProd", "Performance" => "ZConnectPT1", "Test" => "ZConnectTest", "Staging" => "ZConnectQA", "KubeSTG" => "ZConnectDev", "KubeDEV" => "ZConnectDev", "KubePROD" => "ZConnectDev"},
334
282
  "NA" => {"Sandbox" => "ZConnectSbxNA", "Services" => "ZConnectSvcNA", "Production" => "ZConnectProdNA", "Performance" => "ZConnectPT1NA"},
335
283
  "EU" => {"Sandbox" => "ZConnectSbxEU", "Services" => "ZConnectSvcEU", "Production" => "ZConnectProdEU", "Performance" => "ZConnectPT1EU", "Test" => "ZConnectTest"},
336
284
  "Unknown" => {"Unknown" => "Unknown"}}
337
- self.zconnect_provider = mappings[region][environment]
285
+ self.zconnect_provider = mappings[self.region][self.environment]
338
286
  end
339
287
 
340
288
  def aqua_endpoint(url="")
@@ -387,20 +335,20 @@ module ZuoraAPI
387
335
  end
388
336
 
389
337
  def new_session(auth_type: :basic, debug: false, zuora_track_id: nil)
390
- tries ||= 2
338
+ retries ||= 2
391
339
  yield
392
340
 
393
341
  rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
394
- self.status = 'Inactive/Invalid'
342
+ self.status = 'Invalid'
395
343
  self.current_error = ex.message
396
344
  raise
397
345
  rescue ZuoraAPI::Exceptions::ZuoraAPIError => ex
398
346
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(ex.message, ex.response)
399
347
 
400
348
  rescue ZuoraAPI::Exceptions::ZuoraAPIInternalServerError => ex
401
- raise ex if tries.zero?
349
+ raise ex if retries.zero?
402
350
 
403
- tries -= 1
351
+ retries -= 1
404
352
  sleep(self.timeout_sleep)
405
353
  retry
406
354
 
@@ -571,7 +519,7 @@ module ZuoraAPI
571
519
  end
572
520
 
573
521
  self.log(location: "SOAP Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
574
- ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.instance_of?(Net::ReadTimeout)
522
+ ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read/write timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.is_a?(Timeout::Error) && !ex.instance_of?(ZuoraAPI::Exceptions::ZuoraAPIReadTimeout)
575
523
  raise ex
576
524
 
577
525
  rescue => ex
@@ -586,7 +534,7 @@ module ZuoraAPI
586
534
  when ZuoraAPI::Exceptions::ZuoraAPIUnkownError, ZuoraAPI::Exceptions::ZuoraDataIntegrity
587
535
  Rails.logger.error('Zuora Unknown/Integrity Error', ex, exception_args)
588
536
  when ZuoraAPI::Exceptions::ZuoraAPIRequestLimit
589
- Rails.logger.info('Zuora APILimit Reached', exception_args)
537
+ Rails.logger.info('Zuora APILimit Reached', ex, exception_args)
590
538
  when *(ZuoraAPI::Login::ZUORA_API_ERRORS-ZuoraAPI::Login::ZUORA_SERVER_ERRORS)
591
539
  #Rails.logger.debug('Zuora API Error', ex, self.exception_args(ex))
592
540
  when *ZuoraAPI::Login::ZUORA_SERVER_ERRORS
@@ -605,12 +553,7 @@ module ZuoraAPI
605
553
 
606
554
  def exception_args(ex)
607
555
  args = {}
608
- if ex.class == ZuoraAPI::Exceptions::ZuoraAPIRequestLimit
609
- args.merge!({
610
- zuora_trace_id: ex.response.headers["zuora-request-id"],
611
- zuora_track_id: ex.response.request.options[:headers]["Zuora-Track-Id"]
612
- })
613
- elsif defined?(ex.response) && ex.response.present?
556
+ if defined?(ex.response) && ex.response.present?
614
557
  args.merge!({
615
558
  request: {
616
559
  path: ex.response.request.path.to_s,
@@ -755,14 +698,27 @@ module ZuoraAPI
755
698
  if body['code'].present? && /61$/.match(body['code'].to_s).present? # if last 2 digits of code are 61
756
699
  raise ZuoraAPI::Exceptions::ZuoraAPITemporaryError.new(body['message'], nil, body['details'])
757
700
  end
701
+ when /^\/api\/v1\/payment_plans.*/
702
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(body['error'], response) if body['error']
758
703
  end
759
704
 
760
705
  body = body.dig("results").present? ? body["results"] : body if body.class == Hash
761
706
  if body.class == Hash && (!body["success"] || !body["Success"] || response.code != 200)
762
- messages_array = body.fetch("reasons", []).map {|error| error['message']}.compact
763
- messages_array = messages_array.push(body.dig("error", 'message')).compact if body.dig('error').class == Hash
764
- codes_array = body.fetch("reasons", []).map {|error| error['code']}.compact
765
- codes_array = codes_array.push(body.dig("error", 'code')).compact if body.dig('error').class == Hash
707
+ reason_keys = %w(reasons errors)
708
+ message_keys = %w(message title)
709
+ messages_array, codes_array = [[],[]]
710
+ reason_keys.each do |rsn_key|
711
+ message_keys.each do |msg_key|
712
+ messages_array = body.fetch(rsn_key, []).map {|error| error[msg_key]}.compact
713
+ break if messages_array.present?
714
+ end
715
+ codes_array = body.fetch(rsn_key, []).map {|error| error['code']}.compact
716
+ break if messages_array.present? && codes_array.present?
717
+ end
718
+ if body.dig('error').class == Hash
719
+ messages_array = messages_array.push(body.dig("error", 'message')).compact
720
+ codes_array = codes_array.push(body.dig("error", 'code')).compact
721
+ end
766
722
 
767
723
  if body['message'] == 'request exceeded limit'
768
724
  raise ZuoraAPI::Exceptions::ZuoraAPIRequestLimit.new("The total number of concurrent requests has exceeded the limit allowed by the system. Please resubmit your request later.", response)
@@ -905,7 +861,7 @@ module ZuoraAPI
905
861
  output_json = JSON.parse(response.body)
906
862
  self.raise_errors(type: :JSON, body: output_json, response: response)
907
863
 
908
- elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml')) and type != :xml
864
+ elsif (response_content_types.include?('application/xml') || response_content_types.include?('text/xml') || response_content_types.include?('application/soap+xml')) and type != :xml
909
865
  output_xml = Nokogiri::XML(response.body)
910
866
  self.raise_errors(type: :SOAP, body: output_xml, response: response)
911
867
 
@@ -953,6 +909,11 @@ module ZuoraAPI
953
909
  message = body.xpath('//ns1:Message', 'ns1' =>'http://api.zuora.com/').text
954
910
  end
955
911
 
912
+ if error.blank? || message.blank?
913
+ error = body.xpath('//soapenv:Value', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
914
+ message = body.xpath('//soapenv:Text', 'soapenv'=>'http://www.w3.org/2003/05/soap-envelope').text
915
+ end
916
+
956
917
  #Update/Create/Delete Calls with multiple requests and responses
957
918
  if body.xpath('//ns1:result', 'ns1' =>'http://api.zuora.com/').size > 0 && body.xpath('//ns1:Errors', 'ns1' =>'http://api.zuora.com/').size > 0
958
919
  error = []
@@ -1003,10 +964,15 @@ module ZuoraAPI
1003
964
  when /.*soapenv:Server.*/
1004
965
  if /^Invalid value.*for type.*|^Id is invalid|^date string can not be less than 19 charactors$/.match(message).present?
1005
966
  raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
1006
- elsif /^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
967
+ elsif /^unknown$|^Invalid white space character \(.*\) in text to output$|^Invalid null character in text to output$/.match(message).present?
1007
968
  raise ZuoraAPI::Exceptions::ZuoraAPIUnkownError.new(message, response, errors, success)
1008
969
  end
1009
970
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
971
+ when /soapenv:Receiver/
972
+ if /^com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character.*$/.match(message).present?
973
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new(message, response, errors, success)
974
+ end
975
+ raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new(message, response, errors, success)
1010
976
  else
1011
977
  raise ZuoraAPI::Exceptions::ZuoraAPIInternalServerError.new("Z:#{error}::#{message}", response, errors, success)
1012
978
  end
@@ -1237,7 +1203,7 @@ module ZuoraAPI
1237
1203
  end
1238
1204
 
1239
1205
  self.log(location: "Rest Call", exception: ex, message: "Timed out", level: :error) if output_exception_messages
1240
- ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.instance_of?(Net::ReadTimeout)
1206
+ ex = ZuoraAPI::Exceptions::ZuoraAPIReadTimeout.new("Received read/write timeout from 'https://#{rest_domain(endpoint: url)}'", nil, request) if ex.is_a?(Timeout::Error) && !ex.instance_of?(ZuoraAPI::Exceptions::ZuoraAPIReadTimeout)
1241
1207
  raise ex
1242
1208
 
1243
1209
  rescue => ex
@@ -1398,13 +1364,21 @@ module ZuoraAPI
1398
1364
  return file_handle
1399
1365
  when Net::HTTPUnauthorized
1400
1366
  if z_session
1401
- if !(retry_count -= 1).zero?
1367
+ unless (retry_count -= 1).zero?
1402
1368
  self.new_session
1403
- raise response.class
1369
+ raise ZuoraAPI::Exceptions::ZuoraAPISessionError, 'Retrying'
1404
1370
  end
1405
1371
  raise ZuoraAPI::Exceptions::ZuoraAPISessionError.new(self.current_error)
1406
1372
  end
1407
1373
  raise
1374
+ when Net::HTTPNotFound
1375
+ if url.include?(self.fileURL)
1376
+ raise ZuoraAPI::Exceptions::FileDownloadError.new(
1377
+ "The current tenant does not have a file with id '#{url.split('/').last}'"
1378
+ )
1379
+ else
1380
+ raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
1381
+ end
1408
1382
  else
1409
1383
  raise ZuoraAPI::Exceptions::FileDownloadError.new("File Download Failed #{response.class}")
1410
1384
  end
@@ -1421,68 +1395,35 @@ module ZuoraAPI
1421
1395
 
1422
1396
  def getDataSourceExport(query, extract: true, encrypted: false, zip: true, z_track_id: "")
1423
1397
  tries ||= 3
1424
- request = Nokogiri::XML::Builder.new do |xml|
1425
- 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
1426
- xml['SOAP-ENV'].Header do
1427
- xml['ns1'].SessionHeader do
1428
- xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
1429
- end
1430
- end
1431
- xml['SOAP-ENV'].Body do
1432
- xml['ns1'].create do
1433
- xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1434
- xml['ns2'].Format 'csv'
1435
- xml['ns2'].Zip zip
1436
- xml['ns2'].Name 'googman'
1437
- xml['ns2'].Query query
1438
- xml['ns2'].Encrypted encrypted
1439
- end
1440
- end
1398
+
1399
+ output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true, zuora_track_id: z_track_id) do |xml|
1400
+ xml['ns1'].create do
1401
+ xml['ns1'].zObjects('xsi:type' => "ns2:Export") do
1402
+ xml['ns2'].Format 'csv'
1403
+ xml['ns2'].Zip zip
1404
+ xml['ns2'].Name 'googman'
1405
+ xml['ns2'].Query query
1406
+ xml['ns2'].Encrypted encrypted
1441
1407
  end
1442
1408
  end
1443
1409
  end
1444
-
1445
- 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)
1446
-
1447
- output_xml = Nokogiri::XML(response_query.body)
1448
- raise_errors(type: :SOAP, body: output_xml, response: response_query) if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1449
-
1450
- # raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if output_xml.xpath('//ns1:Success', 'ns1' =>'http://api.zuora.com/').text != "true"
1451
1410
 
1452
1411
  id = output_xml.xpath('//ns1:Id', 'ns1' =>'http://api.zuora.com/').text
1453
1412
 
1454
- confirmRequest = Nokogiri::XML::Builder.new do |xml|
1455
- 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
1456
- xml['SOAP-ENV'].Header do
1457
- xml['ns1'].SessionHeader do
1458
- xml['ns1'].session self.get_session(prefix: false, auth_type: :basic, zuora_track_id: z_track_id)
1459
- end
1460
- end
1461
- xml['SOAP-ENV'].Body do
1462
- xml['ns1'].query do
1463
- xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
1464
- end
1465
- end
1466
- end
1467
- end
1468
1413
  result = 'Waiting'
1469
-
1470
1414
  while result != "Completed"
1471
1415
  sleep 3
1472
- 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)
1473
-
1474
- output_xml = Nokogiri::XML(response_query.body)
1416
+ output_xml, input_xml = self.soap_call(debug: false, timeout_retry: true, zuora_track_id: z_track_id) do |xml|
1417
+ xml['ns1'].query do
1418
+ xml['ns1'].queryString "SELECT Id, CreatedById, CreatedDate, Encrypted, FileId, Format, Name, Query, Size, Status, StatusReason, UpdatedById, UpdatedDate, Zip From Export where Id = '#{id}'"
1419
+ end
1420
+ end
1475
1421
  result = output_xml.xpath('//ns2:Status', 'ns2' =>'http://object.api.zuora.com/').text
1476
- status_code = response_query.code if response_query
1477
-
1478
- raise_errors(type: :SOAP, body: output_xml, response: response_query) if result.blank? || result == "Failed"
1479
- # raise "Export Creation Unsuccessful : #{response_query.code}: #{response_query.parsed_response}" if result.blank? || result == "Failed"
1480
1422
  end
1481
1423
 
1482
1424
  file_id = output_xml.xpath('//ns2:FileId', 'ns2' =>'http://object.api.zuora.com/').text
1483
1425
  export_file = get_file(:url => self.fileURL(file_id))
1484
1426
  export_file_path = export_file.path
1485
- Rails.logger.debug("=====> Export path #{export_file.path}")
1486
1427
 
1487
1428
  if extract && zip
1488
1429
  require "zip"
@@ -1506,24 +1447,18 @@ module ZuoraAPI
1506
1447
  rescue ZuoraAPI::Exceptions::ZuoraUnexpectedError => ex
1507
1448
  if !(tries -= 1).zero?
1508
1449
  Rails.logger.info("Trace ID: #{z_track_id} UnexpectedError, will retry after 10 seconds")
1509
- sleep 10
1450
+ sleep(self.timeout_sleep)
1510
1451
  retry
1511
1452
  end
1512
1453
  raise ex
1513
-
1514
- rescue *ZUORA_API_ERRORS => ex
1515
- raise ex
1516
-
1454
+
1517
1455
  rescue *(CONNECTION_EXCEPTIONS + CONNECTION_READ_EXCEPTIONS) => ex
1518
1456
  if !(tries -= 1).zero?
1519
1457
  Rails.logger.info("Trace ID: #{z_track_id} Timed out will retry after 5 seconds")
1520
- sleep 5
1458
+ sleep(self.timeout_sleep)
1521
1459
  retry
1522
1460
  end
1523
1461
  raise ex
1524
-
1525
- rescue ZuoraAPI::Exceptions::BadEntityError => ex
1526
- raise ex
1527
1462
  end
1528
1463
 
1529
1464
  def query(query, parse = false)
@@ -1,3 +1,3 @@
1
1
  module ZuoraAPI
2
- VERSION = "1.7.66k"
2
+ VERSION = "1.7.81"
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.7.66k
4
+ version: 1.7.81
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: 2020-06-23 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -145,7 +145,7 @@ dependencies:
145
145
  version: 4.1.0
146
146
  - - "<"
147
147
  - !ruby/object:Gem::Version
148
- version: '6'
148
+ version: '6.1'
149
149
  type: :runtime
150
150
  prerelease: false
151
151
  version_requirements: !ruby/object:Gem::Requirement
@@ -155,7 +155,7 @@ dependencies:
155
155
  version: 4.1.0
156
156
  - - "<"
157
157
  - !ruby/object:Gem::Version
158
- version: '6'
158
+ version: '6.1'
159
159
  description: Gem that provides easy integration to Zuora
160
160
  email:
161
161
  - connect@zuora.com
@@ -163,17 +163,9 @@ executables: []
163
163
  extensions: []
164
164
  extra_rdoc_files: []
165
165
  files:
166
- - ".gitignore"
167
- - ".gitlab-ci.yml"
168
- - ".rspec"
169
- - ".travis.yml"
170
- - CHANGELOG.md
171
- - Gemfile
172
- - Gemfile.lock
166
+ - MIT-LICENSE
173
167
  - README.md
174
168
  - Rakefile
175
- - bin/console
176
- - bin/setup
177
169
  - lib/insights_api/login.rb
178
170
  - lib/zuora_api.rb
179
171
  - lib/zuora_api/exceptions.rb
@@ -181,7 +173,6 @@ files:
181
173
  - lib/zuora_api/logins/basic.rb
182
174
  - lib/zuora_api/logins/oauth.rb
183
175
  - lib/zuora_api/version.rb
184
- - zuora_api.gemspec
185
176
  homepage: https://connect.zuora.com
186
177
  licenses: []
187
178
  metadata: {}
@@ -196,11 +187,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
196
187
  version: '0'
197
188
  required_rubygems_version: !ruby/object:Gem::Requirement
198
189
  requirements:
199
- - - ">"
190
+ - - ">="
200
191
  - !ruby/object:Gem::Version
201
- version: 1.3.1
192
+ version: '0'
202
193
  requirements: []
203
- rubygems_version: 3.1.2
194
+ rubygems_version: 3.1.4
204
195
  signing_key:
205
196
  specification_version: 4
206
197
  summary: Gem that provides easy integration to Zuora
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- .idea
data/.gitlab-ci.yml DELETED
@@ -1,64 +0,0 @@
1
- image: ruby:2.7
2
- stages:
3
- - setup
4
- - test
5
- - deploy
6
-
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
- rubocop-testing:
20
- stage: test
21
- allow_failure: true
22
- script:
23
- - gem install rubocop
24
- - rubocop --lint
25
-
26
- security-testing:
27
- stage: test
28
- allow_failure: true
29
- script:
30
- - gem install brakeman
31
- - brakeman
32
-
33
- rspec-testing:
34
- stage: test
35
- script:
36
- - bundle install
37
- - rspec
38
- coverage: '/\(\d+.\d+\%\) covered/'
39
-
40
- rubygems-deploy:
41
- stage: deploy
42
- allow_failure: false
43
- script:
44
- - echo "deb http://ftp.us.debian.org/debian testing main contrib non-free" >> /etc/apt/sources.list
45
- - apt-get update
46
- - apt-get install -y git
47
- - apt-get clean all
48
- - gem install dpl
49
- - 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
50
- - 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
51
- - echo $VERSION
52
- - sed -i "s/0.0.1/$VERSION/" lib/zuora_api/version.rb
53
- - git add lib/zuora_api/version.rb
54
- - git config --global user.email "connect@zuora.com"
55
- - git config --global user.name "Connect Automation"
56
- - git commit -m "Automated Version Update $VERSION"
57
- - bundle install
58
- - gem install rake
59
- - version=$(rake install | grep -o 'pkg/zuora_api-.*gem')
60
- - curl -u $USERNAME:$PASSWORD https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
61
- - gem push $version
62
- only:
63
- - master
64
- - staging
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --format documentation
2
- --color
data/.travis.yml DELETED
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.3.0
5
- before_install: gem install bundler -v 1.12.5
data/CHANGELOG.md DELETED
@@ -1,105 +0,0 @@
1
- # Changelog
2
- All notable changes to this project will be documented in this file.
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
-
21
- ## [1.7.01] - 2018-8-06
22
- ### Changed
23
- - Changed library used to determine host
24
- - Added retry for 504 timouts in file download
25
-
26
- ## [1.7.00] - 2018-8-05
27
- ### Changed
28
- - Raise proper exception when oauth client is from deactivated user.
29
- - Support for rails < 6
30
-
31
- ## [1.6.53] - 2018-7-29
32
- ### Changed
33
- - Don't attempt zsession login if bearer token is bad
34
-
35
- ## [1.6.51 - 1.6.51] - 2018-7-22
36
- ### Changed
37
- - Retry on address not available.
38
-
39
- ## [1.6.47 - 1.6.48] - 2018-6-26
40
- ### Changed
41
- - Changed error raise statements when incorrect credentials are supplied to Basic/Oauth Logins
42
-
43
- ## [1.6.45] - 2018-5-30
44
- ### Changed
45
- - Fix retry so headers are reinstaniated on session failure.
46
-
47
- ## [1.6.41] - 2018-5-30
48
- ### Changed
49
- - Retry added on SSL connection failure
50
-
51
- ## [1.6.39-1.6.40] - 2018-5-30
52
- ### Changed
53
- - Added validation to fix bad urls entered into object initialization
54
-
55
- ## [1.6.38] - 2018-5-26
56
- ### Changed
57
- - HttpParty validation before code extraction
58
-
59
- ## [1.6.37] - 2018-5-23
60
- ### Added
61
- - Added method to determine rest endpoint domain
62
-
63
- ## [1.6.36] - 2018-5-22
64
- ### Changed
65
- - Fixed zuora staging 2 endpoint
66
-
67
- ## [1.6.33] - 2018-5-15
68
- ### Changed
69
- - Added Errno::EHOSTUNREACH to list of retriable error codes
70
-
71
- ## [1.6.32] - 2018-5-14
72
- ### Changed
73
- - Don't log fatal errors, allow application to decide for file download
74
- - Don't change api url if the user set a high api url
75
-
76
- ###Removed
77
- - Force encoding
78
-
79
- ## [1.6.28] - 2018-3-12
80
- ### Added
81
- - Way to avoid force encoding for filedownload
82
-
83
- ## [1.6.28] - 2018-3-12
84
- ### Added
85
- - Way to avoid force encoding for filedownload
86
-
87
- ## [1.6.22] - 2019-01-03
88
- ### Changed
89
- - get_identity method - supports ZSession auth now
90
- - updated rspecs accordingly
91
-
92
- ## [1.6.18] - 2018-12-06
93
- ### Added
94
- - zconnect_provider attribute accessor for identifying ZConnect cookies
95
- - Methods for Hallway integration:
96
- - get_identity
97
- - get_full_nav
98
- - set_nav
99
- - refresh_nav
100
- - get_oauth_client
101
-
102
- ### Changed
103
- - The way environment and region are set
104
-
105
-
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
- ruby "2.7.1"
3
- # Specify your gem's dependencies in zuora.gemspec
4
- gemspec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "zuora"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/zuora_api.gemspec DELETED
@@ -1,31 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'zuora_api/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "zuora_api"
8
- spec.version = ZuoraAPI::VERSION
9
- spec.authors = ["Zuora Strategic Solutions Group"]
10
- spec.email = ["connect@zuora.com"]
11
-
12
- spec.summary = %q{Gem that provides easy integration to Zuora}
13
- spec.description = %q{Gem that provides easy integration to Zuora}
14
- spec.homepage = "https://connect.zuora.com"
15
-
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "~> 3.0"
24
- spec.add_development_dependency("webmock")
25
- spec.add_development_dependency("simplecov", "~> 0.18.5")
26
- spec.add_dependency("ougai")
27
- spec.add_dependency("nokogiri")
28
- spec.add_dependency("httparty")
29
- spec.add_dependency("rubyzip")
30
- spec.add_dependency("railties", ">= 4.1.0", "< 6")
31
- end