iron_bank 0.1.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.env.example +7 -0
- data/.github/CODEOWNERS +1 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- data/.gitignore +6 -1
- data/.reek +95 -0
- data/.rspec +1 -2
- data/.rubocop.yml +55 -0
- data/.travis.yml +22 -3
- data/Gemfile +3 -3
- data/LICENSE +176 -0
- data/README.md +133 -0
- data/Rakefile +39 -3
- data/bin/console +13 -1
- data/bin/setup +1 -0
- data/iron_bank.gemspec +34 -10
- data/lib/generators/iron_bank/install/install_generator.rb +20 -0
- data/lib/generators/iron_bank/install/templates/README +16 -0
- data/lib/generators/iron_bank/install/templates/iron_bank.rb +29 -0
- data/lib/iron_bank/action.rb +39 -0
- data/lib/iron_bank/actions/amend.rb +16 -0
- data/lib/iron_bank/actions/create.rb +19 -0
- data/lib/iron_bank/actions/delete.rb +19 -0
- data/lib/iron_bank/actions/execute.rb +21 -0
- data/lib/iron_bank/actions/generate.rb +19 -0
- data/lib/iron_bank/actions/query.rb +16 -0
- data/lib/iron_bank/actions/query_more.rb +21 -0
- data/lib/iron_bank/actions/subscribe.rb +32 -0
- data/lib/iron_bank/actions/update.rb +19 -0
- data/lib/iron_bank/associations.rb +73 -0
- data/lib/iron_bank/authentication.rb +37 -0
- data/lib/iron_bank/authentications/cookie.rb +80 -0
- data/lib/iron_bank/authentications/token.rb +82 -0
- data/lib/iron_bank/cacheable.rb +52 -0
- data/lib/iron_bank/client.rb +96 -0
- data/lib/iron_bank/collection.rb +31 -0
- data/lib/iron_bank/configuration.rb +86 -0
- data/lib/iron_bank/csv.rb +29 -0
- data/lib/iron_bank/describe/field.rb +65 -0
- data/lib/iron_bank/describe/object.rb +81 -0
- data/lib/iron_bank/describe/related.rb +40 -0
- data/lib/iron_bank/describe/tenant.rb +50 -0
- data/lib/iron_bank/endpoint.rb +36 -0
- data/lib/iron_bank/error.rb +45 -0
- data/lib/iron_bank/instrumentation.rb +14 -0
- data/lib/iron_bank/local.rb +72 -0
- data/lib/iron_bank/local_records.rb +52 -0
- data/lib/iron_bank/logger.rb +23 -0
- data/lib/iron_bank/metadata.rb +36 -0
- data/lib/iron_bank/object.rb +89 -0
- data/lib/iron_bank/open_tracing.rb +17 -0
- data/lib/iron_bank/operation.rb +33 -0
- data/lib/iron_bank/operations/billing_preview.rb +16 -0
- data/lib/iron_bank/query_builder.rb +72 -0
- data/lib/iron_bank/queryable.rb +55 -0
- data/lib/iron_bank/resource.rb +70 -0
- data/lib/iron_bank/resources/account.rb +62 -0
- data/lib/iron_bank/resources/amendment.rb +13 -0
- data/lib/iron_bank/resources/catalog_tiers/discount_amount.rb +26 -0
- data/lib/iron_bank/resources/catalog_tiers/discount_percentage.rb +26 -0
- data/lib/iron_bank/resources/catalog_tiers/price.rb +26 -0
- data/lib/iron_bank/resources/contact.rb +13 -0
- data/lib/iron_bank/resources/export.rb +11 -0
- data/lib/iron_bank/resources/import.rb +12 -0
- data/lib/iron_bank/resources/invoice.rb +37 -0
- data/lib/iron_bank/resources/invoice_adjustment.rb +13 -0
- data/lib/iron_bank/resources/invoice_item.rb +25 -0
- data/lib/iron_bank/resources/invoice_payment.rb +13 -0
- data/lib/iron_bank/resources/payment.rb +17 -0
- data/lib/iron_bank/resources/payment_method.rb +14 -0
- data/lib/iron_bank/resources/product.rb +14 -0
- data/lib/iron_bank/resources/product_rate_plan.rb +32 -0
- data/lib/iron_bank/resources/product_rate_plan_charge.rb +27 -0
- data/lib/iron_bank/resources/product_rate_plan_charge_tier.rb +60 -0
- data/lib/iron_bank/resources/rate_plan.rb +21 -0
- data/lib/iron_bank/resources/rate_plan_charge.rb +30 -0
- data/lib/iron_bank/resources/rate_plan_charge_tier.rb +26 -0
- data/lib/iron_bank/resources/subscription.rb +28 -0
- data/lib/iron_bank/resources/usage.rb +16 -0
- data/lib/iron_bank/response/raise_error.rb +16 -0
- data/lib/iron_bank/schema.rb +58 -0
- data/lib/iron_bank/utils.rb +59 -0
- data/lib/iron_bank/version.rb +4 -1
- data/lib/iron_bank.rb +152 -2
- metadata +300 -12
data/iron_bank.gemspec
CHANGED
@@ -1,15 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require 'iron_bank/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
8
|
spec.name = 'iron_bank'
|
7
9
|
spec.version = IronBank::VERSION
|
8
|
-
spec.
|
9
|
-
spec.
|
10
|
-
spec.
|
11
|
-
spec.
|
12
|
-
|
10
|
+
spec.summary = 'An opinionated Ruby interface to the Zuora API.'
|
11
|
+
spec.homepage = 'https://github.com/zendesk/iron_bank'
|
12
|
+
spec.license = 'Apache-2.0'
|
13
|
+
spec.authors = ['Mickael Pham', 'Cheng Cui', 'Ryan Ringler', 'Mustafa Turan']
|
14
|
+
|
15
|
+
spec.email = [
|
16
|
+
'mickael@zendesk.com',
|
17
|
+
'ccui@zendesk.com',
|
18
|
+
'rringler@zendesk.com',
|
19
|
+
'mturan@zendesk.com'
|
20
|
+
]
|
13
21
|
|
14
22
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
15
23
|
f.match(%r{^(test|spec|features)/})
|
@@ -19,8 +27,24 @@ Gem::Specification.new do |spec|
|
|
19
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
28
|
spec.require_paths = ['lib']
|
21
29
|
|
22
|
-
spec.add_development_dependency '
|
23
|
-
spec.add_development_dependency '
|
24
|
-
spec.add_development_dependency '
|
25
|
-
spec.add_development_dependency '
|
30
|
+
spec.add_development_dependency 'bump', '~> 0.5'
|
31
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
32
|
+
spec.add_development_dependency 'dotenv', '~> 2.2'
|
33
|
+
spec.add_development_dependency 'factory_bot', '~> 4.10'
|
34
|
+
spec.add_development_dependency 'private_gem', '~> 1.1'
|
35
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.4'
|
36
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
37
|
+
spec.add_development_dependency 'reek', '~> 4.6'
|
38
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
39
|
+
spec.add_development_dependency 'rubocop', '~> 0.52'
|
40
|
+
spec.add_development_dependency 'shoulda-matchers', '~> 3.1'
|
41
|
+
spec.add_development_dependency 'simplecov', '~> 0.15'
|
42
|
+
spec.add_development_dependency 'timecop', '~> 0.9.0'
|
43
|
+
spec.add_development_dependency 'vcr', '~> 4.0'
|
44
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
45
|
+
|
46
|
+
spec.add_dependency 'ddtrace', '~> 0'
|
47
|
+
spec.add_dependency 'faraday', '~> 0'
|
48
|
+
spec.add_dependency 'faraday_middleware', '~> 0'
|
49
|
+
spec.add_dependency 'nokogiri', '~> 1'
|
26
50
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
module IronBank
|
6
|
+
module Generators
|
7
|
+
# Allow us to run `rails generate iron_bank:install`
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
10
|
+
|
11
|
+
def create_iron_bank_initializer
|
12
|
+
copy_file 'iron_bank.rb', 'config/initializers/iron_bank.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
def display_readme_in_terminal
|
16
|
+
readme 'README'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
*******************************************************************************
|
2
|
+
|
3
|
+
Next steps:
|
4
|
+
|
5
|
+
1. Edit the config/initializers/iron_bank.rb file with your Zuora credentials
|
6
|
+
|
7
|
+
2. Export the schema for your Zuora tenant by running in the Rails console:
|
8
|
+
|
9
|
+
IronBank::Schema.export
|
10
|
+
|
11
|
+
3. (optional) Export your product catalog by running in the Rails console:
|
12
|
+
|
13
|
+
IronBank::LocalRecords.export
|
14
|
+
exit # need to exit the session to finish exporting the local records
|
15
|
+
|
16
|
+
*******************************************************************************
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
IronBank.configure do |config|
|
4
|
+
# Zuora OAuth client ID
|
5
|
+
config.client_id = '<my-client-id-from-zuora>'
|
6
|
+
|
7
|
+
# Zuora OAuth client secret
|
8
|
+
config.client_secret = '<my-secret-from-zuora>'
|
9
|
+
|
10
|
+
# Zuora API domain (apisandbox, production, etc.)
|
11
|
+
config.domain = 'rest.apisandbox.zuora.com'
|
12
|
+
|
13
|
+
# Directory where the metadata XML files (Zuora schema) will be stored
|
14
|
+
config.schema_directory = 'config/zuora/schema'
|
15
|
+
|
16
|
+
# Directory where the local export CSV files will be stored
|
17
|
+
config.export_directory = 'config/zuora/local_records'
|
18
|
+
|
19
|
+
# Zuora authentication type:
|
20
|
+
# - `token` uses OAuth and is the *recommended* approach
|
21
|
+
# - `cookie` uses username/password for Zuora environments that do not
|
22
|
+
# support OAuth authentication, e.g., services environment. If
|
23
|
+
# using `cookie` authentication, then use an API user username as
|
24
|
+
# the `client_id` and the API user password as `client_secret`
|
25
|
+
config.auth_type = 'token'
|
26
|
+
|
27
|
+
# Set the gem to use the Rails logger
|
28
|
+
config.logger = Rails.logger
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Base class for Zuora actions, e.g., subscribe
|
5
|
+
#
|
6
|
+
class Action
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
def self.call(args)
|
10
|
+
new(args).call
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
IronBank.client.connection.post(endpoint, params).body
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :args
|
20
|
+
|
21
|
+
def initialize(args)
|
22
|
+
@args = args
|
23
|
+
end
|
24
|
+
|
25
|
+
def endpoint
|
26
|
+
"v1/action/#{name.downcase}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
self.class.name.split('::').last
|
31
|
+
end
|
32
|
+
|
33
|
+
def requests(type: :upper)
|
34
|
+
args.fetch(:objects).map do |object|
|
35
|
+
IronBank::Object.new(object).deep_camelize(type: type)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Use the amend call to change a subscription
|
6
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTamend
|
7
|
+
#
|
8
|
+
class Amend < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{ requests: args }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Create one or more objects of a specific type
|
6
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTcreate
|
7
|
+
#
|
8
|
+
class Create < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{
|
13
|
+
objects: requests,
|
14
|
+
type: args.fetch(:type)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Delete one or more objects of the same type
|
6
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTdelete
|
7
|
+
#
|
8
|
+
class Delete < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{
|
13
|
+
ids: args.fetch(:ids),
|
14
|
+
type: args.fetch(:type)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Use the execute call to execute the process to split an invoice into
|
6
|
+
# multiple invoices (the original invoice must be in draft status)
|
7
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTexecute
|
8
|
+
#
|
9
|
+
class Execute < Action
|
10
|
+
private
|
11
|
+
|
12
|
+
def params
|
13
|
+
{
|
14
|
+
ids: args.fetch(:ids),
|
15
|
+
synchronous: args.fetch(:synchronous),
|
16
|
+
type: args.fetch(:type)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Generate an on-demand invoice for a specific customer
|
6
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTgenerate
|
7
|
+
#
|
8
|
+
class Generate < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{
|
13
|
+
objects: args.fetch(:objects).map(&:deep_camelize),
|
14
|
+
type: args.fetch(:type)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Query Zuora using ZOQL
|
6
|
+
# https://knowledgecenter.zuora.com/DC_Developers/K_Zuora_Object_Query_Language
|
7
|
+
#
|
8
|
+
class Query < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{ 'queryString' => args }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Query More call to Zuora REST API
|
6
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTqueryMore
|
7
|
+
#
|
8
|
+
class QueryMore < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{ 'queryLocator' => args }
|
13
|
+
end
|
14
|
+
|
15
|
+
# NOTE: Zuora API endpoint is case-sensitive.
|
16
|
+
def endpoint
|
17
|
+
'v1/action/queryMore'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Use the subscribe call to bundle information required to create at least
|
6
|
+
# one new subscription
|
7
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTsubscribe
|
8
|
+
#
|
9
|
+
class Subscribe < Action
|
10
|
+
def call
|
11
|
+
body = IronBank.client.connection.post(endpoint, params).body
|
12
|
+
|
13
|
+
if body.is_a?(Array)
|
14
|
+
body.map { |result| IronBank::Object.new(result).deep_underscore }
|
15
|
+
else
|
16
|
+
IronBank::Object.new(body).deep_underscore
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def params
|
23
|
+
{ subscribes: subscribe_requests }
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscribe_requests
|
27
|
+
requests = [args].flatten
|
28
|
+
requests.map { |request| IronBank::Object.new(request).deep_camelize }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Actions
|
5
|
+
# Update the information in one or more objects of the same type
|
6
|
+
# https://www.zuora.com/developer/api-reference/#operation/Action_POSTupdate
|
7
|
+
#
|
8
|
+
class Update < Action
|
9
|
+
private
|
10
|
+
|
11
|
+
def params
|
12
|
+
{
|
13
|
+
objects: requests,
|
14
|
+
type: args.fetch(:type)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Define association methods for Zuora resources.
|
5
|
+
#
|
6
|
+
module Associations
|
7
|
+
# Define class methods
|
8
|
+
#
|
9
|
+
module ClassMethods
|
10
|
+
def with_one(name, options = {})
|
11
|
+
resource_name = options.fetch(
|
12
|
+
:resource_name,
|
13
|
+
IronBank::Utils.camelize(name)
|
14
|
+
)
|
15
|
+
foreign_key = options.fetch(:foreign_key, name.to_s + '_id')
|
16
|
+
|
17
|
+
define_method(name) do
|
18
|
+
return unless (foreign_key_value = public_send(foreign_key))
|
19
|
+
|
20
|
+
with_memoization(name) do
|
21
|
+
# NOTE: we retrieve the constant here to prevent the need to order
|
22
|
+
# our `require <file>` statements in `iron_bank.rb`
|
23
|
+
klass = IronBank::Resources.const_get(resource_name)
|
24
|
+
klass.find(foreign_key_value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Association is "also known as"
|
29
|
+
aka = options[:aka]
|
30
|
+
alias_method aka, name if aka
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_many(name, options = {})
|
34
|
+
resource_name = options.fetch(
|
35
|
+
:resource_name,
|
36
|
+
IronBank::Utils.camelize(name.to_s.chop)
|
37
|
+
)
|
38
|
+
|
39
|
+
foreign_key = options.fetch(
|
40
|
+
:foreign_key,
|
41
|
+
IronBank::Utils.underscore(object_name) + '_id'
|
42
|
+
)
|
43
|
+
|
44
|
+
define_method(name) do
|
45
|
+
with_memoization(name) do
|
46
|
+
# NOTE: we retrieve the constant here to prevent the need to order
|
47
|
+
# our `require <file>` statements in `iron_bank.rb`
|
48
|
+
klass = IronBank::Resources.const_get(resource_name)
|
49
|
+
conditions = options.fetch(:conditions, {}).
|
50
|
+
merge("#{foreign_key}": id)
|
51
|
+
items = klass.where(conditions)
|
52
|
+
IronBank::Collection.new(klass, conditions, items)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Association is "also known as"
|
57
|
+
aka = options[:aka]
|
58
|
+
alias_method aka, name if aka
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_memoization(name)
|
63
|
+
# NOTE: We use `instance_variables.include?` instead of `defined?`.
|
64
|
+
# Later it will always evaluate to `true` because it's an expression.
|
65
|
+
if instance_variables.include?(:"@#{name}")
|
66
|
+
return instance_variable_get(:"@#{name}")
|
67
|
+
end
|
68
|
+
|
69
|
+
memoizable = yield
|
70
|
+
instance_variable_set(:"@#{name}", memoizable)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Get a valid token or session HTTP request header for IronBank
|
5
|
+
#
|
6
|
+
class Authentication
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :session
|
10
|
+
|
11
|
+
def_delegators :session, :header, :expired?
|
12
|
+
|
13
|
+
def initialize(params)
|
14
|
+
@auth_type = params.delete(:auth_type)
|
15
|
+
@params = params
|
16
|
+
create_session
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_session
|
20
|
+
@session = adapter.new(params)
|
21
|
+
end
|
22
|
+
alias renew_session create_session
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :auth_type, :params
|
27
|
+
|
28
|
+
def adapter
|
29
|
+
@adapter ||=
|
30
|
+
if auth_type == 'cookie'
|
31
|
+
IronBank::Authentications::Cookie
|
32
|
+
else
|
33
|
+
IronBank::Authentications::Token
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Authentications
|
5
|
+
# Get a cookie to enable authenticated calls to Zuora through Session.
|
6
|
+
#
|
7
|
+
class Cookie
|
8
|
+
include IronBank::OpenTracing
|
9
|
+
|
10
|
+
TEN_MINUTES = 600
|
11
|
+
ONE_HOUR = 3600
|
12
|
+
|
13
|
+
def initialize(client_id:, client_secret:, base_url:)
|
14
|
+
@client_id = client_id
|
15
|
+
@client_secret = client_secret
|
16
|
+
@base_url = base_url
|
17
|
+
fetch_cookie
|
18
|
+
end
|
19
|
+
|
20
|
+
def expired?
|
21
|
+
# Ten minutes as a margin of error in order to renew token in time
|
22
|
+
expires_at < Time.now + TEN_MINUTES
|
23
|
+
end
|
24
|
+
|
25
|
+
def header
|
26
|
+
{ 'Cookie' => use }
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :client_id, :client_secret, :base_url, :cookie, :zsession,
|
32
|
+
:expires_at
|
33
|
+
|
34
|
+
def use
|
35
|
+
refetch_cookie if expired?
|
36
|
+
zsession
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_cookie
|
40
|
+
response = authenticate
|
41
|
+
@cookie = response.headers['set-cookie']
|
42
|
+
@zsession = fetch_zsession
|
43
|
+
@expires_at = Time.now + ONE_HOUR
|
44
|
+
end
|
45
|
+
alias refetch_cookie fetch_cookie
|
46
|
+
|
47
|
+
def fetch_zsession
|
48
|
+
/ZSession=([^\;]+)/.match(cookie)[0]
|
49
|
+
end
|
50
|
+
|
51
|
+
def authenticate
|
52
|
+
connection.post('v1/connections', {})
|
53
|
+
end
|
54
|
+
|
55
|
+
def connection
|
56
|
+
@connection ||= Faraday.new(faraday_config) do |conn|
|
57
|
+
conn.use :ddtrace, open_tracing_options if open_tracing_enabled?
|
58
|
+
conn.request :url_encoded
|
59
|
+
conn.response :logger, IronBank.logger
|
60
|
+
conn.response :json
|
61
|
+
conn.adapter Faraday.default_adapter
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def faraday_config
|
66
|
+
{
|
67
|
+
url: base_url,
|
68
|
+
headers: authentication_headers
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def authentication_headers
|
73
|
+
{
|
74
|
+
apiaccesskeyid: client_id,
|
75
|
+
apisecretaccesskey: client_secret
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Authentications
|
5
|
+
# Get a bearer token to enable authenticated calls to Zuora through OAuth.
|
6
|
+
#
|
7
|
+
class Token
|
8
|
+
include IronBank::OpenTracing
|
9
|
+
|
10
|
+
# Generic token error.
|
11
|
+
#
|
12
|
+
class Error < StandardError; end
|
13
|
+
|
14
|
+
# Thrown when the access_token is not valid.
|
15
|
+
#
|
16
|
+
class InvalidAccessToken < Error; end
|
17
|
+
|
18
|
+
TEN_MINUTES = 600
|
19
|
+
ONE_HOUR = 3600
|
20
|
+
|
21
|
+
def initialize(client_id:, client_secret:, base_url:)
|
22
|
+
@client_id = client_id
|
23
|
+
@client_secret = client_secret
|
24
|
+
@base_url = base_url
|
25
|
+
fetch_token
|
26
|
+
end
|
27
|
+
|
28
|
+
def expired?
|
29
|
+
# Ten minutes as a margin of error in order to renew token in time
|
30
|
+
expires_at < Time.now + TEN_MINUTES
|
31
|
+
end
|
32
|
+
|
33
|
+
def header
|
34
|
+
{ 'Authorization' => "Bearer #{use}" }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :client_id, :client_secret, :base_url, :access_token,
|
40
|
+
:expires_at
|
41
|
+
|
42
|
+
def use
|
43
|
+
refetch_token if expired?
|
44
|
+
access_token
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_token
|
48
|
+
response = authenticate || {}
|
49
|
+
@access_token = response.fetch('access_token', nil)
|
50
|
+
@expires_at = Time.now + response.fetch('expires_in', ONE_HOUR).to_i
|
51
|
+
validate_access_token
|
52
|
+
end
|
53
|
+
alias refetch_token fetch_token
|
54
|
+
|
55
|
+
def authenticate
|
56
|
+
connection.post('/oauth/token', authentication_params).body
|
57
|
+
end
|
58
|
+
|
59
|
+
def connection
|
60
|
+
@connection ||= Faraday.new(url: base_url) do |conn|
|
61
|
+
conn.use :ddtrace, open_tracing_options if open_tracing_enabled?
|
62
|
+
conn.request :url_encoded
|
63
|
+
conn.response :logger, IronBank.logger
|
64
|
+
conn.response :json
|
65
|
+
conn.adapter Faraday.default_adapter
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def authentication_params
|
70
|
+
{
|
71
|
+
client_id: client_id,
|
72
|
+
client_secret: client_secret,
|
73
|
+
grant_type: 'client_credentials'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_access_token
|
78
|
+
raise InvalidAccessToken, access_token unless access_token
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
# Use the provided cache if present.
|
5
|
+
#
|
6
|
+
module Cacheable
|
7
|
+
def reload
|
8
|
+
remove_instance_vars
|
9
|
+
@remote = self.class.find(id, force: true).remote
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def remove_instance_vars
|
15
|
+
# Substract predefined variables from the instance variables
|
16
|
+
(instance_variables - [:@remote]).each do |var|
|
17
|
+
remove_instance_variable(:"#{var}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def cache
|
22
|
+
self.class.cache
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override queryable class methods to use cache if present.
|
26
|
+
#
|
27
|
+
module ClassMethods
|
28
|
+
def find(id, force: false)
|
29
|
+
return super(id) unless cache
|
30
|
+
|
31
|
+
cache.fetch(id, force: force) { super(id) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def where(conditions)
|
35
|
+
# Conditions can be empty when called from #all, it does not make sense
|
36
|
+
# to try to cache all records returned then.
|
37
|
+
return super if conditions.empty?
|
38
|
+
|
39
|
+
return super unless cache
|
40
|
+
|
41
|
+
# Ensure we are not colliding when the conditions are similar for two
|
42
|
+
# different resources, like Account and Subscription.
|
43
|
+
cache_key = conditions.merge(object_name: name)
|
44
|
+
cache.fetch(cache_key) { super }
|
45
|
+
end
|
46
|
+
|
47
|
+
def cache
|
48
|
+
IronBank.configuration.cache
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|