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 +4 -4
- data/README.md +1 -1
- data/figo.gemspec +1 -1
- data/lib/cacert.pem +41 -0
- data/lib/figo.rb +151 -32
- data/lib/models.rb +169 -4
- data/test/test_figo.rb +54 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9bbbd2adbe88269274fcff1ddcd8f342bf56e1a
|
4
|
+
data.tar.gz: 7267a93b66bc7aea0e1ae909ebf7e6bf9a0878c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02cc595570b661a625333d589e7eff74632dbb289d3f20a6fd5ed1e67520234eac7d68e66325471a0e9cd616bba8ba34200fa207d695fac43f737f20a5a2af27
|
7
|
+
data.tar.gz: 85d7e1e88a2ead733cdfd1cefe03a978d1b2065701e483f94bdff50cc588f7704fe1dfdd3ec393ffd14aa2dd997f77ac6e11b04a4d36a818998e69c18bd22f6c
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
ruby-figo [](https://travis-ci.org/figo-connect/ruby-figo)
|
2
2
|
=========
|
3
3
|
|
4
|
-
Ruby bindings for the figo
|
4
|
+
Ruby bindings for the figo Connect API: http://developer.figo.me
|
5
5
|
|
6
6
|
Usage
|
7
7
|
=====
|
data/figo.gemspec
CHANGED
data/lib/cacert.pem
ADDED
@@ -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-----
|
data/lib/figo.rb
CHANGED
@@ -24,48 +24,65 @@ require "json"
|
|
24
24
|
require "logger"
|
25
25
|
require 'net/http/persistent'
|
26
26
|
require "digest/sha1"
|
27
|
-
|
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
|
-
|
33
|
+
$api_endpoint = "api.leanbank.com"
|
34
34
|
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
120
|
-
|
147
|
+
# Send HTTP request.
|
148
|
+
response = @https.request(uri, request)
|
121
149
|
|
122
|
-
|
123
|
-
|
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://#{
|
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://#{
|
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
|
-
|
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://#{
|
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
|
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
|
-
|
237
|
-
|
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
|
|
data/lib/models.rb
CHANGED
@@ -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
|
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
|
data/test/test_figo.rb
CHANGED
@@ -21,8 +21,61 @@
|
|
21
21
|
#
|
22
22
|
|
23
23
|
require "test/unit"
|
24
|
-
|
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.
|
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-
|
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
|