increase 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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: