blib/lib/Finance/Bank/Postbank_de.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 79 | 96 | 82.2 |
branch | 12 | 18 | 66.6 |
condition | 6 | 9 | 66.6 |
subroutine | 22 | 31 | 70.9 |
pod | 8 | 18 | 44.4 |
total | 127 | 172 | 73.8 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Finance::Bank::Postbank_de; | ||||||
2 | |||||||
3 | 9 | 9 | 666271 | use 5.006; # we use lexical filehandles now | |||
9 | 67 | ||||||
4 | 9 | 9 | 62 | use strict; | |||
9 | 23 | ||||||
9 | 223 | ||||||
5 | 9 | 9 | 58 | use warnings; | |||
9 | 18 | ||||||
9 | 290 | ||||||
6 | 9 | 9 | 68 | use Carp; | |||
9 | 26 | ||||||
9 | 707 | ||||||
7 | 9 | 9 | 5002 | use Moo 2; | |||
9 | 103934 | ||||||
9 | 57 | ||||||
8 | |||||||
9 | 9 | 9 | 16516 | use Time::Local; | |||
9 | 15150 | ||||||
9 | 561 | ||||||
10 | 9 | 9 | 4529 | use POSIX 'strftime'; | |||
9 | 56829 | ||||||
9 | 46 | ||||||
11 | |||||||
12 | 9 | 9 | 17496 | use Finance::Bank::Postbank_de::Account; | |||
9 | 43 | ||||||
9 | 331 | ||||||
13 | 9 | 9 | 4303 | use Finance::Bank::Postbank_de::APIv1; | |||
9 | 40 | ||||||
9 | 451 | ||||||
14 | 9 | 9 | 75 | use Encode qw(decode); | |||
9 | 20 | ||||||
9 | 914 | ||||||
15 | 9 | 9 | 69 | use Mozilla::CA; | |||
9 | 22 | ||||||
9 | 2443 | ||||||
16 | |||||||
17 | #use IO::Socket::SSL qw(SSL_VERIFY_PEER SSL_VERIFY_NONE); | ||||||
18 | |||||||
19 | our $VERSION = '0.57'; | ||||||
20 | |||||||
21 | has 'login' => ( | ||||||
22 | is => 'ro', | ||||||
23 | ); | ||||||
24 | |||||||
25 | has 'password' => ( | ||||||
26 | is => 'ro', | ||||||
27 | ); | ||||||
28 | |||||||
29 | has 'urls' => ( | ||||||
30 | is => 'ro', | ||||||
31 | default => sub { {} }, | ||||||
32 | ); | ||||||
33 | |||||||
34 | has 'logger' => ( | ||||||
35 | is => 'ro', | ||||||
36 | default => sub { {} }, | ||||||
37 | ); | ||||||
38 | |||||||
39 | has 'past_days' => ( | ||||||
40 | is => 'ro', | ||||||
41 | default => sub { {} }, | ||||||
42 | ); | ||||||
43 | |||||||
44 | has 'api' => ( | ||||||
45 | is => 'rw', | ||||||
46 | default => sub { | ||||||
47 | my $api = Finance::Bank::Postbank_de::APIv1->new(); | ||||||
48 | $api->configure_ua(); | ||||||
49 | $api | ||||||
50 | }, | ||||||
51 | ); | ||||||
52 | |||||||
53 | has 'session' => ( | ||||||
54 | is => 'rw', | ||||||
55 | ); | ||||||
56 | |||||||
57 | has '_account_numbers' => ( | ||||||
58 | is => 'rw', | ||||||
59 | ); | ||||||
60 | |||||||
61 | our %functions; | ||||||
62 | BEGIN { | ||||||
63 | 9 | 9 | 10914 | %functions = ( | |||
64 | quit => [ text_regex => qr'\bBanking\s+beenden\b' ], | ||||||
65 | accountstatement => [ text_regex => qr'\bUms.*?tze\b' ], | ||||||
66 | ); | ||||||
67 | }; | ||||||
68 | |||||||
69 | around BUILDARGS => sub { | ||||||
70 | my ($orig,$class,%args) = @_; | ||||||
71 | |||||||
72 | croak "Login/Account number must be specified" | ||||||
73 | unless $args{login}; | ||||||
74 | croak "Password/PIN must be specified" | ||||||
75 | unless $args{password}; | ||||||
76 | if( exists $args{ status }) { | ||||||
77 | $args{ logger } = delete $args{ status }; | ||||||
78 | }; | ||||||
79 | |||||||
80 | $orig->($class, %args); | ||||||
81 | }; | ||||||
82 | |||||||
83 | 0 | 0 | 0 | 0 | sub log { $_[0]->logger->(@_); }; | ||
84 | 0 | 0 | 0 | 0 | sub log_httpresult { $_[0]->log("HTTP Code",$_[0]->agent->status,$_[0]->agent->res->headers->as_string . $_[0]->agent->content) }; | ||
85 | |||||||
86 | sub new_session { | ||||||
87 | 7 | 7 | 1 | 643 | my ($self) = @_; | ||
88 | |||||||
89 | 7 | 50 | 54 | $self->close_session() | |||
90 | if ($self->session); | ||||||
91 | 7 | 18 | my $pb; | ||||
92 | 7 | 17 | my $ok = eval { | ||||
93 | 7 | 65 | $pb = $self->api->login( $self->login, $self->password ); | ||||
94 | 4 | 7106 | 1 | ||||
95 | }; | ||||||
96 | 7 | 100 | 130 | if( ! $ok ) { | |||
97 | #warn sprintf "Got HTTP error %d, message %s", $self->api->ua->status, $self->api->ua->message; | ||||||
98 | #croak $@; | ||||||
99 | } else { | ||||||
100 | 4 | 40 | $self->session( $pb ); | ||||
101 | } | ||||||
102 | }; | ||||||
103 | |||||||
104 | sub is_security_advice { | ||||||
105 | 0 | 0 | 0 | 0 | my ($self) = @_; | ||
106 | #$self->agent->content() =~ /\bZum\s+Finanzstatus\b/; | ||||||
107 | }; | ||||||
108 | |||||||
109 | sub is_nutzungshinweis { | ||||||
110 | 0 | 0 | 0 | 0 | my ($self) = @_; | ||
111 | #$self->agent->content() =~ /\bAus Sicherheitsgr.*?nden haben wir einige\b/; | ||||||
112 | }; | ||||||
113 | |||||||
114 | |||||||
115 | sub skip_security_advice { | ||||||
116 | 0 | 0 | 0 | 0 | my ($self) = @_; | ||
117 | #$self->log('Skipping security advice page'); | ||||||
118 | #$self->agent->follow_link(text_regex => qr/\bZum\s+Finanzstatus\b/); | ||||||
119 | # $self->agent->content() =~ /Sicherheitshinweis/; | ||||||
120 | }; | ||||||
121 | |||||||
122 | sub skip_nutzungshinweis { | ||||||
123 | 0 | 0 | 0 | 0 | my ($self) = @_; | ||
124 | #$self->log('Skipping nutzungshinweis page'); | ||||||
125 | #$self->agent->follow_link(text_regex => qr/\bZur\s+Konten.bersicht\b/); | ||||||
126 | # $self->agent->content() =~ /Sicherheitshinweis/; | ||||||
127 | }; | ||||||
128 | |||||||
129 | sub error_page { | ||||||
130 | # Check if an error page is shown | ||||||
131 | 3 | 3 | 0 | 22 | my ($self) = @_; | ||
132 | 3 | 28 | $self->api->ua->status != 200 | ||||
133 | #return unless $self->agent; | ||||||
134 | # | ||||||
135 | #$self->agent->content =~ m! !sm |
||||||
136 | # or | ||||||
137 | #$self->agent->content =~ m! !sm |
||||||
138 | # or $self->maintenance; | ||||||
139 | }; | ||||||
140 | |||||||
141 | sub error_message { | ||||||
142 | 0 | 0 | 0 | 0 | my ($self) = @_; | ||
143 | #return unless $self->agent; | ||||||
144 | #die "No error condition detected in:\n" . $self->agent->content | ||||||
145 | # unless $self->error_page; | ||||||
146 | #if( | ||||||
147 | #$self->agent->content =~ m! \s*\s*(.*?)\s*\s* !sm |
||||||
148 | # or | ||||||
149 | #$self->agent->content =~ m! \s*(.*?)\s* !sm |
||||||
150 | # ) { return $1 } | ||||||
151 | # #or croak "No error message found in:\n" . $self->agent->content; | ||||||
152 | 0 | 0 | return '' | ||||
153 | }; | ||||||
154 | |||||||
155 | sub maintenance { | ||||||
156 | 1 | 1 | 1 | 19 | my ($self) = @_; | ||
157 | #return unless $self->agent; | ||||||
158 | ##$self->error_page and | ||||||
159 | #$self->agent->content =~ m!Sehr geehrter Online-Banking\s+Nutzer,\s+wegen einer hohen Auslastung kommt es derzeit im Online-Banking zu\s*längeren Wartezeiten.!sm | ||||||
160 | #or $self->agent->content =~ m! Wartung\b! | ||||||
161 | #or $self->agent->content =~ m! \s*\s*Diese Funktion steht auf Grund einer technischen St.*?rung derzeit leider nicht zur Verf.*?gung.*?\s* !sm # Testumgebung... |
||||||
162 | () | ||||||
163 | 1 | 3 | }; | ||||
164 | |||||||
165 | sub access_denied { | ||||||
166 | 3 | 3 | 0 | 1424 | my ($self) = @_; | ||
167 | 3 | 19 | $self->api->ua->status == 401 | ||||
168 | #if ($self->error_page) { | ||||||
169 | # my $message = $self->error_message; | ||||||
170 | # | ||||||
171 | # return ( | ||||||
172 | # $message =~ m!^Die Kontonummer ist nicht für das Internet Online-Banking freigeschaltet. Bitte verwenden Sie zur Freischaltung den Link "Online-Banking freischalten"\. \s*$!sm |
||||||
173 | # or $message =~ m!^Sie haben zu viele Zeichen in das Feld eingegeben. \s*$!sm |
||||||
174 | # or $message =~ m!^Die eingegebene Postbank Girokontonummer ist zu lang. Bitte überprüfen Sie Ihre Eingabe.$!sm | ||||||
175 | # or $message =~ m!^Die Anmeldung ist fehlgeschlagen. Bitte vergewissern Sie sich der Richtigkeit Ihrer Eingaben und f.*?hren Sie den Anmeldevorgang erneut durch.\s*$!sm | ||||||
176 | # ) | ||||||
177 | #} else { | ||||||
178 | # return; | ||||||
179 | #}; | ||||||
180 | }; | ||||||
181 | |||||||
182 | sub session_timed_out { | ||||||
183 | 0 | 0 | 1 | 0 | my ($self) = @_; | ||
184 | #$self->agent->content =~ /Die Sitzungsdaten sind ungültig, bitte führen Sie einen erneuten Login durch.\s+\(27000\)/; | ||||||
185 | () | ||||||
186 | 0 | 0 | }; | ||||
187 | |||||||
188 | sub select_function { | ||||||
189 | 0 | 0 | 1 | 0 | my ($self,$function) = @_; | ||
190 | 0 | 0 | 0 | if (! $self->session) { | |||
191 | 0 | 0 | $self->new_session; | ||||
192 | }; | ||||||
193 | croak "Unknown account function '$function'" | ||||||
194 | 0 | 0 | 0 | unless exists $functions{$function}; | |||
195 | 0 | 0 | my $method = $functions{ $function }; | ||||
196 | |||||||
197 | 0 | 0 | my $res = $self->session->navigate($method); | ||||
198 | 0 | 0 | $res | ||||
199 | }; | ||||||
200 | |||||||
201 | sub close_session { | ||||||
202 | 2 | 2 | 1 | 2538 | my ($self) = @_; | ||
203 | 2 | 24 | $self->session(undef); | ||||
204 | 2 | 155 | $self->api(undef); | ||||
205 | 2 | 1690 | 1 | ||||
206 | }; | ||||||
207 | |||||||
208 | sub finanzstatus { | ||||||
209 | 3 | 3 | 0 | 9 | my( $self ) = @_; | ||
210 | 3 | 100 | 20 | $self->new_session unless $self->session; | |||
211 | 3 | 28 | my $finanzstatus = $self->session->navigate( | ||||
212 | class => 'Finance::Bank::Postbank_de::APIv1::Finanzstatus', | ||||||
213 | path => ['banking_v1' => 'financialstatus'] | ||||||
214 | ); | ||||||
215 | } | ||||||
216 | |||||||
217 | sub _build_account_numbers { | ||||||
218 | 2 | 2 | 8 | my ($self,%args) = @_; | |||
219 | |||||||
220 | 2 | 8 | my $finanzstatus = $self->finanzstatus; | ||||
221 | 2 | 103 | (my $bp) = $finanzstatus->get_businesspartners; # always take the first... | ||||
222 | 2 | 151 | my %numbers; | ||||
223 | # this currently includes the credit card numbers ... | ||||||
224 | 2 | 17 | for my $acc ( $bp->get_accounts() ) { | ||||
225 | 14 | 100 | 100 | 90 | $numbers{ $acc->iban } = $acc if (!$acc->is_depot and !$acc->is_mortgage); | ||
226 | }; | ||||||
227 | |||||||
228 | 2 | 122 | return $self->_account_numbers( \%numbers ); | ||||
229 | } | ||||||
230 | |||||||
231 | sub account_numbers { | ||||||
232 | 1 | 1 | 1 | 7 | my ($self,%args) = @_; | ||
233 | |||||||
234 | 1 | 33 | 10 | my $n = $self->_account_numbers || $self->_build_account_numbers; | |||
235 | |||||||
236 | 1 | 3 | sort keys %{ $n }; | ||||
1 | 12 | ||||||
237 | }; | ||||||
238 | |||||||
239 | sub get_account_statement { | ||||||
240 | 11 | 11 | 1 | 1003455 | my ($self,%args) = @_; | ||
241 | |||||||
242 | #my $past_days = $args{past_days} || $self->{past_days}; | ||||||
243 | #if($past_days) { | ||||||
244 | # my ($day, $month, $year) = split/\./, $agent->current_form->value('umsatzanzeigeGiro:salesForm:umsatzFilterOptionenAufklappbarSuchfeldPanel:accordion:vonBisDatum:datumForm:bisGruppe:bisDatum'); | ||||||
245 | # my $end_epoch = timegm(0, 0, 0, $day, $month-1, $year); | ||||||
246 | # my $from_date = strftime '%d.%m.%Y', localtime($end_epoch-($past_days-1)*60*60*24); | ||||||
247 | # $agent->current_form->value('umsatzanzeigeGiro:salesForm:umsatzFilterOptionenAufklappbarSuchfeldPanel:accordion:vonBisDatum:datumForm:vonGruppe:vonDatum' => $from_date); | ||||||
248 | #}; | ||||||
249 | |||||||
250 | 11 | 66 | 177 | my $accounts = $self->_account_numbers || $self->_build_account_numbers; | |||
251 | |||||||
252 | 11 | 100 | 104 | if( ! $args{ account_number }) { | |||
253 | # Hopefully we only got one account (?!) | ||||||
254 | 1 | 5 | ($args{ account_number }) = keys %$accounts; | ||||
255 | }; | ||||||
256 | |||||||
257 | 11 | 61 | my $account = $accounts->{ $args{ account_number }}; | ||||
258 | |||||||
259 | #if (exists $args{account_number}) { | ||||||
260 | # $self->log("Getting account statement for $args{account_number}"); | ||||||
261 | # # Load the account numbers if not already loaded | ||||||
262 | # $self->account_numbers; | ||||||
263 | # if(! exists $self->{account_numbers}->{$args{account_number}}) { | ||||||
264 | # croak "Unknown account number '$args{account_number}'"; | ||||||
265 | # }; | ||||||
266 | # my $index = $self->{account_numbers}->{$args{account_number}}; | ||||||
267 | # $agent->current_form->param( 'selectForm:kontoauswahl' => $index ); | ||||||
268 | #} else { | ||||||
269 | # my @accounts = $agent->current_form->value('selectForm:kontoauswahl'); | ||||||
270 | # $self->log("Getting account statement via default (@accounts)"); | ||||||
271 | #}; | ||||||
272 | 11 | 108 | my $content = $account->transactions_csv(); | ||||
273 | 11 | 100 | 1819 | if( $args{ file }) { | |||
274 | open my $fh, '>', $args{ file } | ||||||
275 | 1 | 50 | 131 | or croak "Couldn't create '$args{ file }': $!"; | |||
276 | 1 | 20 | binmode $fh, ':encoding(UTF-8)'; | ||||
277 | 1 | 238 | print $fh $content; | ||||
278 | }; | ||||||
279 | #if ($agent->status == 200) { | ||||||
280 | 11 | 43 | my $result = $content; | ||||
281 | # Result is in UTF-8 | ||||||
282 | 11 | 151 | return Finance::Bank::Postbank_de::Account->parse_statement(content => $result); | ||||
283 | }; | ||||||
284 | |||||||
285 | sub unread_messages { | ||||||
286 | 1 | 1 | 1 | 12 | my( $self )= @_; | ||
287 | 1 | 5 | $self->finanzstatus->available_messages | ||||
288 | } | ||||||
289 | |||||||
290 | 1; | ||||||
291 | __END__ |