figo 1.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
  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