bankscrap-ing 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c8e73208b62cf62248c917e01f2a72da389b603a
4
+ data.tar.gz: 85c8b2218a81abdd5d7459c98d89d3bb89f47b19
5
+ SHA512:
6
+ metadata.gz: 5e121dbf85496c7af677fc900e00f48dbf0f6f51e1352f646d47d7a194fbf8aa9cb0bdc9ddc0a7b0df79aeca0ad5c433e4c3a9e930147c248b81549e0010e3d4
7
+ data.tar.gz: 71299e92b3f55be6feed04afed43e84b44bc7c26171ce19f290ec5c576a7d8effc3354ab96edf0032bf67a66af4f43e3566dc99ca242e6c58180ae3f1c36fe22
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.1
3
+
4
+ Metrics/LineLength:
5
+ Max: 120
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bankscrap-ing.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Raúl Marcos
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Bankscrap::ING
2
+
3
+ Bankscrap adapter for the API behind ING's [mobile app](https://play.google.com/store/apps/details?id=www.ingdirect.nativeframe&hl=en).
4
+ This adapter is only valid for personal accounts (the ones that work with that mobile APP), not for company accounts.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'bankscrap-ing'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install bankscrap-ing
21
+
22
+ ## Usage
23
+
24
+ For usage instructions please read [Bankscrap readme](https://github.com/bankscrap/bankscrap#usage).
25
+
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/bankscrap/bankscrap-ing/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bankscrap/ing/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'bankscrap-ing'
8
+ spec.version = Bankscrap::ING::VERSION
9
+ spec.authors = ['Raúl']
10
+ spec.email = ['raulmarcosl@gmail.coms']
11
+ spec.summary = 'ING adapter for Bankscrap'
12
+ spec.homepage = ''
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_runtime_dependency 'bankscrap', '~> 1.0'
21
+ spec.add_runtime_dependency 'rmagick', '~> 2.2', '>= 2.2.2'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'byebug', '~> 8.2', '>= 8.2.5'
26
+ spec.add_development_dependency 'rubocop', '~> 0.39.0'
27
+ end
@@ -0,0 +1,246 @@
1
+ require 'bankscrap'
2
+
3
+ require 'json'
4
+ require 'base64'
5
+ require 'rmagick'
6
+ require 'tempfile'
7
+
8
+ module Bankscrap
9
+ module ING
10
+ class Bank < ::Bankscrap::Bank
11
+ BASE_ENDPOINT = 'https://ing.ingdirect.es/'.freeze
12
+ LOGIN_ENDPOINT = BASE_ENDPOINT + 'genoma_login/rest/session'
13
+ POST_AUTH_ENDPOINT = BASE_ENDPOINT + 'genoma_api/login/auth/response'
14
+ CLIENT_ENDPOINT = BASE_ENDPOINT + 'genoma_api/rest/client'
15
+ PRODUCTS_ENDPOINT = BASE_ENDPOINT + 'genoma_api/rest/products'
16
+
17
+ SAMPLE_WIDTH = 30
18
+ SAMPLE_HEIGHT = 30
19
+ SAMPLE_ROOT_PATH = '/numbers'.freeze
20
+
21
+ def initialize(user, password, log: false, debug: false, extra_args:)
22
+ @dni = user
23
+ @password = password.to_s
24
+ @birthday = extra_args.with_indifferent_access['birthday']
25
+ @log = log
26
+ @debug = debug
27
+
28
+ initialize_connection
29
+ bundled_login
30
+
31
+ @investments = fetch_investments
32
+
33
+ super
34
+ end
35
+
36
+ def balances
37
+ log 'get_balances'
38
+ balances = {}
39
+ total_balance = 0
40
+ @accounts.each do |account|
41
+ balances[account.description] = account.balance
42
+ total_balance += account.balance
43
+ end
44
+
45
+ balances['TOTAL'] = total_balance
46
+ balances
47
+ end
48
+
49
+ def fetch_accounts
50
+ log 'fetch_accounts'
51
+ add_headers(
52
+ 'Accept' => '*/*',
53
+ 'Content-Type' => 'application/json; charset=utf-8'
54
+ )
55
+
56
+ JSON.parse(get(PRODUCTS_ENDPOINT)).map do |account|
57
+ build_account(account) if account['iban']
58
+ end.compact
59
+ end
60
+
61
+ def fetch_investments
62
+ log 'fetch_investments'
63
+ add_headers(
64
+ 'Accept' => '*/*',
65
+ 'Content-Type' => 'application/json; charset=utf-8'
66
+ )
67
+
68
+ JSON.parse(get(PRODUCTS_ENDPOINT)).map do |investment|
69
+ build_investment(investment) if investment['investment']
70
+ end.compact
71
+ end
72
+
73
+ def fetch_transactions_for(account, start_date: Date.today - 1.month, end_date: Date.today)
74
+ log "fetch_transactions for #{account.id}"
75
+
76
+ # The API allows any limit to be passed, but we better keep
77
+ # being good API citizens and make a loop with a short limit
78
+ params = {
79
+ fromDate: start_date.strftime('%d/%m/%Y'),
80
+ toDate: end_date.strftime('%d/%m/%Y'),
81
+ limit: 25,
82
+ offset: 0
83
+ }
84
+
85
+ transactions = []
86
+ loop do
87
+ request = get("#{PRODUCTS_ENDPOINT}/#{account.id}/movements", params: params)
88
+ json = JSON.parse(request)
89
+ transactions += (json['elements'] || []).map do |transaction|
90
+ build_transaction(transaction, account)
91
+ end
92
+ params[:offset] += 25
93
+ break if (params[:offset] > json['total']) || json['elements'].blank?
94
+ end
95
+ transactions
96
+ end
97
+
98
+ private
99
+
100
+ def bundled_login
101
+ selected_positions = login
102
+ ticket = pass_pinpad(selected_positions)
103
+ post_auth(ticket)
104
+ end
105
+
106
+ def login
107
+ add_headers(
108
+ 'Accept' => 'application/json, text/javascript, */*; q=0.01',
109
+ 'Content-Type' => 'application/json; charset=utf-8'
110
+ )
111
+
112
+ params = {
113
+ loginDocument: {
114
+ documentType: 0,
115
+ document: @dni.to_s
116
+ },
117
+ birthday: @birthday.to_s,
118
+ companyDocument: nil,
119
+ device: 'desktop'
120
+ }
121
+
122
+ response = JSON.parse(post(LOGIN_ENDPOINT, fields: params.to_json))
123
+ current_pinpad_paths = save_pinpad_numbers(response['pinpad'])
124
+ pinpad_numbers = recognize_pinpad_numbers(current_pinpad_paths)
125
+
126
+ get_correct_positions(pinpad_numbers, response['pinPositions'])
127
+ end
128
+
129
+ def pass_pinpad(positions)
130
+ fields = { pinPositions: positions }
131
+ response = put(LOGIN_ENDPOINT, fields: fields.to_json)
132
+ JSON.parse(response)['ticket']
133
+ end
134
+
135
+ def post_auth(ticket)
136
+ add_headers(
137
+ 'Accept' => 'application/json, text/javascript, */*; q=0.01',
138
+ 'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'
139
+ )
140
+
141
+ params = "ticket=#{ticket}&device=desktop"
142
+ post(POST_AUTH_ENDPOINT, fields: params)
143
+ end
144
+
145
+ def save_pinpad_numbers(pinpad)
146
+ current_pinpad_paths = []
147
+ pinpad.each_with_index do |digit, index|
148
+ tmp = Tempfile.new(["pinpad_number_#{index}", '.png'])
149
+ File.open(tmp.path, 'wb') { |f| f.write(Base64.decode64(digit)) }
150
+ current_pinpad_paths << tmp.path
151
+ end
152
+
153
+ current_pinpad_paths
154
+ end
155
+
156
+ def recognize_pinpad_numbers(current_pinpad_paths)
157
+ real_numbers = []
158
+ current_numbers = Magick::ImageList.new(*current_pinpad_paths)
159
+ 0.upto(9) do |i|
160
+ pixel_diffs = []
161
+ 0.upto(9) do |j|
162
+ sample_number_img = Magick::ImageList.new(sample_number_path(j)).first
163
+ pixel_diffs << images_diff(sample_number_img, current_numbers[i])
164
+ end
165
+ real_numbers << pixel_diffs.each_with_index.min.last
166
+ end
167
+ real_numbers
168
+ end
169
+
170
+ def sample_number_path(number)
171
+ File.join(File.dirname(__FILE__), "#{SAMPLE_ROOT_PATH}/#{number}.png")
172
+ end
173
+
174
+ def images_diff(sample_number_img, current_number_img)
175
+ diff = 0
176
+ sample_pixels = pixels_from_coordinates(sample_number_img, 0, 0)
177
+ current_pixels = pixels_from_coordinates(current_number_img, 0, 0)
178
+ current_pixels.each_with_index do |pixel, index|
179
+ sample_pixel = sample_pixels[index]
180
+ red_diff = (pixel.red - sample_pixel.red).abs
181
+ green_diff = (pixel.green - sample_pixel.green).abs
182
+ blue_diff = (pixel.blue - sample_pixel.blue).abs
183
+
184
+ diff += red_diff + green_diff + blue_diff
185
+ end
186
+ diff
187
+ end
188
+
189
+ def pixels_from_coordinates(img, x, y)
190
+ img.get_pixels(x, y, SAMPLE_WIDTH, SAMPLE_HEIGHT)
191
+ end
192
+
193
+ def get_correct_positions(pinpad_numbers, positions)
194
+ first_digit = @password[positions[0] - 1]
195
+ second_digit = @password[positions[1] - 1]
196
+ third_digit = @password[positions[2] - 1]
197
+
198
+ [
199
+ pinpad_numbers.index(first_digit.to_i),
200
+ pinpad_numbers.index(second_digit.to_i),
201
+ pinpad_numbers.index(third_digit.to_i)
202
+ ]
203
+ end
204
+
205
+ # Build an Account object from API data
206
+ def build_account(data)
207
+ Account.new(
208
+ bank: self,
209
+ id: data['uuid'],
210
+ name: data['name'],
211
+ balance: data['balance'],
212
+ currency: 'EUR',
213
+ available_balance: data['availableBalance'],
214
+ description: (data['alias'] || data['name']),
215
+ iban: data['iban'],
216
+ bic: data['bic']
217
+ )
218
+ end
219
+
220
+ def build_investment(data)
221
+ Investment.new(
222
+ bank: self,
223
+ id: data['uuid'],
224
+ name: data['name'],
225
+ balance: data['balance'],
226
+ currency: 'EUR',
227
+ investment: data['investment']
228
+ )
229
+ end
230
+
231
+ # Build a transaction object from API data
232
+ def build_transaction(data, account)
233
+ amount = Money.new(data['amount'] * 100, data['currency'])
234
+ Transaction.new(
235
+ account: account,
236
+ id: data['uuid'],
237
+ amount: amount,
238
+ currency: data['EUR'],
239
+ effective_date: Date.strptime(data['effectiveDate'], '%d/%m/%Y'),
240
+ description: data['description'],
241
+ balance: data['balance']
242
+ )
243
+ end
244
+ end
245
+ end
246
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ module Bankscrap
2
+ module ING
3
+ VERSION = '1.0.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'bankscrap/ing/bank'
2
+ require_relative 'bankscrap/ing/version'
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bankscrap-ing
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Raúl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bankscrap
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rmagick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.2.2
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.2'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.2.2
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.7'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.7'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '10.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '10.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '8.2'
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 8.2.5
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '8.2'
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 8.2.5
95
+ - !ruby/object:Gem::Dependency
96
+ name: rubocop
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: 0.39.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: 0.39.0
109
+ description:
110
+ email:
111
+ - raulmarcosl@gmail.coms
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - ".gitignore"
117
+ - ".rubocop.yml"
118
+ - Gemfile
119
+ - LICENSE.txt
120
+ - README.md
121
+ - Rakefile
122
+ - bankscrap-ing.gemspec
123
+ - lib/bankscrap-ing.rb
124
+ - lib/bankscrap/ing/bank.rb
125
+ - lib/bankscrap/ing/numbers/0.png
126
+ - lib/bankscrap/ing/numbers/1.png
127
+ - lib/bankscrap/ing/numbers/2.png
128
+ - lib/bankscrap/ing/numbers/3.png
129
+ - lib/bankscrap/ing/numbers/4.png
130
+ - lib/bankscrap/ing/numbers/5.png
131
+ - lib/bankscrap/ing/numbers/6.png
132
+ - lib/bankscrap/ing/numbers/7.png
133
+ - lib/bankscrap/ing/numbers/8.png
134
+ - lib/bankscrap/ing/numbers/9.png
135
+ - lib/bankscrap/ing/version.rb
136
+ homepage: ''
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.2.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: ING adapter for Bankscrap
160
+ test_files: []