starling-ruby 0.1.0 → 0.2.0
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 +4 -4
- data/.circleci/config.yml +8 -0
- data/.gitignore +1 -0
- data/.reek +27 -0
- data/.rubocop.yml +6 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +37 -2
- data/README.md +102 -17
- data/bin/console +2 -6
- data/lib/starling.rb +40 -7
- data/lib/starling/api_service.rb +45 -37
- data/lib/starling/client.rb +165 -6
- data/lib/starling/errors/api_error.rb +35 -2
- data/lib/starling/errors/base_error.rb +6 -24
- data/lib/starling/middlewares/raise_starling_errors.rb +11 -10
- data/lib/starling/request.rb +17 -19
- data/lib/starling/resources/account_balance_resource.rb +14 -6
- data/lib/starling/resources/account_resource.rb +10 -0
- data/lib/starling/resources/address_resource.rb +26 -0
- data/lib/starling/resources/addresses_resource.rb +18 -0
- data/lib/starling/resources/base_resource.rb +34 -9
- data/lib/starling/resources/card_resource.rb +46 -0
- data/lib/starling/resources/contact_account_resource.rb +31 -0
- data/lib/starling/resources/contact_resource.rb +16 -0
- data/lib/starling/resources/customer_resource.rb +36 -0
- data/lib/starling/resources/direct_debit_mandate_resource.rb +43 -0
- data/lib/starling/resources/direct_debit_transaction_resource.rb +54 -0
- data/lib/starling/resources/inbound_faster_payments_transaction_resource.rb +56 -0
- data/lib/starling/resources/mastercard_transaction_resource.rb +73 -0
- data/lib/starling/resources/me_resource.rb +26 -0
- data/lib/starling/resources/merchant_location_resource.rb +33 -0
- data/lib/starling/resources/merchant_resource.rb +34 -0
- data/lib/starling/resources/outbound_faster_payments_transaction_resource.rb +56 -0
- data/lib/starling/resources/payment_resource.rb +78 -0
- data/lib/starling/resources/transaction_resource.rb +42 -0
- data/lib/starling/services/account_balance_service.rb +11 -2
- data/lib/starling/services/account_service.rb +16 -3
- data/lib/starling/services/addresses_service.rb +26 -0
- data/lib/starling/services/base_service.rb +22 -1
- data/lib/starling/services/card_service.rb +26 -0
- data/lib/starling/services/contact_accounts_service.rb +54 -0
- data/lib/starling/services/contacts_service.rb +73 -0
- data/lib/starling/services/customer_service.rb +25 -0
- data/lib/starling/services/direct_debit_mandates_service.rb +61 -0
- data/lib/starling/services/direct_debit_transactions_service.rb +46 -0
- data/lib/starling/services/inbound_faster_payments_transactions_service.rb +46 -0
- data/lib/starling/services/mastercard_transactions_service.rb +30 -0
- data/lib/starling/services/me_service.rb +26 -0
- data/lib/starling/services/merchant_locations_service.rb +34 -0
- data/lib/starling/services/merchants_service.rb +26 -0
- data/lib/starling/services/outbound_faster_payments_transactions_service.rb +46 -0
- data/lib/starling/services/payments_service.rb +30 -0
- data/lib/starling/services/transactions_service.rb +44 -0
- data/lib/starling/utils.rb +30 -0
- data/lib/starling/version.rb +1 -1
- data/starling-ruby.gemspec +2 -0
- metadata +63 -2
data/lib/starling/client.rb
CHANGED
@@ -1,33 +1,192 @@
|
|
1
1
|
require 'uri'
|
2
2
|
|
3
3
|
module Starling
|
4
|
+
# The client for the Starling Bank API, providing authenticated access to the various
|
5
|
+
# endpoints offered in the API, with a uniform, idiomatic Ruby interface.
|
4
6
|
class Client
|
7
|
+
# URLs for the two Starling Bank API environments, production and sandbox
|
5
8
|
ENVIRONMENT_BASE_URLS = {
|
6
9
|
production: 'https://api.starlingbank.com',
|
7
10
|
sandbox: 'https://api-sandbox.starlingbank.com'
|
8
11
|
}.freeze
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
# Instantiates a client for accessing the Starling Bank API
|
14
|
+
#
|
15
|
+
# @param access_token [String] A personal access token for the Starling Bank API
|
16
|
+
# @param environment [Symbol] The API environment to use, either :production or
|
17
|
+
# :sandbox
|
18
|
+
# @param default_headers [Hash] A set of HTTP headers to add to each request,
|
19
|
+
# alongside the library's defaults defined in
|
20
|
+
# {Starling::ApiService#library_default_headers}
|
21
|
+
# @param connection_options [Hash] A hash of options to be passed in when
|
22
|
+
# instantiating Faraday (for example for setting
|
23
|
+
# the request timeout)
|
24
|
+
# @return [Starling::Client] the configured Starling client
|
25
|
+
def initialize(access_token:, environment: :production, default_headers: {},
|
26
|
+
connection_options: {})
|
15
27
|
@api_service = ApiService.new(fetch_base_url_for_environment(environment),
|
16
|
-
|
28
|
+
access_token: access_token,
|
29
|
+
default_headers: default_headers,
|
30
|
+
connection_options: connection_options)
|
17
31
|
end
|
18
32
|
|
33
|
+
# Provides access to the Account API
|
34
|
+
#
|
35
|
+
# @return [Starling::Services::AccountService] a configured service for accessing the
|
36
|
+
# Account API
|
19
37
|
def account
|
20
38
|
Services::AccountService.new(@api_service)
|
21
39
|
end
|
22
40
|
|
41
|
+
# Provides access to the Account Balance API
|
42
|
+
#
|
43
|
+
# @return [Starling::Services::AccountBalanceService] a configured service for
|
44
|
+
# accessing the Account Balance
|
45
|
+
# API
|
23
46
|
def account_balance
|
24
47
|
Services::AccountBalanceService.new(@api_service)
|
25
48
|
end
|
26
49
|
|
50
|
+
# Provides access to the Transactions API
|
51
|
+
#
|
52
|
+
# @return [Starling::Services::TransactionsService] a configured service for
|
53
|
+
# accessing the Transactions API
|
27
54
|
def transactions
|
28
55
|
Services::TransactionsService.new(@api_service)
|
29
56
|
end
|
30
57
|
|
58
|
+
# Provides access to the Merchants API
|
59
|
+
#
|
60
|
+
# @return [Starling::Services::MerchantsService] a configured service for accessing
|
61
|
+
# the Merchants API
|
62
|
+
def merchants
|
63
|
+
Services::MerchantsService.new(@api_service)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Provides access to the Merchants Locations API
|
67
|
+
#
|
68
|
+
# @return [Starling::Services::MerchantLocationsService] a configured service for
|
69
|
+
# accessing the Merchant
|
70
|
+
# Locations API
|
71
|
+
def merchant_locations
|
72
|
+
Services::MerchantLocationsService.new(@api_service)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Provides access to the Card API
|
76
|
+
#
|
77
|
+
# @return [Starling::Services::CardService] a configured service for accessing the
|
78
|
+
# Card API
|
79
|
+
def card
|
80
|
+
Services::CardService.new(@api_service)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Provides access to the Who Am I (Me) API
|
84
|
+
#
|
85
|
+
# @return [Starling::Services::MeService] a configured service for accessing the Who
|
86
|
+
# Am I (Me) API
|
87
|
+
def me
|
88
|
+
Services::MeService.new(@api_service)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Provides access to the Customer API
|
92
|
+
#
|
93
|
+
# @return [Starling::Services::CustomerService] a configured service for accessing
|
94
|
+
# the Customer API
|
95
|
+
def customer
|
96
|
+
Services::CustomerService.new(@api_service)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Provides access to the Addresses API
|
100
|
+
#
|
101
|
+
# @return [Starling::Services::AddressesService] a configured service for accessing
|
102
|
+
# the Addresses API
|
103
|
+
def addresses
|
104
|
+
Services::AddressesService.new(@api_service)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Provides access to the Direct Debit Mandates API
|
108
|
+
#
|
109
|
+
# @return [Starling::Services::DirectDebitMandatesService] a configured service for
|
110
|
+
# accessing the Direct Debit
|
111
|
+
# Mandates API
|
112
|
+
def direct_debit_mandates
|
113
|
+
Services::DirectDebitMandatesService.new(@api_service)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Provides access to the Contacts API
|
117
|
+
#
|
118
|
+
# @return [Starling::Services::ContactsService] a configured service for accessing
|
119
|
+
# the Contacts API
|
120
|
+
def contacts
|
121
|
+
Services::ContactsService.new(@api_service)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Provides access to the Contact Accounts API
|
125
|
+
#
|
126
|
+
# @return [Starling::Services::ContactAccountsService] a configured service for
|
127
|
+
# accessing the Contact Accounts
|
128
|
+
# API
|
129
|
+
def contact_accounts
|
130
|
+
Services::ContactAccountsService.new(@api_service)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Provides access to the Transaction Faster Payment In API
|
134
|
+
#
|
135
|
+
# @return [Starling::Services::InboundFasterPaymentsTransactionsService] a configured
|
136
|
+
# service for
|
137
|
+
# accessing
|
138
|
+
# the
|
139
|
+
# Transaction
|
140
|
+
# Faster
|
141
|
+
# Payment In
|
142
|
+
# API
|
143
|
+
def inbound_faster_payments_transactions
|
144
|
+
Services::InboundFasterPaymentsTransactionsService.new(@api_service)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Provides access to the Transaction Faster Payment Out API
|
148
|
+
#
|
149
|
+
# @return [Starling::Services::OutboundFasterPaymentsTransactionsService] a
|
150
|
+
# configured
|
151
|
+
# service for
|
152
|
+
# accessing
|
153
|
+
# the
|
154
|
+
# Transaction
|
155
|
+
# Faster
|
156
|
+
# Payment Out
|
157
|
+
# API
|
158
|
+
def outbound_faster_payments_transactions
|
159
|
+
Services::OutboundFasterPaymentsTransactionsService.new(@api_service)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Provides access to the Transaction Direct Debit API
|
163
|
+
#
|
164
|
+
# @return [Starling::Services::DirectDebitTransactionsService] a configured service
|
165
|
+
# for accessing the
|
166
|
+
# Transaction Direct
|
167
|
+
# Debit API
|
168
|
+
def direct_debit_transactions
|
169
|
+
Services::DirectDebitTransactionsService.new(@api_service)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Provides access to the Payment API
|
173
|
+
#
|
174
|
+
# @return [Starling::Services::PaymentsService] a configured service for accessing
|
175
|
+
# the Payment API
|
176
|
+
def payments
|
177
|
+
Services::PaymentsService.new(@api_service)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Provides access to the Transaction Mastercard API
|
181
|
+
#
|
182
|
+
# @return [Starling::Services::MastercardTransactionsService] a configured service
|
183
|
+
# for accessing the
|
184
|
+
# Transaction Mastercard
|
185
|
+
# API
|
186
|
+
def mastercard_transactions
|
187
|
+
Services::MastercardTransactionsService.new(@api_service)
|
188
|
+
end
|
189
|
+
|
31
190
|
private
|
32
191
|
|
33
192
|
def fetch_base_url_for_environment(environment)
|
@@ -2,13 +2,46 @@ require 'json'
|
|
2
2
|
|
3
3
|
module Starling
|
4
4
|
module Errors
|
5
|
+
# An error raised when the Starling Bank API responds in a way indicating an error
|
5
6
|
class ApiError < BaseError
|
7
|
+
# @return [String] a helpful message explaining the error, incorporating the
|
8
|
+
# HTTP status code and the error message (either parsed from the
|
9
|
+
# JSON for a JSON response, or the whole body)
|
6
10
|
def message
|
7
|
-
|
8
|
-
return
|
11
|
+
# If there response isn't JSON or either the Starling-provided error or error
|
12
|
+
# description is missing, return a simpler error from BaseError
|
13
|
+
return super unless error && error_description
|
9
14
|
|
10
15
|
"#{status}: #{error_description} (#{error})"
|
11
16
|
end
|
17
|
+
alias to_s message
|
18
|
+
|
19
|
+
# @return [String] the error name returned by the Starling Bank API
|
20
|
+
def error
|
21
|
+
return unless json?
|
22
|
+
parsed_body['error']
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] the error description returned by the Starling Bank API
|
26
|
+
def error_description
|
27
|
+
return unless json?
|
28
|
+
parsed_body['error_description']
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] the parsed JSON response, if there is a valid JSON body
|
32
|
+
# @return [nil] if there is no body, or the body is not valid JSON
|
33
|
+
def parsed_body
|
34
|
+
return unless body
|
35
|
+
JSON.parse(body)
|
36
|
+
rescue JSON::ParserError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def json?
|
43
|
+
parsed_body
|
44
|
+
end
|
12
45
|
end
|
13
46
|
end
|
14
47
|
end
|
@@ -2,9 +2,13 @@ require 'json'
|
|
2
2
|
|
3
3
|
module Starling
|
4
4
|
module Errors
|
5
|
+
# A basic implementation of an error thrown from a {Faraday::Response::Middleware},
|
6
|
+
# receiving the Faraday environment as an argument, providing access to the response
|
7
|
+
# status and body
|
5
8
|
class BaseError < StandardError
|
6
9
|
extend Forwardable
|
7
10
|
|
11
|
+
# @param env The Faraday environment, providing access to the response
|
8
12
|
def initialize(env)
|
9
13
|
@env = env
|
10
14
|
end
|
@@ -12,37 +16,15 @@ module Starling
|
|
12
16
|
def_delegator :@env, :status
|
13
17
|
def_delegator :@env, :body
|
14
18
|
|
19
|
+
# @return [String] a helpful message explaining the error, incorporating the
|
20
|
+
# HTTP status code and body returned
|
15
21
|
def message
|
16
22
|
message = status.to_s
|
17
23
|
message += ": #{body}" if body
|
18
24
|
|
19
25
|
message
|
20
26
|
end
|
21
|
-
|
22
27
|
alias to_s message
|
23
|
-
|
24
|
-
def error
|
25
|
-
return unless json?
|
26
|
-
parsed_body['error']
|
27
|
-
end
|
28
|
-
|
29
|
-
def error_description
|
30
|
-
return unless json?
|
31
|
-
parsed_body['error_description']
|
32
|
-
end
|
33
|
-
|
34
|
-
def parsed_body
|
35
|
-
return if body.nil?
|
36
|
-
JSON.parse(body)
|
37
|
-
rescue JSON::ParserError
|
38
|
-
nil
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def json?
|
44
|
-
!parsed_body.nil?
|
45
|
-
end
|
46
28
|
end
|
47
29
|
end
|
48
30
|
end
|
@@ -1,21 +1,22 @@
|
|
1
1
|
module Starling
|
2
2
|
module Middlewares
|
3
|
+
# A Faradfay::Response::Middleware used to raise an {{Errors::ApiError}} when the
|
4
|
+
# Starling Bank API responds with an HTTP status code indicating an error. The raised
|
5
|
+
# error provides access to the response.
|
3
6
|
class RaiseStarlingErrors < Faraday::Response::Middleware
|
7
|
+
# HTTP status codes which are considered to be an error (alongside non-JSON)
|
8
|
+
# responses
|
4
9
|
ERROR_STATUSES = 400..599
|
5
10
|
|
11
|
+
# @param env The Faraday environment, providing access to the response
|
12
|
+
# @raise [Errors::ApiError] if the response from the Starling Bank API indicates an
|
13
|
+
# error
|
14
|
+
# @return [nil] if the response from the Starling Bank API doesn't indicate an
|
15
|
+
# error
|
6
16
|
def on_complete(env)
|
7
|
-
return unless
|
17
|
+
return unless ERROR_STATUSES.include?(env.status)
|
8
18
|
raise Errors::ApiError, env
|
9
19
|
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def json?(env)
|
14
|
-
content_type = env.response_headers['Content-Type'] ||
|
15
|
-
env.response_headers['content-type'] || ''
|
16
|
-
|
17
|
-
content_type.include?('application/json')
|
18
|
-
end
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
data/lib/starling/request.rb
CHANGED
@@ -1,19 +1,31 @@
|
|
1
1
|
module Starling
|
2
|
+
# The interface between Starling and Faraday, which is used under the hood to make
|
3
|
+
# HTTP requests
|
2
4
|
class Request
|
3
|
-
|
5
|
+
# @param connection [Faraday] A Faraday connection
|
6
|
+
# @param method [Symbol] The HTTP method for the request
|
7
|
+
# @param path [String] The path of the API endpoint, which will be added to the
|
8
|
+
# base URL (from {Starling::Client::ENVIRONMENT_BASE_URLS}) and
|
9
|
+
# the API version-specific base path ({ApiService::BASE_PATH})
|
10
|
+
# @param params [Hash] The parameters which will be included in the request, either
|
11
|
+
# in the URL or the body, depending on the method
|
12
|
+
# @param headers [Hash] The headers to be included in the request
|
13
|
+
def initialize(connection, method, path, params: {}, headers: {})
|
4
14
|
@connection = connection
|
5
15
|
@method = method
|
6
16
|
@path = path
|
7
|
-
@headers =
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@request_query = request_query
|
17
|
+
@headers = headers
|
18
|
+
@request_body = params if %i[post put delete].include?(method)
|
19
|
+
@request_query = method == :get ? params : {}
|
11
20
|
|
12
21
|
return unless @request_body.is_a?(Hash)
|
13
22
|
@request_body = @request_body.to_json
|
14
23
|
@headers['Content-Type'] ||= 'application/json'
|
15
24
|
end
|
16
25
|
|
26
|
+
# Dispatch the configured HTTP request
|
27
|
+
#
|
28
|
+
# @return [Faraday::Request] The response from the HTTP request
|
17
29
|
def make_request
|
18
30
|
@connection.send(@method) do |request|
|
19
31
|
request.url @path
|
@@ -22,19 +34,5 @@ module Starling
|
|
22
34
|
request.headers.merge!(@headers)
|
23
35
|
end
|
24
36
|
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def request_body
|
29
|
-
return @options.fetch(:params, {}) if %i[post put delete].include?(@method)
|
30
|
-
|
31
|
-
nil
|
32
|
-
end
|
33
|
-
|
34
|
-
def request_query
|
35
|
-
return @options.fetch(:params, {}) if @method == :get
|
36
|
-
|
37
|
-
{}
|
38
|
-
end
|
39
37
|
end
|
40
38
|
end
|
@@ -1,32 +1,40 @@
|
|
1
1
|
module Starling
|
2
2
|
module Resources
|
3
|
+
# A resource representing a response from the Account Balance API
|
3
4
|
class AccountBalanceResource < BaseResource
|
5
|
+
# @return [Float] the account's accepted overdraft
|
4
6
|
def accepted_overdraft
|
5
|
-
parsed_data['acceptedOverdraft']
|
7
|
+
present_float(parsed_data['acceptedOverdraft'])
|
6
8
|
end
|
7
9
|
|
10
|
+
# @return [Float] the account's balance
|
8
11
|
def amount
|
9
|
-
parsed_data['amount']
|
12
|
+
present_float(parsed_data['amount'])
|
10
13
|
end
|
11
14
|
|
15
|
+
# @return [Float] the account's amount available to spend
|
12
16
|
def available_to_spend
|
13
|
-
parsed_data['availableToSpend']
|
17
|
+
present_float(parsed_data['availableToSpend'])
|
14
18
|
end
|
15
19
|
|
20
|
+
# @return [Float] the account's cleared balance
|
16
21
|
def cleared_balance
|
17
|
-
parsed_data['clearedBalance']
|
22
|
+
present_float(parsed_data['clearedBalance'])
|
18
23
|
end
|
19
24
|
|
25
|
+
# @return [String] the account's currency (e.g. "GBP")
|
20
26
|
def currency
|
21
27
|
parsed_data['currency']
|
22
28
|
end
|
23
29
|
|
30
|
+
# @return [Float] the account's effective balance
|
24
31
|
def effective_balance
|
25
|
-
parsed_data['effectiveBalance']
|
32
|
+
present_float(parsed_data['effectiveBalance'])
|
26
33
|
end
|
27
34
|
|
35
|
+
# @return [Float] the total of the account's pending transactions
|
28
36
|
def pending_transactions
|
29
|
-
parsed_data['pendingTransactions']
|
37
|
+
present_float(parsed_data['pendingTransactions'])
|
30
38
|
end
|
31
39
|
end
|
32
40
|
end
|
@@ -1,34 +1,44 @@
|
|
1
1
|
module Starling
|
2
2
|
module Resources
|
3
|
+
# A resource representing a response from the Account API
|
3
4
|
class AccountResource < BaseResource
|
5
|
+
# @return [Time] the time and date when the account was created
|
4
6
|
def created_at
|
5
7
|
present_datetime(parsed_data['createdAt'])
|
6
8
|
end
|
7
9
|
|
10
|
+
# @return [String] the currency of the account
|
8
11
|
def currency
|
9
12
|
parsed_data['currency']
|
10
13
|
end
|
11
14
|
|
15
|
+
# @return [String] the IBAN of the account
|
12
16
|
def iban
|
13
17
|
parsed_data['iban']
|
14
18
|
end
|
15
19
|
|
20
|
+
# @return [String] the BIC of the account
|
16
21
|
def bic
|
17
22
|
parsed_data['bic']
|
18
23
|
end
|
19
24
|
|
25
|
+
# @return [String] the Starling internal ID of the account
|
20
26
|
def id
|
21
27
|
parsed_data['id']
|
22
28
|
end
|
23
29
|
|
30
|
+
# @return [String] the name of the account
|
24
31
|
def name
|
25
32
|
parsed_data['name']
|
26
33
|
end
|
27
34
|
|
35
|
+
# @return [String] the account number of the account
|
28
36
|
def number
|
29
37
|
parsed_data['number']
|
30
38
|
end
|
39
|
+
alias account_number number
|
31
40
|
|
41
|
+
# @return [String] the sort code of the account
|
32
42
|
def sort_code
|
33
43
|
parsed_data['sortCode']
|
34
44
|
end
|