zuora_api 1.7.66j → 1.7.80
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.gitlab-ci.yml +15 -16
- data/Gemfile +1 -1
- data/catalog-info.yaml +12 -0
- data/docs/index.md +147 -0
- data/gemfiles/Gemfile-rails.5.0.x +5 -0
- data/gemfiles/Gemfile-rails.5.1.x +5 -0
- data/gemfiles/Gemfile-rails.5.2.x +5 -0
- data/gemfiles/Gemfile-rails.6.0.x +5 -0
- data/lib/zuora_api/login.rb +84 -149
- data/lib/zuora_api/version.rb +1 -1
- data/zuora_api.gemspec +1 -1
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0865872f720e280a3a4805d314d27a3e2649b861495f80cf8523f4eb43f2c1e
|
4
|
+
data.tar.gz: cc80008e33f6b47245acc1a0b8cdb0ec326f4a714c842e434b25bd5643e94123
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 051c3a5244695357f0ecf6a42ee1eda7981c0145ed3b50d9e0b751dbbdf6e4aba1d7e36e69eec28056e2fb3b56888c48113699d32ab0f6479272e339b3c1ade2
|
7
|
+
data.tar.gz: f20c6fd7e534de55b5b94120e812ba19e18fda5f41e63f8f9183fb66d8ea4a0a01f441918d84196b2aa027724228b63332f54aea94b9515142534d5fff15574c
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
CHANGED
@@ -1,21 +1,8 @@
|
|
1
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,23 @@ security-testing:
|
|
30
17
|
- gem install brakeman
|
31
18
|
- brakeman
|
32
19
|
|
33
|
-
|
20
|
+
ruby:test:
|
34
21
|
stage: test
|
22
|
+
cache:
|
23
|
+
key: ruby:$RUBY_VERSION-rails:$RAILS_VERSION
|
24
|
+
paths:
|
25
|
+
- vendor/ruby
|
26
|
+
parallel:
|
27
|
+
matrix:
|
28
|
+
- RUBY_VERSION: "2.7"
|
29
|
+
RAILS_VERSION: ["5.0", "5.1", "5.2", "6.0"]
|
30
|
+
before_script:
|
31
|
+
- bundle config set path 'vendor/ruby'
|
32
|
+
- bundle config --global gemfile "gemfiles/Gemfile-rails.$RAILS_VERSION.x"
|
33
|
+
- bundle install
|
35
34
|
script:
|
36
|
-
|
37
|
-
|
35
|
+
- bundle exec rails -v
|
36
|
+
- bundle exec rspec
|
38
37
|
coverage: '/\(\d+.\d+\%\) covered/'
|
39
38
|
|
40
39
|
rubygems-deploy:
|
data/Gemfile
CHANGED
data/catalog-info.yaml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
---
|
2
|
+
apiVersion: backstage.io/v1alpha1
|
3
|
+
kind: Component
|
4
|
+
metadata:
|
5
|
+
annotations:
|
6
|
+
backstage.io/techdocs-ref: "gitlab:https://gitlab.zeta.tools/extension-products/shared-libraries/zuora-gem.git"
|
7
|
+
description: "Zuora API Rails Gem"
|
8
|
+
name: Zuora-Gem
|
9
|
+
spec:
|
10
|
+
lifecycle: production
|
11
|
+
owner: connect@zuora.com
|
12
|
+
type: library
|
data/docs/index.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# Zuora Gem
|
2
|
+
|
3
|
+
[![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
|
+
|
5
|
+
## Installation
|
6
|
+
Add this line to your application's Gemfile:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
gem 'zuora_api'
|
10
|
+
```
|
11
|
+
Then execute `bundle install` in your terminal
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Zuora Login Object
|
16
|
+
In order to make API calls a Zuora Login object must be created
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
zuora_client = ZuoraAPI::Login.new(username: "username", password: "password", url: "url")
|
20
|
+
```
|
21
|
+
|
22
|
+
| Name | Type | Description | Example |
|
23
|
+
| ------------------- | ----------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
24
|
+
| username | `Attribute` | Username to the Zuora environment | `zuora_client.username = "username"` |
|
25
|
+
| password | `Attribute` | password to the Zuora environment | `zuora_client.password = "Password"` |
|
26
|
+
| url | `Attribute` | Endpoint to the Zuora tenant | `zuora_client.url = "www.zuora.com"` |
|
27
|
+
| wsdl_number | `Attribute` | WSDL number of the zuora login | `wsdl = zuora_client.wsdl_number` |
|
28
|
+
| status | `Attribute` | Status of the login | `zuora_client.status` |
|
29
|
+
| current_session | `Attribute` | Current session for the login | `zuora_client.current_session` |
|
30
|
+
| environment | `Attribute` | environment of the login | `zuora_client.environment` |
|
31
|
+
| errors | `Attribute` | Any errors that the login has based on the login call | `zuora_client.errors` |
|
32
|
+
| current_error | `Attribute` | Current error from the new_session call | `zuora_client.current_error` |
|
33
|
+
| user_info | `Attribute` | Information related to the login | `zuora_client.user_info` |
|
34
|
+
| tenant_id | `Attribute` | Tenant ID the login is associated to | `zuora_client.tenant_id` |
|
35
|
+
| tenant_name | `Attribute` | Tenant Name of tenant the login is associated to | `zuora_client.tenant_name` |
|
36
|
+
| entity_id | `Attribute` | Current entity the login session is associated to | `zuora_client.entity_id` |
|
37
|
+
| rest_call | `Method` | Executes a REST call | `zuora_client.rest_call()` |
|
38
|
+
| soap_call | `Method` | Executes a SOAP call | `output_xml, input_xml = zuora_client.soap_call() do `|xml, args|` xml['ns1'].query do xml['ns1'].queryString "select id, name from account" end end` |
|
39
|
+
| query | `Method` | Executes a query call | `zuora_client.query("select id, name from account")` |
|
40
|
+
| getDataSourceExport | `Method` | Pulls a data source export with the given query and returns the file location | `zuora_client.getDataSourceExport("select id, name from account")` |
|
41
|
+
| describe_call | `Method` | Performs the describe call against the Zuora tenant for all objects or a specific object | `response = zuora_client.describe_call("Account")` |
|
42
|
+
| createJournalRun | `Method` | Creates a Journal Run | `zuora_client.createJournalRun(call)` |
|
43
|
+
| checkJRStatus | `Method` | Checks the status of a journal run | `zuora_client.checkJRStatus(journal_run_id)` |
|
44
|
+
| update_environment | `Method` | Sets the login's environment based on the url | `zuora_client.update_environment` |
|
45
|
+
| aqua_endpoint | `Method` | Returns the AQuA endpoint for the login based off the environment | `zuora_client.aqua_endpoint` |
|
46
|
+
| rest_endpoint | `Method` | Returns the REST endpoint for the login based off the environment | `zuora_client.rest_endpoint` |
|
47
|
+
| fileURL | `Method` | Returns the URL for files | `zuora_client.fileURL` |
|
48
|
+
| dateFormat | `Method` | Returns the data format syntax based on the wsdl_number | `zuora_client.dateFormat` |
|
49
|
+
| new_session | `Method` | Create a new session | `zuora_client.new_session` |
|
50
|
+
| get_session | `Method` | Returns the current session | `zuora_client.get_session`|
|
51
|
+
|
52
|
+
## Rest Call
|
53
|
+
```ruby
|
54
|
+
zuora_client.rest_call(method: :get, body: {}, url: zuora_client.rest_endpoint("catalog/products?pageSize=4"))
|
55
|
+
```
|
56
|
+
|
57
|
+
### Soap Call
|
58
|
+
Returns both output and input XML
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
zuora_client.soap_call(ns1: 'ns1', ns2: 'ns2', batch_size: nil, single_transaction: false)
|
62
|
+
```
|
63
|
+
|
64
|
+
Example Call
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
output_xml, input_xml = zuora_client.soap_call() do |xml, args|
|
68
|
+
xml['ns1'].query do
|
69
|
+
xml['ns1'].queryString "select id, name from account"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
### Query
|
74
|
+
```ruby
|
75
|
+
zuora_client.query("select id from account")
|
76
|
+
```
|
77
|
+
### Data Export
|
78
|
+
Returns the file location of the data source export after downloading from Zuora
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
zuora_client.getDataSourceExport("select id from account")
|
82
|
+
```
|
83
|
+
|
84
|
+
| Name | Description | Default | Example |
|
85
|
+
| --------- | ---------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------- |
|
86
|
+
| query | The query to execute | `N/A` | `zuora_client.getDataSourceExport("select id from account")` |
|
87
|
+
| zip | Indicates if the data source export should be a zip | `true` | `zuora_client.getDataSourceExport("select id from account", zip: false)` |
|
88
|
+
| extract | Indicates if the data source export should be extracted if it is a zip | `true` | `zuora_client.getDataSourceExport("select id from account", extract: false)` |
|
89
|
+
| encrypted | Indicates if the data source export should be encrypted | `false` | `zuora_client.getDataSourceExport("select id from account", encrypted: true)` |
|
90
|
+
|
91
|
+
### Describe Call
|
92
|
+
This returns all available objects from the describe call as a hash. This response can be accessed by using response["Account"] to retrieve all related data about that object.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
response = zuora_client.describe_call("Account")
|
96
|
+
```
|
97
|
+
This returns all information and fields related to that object model as a hash.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
response = zuora_client.describe_call()
|
101
|
+
```
|
102
|
+
|
103
|
+
### Journal Run
|
104
|
+
```ruby
|
105
|
+
zuora_client.createJournalRun(call)
|
106
|
+
```
|
107
|
+
|
108
|
+
## Insights API
|
109
|
+
|
110
|
+
In order to make API calls a Zuora Login object must be created by running:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
insightsapi = InsightsAPI::Login.new(api_token: "api token", url: "Nw1.api.insights.zuora.com/api/")
|
114
|
+
```
|
115
|
+
|
116
|
+
Note that the login will default to the insights production url.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
Date format: "YYYY-MM-DDT00:00:00Z"
|
120
|
+
```
|
121
|
+
|
122
|
+
### Uploading Data into Insights
|
123
|
+
```ruby
|
124
|
+
insightsapi.upload_into_insights(dataSourceName, recordType, batchDate, filePath)
|
125
|
+
```
|
126
|
+
dataSourceName: What system the data is coming from.
|
127
|
+
recordType: The type of records ie: "EVENTS, ATTRIBUTES, and METRICS"
|
128
|
+
batachDate: The date the data applies to.
|
129
|
+
|
130
|
+
### Describing Insights Data
|
131
|
+
```ruby
|
132
|
+
insightsapi.describe(type: "ACCOUNT/USER", object: "ATTRIBUTES/EVENTS/SEGMENTS/METRICS")
|
133
|
+
```
|
134
|
+
Returns json payload describing attributes, events, metrics for each Account or User.
|
135
|
+
|
136
|
+
### Downloading Data from Insights
|
137
|
+
```ruby
|
138
|
+
insightsapi.data_export_insights(objecttype, segmentuuid, startDate: nil, endDate: nil, tries: 30)
|
139
|
+
```
|
140
|
+
```ruby
|
141
|
+
insightsapi.data_export_insights_file(objecttype, segmentuuid, startDate: nil, endDate: nil, tries: 30)
|
142
|
+
```
|
143
|
+
Both do the same thing except one returns a url(data_export_insights) to download the file yourself and the other returns an actual Ruby temporary file(data_export_insights_file).
|
144
|
+
|
145
|
+
objectype: "ACCOUNT/USER"
|
146
|
+
|
147
|
+
segmentuuid: A single or array of string or int of a segment uuid(s) that you get from the describe call. The csv holds a column with a bool that represents if that User or Account belongs to that segment.
|
data/lib/zuora_api/login.rb
CHANGED
@@ -7,7 +7,7 @@ module ZuoraAPI
|
|
7
7
|
class Login
|
8
8
|
ENVIRONMENTS = [TEST = 'Test', SANDBOX = 'Sandbox', PRODUCTION = 'Production', PREFORMANCE = 'Preformance', SERVICES = 'Services', UNKNOWN = 'Unknown', STAGING = 'Staging' ]
|
9
9
|
REGIONS = [EU = 'EU', US = 'US', NA = 'NA' ]
|
10
|
-
|
10
|
+
MIN_Endpoints = {'Test': '107.0', 'Sandbox': '107.0', 'Production': '107.0', 'Performance': '107.0', 'Services': '96.0', 'Unknown': '96.0', 'Staging': '107.0'}.freeze
|
11
11
|
XML_SAVE_OPTIONS = Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
|
12
12
|
|
13
13
|
CONNECTION_EXCEPTIONS = [
|
@@ -50,10 +50,12 @@ module ZuoraAPI
|
|
50
50
|
raise "URL is nil or empty, but URL is required" if url.nil? || url.empty?
|
51
51
|
# raise "URL is improper. URL must contain zuora.com, zuora.eu, or zuora.na" if /zuora.com|zuora.eu|zuora.na/ === url
|
52
52
|
self.hostname = /(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url)[0] if !/(?<=https:\/\/|http:\/\/)(.*?)(?=\/|$)/.match(url).nil?
|
53
|
+
self.update_environment
|
54
|
+
min_endpoint = MIN_Endpoints[self.environment.to_sym]
|
53
55
|
if !/apps\/services\/a\/\d+\.\d$/.match(url.strip)
|
54
|
-
self.url = "https://#{hostname}/apps/services/a/#{
|
55
|
-
elsif
|
56
|
-
self.url = url.gsub(/(\d+\.\d)$/,
|
56
|
+
self.url = "https://#{hostname}/apps/services/a/#{min_endpoint}"
|
57
|
+
elsif min_endpoint.to_f > url.scan(/(\d+\.\d)$/).dig(0,0).to_f
|
58
|
+
self.url = url.gsub(/(\d+\.\d)$/, min_endpoint)
|
57
59
|
else
|
58
60
|
self.url = url
|
59
61
|
end
|
@@ -65,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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
332
|
-
|
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
|
-
|
338
|
+
retries ||= 2
|
391
339
|
yield
|
392
340
|
|
393
341
|
rescue ZuoraAPI::Exceptions::ZuoraAPISessionError => ex
|
394
|
-
self.status = '
|
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
|
349
|
+
raise ex if retries.zero?
|
402
350
|
|
403
|
-
|
351
|
+
retries -= 1
|
404
352
|
sleep(self.timeout_sleep)
|
405
353
|
retry
|
406
354
|
|
@@ -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.
|
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,
|
@@ -697,12 +640,12 @@ module ZuoraAPI
|
|
697
640
|
).present?
|
698
641
|
result = body.xpath('//ns2:Status', 'ns2' => 'http://object.api.zuora.com/').text
|
699
642
|
if result == 'Failed'
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
643
|
+
message = body.xpath('//ns2:StatusReason', 'ns2' => 'http://object.api.zuora.com/').text
|
644
|
+
error = 'FATAL_ERROR'
|
645
|
+
if message.present?
|
646
|
+
identifier, new_message = message.scan(/^([\w\d]{16})\: (.*)/).first
|
647
|
+
error, message = ['UNEXPECTED_ERROR', new_message] if new_message.present?
|
704
648
|
else
|
705
|
-
error = 'FATAL_ERROR'
|
706
649
|
message = 'Export failed due to unknown reason. Consult api logs.'
|
707
650
|
end
|
708
651
|
end
|
@@ -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
|
-
|
763
|
-
|
764
|
-
codes_array =
|
765
|
-
|
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
|
@@ -1398,13 +1364,21 @@ module ZuoraAPI
|
|
1398
1364
|
return file_handle
|
1399
1365
|
when Net::HTTPUnauthorized
|
1400
1366
|
if z_session
|
1401
|
-
|
1367
|
+
unless (retry_count -= 1).zero?
|
1402
1368
|
self.new_session
|
1403
|
-
raise
|
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
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
xml['
|
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
|
-
|
1473
|
-
|
1474
|
-
|
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
|
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
|
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)
|
data/lib/zuora_api/version.rb
CHANGED
data/zuora_api.gemspec
CHANGED
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.
|
4
|
+
version: 1.7.80
|
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:
|
11
|
+
date: 2021-01-22 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
|
@@ -174,6 +174,12 @@ files:
|
|
174
174
|
- Rakefile
|
175
175
|
- bin/console
|
176
176
|
- bin/setup
|
177
|
+
- catalog-info.yaml
|
178
|
+
- docs/index.md
|
179
|
+
- gemfiles/Gemfile-rails.5.0.x
|
180
|
+
- gemfiles/Gemfile-rails.5.1.x
|
181
|
+
- gemfiles/Gemfile-rails.5.2.x
|
182
|
+
- gemfiles/Gemfile-rails.6.0.x
|
177
183
|
- lib/insights_api/login.rb
|
178
184
|
- lib/zuora_api.rb
|
179
185
|
- lib/zuora_api/exceptions.rb
|
@@ -196,11 +202,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
196
202
|
version: '0'
|
197
203
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
204
|
requirements:
|
199
|
-
- - "
|
205
|
+
- - ">="
|
200
206
|
- !ruby/object:Gem::Version
|
201
|
-
version:
|
207
|
+
version: '0'
|
202
208
|
requirements: []
|
203
|
-
rubygems_version: 3.1.
|
209
|
+
rubygems_version: 3.1.4
|
204
210
|
signing_key:
|
205
211
|
specification_version: 4
|
206
212
|
summary: Gem that provides easy integration to Zuora
|