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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +8 -0
  3. data/.gitignore +1 -0
  4. data/.reek +27 -0
  5. data/.rubocop.yml +6 -4
  6. data/CHANGELOG.md +17 -0
  7. data/Gemfile.lock +37 -2
  8. data/README.md +102 -17
  9. data/bin/console +2 -6
  10. data/lib/starling.rb +40 -7
  11. data/lib/starling/api_service.rb +45 -37
  12. data/lib/starling/client.rb +165 -6
  13. data/lib/starling/errors/api_error.rb +35 -2
  14. data/lib/starling/errors/base_error.rb +6 -24
  15. data/lib/starling/middlewares/raise_starling_errors.rb +11 -10
  16. data/lib/starling/request.rb +17 -19
  17. data/lib/starling/resources/account_balance_resource.rb +14 -6
  18. data/lib/starling/resources/account_resource.rb +10 -0
  19. data/lib/starling/resources/address_resource.rb +26 -0
  20. data/lib/starling/resources/addresses_resource.rb +18 -0
  21. data/lib/starling/resources/base_resource.rb +34 -9
  22. data/lib/starling/resources/card_resource.rb +46 -0
  23. data/lib/starling/resources/contact_account_resource.rb +31 -0
  24. data/lib/starling/resources/contact_resource.rb +16 -0
  25. data/lib/starling/resources/customer_resource.rb +36 -0
  26. data/lib/starling/resources/direct_debit_mandate_resource.rb +43 -0
  27. data/lib/starling/resources/direct_debit_transaction_resource.rb +54 -0
  28. data/lib/starling/resources/inbound_faster_payments_transaction_resource.rb +56 -0
  29. data/lib/starling/resources/mastercard_transaction_resource.rb +73 -0
  30. data/lib/starling/resources/me_resource.rb +26 -0
  31. data/lib/starling/resources/merchant_location_resource.rb +33 -0
  32. data/lib/starling/resources/merchant_resource.rb +34 -0
  33. data/lib/starling/resources/outbound_faster_payments_transaction_resource.rb +56 -0
  34. data/lib/starling/resources/payment_resource.rb +78 -0
  35. data/lib/starling/resources/transaction_resource.rb +42 -0
  36. data/lib/starling/services/account_balance_service.rb +11 -2
  37. data/lib/starling/services/account_service.rb +16 -3
  38. data/lib/starling/services/addresses_service.rb +26 -0
  39. data/lib/starling/services/base_service.rb +22 -1
  40. data/lib/starling/services/card_service.rb +26 -0
  41. data/lib/starling/services/contact_accounts_service.rb +54 -0
  42. data/lib/starling/services/contacts_service.rb +73 -0
  43. data/lib/starling/services/customer_service.rb +25 -0
  44. data/lib/starling/services/direct_debit_mandates_service.rb +61 -0
  45. data/lib/starling/services/direct_debit_transactions_service.rb +46 -0
  46. data/lib/starling/services/inbound_faster_payments_transactions_service.rb +46 -0
  47. data/lib/starling/services/mastercard_transactions_service.rb +30 -0
  48. data/lib/starling/services/me_service.rb +26 -0
  49. data/lib/starling/services/merchant_locations_service.rb +34 -0
  50. data/lib/starling/services/merchants_service.rb +26 -0
  51. data/lib/starling/services/outbound_faster_payments_transactions_service.rb +46 -0
  52. data/lib/starling/services/payments_service.rb +30 -0
  53. data/lib/starling/services/transactions_service.rb +44 -0
  54. data/lib/starling/utils.rb +30 -0
  55. data/lib/starling/version.rb +1 -1
  56. data/starling-ruby.gemspec +2 -0
  57. metadata +63 -2
@@ -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
- def initialize(options = {})
11
- raise ArgumentError, 'access_token must be provided' unless options[:access_token]
12
-
13
- environment = options.delete(:environment) { :production }
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
- options)
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
- return super unless json?
8
- return super if error.nil? || error_description.nil?
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 !json?(env) || ERROR_STATUSES.include?(env.status)
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
@@ -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
- def initialize(connection, method, path, options)
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 = options.delete(:headers) || {}
8
- @options = options
9
- @request_body = request_body
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