increase 0.1.0 → 0.1.1

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: d9243d27fe1bd52cec9e7de6d0f9437258c21fd6d3ca2b87d3aecaa72cb94b5f
4
- data.tar.gz: a3074a286a31dcb1803edf08a06b1a8fe22df790c09adeeb8ab9546dc993d5c6
3
+ metadata.gz: 43bc6e7b10ff9beb7aad80057ffd20e38540f60b6d2324e119326218794e14b3
4
+ data.tar.gz: 8bb3bb23e78e81801fb0fa3ab29019c2b663a9dd2cd0462d3946698c347fff5a
5
5
  SHA512:
6
- metadata.gz: 68e401c32b3ccc3a4dcc782b9d46cc2cce6fd7cee70a04581f78fbdb9be8a278bde79feaa1b58b9ebe54e00bf80e0361564eebc4370e538f96029106664041b0
7
- data.tar.gz: ef900afd66bea27938146e787a7e14679d230d1a05ccee069699277101d4af659a15de0eb091719a1040f9c50f754f7782a1435a83f6611c81104611f06495f8
6
+ metadata.gz: af671bbe00de695ebee4d75c90e0207f87f79c20b2ad431919d6016987859821397c15ab596268ace2958cbee054832349262764413cc53ddda844a4bc7a4178
7
+ data.tar.gz: 1187e2cefba42d32e0c18c13bfbac49be565def33cbc832ef4a897d88aaf87d087a781a3d3750979fb697026fce327c16b74131058e3e76e2426502126de5968
data/Gemfile CHANGED
@@ -4,9 +4,3 @@ source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in increase.gemspec
6
6
  gemspec
7
-
8
- gem "rake", "~> 13.0"
9
-
10
- gem "rspec", "~> 3.0"
11
-
12
- gem "standard", "~> 1.3"
@@ -0,0 +1,11 @@
1
+ require "faraday"
2
+
3
+ module FaradayMiddleware
4
+ class RaiseIncreaseApiError < Faraday::Middleware
5
+ def on_complete(env)
6
+ return if env[:status] < 400
7
+
8
+ raise Increase::ApiError.from_response(env)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/configuration"
4
+
5
+ require "faraday"
6
+ require "faraday/follow_redirects"
7
+
8
+ # Custom Faraday Middleware to handle raising errors
9
+ require "faraday/raise_increase_api_error"
10
+
11
+ module Increase
12
+ class Client
13
+ extend Forwardable
14
+
15
+ attr_accessor :configuration
16
+ def_delegators :configuration, :configure
17
+
18
+ def_delegators :configuration, :base_url, :base_url=
19
+ def_delegators :configuration, :api_key, :api_key=
20
+ def_delegators :configuration, :raise_api_errors, :raise_api_errors=
21
+
22
+ def initialize(config = nil)
23
+ @configuration = config.is_a?(Configuration) ? config : Configuration.new(config)
24
+ end
25
+
26
+ def connection
27
+ Faraday.new(
28
+ url: @configuration.base_url,
29
+ headers: {
30
+ Authorization: "Bearer #{@configuration.api_key}"
31
+ }
32
+ ) do |f|
33
+ f.request :json
34
+
35
+ if @configuration.raise_api_errors
36
+ # This custom middleware for raising Increase API errors must be
37
+ # located before the JSON response middleware.
38
+ f.use FaradayMiddleware::RaiseIncreaseApiError
39
+ end
40
+
41
+ f.response :json
42
+ f.response :follow_redirects
43
+ f.adapter Faraday.default_adapter
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Increase
4
+ class Configuration
5
+ attr_reader :base_url
6
+ attr_accessor :api_key
7
+ attr_accessor :raise_api_errors
8
+ # TODO: support Faraday config
9
+
10
+ def initialize(config = nil)
11
+ if config.nil?
12
+ reset
13
+ else
14
+ configure(config)
15
+ end
16
+ end
17
+
18
+ def reset
19
+ @base_url = ENV["INCREASE_BASE_URL"] || Increase::PRODUCTION_URL
20
+ @api_key = nil || ENV["INCREASE_API_KEY"]
21
+ @raise_api_errors = true
22
+ end
23
+
24
+ def configure(config = nil)
25
+ if config.is_a?(Hash)
26
+ config.each do |key, value|
27
+ unless respond_to?("#{key}=")
28
+ raise Error, "Invalid configuration key: #{key}"
29
+ end
30
+ public_send("#{key}=", value)
31
+ end
32
+ end
33
+
34
+ if block_given?
35
+ yield self
36
+ end
37
+
38
+ self
39
+ end
40
+
41
+ def base_url=(url)
42
+ url = PRODUCTION_URL if url == :production
43
+ url = SANDBOX_URL if [:sandbox, :development].include?(url)
44
+
45
+ # Validate url
46
+ unless url&.match?(URI::DEFAULT_PARSER.make_regexp(%w[http https]))
47
+ raise ArgumentError, "Invalid url: #{url}"
48
+ end
49
+
50
+ @base_url = url
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Increase
4
+ class Error < StandardError; end
5
+
6
+ class ApiError < Error
7
+ attr_reader :response
8
+
9
+ attr_reader :detail
10
+ attr_reader :status
11
+ attr_reader :title
12
+ attr_reader :type
13
+
14
+ def initialize(message, response: nil, detail: nil, status: nil, title: nil, type: nil)
15
+ @response = response
16
+
17
+ @detail = detail || response.body["detail"]
18
+ @status = status || response.body["status"]
19
+ @title = title || response.body["title"]
20
+ @type = type || response.body["type"]
21
+
22
+ super(message)
23
+ end
24
+
25
+ class << self
26
+ def from_response(response)
27
+ type = response.body["type"]
28
+ klass = ERROR_TYPES[type]
29
+
30
+ # Fallback in case of really bad 5xx error
31
+ klass ||= InternalServerError if (500..599).cover?(response.status)
32
+
33
+ # Handle case of an unknown error
34
+ klass ||= ApiError
35
+
36
+ code = [response.body["status"] || response.status, type].compact.join(": ") || "Error"
37
+ message = [response.body["title"], response.body["detail"]].compact.join(" ") || "Increase API Error"
38
+
39
+ klass.new("[#{code}] #{message}", response: response)
40
+ end
41
+ end
42
+ end
43
+
44
+ class ApiMethodNotFoundError < ApiError; end
45
+
46
+ class EnvironmentMismatchError < ApiError; end
47
+
48
+ class IdempotencyConflictError < ApiError; end
49
+
50
+ class IdempotencyUnprocessableError < ApiError; end
51
+
52
+ class InsufficientPermissionsError < ApiError; end
53
+
54
+ class InternalServerError < ApiError; end
55
+
56
+ class InvalidApiKeyError < ApiError; end
57
+
58
+ class InvalidOperationError < ApiError; end
59
+
60
+ class InvalidParametersError < ApiError; end
61
+
62
+ class MalformedRequestError < ApiError; end
63
+
64
+ class ObjectNotFoundError < ApiError; end
65
+
66
+ class PrivateFeatureError < ApiError; end
67
+
68
+ class RateLimitedError < ApiError; end
69
+
70
+ ERROR_TYPES = {
71
+ "api_method_not_found_error" => ApiMethodNotFoundError,
72
+ "environment_mismatch_error" => EnvironmentMismatchError,
73
+ "idempotency_conflict_error" => IdempotencyConflictError,
74
+ "idempotency_unprocessable_error" => IdempotencyUnprocessableError,
75
+ "insufficient_permissions_error" => InsufficientPermissionsError,
76
+ "internal_server_error" => InternalServerError,
77
+ "invalid_api_key_error" => InvalidApiKeyError,
78
+ "invalid_operation_error" => InvalidOperationError,
79
+ "invalid_parameters_error" => InvalidParametersError,
80
+ "malformed_request_error" => MalformedRequestError,
81
+ "object_not_found_error" => ObjectNotFoundError,
82
+ "private_feature_error" => PrivateFeatureError,
83
+ "rate_limited_error" => RateLimitedError
84
+ }
85
+ end
@@ -0,0 +1,130 @@
1
+ require "increase/response_hash"
2
+
3
+ module Increase
4
+ class Resource
5
+ def initialize(client: nil)
6
+ if instance_of?(Resource)
7
+ raise NotImplementedError, "Resource is an abstract class. You should perform actions on its subclasses (Accounts, Transactions, Card, etc.)"
8
+ end
9
+ @client = client || Increase.default_client
10
+ end
11
+
12
+ def self.with_config(config)
13
+ if config.is_a?(Client)
14
+ new(client: config)
15
+ else
16
+ new(client: Client.new(config))
17
+ end
18
+ end
19
+
20
+ def self.resource_url
21
+ "/#{resource_name.downcase.tr(" ", "_")}"
22
+ end
23
+
24
+ def self.resource_name
25
+ if self == Resource
26
+ raise NotImplementedError, "Resource is an abstract class. You should perform actions on its subclasses (Accounts, Transactions, Card, etc.)"
27
+ end
28
+
29
+ name.split("::").last.gsub(/[A-Z]/, ' \0').strip
30
+ end
31
+
32
+ def self.endpoint(method, as: nil, with: nil)
33
+ if as == :action
34
+ raise Error, "`with` must be a valid HTTP method" unless %i[get post put patch delete].include?(with)
35
+ return endpoint_action(method, with)
36
+ end
37
+ raise Error, "as must be :action" if as
38
+
39
+ define_singleton_method(method) do |*args, &block|
40
+ new.send(method, *args, &block)
41
+ end
42
+
43
+ public method
44
+ end
45
+
46
+ private_class_method :endpoint
47
+
48
+ def self.endpoint_action(method, http_method)
49
+ define_singleton_method(method) do |*args, &block|
50
+ new.send(:action, method, http_method, *args, &block)
51
+ end
52
+
53
+ define_method(method) do |*args, &block|
54
+ new.send(:action, method, http_method, *args, &block)
55
+ end
56
+ end
57
+
58
+ private_class_method :endpoint_action
59
+
60
+ private
61
+
62
+ def create(params = nil, headers = nil)
63
+ request(:post, self.class.resource_url, params, headers)
64
+ end
65
+
66
+ def list(params = nil, headers = nil, &block)
67
+ results = []
68
+ count = 0
69
+ limit = params&.[](:limit) || params&.[]("limit")
70
+ if limit == :all || limit&.>(100)
71
+ params&.delete(:limit)
72
+ params&.delete("limit")
73
+ end
74
+
75
+ loop do
76
+ res = request(:get, self.class.resource_url, params, headers)
77
+ data = res["data"]
78
+ count += data.size
79
+ if ![nil, :all].include?(limit) && count >= limit
80
+ data = data[0..(limit - (count - data.size) - 1)]
81
+ end
82
+
83
+ if block
84
+ block.call(data)
85
+ else
86
+ results += data
87
+ end
88
+
89
+ if limit.nil? || (limit != :all && count >= limit) || res["next_cursor"].nil?
90
+ if block
91
+ break
92
+ else
93
+ return results
94
+ end
95
+ end
96
+
97
+ params = (params || {}).merge({cursor: res["next_cursor"]})
98
+ end
99
+ end
100
+
101
+ def update(id, params = nil, headers = nil)
102
+ raise Error, "id must be a string" unless id.is_a?(String)
103
+ path = "#{self.class.resource_url}/#{id}"
104
+ request(:patch, path, params, headers)
105
+ end
106
+
107
+ def retrieve(id, params = nil, headers = nil)
108
+ raise Error, "id must be a string" unless id.is_a?(String)
109
+ path = "#{self.class.resource_url}/#{id}"
110
+ request(:get, path, params, headers)
111
+ end
112
+
113
+ # Such as for "/accounts/{account_id}/close"
114
+ # "close" is the action.
115
+ def action(action, http_method, id, params = nil, headers = nil)
116
+ raise Error, "id must be a string" unless id.is_a?(String)
117
+ path = "#{self.class.resource_url}/#{id}/#{action}"
118
+ request(http_method, path, params, headers)
119
+ end
120
+
121
+ def request(method, path, params = nil, headers = nil)
122
+ if method == :post
123
+ headers = {"Content-Type" => "application/json"}.merge!(headers || {})
124
+ end
125
+
126
+ response = @client.connection.send(method, path, params, headers)
127
+ ResponseHash.new(response.body, response: response)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class AccountNumbers < Resource
7
+ endpoint :create
8
+ endpoint :list
9
+ endpoint :update
10
+ endpoint :retrieve
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class AccountTransfers < Resource
7
+ endpoint :create
8
+ endpoint :list
9
+ endpoint :retrieve
10
+ endpoint :approve, as: :action, with: :post
11
+ endpoint :cancel, as: :action, with: :post
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class Accounts < Resource
7
+ endpoint :create
8
+ endpoint :list
9
+ endpoint :update
10
+ endpoint :retrieve
11
+ endpoint :close, as: :action, with: :post
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class AchTransfers < Resource
7
+ endpoint :create
8
+ endpoint :list
9
+ endpoint :retrieve
10
+ endpoint :approve, as: :action, with: :post
11
+ endpoint :cancel, as: :action, with: :post
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class Cards < Resource
7
+ endpoint :create
8
+ endpoint :list
9
+ endpoint :details, as: :action, with: :get
10
+ endpoint :update
11
+ endpoint :retrieve
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class Events < Resource
7
+ endpoint :list
8
+ endpoint :retrieve
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class PendingTransactions < Resource
7
+ endpoint :list
8
+ endpoint :retrieve
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "increase/resource"
4
+
5
+ module Increase
6
+ class Transactions < Resource
7
+ endpoint :list
8
+ endpoint :retrieve
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ # Require all files in the lib/increase/resources directory
2
+ Dir[File.expand_path("resources/*.rb", __dir__)].sort.each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,15 @@
1
+ module Increase
2
+ class ResponseHash < Hash
3
+ attr_reader :response
4
+
5
+ def initialize(hash, response: nil)
6
+ @response = response
7
+ merge!(hash)
8
+ end
9
+
10
+ # https://increase.com/documentation/api#idempotency
11
+ def idempotent_replayed
12
+ response.headers["Idempotent-Replayed"]
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Increase
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -0,0 +1,16 @@
1
+ require "openssl"
2
+ require "securecompare"
3
+
4
+ module Increase
5
+ class Webhooks
6
+ def self.verify?(payload:, signature_header:, secret:, scheme: "v1")
7
+ sig_values = signature_header.split(",").map { |pair| pair.split("=") }
8
+ sig_values = sig_values.to_h
9
+
10
+ signed_payload = sig_values["t"] + "." + payload.to_s
11
+
12
+ expected_sig = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
13
+ SecureCompare.compare(expected_sig, sig_values["v1"])
14
+ end
15
+ end
16
+ end
data/lib/increase.rb CHANGED
@@ -1,8 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "increase/version"
3
+ require "increase/version"
4
+ require "increase/client"
5
+ require "increase/configuration"
6
+ require "increase/errors"
7
+ require "increase/resources"
8
+ require "increase/webhooks"
4
9
 
5
10
  module Increase
6
- class Error < StandardError; end
7
- # Your code goes here...
11
+ PRODUCTION_URL = "https://api.increase.com"
12
+ SANDBOX_URL = "https://sandbox.increase.com"
13
+
14
+ @default_client = Client.new
15
+
16
+ class << self
17
+ extend Forwardable
18
+
19
+ attr_accessor :default_client
20
+ def_delegators :default_client, :configure
21
+
22
+ def_delegators :default_client, :base_url, :base_url=
23
+ def_delegators :default_client, :api_key, :api_key=
24
+ def_delegators :default_client, :raise_api_errors, :raise_api_errors=
25
+ end
8
26
  end
metadata CHANGED
@@ -1,15 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: increase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gary Tou
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-14 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-follow_redirects
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: securecompare
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: standard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.13'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.13'
13
125
  description: Ruby API client for Increase, a platform for Bare-Metal Banking APIs
14
126
  email:
15
127
  - gary@garytou.com
@@ -23,8 +135,24 @@ files:
23
135
  - LICENSE.txt
24
136
  - README.md
25
137
  - Rakefile
138
+ - lib/faraday/raise_increase_api_error.rb
26
139
  - lib/increase.rb
140
+ - lib/increase/client.rb
141
+ - lib/increase/configuration.rb
142
+ - lib/increase/errors.rb
143
+ - lib/increase/resource.rb
144
+ - lib/increase/resources.rb
145
+ - lib/increase/resources/account_numbers.rb
146
+ - lib/increase/resources/account_transfers.rb
147
+ - lib/increase/resources/accounts.rb
148
+ - lib/increase/resources/ach_transfers.rb
149
+ - lib/increase/resources/cards.rb
150
+ - lib/increase/resources/events.rb
151
+ - lib/increase/resources/pending_transactions.rb
152
+ - lib/increase/resources/transactions.rb
153
+ - lib/increase/response_hash.rb
27
154
  - lib/increase/version.rb
155
+ - lib/increase/webhooks.rb
28
156
  - sig/increase.rbs
29
157
  homepage: https://github.com/garyhtou/increase-ruby
30
158
  licenses: