line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Finance::Bank::INGDirect; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
9142
|
use strict; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
38
|
|
4
|
1
|
|
|
1
|
|
5
|
use Carp qw(carp croak); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
60
|
|
5
|
1
|
|
|
1
|
|
934
|
use HTTP::Cookies; |
|
1
|
|
|
|
|
13944
|
|
|
1
|
|
|
|
|
34
|
|
6
|
1
|
|
|
1
|
|
1195
|
use LWP::UserAgent; |
|
1
|
|
|
|
|
62241
|
|
|
1
|
|
|
|
|
37
|
|
7
|
1
|
|
|
1
|
|
2784
|
use HTML::Parser; |
|
1
|
|
|
|
|
9456
|
|
|
1
|
|
|
|
|
1321
|
|
8
|
|
|
|
|
|
|
#use Data::Dump qw (dump); |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '1.05'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# $Id: INGDirect.pm,v 1.2 2005/12/16 22:30:20 jmrenouard Exp $ |
13
|
|
|
|
|
|
|
# $Log: INGDirect.pm,v $ |
14
|
|
|
|
|
|
|
# Revision 1.2 2005/12/16 22:30:20 jmrenouard |
15
|
|
|
|
|
|
|
# Modification tag TD empéchant la lecture des transactions |
16
|
|
|
|
|
|
|
# |
17
|
|
|
|
|
|
|
# Revision 1.1.1.1 2005/12/16 22:01:09 jmrenouard |
18
|
|
|
|
|
|
|
# Imported sources |
19
|
|
|
|
|
|
|
# |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=pod |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 NAME |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
Finance::Bank::INGDirect - Check your "ING Direct France" accounts from Perl |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Finance::Bank::INGDirect; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
my @accounts = Finance::Bank::INGDirect->check_balance( |
32
|
|
|
|
|
|
|
ACN => "167845", |
33
|
|
|
|
|
|
|
PIN => "1234", |
34
|
|
|
|
|
|
|
JOUR => "25", # Day of birthday |
35
|
|
|
|
|
|
|
MOIS => "8", # month of birthday |
36
|
|
|
|
|
|
|
ANNEE => "1952" # year of birthday |
37
|
|
|
|
|
|
|
); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
foreach my $account (@accounts) { |
40
|
|
|
|
|
|
|
print "Name: ", $account->name, " Account_no: ", $account->account_no, "\n", "*" x 80, "\n"; |
41
|
|
|
|
|
|
|
print $_->as_string, "\n" foreach $account->statements; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 DESCRIPTION |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
This module provides a read-only interface to the INGDirect online banking |
47
|
|
|
|
|
|
|
system at L. You will need either Crypt::SSLeay |
48
|
|
|
|
|
|
|
installed. |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
The interface of this module is similar to other Finance::Bank::* modules. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head1 WARNING |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
This is code for B, and that means B, and that |
55
|
|
|
|
|
|
|
means B. You are encouraged, nay, expected, to audit the source |
56
|
|
|
|
|
|
|
of this module yourself to reassure yourself that I am not doing anything |
57
|
|
|
|
|
|
|
untoward with your banking data. This software is useful to me, but is |
58
|
|
|
|
|
|
|
provided under B, explicit or implied. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=cut |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=pod |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 METHODS |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=head2 new( ACN => "167845", PIN => "1234", JOUR => "25", MOIS => "8", ANNEE => "1952" feedback => sub { warn "Finance::Bank::INGDirect : $_[0]\n" }) |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
Return an object . You can optionally provide to this method a LWP::UserAgent |
69
|
|
|
|
|
|
|
object (argument named "ua"). You can also provide a function used for |
70
|
|
|
|
|
|
|
feedback (useful for verbose mode or debugging) (argument named "feedback") |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=cut |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
my $urlMain="https://www.ingdirect.fr/secure/general"; |
75
|
|
|
|
|
|
|
my $urlLogin="$urlMain?command=displayLogin"; |
76
|
|
|
|
|
|
|
my $urlContent="$urlMain?command=displayTRAccountSummary"; |
77
|
|
|
|
|
|
|
my $urlAccount="$urlMain?command=goToAccount&account="; |
78
|
|
|
|
|
|
|
my $urlAccount2="$urlMain?command=displayTRHistorique"; |
79
|
|
|
|
|
|
|
sub normalize_number { |
80
|
0
|
|
|
0
|
|
|
my ($self,$s) = @_; |
81
|
|
|
|
|
|
|
|
82
|
0
|
|
|
|
|
|
$s =~ s/ //; |
83
|
0
|
|
|
|
|
|
$s =~ s/,/./; |
84
|
0
|
|
|
|
|
|
$s; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub _parse_content { |
88
|
0
|
|
|
0
|
|
|
my ($self, $content) = @_; |
89
|
0
|
|
|
|
|
|
my ($type, $num, $balance); |
90
|
0
|
|
|
|
|
|
my $f=0; |
91
|
0
|
|
|
|
|
|
my $i=0; |
92
|
0
|
|
|
|
|
|
@{$self->{Accounts}}=(); |
|
0
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
|
while ( $content =~ /(.*)\n/g ) { |
94
|
0
|
0
|
0
|
|
|
|
if ( !$f && $1 =~ /class=\"Bleu11\">(.*?)-(.*?)<\/a><\/td>/ ) { |
95
|
0
|
|
|
|
|
|
$type=$1; |
96
|
0
|
|
|
|
|
|
$num=$2; |
97
|
0
|
|
|
|
|
|
$f=1; |
98
|
|
|
|
|
|
|
# print "\n#Found : $type $num"; |
99
|
|
|
|
|
|
|
} |
100
|
0
|
0
|
0
|
|
|
|
if ( $f && $1 =~ /class="Bleu11">(.*)<\/a><\/td>/) { |
101
|
0
|
|
|
|
|
|
push ( @{$self->{Accounts}}, Finance::Bank::INGDirect::Account->new( $type, $num, $self->normalize_number($1), $self->{ua}, "$urlAccount$i" )); |
|
0
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
#print "\n\t $type, $num, $1, $urlAccount$i"; |
103
|
0
|
|
|
|
|
|
$f=0; |
104
|
0
|
|
|
|
|
|
$i++; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub _get_cookie { |
110
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
111
|
0
|
0
|
|
|
|
|
$self->{feedback}->("get cookie") if $self->{feedback}; |
112
|
0
|
|
|
|
|
|
my $cookie_jar = HTTP::Cookies->new; |
113
|
0
|
|
|
|
|
|
my $response = $self->{ua}->simple_request(HTTP::Request->new(GET => $urlLogin)); |
114
|
0
|
|
|
|
|
|
$cookie_jar->extract_cookies($response); |
115
|
0
|
|
|
|
|
|
$self->{ua}->cookie_jar($cookie_jar); |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub _login { |
119
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
120
|
0
|
0
|
|
|
|
|
$self->{feedback}->("login") if $self->{feedback}; |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new(POST => $urlMain); |
123
|
0
|
|
|
|
|
|
$request->content_type('application/x-www-form-urlencoded'); |
124
|
0
|
|
|
|
|
|
$request->content("ACN=$self->{ACN}&PIN=$self->{PIN}&command=login&locale=fr_FR&device=web&logdatelogin=1&JOUR=$self->{JOUR}&MOIS=$self->{MOIS}&ANNEE=$self->{ANNEE}"); |
125
|
0
|
|
|
|
|
|
my $response = $self->{ua}->request($request); |
126
|
0
|
0
|
|
|
|
|
$response->is_success or die "login failed\n" . $response->error_as_HTML; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub _list_accounts { |
130
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
131
|
0
|
0
|
|
|
|
|
$self->{feedback}->("list accounts") if $self->{feedback}; |
132
|
0
|
|
|
|
|
|
my $response = $self->{ua}->request(HTTP::Request->new(GET => "$urlContent")); |
133
|
0
|
0
|
|
|
|
|
$response->is_success or die "can't access account\n" . $response->error_as_HTML; |
134
|
|
|
|
|
|
|
|
135
|
0
|
|
|
|
|
|
_parse_content($self, $response->content); |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub new { |
139
|
0
|
|
|
0
|
|
|
my ($class, %opts) = @_; |
140
|
0
|
|
|
|
|
|
my $self = bless \%opts, $class; |
141
|
|
|
|
|
|
|
|
142
|
0
|
0
|
|
|
|
|
exists $self->{ACN} or croak "Must provide a ACN"; |
143
|
0
|
0
|
|
|
|
|
exists $self->{PIN} or croak "Must provide a PIN"; |
144
|
0
|
0
|
|
|
|
|
exists $self->{JOUR} or croak "Must provide a JOUR"; |
145
|
0
|
0
|
|
|
|
|
exists $self->{MOIS} or croak "Must provide a MOIS"; |
146
|
0
|
0
|
|
|
|
|
exists $self->{ANNEE} or croak "Must provide a ANNEE"; |
147
|
|
|
|
|
|
|
|
148
|
0
|
|
0
|
|
|
|
$self->{ua} ||= LWP::UserAgent->new; |
149
|
|
|
|
|
|
|
|
150
|
0
|
|
|
|
|
|
_get_cookie($self); |
151
|
0
|
|
|
|
|
|
_login($self); |
152
|
0
|
|
|
|
|
|
_list_accounts($self); |
153
|
0
|
|
|
|
|
|
$self; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub default_account { |
157
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
158
|
0
|
|
|
|
|
|
return $self->{Accounts}[0]; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=pod |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 check_balance( ACN => "167845", PIN => "1234", JOUR => "25", MOIS => "8", ANNEE => "1952" feedback => sub { warn "Finance::Bank::INGDirect : $_[0]\n" }) |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Return a list of account (F::B::INGDirect::Account) objects, one for each of |
166
|
|
|
|
|
|
|
your bank accounts. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=cut |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub check_balance { |
171
|
0
|
|
|
0
|
|
|
my $self = &new; |
172
|
0
|
|
|
|
|
|
@{$self->{Accounts}}; |
|
0
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
package Finance::Bank::INGDirect::Account; |
176
|
1
|
|
|
1
|
|
1568
|
use Data::Dump qw (dump); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=pod |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head1 Account methods |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=head2 type( ) |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Returns the human-readable name of the account. |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=head2 account_no( ) |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
Return the account number, in the form C<0123456L012>. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=head2 balance( ) |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Returns the balance of the account. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head2 statements( ) |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Return a list of Statement object (Finance::Bank::INGDirect::Statement). |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 currency( ) |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Returns the currency of the account as a three letter ISO code (EUR, CHF,etc.). |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=cut |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub new { |
205
|
|
|
|
|
|
|
my ($class, $type, $num, $bal, $ua, $url) = @_; |
206
|
|
|
|
|
|
|
my %account; |
207
|
|
|
|
|
|
|
$account{type}=$type; |
208
|
|
|
|
|
|
|
$account{account_no}=$num; |
209
|
|
|
|
|
|
|
$account{balance}=$bal; |
210
|
|
|
|
|
|
|
$account{ua}=$ua; |
211
|
|
|
|
|
|
|
$account{url}=$url; |
212
|
|
|
|
|
|
|
$account{statements}=(); |
213
|
|
|
|
|
|
|
my $self2 = bless \%account, $class; |
214
|
|
|
|
|
|
|
$self2; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
sub type { $_[0]->{type} } |
218
|
|
|
|
|
|
|
sub account_no { $_[0]->{account_no} } |
219
|
|
|
|
|
|
|
sub balance { $_[0]->{balance} } |
220
|
|
|
|
|
|
|
sub currency { 'EUR' } |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
my $response; |
223
|
|
|
|
|
|
|
sub statements { |
224
|
|
|
|
|
|
|
my ($self) = @_; |
225
|
|
|
|
|
|
|
$self->{url} or return; |
226
|
|
|
|
|
|
|
unless (defined @{$self->{statements}}) { |
227
|
|
|
|
|
|
|
$self->{feedback}->("get statements") if $self->{feedback}; |
228
|
|
|
|
|
|
|
my $response = $self->{ua}->request(HTTP::Request->new(GET => $self->{url})); |
229
|
|
|
|
|
|
|
$response->is_success or die "can't access account $self->{url} statements\n" . $response->error_as_HTML; |
230
|
|
|
|
|
|
|
$response = $self->{ua}->request(HTTP::Request->new(GET => $urlAccount2)); |
231
|
|
|
|
|
|
|
$response->is_success or die "can't access account $urlAccount2 statements\n" . $response->error_as_HTML; |
232
|
|
|
|
|
|
|
_parse_content_account($self, $response->content); |
233
|
|
|
|
|
|
|
}; |
234
|
|
|
|
|
|
|
@{$self->{statements}}; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub normalize_number { |
238
|
|
|
|
|
|
|
my ($self, $s) = @_; |
239
|
|
|
|
|
|
|
$s =~ s/ //; |
240
|
|
|
|
|
|
|
$s =~ s/,/./; |
241
|
|
|
|
|
|
|
$s; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub _parse_content_account { |
245
|
|
|
|
|
|
|
my ($self, $content)=@_; |
246
|
|
|
|
|
|
|
#Parsing html content |
247
|
|
|
|
|
|
|
while ( $content =~ /BgdTabOra\">(\d+\/\d+\/\d+)<\/TD>(.|\n)+?Bleu11\">(.*?)<\/span>(.|\n)+?BgdTabOra" align="right">(.*?)<\/TD>/g) { |
248
|
|
|
|
|
|
|
#print "\n# $1 $3 $5"; |
249
|
|
|
|
|
|
|
push (@{$self->{statements}}, Finance::Bank::INGDirect::Statement->new ($1, $3, $self->normalize_number($5))); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
package Finance::Bank::INGDirect::Statement; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=pod |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head1 Statement methods |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head2 date( ) |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Returns the date when the statement occured, in DD/MM/YY format. |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=head2 description( ) |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
Returns a brief description of the statement. |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head2 amount( ) |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Returns the amount of the statement (expressed in Euros or the account's currency). |
270
|
|
|
|
|
|
|
Although the Crédit Mutuel website displays number in continental |
271
|
|
|
|
|
|
|
format (i.e. with a coma as decimal separator), amount() returns a real number. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head2 as_string( $separator ) |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Returns a tab-delimited representation of the statement. By default, it uses |
276
|
|
|
|
|
|
|
a tabulation to separate the fields, but the user can provide its own |
277
|
|
|
|
|
|
|
separator. |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=cut |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub new { |
282
|
|
|
|
|
|
|
my ($class, $date, $description, $amount) = @_; |
283
|
|
|
|
|
|
|
my %stat; |
284
|
|
|
|
|
|
|
$stat{date}=$date; |
285
|
|
|
|
|
|
|
$stat{description}=$description; |
286
|
|
|
|
|
|
|
$stat{amount}=$amount; |
287
|
|
|
|
|
|
|
bless \%stat, $class; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub description { $_[0]{description} } |
291
|
|
|
|
|
|
|
sub amount { $_[0]{amount} } |
292
|
|
|
|
|
|
|
sub date { $_[0]{date} } |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
sub as_string { |
295
|
|
|
|
|
|
|
my ($self, $separator) = @_; |
296
|
|
|
|
|
|
|
join($separator || "\t", $self->{date}, $self->{description}, $self->{amount}); |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
1; |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=pod |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
=head1 COPYRIGHT |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
Copyright 2005, Jean-Marie Renouard. All Rights Reserved. This module |
305
|
|
|
|
|
|
|
can be redistributed under the same terms as Perl itself. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=head1 AUTHOR |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
Thanks to Pixel for Finance::Bank::LaPoste, Cédric Bouvier for Finance::Bank::CreditMut |
310
|
|
|
|
|
|
|
(and also to Simon Cozens and Briac Pilpré for various Finance::Bank::*) |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
=head1 SEE ALSO |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
Finance::Bank::BNPParibas, Finance::Bank::CreditMut, Finance::Bank::LaPoste, ... |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=cut |