File Coverage

blib/lib/Finance/Bank/INGDirect.pm
Criterion Covered Total %
statement 16 73 21.9
branch 0 24 0.0
condition 0 9 0.0
subroutine 6 14 42.8
pod n/a
total 22 120 18.3


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