figo 1.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
  SHA1:
3
- metadata.gz: 3133d62a73507bbdadc5bfc1a4090da15960f895
4
- data.tar.gz: acb3d79e2d8870dba61f222bc36158050abab8db
3
+ metadata.gz: c9bbbd2adbe88269274fcff1ddcd8f342bf56e1a
4
+ data.tar.gz: 7267a93b66bc7aea0e1ae909ebf7e6bf9a0878c0
5
5
  SHA512:
6
- metadata.gz: 8cb5a5c2ccd4483907c3304c2f842333ee033340f46b1ec23834b8d7c94039cd42fde6f413d7e8ee2a3bed9348722ab6eb63cc2259852ec91fc3152e6aed11c7
7
- data.tar.gz: 31145565afb3cb72a68bf0705d3b26c31b56ed1595615e218034e33f7f96460fa96f188e966d44088a9713196b17eff9e2a81c0f8a5d78bf30a95e4dc650ca62
6
+ metadata.gz: 02cc595570b661a625333d589e7eff74632dbb289d3f20a6fd5ed1e67520234eac7d68e66325471a0e9cd616bba8ba34200fa207d695fac43f737f20a5a2af27
7
+ data.tar.gz: 85d7e1e88a2ead733cdfd1cefe03a978d1b2065701e483f94bdff50cc588f7704fe1dfdd3ec393ffd14aa2dd997f77ac6e11b04a4d36a818998e69c18bd22f6c
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ruby-figo [![Build Status](https://secure.travis-ci.org/figo-connect/ruby-figo.png)](https://travis-ci.org/figo-connect/ruby-figo)
2
2
  =========
3
3
 
4
- Ruby bindings for the figo connect API: http://figo.me
4
+ Ruby bindings for the figo Connect API: http://developer.figo.me
5
5
 
6
6
  Usage
7
7
  =====
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "figo"
3
- s.version = "1.0"
3
+ s.version = "1.1"
4
4
  s.authors = ["Stefan Richter", "Michael Haller"]
5
5
  s.email = ["stefan.richter@figo.me", "michael.haller@figo.me"]
6
6
  s.homepage = "https://github.com/figo-connect/ruby-figo"
@@ -0,0 +1,41 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
3
+ IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
4
+ IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
5
+ Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
6
+ BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
7
+ MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
8
+ ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
9
+ CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
10
+ 8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
11
+ zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
12
+ fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
13
+ w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
14
+ G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
15
+ epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
16
+ laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
17
+ QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
18
+ fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
19
+ YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
20
+ ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
21
+ gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
22
+ MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
23
+ IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
24
+ dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
25
+ czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
26
+ dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
27
+ aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
28
+ AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
29
+ b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
30
+ ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
31
+ nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
32
+ 18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
33
+ gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
34
+ Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
35
+ sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
36
+ SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
37
+ CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
38
+ GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
39
+ zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
40
+ omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
41
+ -----END CERTIFICATE-----
@@ -24,48 +24,65 @@ require "json"
24
24
  require "logger"
25
25
  require 'net/http/persistent'
26
26
  require "digest/sha1"
27
- require "./lib/models.rb"
27
+ require_relative "models.rb"
28
28
 
29
- $logger = Logger.new(STDOUT)
30
29
 
30
+ # Ruby bindings for the figo Connect API: http://developer.figo.me
31
31
  module Figo
32
32
 
33
- API_ENDPOINT = "api.leanbank.com"
33
+ $api_endpoint = "api.leanbank.com"
34
34
 
35
- VALID_FINGERPRINTS = ["A6:FE:08:F4:A8:86:F9:C1:BF:4E:70:0A:BD:72:AE:B8:8E:B7:78:52",
36
- "AD:A0:E3:2B:1F:CE:E8:44:F2:83:BA:AE:E4:7D:F2:AD:44:48:7F:1E"]
35
+ $valid_fingerprints = ["A6:FE:08:F4:A8:86:F9:C1:BF:4E:70:0A:BD:72:AE:B8:8E:B7:78:52",
36
+ "AD:A0:E3:2B:1F:CE:E8:44:F2:83:BA:AE:E4:7D:F2:AD:44:48:7F:1E"]
37
37
 
38
+ $logger = Logger.new(STDOUT)
39
+
40
+ # Base class for all errors transported via the figo Connect API.
38
41
  class Error < RuntimeError
39
42
 
40
- def initialize(error, error_description) # :nodoc:
43
+ # Initialize error object.
44
+ #
45
+ # @param error [String] the error code
46
+ # @param error_description [String] the error description
47
+ def initialize(error, error_description)
41
48
  @error = error
42
49
  @error_description = error_description
43
50
  end
44
51
 
45
- def to_s # :nodoc:
52
+ # Convert error object to string.
53
+ #
54
+ # @return [String] the error description
55
+ def to_s
46
56
  return @error_description
47
57
  end
48
58
 
49
59
  end
50
60
 
51
- class HTTPS < Net::HTTP::Persistent # :nodoc:
61
+ # HTTPS class with certificate authentication and enhanced error handling.
62
+ class HTTPS < Net::HTTP::Persistent
52
63
 
64
+ # Overwrite `initialize` method from `Net::HTTP::Persistent`.
65
+ #
66
+ # Verify fingerprints of server SSL/TLS certificates.
53
67
  def initialize(name = nil, proxy = nil)
54
68
  super(name, proxy)
55
69
 
56
70
  # Attribute ca_file must be set, otherwise verify_callback would never be called.
57
- @ca_file = ""
71
+ @ca_file = "lib/cacert.pem"
58
72
  @verify_callback = proc do |preverify_ok, store_context|
59
73
  if preverify_ok and store_context.error == 0
60
74
  certificate = OpenSSL::X509::Certificate.new(store_context.chain[0])
61
75
  fingerprint = Digest::SHA1.hexdigest(certificate.to_der).upcase.scan(/../).join(":")
62
- VALID_FINGERPRINTS.include?(fingerprint)
76
+ $valid_fingerprints.include?(fingerprint)
63
77
  else
64
78
  false
65
79
  end
66
80
  end
67
81
  end
68
82
 
83
+ # Overwrite `request` method from `Net::HTTP::Persistent`.
84
+ #
85
+ # Raise error when a REST API error is returned.
69
86
  def request(uri, req = nil, &block)
70
87
  response = super(uri, req, &block)
71
88
 
@@ -81,7 +98,7 @@ module Figo
81
98
  when Net::HTTPForbidden
82
99
  raise Error.new("forbidden", "Insufficient permission.")
83
100
  when Net::HTTPNotFound
84
- raise Error.new("not_found", "Requested object does not exist.")
101
+ return nil
85
102
  when Net::HTTPMethodNotAllowed
86
103
  raise Error.new("method_not_allowed", "Unexpected request method.")
87
104
  when Net::HTTPServiceUnavailable
@@ -95,9 +112,15 @@ module Figo
95
112
  end
96
113
 
97
114
  # Represents a non user-bound connection to the figo Connect API.
115
+ #
116
+ # It's main purpose is to let user login via OAuth 2.0.
98
117
  class Connection
99
118
 
100
- # Create connection object with your client ID and client secret.
119
+ # Create connection object with client credentials.
120
+ #
121
+ # @param client_id [String] the client ID
122
+ # @param client_secret [String] the client secret
123
+ # @param redirect_uri [String] optional redirect URI
101
124
  def initialize(client_id, client_secret, redirect_uri = nil)
102
125
  @client_id = client_id
103
126
  @client_secret = client_secret
@@ -105,33 +128,58 @@ module Figo
105
128
  @https = HTTPS.new("figo-#{client_id}")
106
129
  end
107
130
 
108
- def query_api(path, data = nil) # :nodoc:
109
- uri = URI("https://#{API_ENDPOINT}#{path}")
110
- puts uri
131
+ # Helper method for making a OAuth 2.0 request.
132
+ #
133
+ # @param path [String] the URL path on the server
134
+ # @param data [Hash] this optional object will be used as url-encoded POST content.
135
+ # @return [Hash] JSON response
136
+ def query_api(path, data = nil)
137
+ uri = URI("https://#{$api_endpoint}#{path}")
111
138
 
112
- # Setup HTTP request.
113
- request = Net::HTTP::Post.new(path)
114
- request.basic_auth(@client_id, @client_secret)
115
- request["Content-Type"] = "application/x-www-form-urlencoded"
116
- request['User-Agent'] = "ruby-figo"
117
- request.body = URI.encode_www_form(data) unless data.nil?
139
+ # Setup HTTP request.
140
+ request = Net::HTTP::Post.new(path)
141
+ request.basic_auth(@client_id, @client_secret)
142
+ request["Accept"] = "application/json"
143
+ request["Content-Type"] = "application/x-www-form-urlencoded"
144
+ request['User-Agent'] = "ruby-figo"
145
+ request.body = URI.encode_www_form(data) unless data.nil?
118
146
 
119
- # Send HTTP request.
120
- response = @https.request(uri, request)
147
+ # Send HTTP request.
148
+ response = @https.request(uri, request)
121
149
 
122
- # Evaluate HTTP response.
123
- return response.body == "" ? {} : JSON.parse(response.body)
150
+ # Evaluate HTTP response.
151
+ return response.body == "" ? {} : JSON.parse(response.body)
124
152
  end
125
153
 
154
+
126
155
  # Get the URL a user should open in the web browser to start the login process.
156
+ #
157
+ # When the process is completed, the user is redirected to the URL provided to
158
+ # the constructor and passes on an authentication code. This code can be converted
159
+ # into an access token for data access.
160
+ #
161
+ # @param state [String] this string will be passed on through the complete login
162
+ # process and to the redirect target at the end. It should be used to
163
+ # validated the authenticity of the call to the redirect URL
164
+ # @param scope [String] optional scope of data access to ask the user for,
165
+ # e.g. `accounts=ro`
127
166
  def login_url(state, scope = nil)
128
167
  data = { "response_type" => "code", "client_id" => @client_id, "state" => state }
129
168
  data["redirect_uri"] = @redirect_uri unless @redirect_uri.nil?
130
169
  data["scope"] = scope unless scope.nil?
131
- return "https://#{API_ENDPOINT}/auth/code?" + URI.encode_www_form(data)
170
+ return "https://#{$api_endpoint}/auth/code?" + URI.encode_www_form(data)
132
171
  end
133
172
 
173
+
134
174
  # Exchange authorization code or refresh token for access token.
175
+ #
176
+ # @param authorization_code_or_refresh_token [String] either the authorization
177
+ # code received as part of the call to the redirect URL at the end of the
178
+ # logon process, or a refresh token
179
+ # @param scope [String] optional scope of data access to ask the user for,
180
+ # e.g. `accounts=ro`
181
+ # @return [Hash] object with the keys `access_token`, `refresh_token` and
182
+ # `expires,` as documented in the figo Connect API specification.
135
183
  def obtain_access_token(authorization_code_or_refresh_token, scope = nil)
136
184
  # Authorization codes always start with "O" and refresh tokens always start with "R".
137
185
  if authorization_code_or_refresh_token[0] == "O"
@@ -145,6 +193,11 @@ module Figo
145
193
  end
146
194
 
147
195
  # Revoke refresh token or access token.
196
+ #
197
+ # @note this action has immediate effect, i.e. you will not be able use that token anymore after this call.
198
+ #
199
+ # @param token [String] access or refresh token to be revoked
200
+ # @return [nil]
148
201
  def revoke_token(refresh_token_or_access_token)
149
202
  data = { "token" => refresh_token_or_access_token }
150
203
  query_api("/auth/revoke?" + URI.encode_www_form(data))
@@ -157,13 +210,20 @@ module Figo
157
210
  class Session
158
211
 
159
212
  # Create session object with access token.
213
+ #
214
+ # @param access_token [String] the access token
160
215
  def initialize(access_token)
161
216
  @access_token = access_token
162
217
  @https = HTTPS.new("figo-#{access_token}")
163
218
  end
164
219
 
220
+ # Helper method for making a REST request.
221
+ #
222
+ # @param path [String] the URL path on the server
223
+ # @param data [hash] this optional object will be used as JSON-encoded POST content.
224
+ # @return [Hash] JSON response
165
225
  def query_api(path, data=nil, method="GET") # :nodoc:
166
- uri = URI("https://#{API_ENDPOINT}#{path}")
226
+ uri = URI("https://#{$api_endpoint}#{path}")
167
227
 
168
228
  # Setup HTTP request.
169
229
  request = case method
@@ -178,6 +238,7 @@ module Figo
178
238
  end
179
239
 
180
240
  request["Authorization"] = "Bearer #{@access_token}"
241
+ request["Accept"] = "application/json"
181
242
  request["Content-Type"] = "application/json"
182
243
  request['User-Agent'] = "ruby-figo"
183
244
  request.body = JSON.generate(data) unless data.nil?
@@ -186,22 +247,41 @@ module Figo
186
247
  response = @https.request(uri, request)
187
248
 
188
249
  # Evaluate HTTP response.
189
- return response.body == "" ? {} : JSON.parse(response.body)
250
+ if response.nil?
251
+ return nil
252
+ elsif response.body.nil?
253
+ return nil
254
+ else
255
+ return response.body == "" ? nil : JSON.parse(response.body)
256
+ end
190
257
  end
191
258
 
192
259
  # Request list of accounts.
260
+ #
261
+ # @return [Array] an array of `Account` objects, one for each account the user has granted the app access
193
262
  def accounts
194
263
  response = query_api("/rest/accounts")
195
264
  return response["accounts"].map {|account| Account.new(self, account)}
196
265
  end
197
266
 
198
267
  # Request specific account.
268
+ #
269
+ # @param account_id [String] ID of the account to be retrieved.
270
+ # @return [Account] account object
199
271
  def get_account(account_id)
200
272
  response = query_api("/rest/accounts/#{account_id}")
201
273
  return Account.new(self, response)
202
274
  end
203
275
 
204
276
  # Request list of transactions.
277
+ #
278
+ # @param since [String] this parameter can either be a transaction ID or a date
279
+ # @param start_id [String] do only return transactions which were booked after the start transaction ID
280
+ # @param count [Intger] limit the number of returned transactions
281
+ # @param include_pending [Boolean] this flag indicates whether pending transactions should be included
282
+ # in the response; pending transactions are always included as a complete set, regardless of
283
+ # the `since` parameter
284
+ # @return [Array] an array of `Transaction` objects, one for each transaction of the user
205
285
  def transactions(since = nil, start_id = nil, count = 1000, include_pending = false)
206
286
  data = {}
207
287
  data["since"] = (since.is_a?(Date) ? since.to_s : since) unless since.nil?
@@ -213,28 +293,67 @@ module Figo
213
293
  end
214
294
 
215
295
  # Request the URL a user should open in the web browser to start the synchronization process.
296
+ #
297
+ # @param redirect_uri [String] URI the user is redirected to after the process completes
298
+ # @param state [String] this string will be passed on through the complete synchronization process
299
+ # and to the redirect target at the end. It should be used to validated the authenticity of
300
+ # the call to the redirect URL
301
+ # @param disable_notifications [Booleon] this flag indicates whether notifications should be sent
302
+ # @param if_not_synced_since [Integer] if this parameter is set, only those accounts will be
303
+ # synchronized, which have not been synchronized within the specified number of minutes.
304
+ # @return [String] the URL to be opened by the user.
216
305
  def sync_url(redirect_uri, state, disable_notifications = false, if_not_synced_since = 0)
217
306
  data = { "redirect_uri" => redirect_uri, "state" => state, "disable_notifications" => disable_notifications, "if_not_synced_since" => if_not_synced_since }
218
307
  response = query_api("/rest/sync", data, "POST")
219
- return "https://#{API_ENDPOINT}/task/start?id=#{response["task_token"]}"
308
+ return "https://#{$api_endpoint}/task/start?id=#{response["task_token"]}"
220
309
  end
221
310
 
222
311
  # Request list of registered notifications.
312
+ #
313
+ # @return [Notification] an array of `Notification` objects, one for each registered notification
223
314
  def notifications
224
315
  response = query_api("/rest/notifications")
225
316
  return response["notifications"].map {|notification| Notification.new(self, notification)}
226
317
  end
227
318
 
319
+ # Request specific notification.
320
+ #
321
+ # @param notification_id [String] ID of the notification to be retrieved
322
+ # @return [Notification] `Notification` object for the respective notification
323
+ def get_notification(notification_id)
324
+ response = query_api("/rest/notifications/#{notification_id}")
325
+ return response.nil? ? nil : Notification.new(self, response)
326
+ end
327
+
228
328
  # Register notification.
329
+ #
330
+ # @param observe_key [String] one of the notification keys specified in the figo Connect API
331
+ # specification
332
+ # @param notify_uri [String] notification messages will be sent to this URL
333
+ # @param state [String] any kind of string that will be forwarded in the notification message
334
+ # @return [Notification] newly created `Notification` object
229
335
  def add_notification(observe_key, notify_uri, state)
230
336
  data = { "observe_key" => observe_key, "notify_uri" => notify_uri, "state" => state }
231
337
  response = query_api("/rest/notifications", data, "POST")
232
- return response["notification_id"]
338
+ return Notification.new(self, response)
339
+ end
340
+
341
+ # Modify a notification.
342
+ #
343
+ # @param notification [Notification] modified notification object
344
+ # @return [nil]
345
+ def modify_notification(notification)
346
+ data = { "observe_key" => notification.observe_key, "notify_uri" => notification.notify_uri, "state" => notification.state }
347
+ response = query_api("/rest/notifications/#{notification.notification_id}", data, "PUT")
348
+ return nil
233
349
  end
234
350
 
235
351
  # Unregister notification.
236
- def remove_notification(notification_id)
237
- query_api("/rest/notifications/#{notification_id}", nil, "DELETE")
352
+ #
353
+ # @param notification [Notification] notification object which should be deleted
354
+ # @return [nil]
355
+ def remove_notification(notification)
356
+ query_api("/rest/notifications/#{notification.notification_id}", nil, "DELETE")
238
357
  return nil
239
358
  end
240
359
 
@@ -23,11 +23,13 @@
23
23
  require "date"
24
24
  require "flt"
25
25
 
26
+
26
27
  module Figo
27
28
 
28
29
  # Set decimal precision to two digits.
29
30
  Flt::DecNum.context.precision = 2
30
31
 
32
+ # Account type enumeration.
31
33
  class AccountType
32
34
  GIRO = "Giro account"
33
35
  SAVINGS = "Savings account"
@@ -37,6 +39,7 @@ module Figo
37
39
  UNKNOWN = "Unknown"
38
40
  end
39
41
 
42
+ # Transaction type enumeration.
40
43
  class TransactionType
41
44
  TRANSFER = "Transfer"
42
45
  STANDING_ORDER = "Standing order"
@@ -49,15 +52,20 @@ module Figo
49
52
  UNKNOWN = "Unknown"
50
53
  end
51
54
 
52
- class Base # :nodoc:
55
+ # Abstract base class for model objects.
56
+ class Base
53
57
 
58
+ # Instantiate model object from hash.
59
+ #
60
+ # @param session [Session] figo `Session` object
61
+ # @param hash [Hash] use keys of this hash for model object creation
54
62
  def initialize(session, hash)
55
63
  @session = session
56
64
 
57
65
  hash.each do |key, value|
58
66
  if key == "status"
59
67
  value = SynchronizationStatus.new(session, value)
60
- elsif key == "amount" or key == "balance"
68
+ elsif key == "amount" or key == "balance" or key == "credit_line" or key == "monthly_spending_limit"
61
69
  value = Flt::DecNum(value.to_s)
62
70
  elsif key.end_with?("_date")
63
71
  value = DateTime.iso8601(value)
@@ -70,32 +78,90 @@ module Figo
70
78
 
71
79
  end
72
80
 
81
+ # Object representing one bank account of the user.
73
82
  class Account < Base
74
83
 
84
+ # Internal figo Connect account ID.
85
+ # @return [String]
75
86
  attr_accessor :account_id
87
+
88
+ # Internal figo Connect bank ID.
89
+ # @return [String]
76
90
  attr_accessor :bank_id
91
+
92
+ # Account name.
93
+ # @return [String]
77
94
  attr_accessor :name
95
+
96
+ # Account owner.
97
+ # @return [String]
78
98
  attr_accessor :owner
99
+
100
+ # This flag indicates whether the account will be automatically synchronized.
101
+ # @return [Boolean]
79
102
  attr_accessor :auto_sync
103
+
104
+ # Account number.
105
+ # @return [String]
80
106
  attr_accessor :account_number
107
+
108
+ # Bank code.
109
+ # @return [String]
81
110
  attr_accessor :bank_code
111
+
112
+ # Bank name.
113
+ # @return [String]
82
114
  attr_accessor :bank_name
115
+
116
+ # Three-character currency code.
117
+ # @return [String]
83
118
  attr_accessor :currency
119
+
120
+ # IBAN.
121
+ # @return [String]
84
122
  attr_accessor :iban
123
+
124
+ # BIC.
125
+ # @return [String]
85
126
  attr_accessor :bic
127
+
128
+ # Account type. One of the constants of the `AccountType` object.
129
+ # @return [String]
86
130
  attr_accessor :type
131
+
132
+ # Account icon URL.
133
+ # @return [String]
87
134
  attr_accessor :icon
135
+
136
+ # This flag indicates whether the balance of this account is added to the total balance of accounts.
137
+ # @return [Boolean]
88
138
  attr_accessor :in_total_balance
139
+
140
+ # This flag indicates whether this account is only shown as preview for an unpaid premium plan.
141
+ # @return [Boolean]
89
142
  attr_accessor :preview
143
+
144
+ # Synchronization status object.
145
+ # @return [SynchronizationStatus]
90
146
  attr_accessor :status
91
147
 
92
- # Request balance.
148
+ # Request balance of this account.
149
+ #
150
+ # @return [AccountBalance] account balance object
93
151
  def balance
94
152
  response = @session.query_api("/rest/accounts/#{@account_id}/balance")
95
153
  return AccountBalance.new(@session, response)
96
154
  end
97
155
 
98
- # Request list of transactions.
156
+ # Request list of transactions of this account.
157
+ #
158
+ # @param since [String] this parameter can either be a transaction ID or a date
159
+ # @param start_id [String] do only return transactions which were booked after the start transaction ID
160
+ # @param count [Intger] limit the number of returned transactions
161
+ # @param include_pending [Boolean] this flag indicates whether pending transactions should be included
162
+ # in the response; pending transactions are always included as a complete set, regardless of
163
+ # the `since` parameter
164
+ # @return [Array] an array of `Transaction` objects, one for each transaction of the user
99
165
  def transactions(since = nil, start_id = nil, count = 1000, include_pending = false)
100
166
  data = {}
101
167
  data["since"] = (since.is_a?(Date) ? since.to_s : since) unless since.nil?
@@ -106,54 +172,153 @@ module Figo
106
172
  return response["transactions"].map {|transaction| Transaction.new(@session, transaction)}
107
173
  end
108
174
 
175
+ # Request a specific transaction.
176
+ #
177
+ # @param transaction_id [String] ID of the transaction to be retrieved
178
+ # @return [Transaction] transaction object
179
+ def transaction(transaction_id)
180
+ response = @session.query_api("/rest/accounts/#{@account_id}/transactions/#{transaction_id}")
181
+ return Transaction.new(@session, response)
182
+ end
183
+
109
184
  end
110
185
 
186
+ # Object representing the balance of a certain bank account of the user.
111
187
  class AccountBalance < Base
112
188
 
189
+ # Account balance or `nil` if the balance is not yet known.
190
+ # @return [DecNum]
113
191
  attr_accessor :balance
192
+
193
+ # Bank server timestamp of balance or `nil` if the balance is not yet known.
194
+ # @return [Date]
114
195
  attr_accessor :balance_date
196
+
197
+ # Credit line.
198
+ # @return [DecNum]
115
199
  attr_accessor :credit_line
200
+
201
+ # User-defined spending limit.
202
+ # @return [DecNum]
116
203
  attr_accessor :monthly_spending_limit
204
+
205
+ # Synchronization status object.
206
+ # @return [SynchronizationStatus]
117
207
  attr_accessor :status
118
208
 
119
209
  end
120
210
 
211
+ # Object representing one bank transaction on a certain bank account of the user.
121
212
  class Transaction < Base
122
213
 
214
+ # Internal figo Connect transaction ID.
215
+ # @return [String]
123
216
  attr_accessor :transaction_id
217
+
218
+ # Internal figo Connect account ID.
219
+ # @return [String]
124
220
  attr_accessor :account_id
221
+
222
+ # Name of originator or recipient.
223
+ # @return [String]
125
224
  attr_accessor :name
225
+
226
+ # Account number of originator or recipient.
227
+ # @return [String]
126
228
  attr_accessor :account_number
229
+
230
+ # Bank code of originator or recipien.
231
+ # @return [String]
127
232
  attr_accessor :bank_code
233
+
234
+ # Bank name of originator or recipient.
235
+ # @return [String]
128
236
  attr_accessor :bank_name
237
+
238
+ # Transaction amount.
239
+ # @return [DecNum]
129
240
  attr_accessor :amount
241
+
242
+ # Three-character currency code.
243
+ # @return [String]
130
244
  attr_accessor :currency
245
+
246
+ # Booking date.
247
+ # @return [Date]
131
248
  attr_accessor :booking_date
249
+
250
+ # Value date.
251
+ # @return [Date]
132
252
  attr_accessor :value_date
253
+
254
+ # Purpose text.
255
+ # @return [String]
133
256
  attr_accessor :purpose
257
+
258
+ # Transaction type. One of the constants of the `TransactionType` object.
259
+ # @return [String]
134
260
  attr_accessor :type
261
+
262
+ # Booking text.
263
+ # @return [String]
135
264
  attr_accessor :booking_text
265
+
266
+ # This flag indicates whether the transaction is booked or pending.
267
+ # @return [Boolean]
136
268
  attr_accessor :booked
269
+
270
+ # Internal creation timestamp on the figo Connect server.
271
+ # @return [DateTime]
137
272
  attr_accessor :creation_timestamp
273
+
274
+ # Internal modification timestamp on the figo Connect server.
275
+ # @return [DateTime]
138
276
  attr_accessor :modification_timestamp
277
+
278
+ # This flag indicates whether the transaction has already been marked as visited by the user.
279
+ # @return [Boolean]
139
280
  attr_accessor :visited
140
281
 
141
282
  end
142
283
 
284
+ # Object representing the bank server synchronization status.
143
285
  class SynchronizationStatus < Base
144
286
 
287
+ # Internal figo Connect status code.
288
+ # @return [Integer]
145
289
  attr_accessor :code
290
+
291
+ # Human-readable error message.
292
+ # @return [String]
146
293
  attr_accessor :message
294
+
295
+ # Timestamp of last synchronization.
296
+ # @return [DateTime]
147
297
  attr_accessor :sync_timestamp
298
+
299
+ # Timestamp of last successful synchronization.
300
+ # @return [DateTime]
148
301
  attr_accessor :success_timestamp
149
302
 
150
303
  end
151
304
 
305
+ # Object representing a configured notification, e.g. a webhook or email hook.
152
306
  class Notification < Base
153
307
 
308
+ # Internal figo Connect notification ID from the notification registration response.
309
+ # @return [String]
154
310
  attr_accessor :notification_id
311
+
312
+ # One of the notification keys specified in the figo Connect API specification.
313
+ # @return [String]
155
314
  attr_accessor :observe_key
315
+
316
+ # Notification messages will be sent to this URL.
317
+ # @return [String]
156
318
  attr_accessor :notify_uri
319
+
320
+ # State similiar to sync and logon process. It will passed as POST payload for webhooks.
321
+ # @return [String]
157
322
  attr_accessor :state
158
323
 
159
324
  end
@@ -21,8 +21,61 @@
21
21
  #
22
22
 
23
23
  require "test/unit"
24
- require "figo"
24
+ require_relative "../lib/figo"
25
+
25
26
 
26
27
  class FigoTest < Test::Unit::TestCase
27
28
 
29
+ def setup
30
+ $api_endpoint = "api.staging.figo.me"
31
+ $valid_fingerprints = ["AF:FF:C3:2A:45:13:86:FB:28:57:55:80:0A:58:23:C7:7A:70:B6:2D"]
32
+ end
33
+
34
+ def test_accounts
35
+ sut = Figo::Session.new("ASHWLIkouP2O6_bgA2wWReRhletgWKHYjLqDaqb0LFfamim9RjexTo22ujRIP_cjLiRiSyQXyt2kM1eXU2XLFZQ0Hro15HikJQT_eNeT_9XQ")
36
+
37
+ sut.accounts
38
+ sut.get_account "A1.1"
39
+ account = sut.get_account "A1.2"
40
+ assert_equal account.account_id, "A1.2"
41
+
42
+ # account sub-resources
43
+ balance = sut.get_account("A1.2").balance
44
+ assert balance.balance
45
+ assert balance.balance_date
46
+
47
+ transactions = sut.get_account("A1.2").transactions
48
+ assert transactions.length > 0
49
+ end
50
+
51
+ def test_global_transactions
52
+ sut = Figo::Session.new("ASHWLIkouP2O6_bgA2wWReRhletgWKHYjLqDaqb0LFfamim9RjexTo22ujRIP_cjLiRiSyQXyt2kM1eXU2XLFZQ0Hro15HikJQT_eNeT_9XQ")
53
+ transactions = sut.transactions
54
+ self.assert transactions.length > 0
55
+ end
56
+
57
+ def test_sync_uri
58
+ sut = Figo::Session.new("ASHWLIkouP2O6_bgA2wWReRhletgWKHYjLqDaqb0LFfamim9RjexTo22ujRIP_cjLiRiSyQXyt2kM1eXU2XLFZQ0Hro15HikJQT_eNeT_9XQ")
59
+ sut.sync_url("qwe", "qew")
60
+ end
61
+
62
+ def test_create_update_delete_notification
63
+ sut = Figo::Session.new("ASHWLIkouP2O6_bgA2wWReRhletgWKHYjLqDaqb0LFfamim9RjexTo22ujRIP_cjLiRiSyQXyt2kM1eXU2XLFZQ0Hro15HikJQT_eNeT_9XQ")
64
+
65
+ notification = sut.add_notification(observe_key="/rest/transactions", notify_uri="http://figo.me/test", state="qwe")
66
+ assert_equal notification.observe_key, "/rest/transactions"
67
+ assert_equal notification.notify_uri, "http://figo.me/test"
68
+ assert_equal notification.state, "qwe"
69
+
70
+ notification.state = "asd"
71
+ sut.modify_notification(notification)
72
+
73
+ notification = sut.get_notification(notification.notification_id)
74
+ assert_equal notification.observe_key, "/rest/transactions"
75
+ assert_equal notification.notify_uri, "http://figo.me/test"
76
+ assert_equal notification.state, "asd"
77
+
78
+ sut.remove_notification(notification)
79
+ assert_equal sut.get_notification(notification.notification_id), nil
80
+ end
28
81
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: figo
3
3
  version: !ruby/object:Gem::Version
4
- version: "1.0"
4
+ version: "1.1"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Richter
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2013-06-04 00:00:00 Z
13
+ date: 2013-06-07 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: flt
@@ -48,6 +48,7 @@ files:
48
48
  - README.md
49
49
  - Rakefile
50
50
  - figo.gemspec
51
+ - lib/cacert.pem
51
52
  - lib/figo.rb
52
53
  - lib/models.rb
53
54
  - test/test_figo.rb