File Coverage

blib/lib/Finance/Bank/IE/BankOfIreland.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


";
line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Finance::Bank::IE::BankOfIreland - Interface to Bank of Ireland online banking
4              
5             =head1 SYNOPSIS
6              
7             use Finance::Bank::IE::BankOfIreland;
8              
9             # config
10             my $conf = { user => '', pin => '', contact => '', dob => '' };
11              
12             # get balance from all accounts
13             my @accounts = Finance::Bank::IE::BankOfIreland->check_balance( $conf );
14              
15             # get account transaction details
16             my @details = Finance::Bank::IE::BankOfIreland->account_details( $acct );
17              
18             # list beneficiaries for an account
19             my $bene = Finance::Bank::IE::BankOfIreland->list_beneficiaries( $acct );
20              
21             # transfer money to a beneficiary
22             my $tx = Finance::Bank::IE::BankOfIreland->funds_transfer( $from, $to, $amt );
23              
24             =head1 DESCRIPTION
25              
26             Module to interact with BoI's 365 Online service.
27              
28             =head1 FUNCTIONS
29              
30             Note that all functions are set up to act as methods (i.e. they all need to be invoked using F:B:I:B->method()). All functions also take an optional configuration hash as a final parameter.
31              
32             =over
33              
34             =cut
35             package Finance::Bank::IE::BankOfIreland;
36              
37              
38 3     3   137749 use strict;
  3         7  
  3         118  
39 3     3   17 use warnings;
  3         5  
  3         152  
40              
41             our $VERSION = "0.30";
42              
43 3     3   17 use base qw( Finance::Bank::IE );
  3         12  
  3         2237  
44             use POSIX;
45              
46             # headers for account summary page
47             use constant {
48             BALANCE => "Balance Information: Balance",
49             ACCTTYPE => "Account Type",
50             NICKNAME => "Nickname Information: Nickname",
51             CURRENCY => "Currency",
52             ACCTNUM => "Account Number",
53             };
54              
55             # headers for transaction list page
56             use constant {
57             DATE => "Date",
58             DETAIL => "Details",
59             DEBIT => "Debit",
60             CREDIT => "Credit",
61             DETBAL => "Balance Information: Balance",
62             };
63              
64             # headers for payments page
65             use constant {
66             BENNAME => 'Beneficiary Name Information: Beneficiary Name',
67             BENACCT => 'Account Number',
68             BENNSC =>
69             'National Sort Code (NSC) Information: National Sort Code (NSC)',
70             BENREF => 'Reference Number Information: Reference Number',
71             BENDESC =>
72             'Beneficiary Description Information: Beneficiary Description',
73             BENSTATUS => 'Status Information: Status',
74             };
75              
76             my $BASEURL = "https://www.365online.com/";
77              
78             my %pages = (
79             login => {
80             url => 'https://www.365online.com/online365/spring/authentication',
81             sentinel => 'Login.*Step 1 of 2',
82             },
83             login2 => {
84             url => 'https://www.365online.com/online365/spring/authentication',
85             sentinel => 'Login.*Step 2 of 2',
86             },
87             accessDenied => {
88             url => 'https://www.365online.com/online365/spring/accessDenied',
89             sentinel => '

Access Denied',

90             },
91             badcreds => {
92             url => 'https://www.365online.com/online365/spring/authentication',
93             sentinel => 'Your login details are incorrect, please try again',
94             },
95             expired => {
96             url => 'https://www.365online.com/online365/spring/sessionExpired',
97             sentinel => 'The system has logged you out',
98             },
99             # generic interstitial
100             interstitial => {
101             url => 'not important',
102             sentinel => 'Continue to 365 Home',
103             },
104             termsandconds => {
105             url => 'not important',
106             sentinel => '

Terms and Conditions

',
107             },
108             accounts => {
109             url => 'https://www.365online.com/online365/spring/accountSummary?execution=e2s1',
110             sentinel => '>Your Accounts\b',
111             },
112             statements => {
113             url => 'https://www.365online.com/online365/spring/statements?execution=e1s1',
114             sentinel => 'Recent Transactions',
115             },
116             moneyTransfer => {
117             url => 'https://www.365online.com/online365/spring/moneyTransfer?execution=e7s1',
118             sentinel => 'Money Transfer',
119             },
120             manageaccounts => {
121             url => 'https://www.365online.com/online365/spring/manageAccounts?execution=e5s1',
122             sentinel => 'Manage Your Accounts',
123             },
124             managepayees => {
125             url => 'https://www.365online.com/online365/spring/managePayees?execution=e6s1',
126             sentinel => 'Manage Payees',
127             },
128             );
129              
130             use HTML::TokeParser;
131             use Carp;
132             use Date::Parse;
133             use POSIX;
134             use File::Path;
135             use Data::Dumper;
136              
137             sub _pages {
138             return \%pages;
139             }
140              
141             # =item * login_dance( $config );
142              
143             # Logs in or refreshes the current session. The config parameter is a hash reference which is cached the first time it is used, so can be omitted thereafter. The contents of the hash are the login details for your 365 Online account:
144              
145             # =over
146              
147             # =item * user: your six-digit BoI user ID
148              
149             # =item * pin: your six-digit PIN
150              
151             # =item * contact: the last four digits of your contact number
152              
153             # =item * dob: your date of birth in DD/MM/YYYY format
154              
155             # =back
156              
157             # No validation is currently done on the format of the config items. The function returns true or false. Note that this function should rarely need to be directly used as it's invoked by the other functions as a first step.
158              
159             # =cut
160             # sub login_dance {
161             # my $self = shift;
162             # my $confref = shift;
163              
164             # confess();
165              
166             # $confref ||= $self->cached_config();
167              
168             # for my $required ( "user", "pin", "contact", "dob" ) {
169             # if ( !defined( $confref->{$required} )) {
170             # $self->_dprintf( "$required not specified\n" );
171             # return;
172             # }
173             # }
174              
175             # $self->cached_config( $confref );
176              
177             # if ( !$self->_get( $pages{login}->{url}, $confref )) {
178             # croak( "Failed to get login page." );
179             # }
180              
181             # # TODO check sentinel & form name
182             # my $form = $self->_agent()->current_form();
183             # $self->_agent()->field( "form:userId", $confref->{user} );
184             # $self->_set_creds_fields( $confref );
185             # my $res = $self->_agent()->submit_form();
186             # $self->_identify_page();
187             # $self->_save_page();
188              
189             # if ( !$res->is_success ) {
190             # croak( "Failed to submit login form" );
191             # }
192              
193             # $self->_set_creds_fields( $confref );
194             # $res = $self->_agent()->submit_form();
195             # $self->_identify_page();
196             # $self->_save_page();
197              
198             # if ( !$res->is_success ) {
199             # croak( "Failed to submit login form" );
200             # }
201              
202             # if ( $res->content() =~ /$pages{badcreds}->{sentinel}/s ) {
203             # croak "Your login details are incorrect";
204             # } elsif ( $res->content() =~ /$pages{login}->{sentinel}/s ) {
205             # croak( "Looping, bailing out to avoid lockout\n" );
206             # }
207              
208             # # one other fail string: You did not enter the 3 requested digits of your PIN!
209              
210             # # GAH INTERSTITIALS
211             # if ( $self->_identify_page eq 'interstitial' ) {
212             # $self->_agent()->field( "form:continue", "form:continue" );
213             # $res = $self->_agent()->submit_form();
214             # $self->_identify_page();
215             # $self->_save_page();
216             # }
217              
218             # return 1;
219             # }
220              
221             sub _submit_first_login_page {
222             my $self = shift;
223             my $confref = shift||$self->cached_config();
224              
225             my $form = $self->_agent()->current_form();
226             $self->_agent()->field( "form:userId", $confref->{user} );
227             $self->_set_creds_fields( $confref );
228             return $self->_agent()->submit_form();
229             }
230              
231             sub _submit_second_login_page {
232             my $self = shift;
233             my $confref = shift||$self->cached_config();
234              
235             $self->_set_creds_fields( $confref );
236             return $self->_agent()->submit_form();
237             }
238              
239             =item * $self->check_balance()
240              
241             Fetch all account balances from the account summary page. Returns an array of Finance::Bank::IE::BankOfIreland::Account objects.
242              
243             =cut
244              
245             sub check_balance {
246             my $self = shift;
247             my $confref = shift;
248              
249             $confref ||= $self->cached_config();
250             $self->_get( $self->_pages->{accounts}->{url}, $confref );
251              
252             if ( $self->_agent()->content() !~ /$pages{accounts}->{sentinel}/s ) {
253             croak( "Failed to get account summary page" );
254             }
255              
256             my $summary = $self->_agent()->content;
257             my $parser = new HTML::TokeParser( \$summary );
258              
259             my ( @accounts, %account, @headings );
260             my ( $getheadings, $col ) = ( 1, 0 );
261              
262             while ( my $tag = $parser->get_tag( "span" )) {
263             if ( $self->_streq( $tag->[1]{class}, "acc_name" )) {
264             # ugh. accountname
265             while ( my $token = $parser->get_token()) {
266             if ( $token->[0] eq 'T' ) {
267             ( $account{+NICKNAME} = $token->[1] ) =~ s/\s+$//;
268             last;
269             }
270             }
271             $account{+ACCTNUM} = $parser->get_trimmed_text( "/span");
272             } elsif ( $self->_streq( $tag->[1]{class}, "acc_value" )) {
273             $account{+CURRENCY} = $parser->get_trimmed_text( "/span" );
274             $parser->get_tag( "span" );
275             $account{+BALANCE} = $parser->get_trimmed_text;
276              
277             push @accounts,
278             bless {
279             type => delete $account{+ACCTTYPE},
280             nick => delete $account{+NICKNAME},
281             account_no => delete $account{+ACCTNUM},
282             currency => delete $account{+CURRENCY},
283             balance => delete $account{+BALANCE},
284             }, "Finance::Bank::IE::BankOfIreland::Account";
285             }
286             }
287              
288             if ( !@accounts ) {
289             $self->_dprintf( "No accounts found\n" );
290             }
291              
292             return @accounts;
293             }
294              
295             =item * $self->account_details( account [,config] )
296              
297             Return transaction details from the specified account
298              
299             =cut
300             sub account_details {
301             my $self = shift;
302             my $account = shift;
303             my $confref = shift;
304             my ( @headings, @details );
305              
306             $confref ||= $self->cached_config();
307              
308             my $content = $self->_get( $pages{statements}->{url}, $confref );
309              
310             if ( !$content) {
311             croak( "Failed to get account summary page" );
312             }
313              
314             # account selector
315             my $error = 'account not found';
316             my $parser = new HTML::TokeParser( \$content );
317              
318             while ( my $tag = $parser->get_tag( "select" )) {
319             if ( $self->_streq( "form:selectAccountDropDown", $tag->[1]{id})) {
320             while ( my $optiontag = $parser->get_tag("/select", "option")) {
321             last if $optiontag->[0] eq '/select';
322             my $accountname = $parser->get_trimmed_text( "/option" );
323             next if $accountname =~ /Select Account/;
324             my ( $nick, $number ) = split( /\s*~\s*/, $accountname );
325             $self->_dprintf( "Found account '$nick' = '$number'\n" );
326             if ( $account eq $nick or $account eq $number or
327             $account eq '~'.$number ) {
328             if ( $self->_streq( $optiontag->[1]{selected}, "selected" )) {
329             $error = 'ok';
330             } else {
331             $error = 'account not selected';
332             }
333             last;
334             }
335             }
336             last;
337             }
338             }
339              
340             if ( $error ne 'ok' ) {
341             croak( "Account '$account': " . $error );
342             }
343              
344             # now pull out the stuff we were looking for
345             while ( my $tag = $parser->get_tag( "table" )) {
346             if ( $self->_streq( "form:transactionDataTable" ), $tag->[1]{id}) {
347             while ( my $row = $parser->get_tag( "/table", "tr" )) {
348             last if $row->[0] eq '/table';
349             my @row;
350             while ( my $col = $parser->get_tag( "/tr", "th", "td" )) {
351             last if $col->[0] eq '/tr';
352             push @row, $parser->get_trimmed_text( "/" . $col->[0] );
353             }
354             if ( !@headings ) {
355             # Date, Details, Debit, Credit, Balance
356             @headings = @row;
357             } else {
358             # fixups of raw data:
359             my ( $date, $details, $dr, $cr, $balance ) = @row;
360             if ( !$date ) {
361             if ( @details ) {
362             $date = $details[-1]->[0];
363             } else {
364             $date = 0; # can't be helped
365             }
366             } else {
367             my ( $d, $m, $y ) = split( "/", $date );
368             # strftime ilk
369             $date = strftime( "%s", 0, 0, 0, $d, $m - 1, $y - 1900, );
370             }
371              
372             $dr ||= 0.0;
373             $cr ||= 0.0;
374              
375             $balance ||= ( @details ? $details[-1]->[-1] : 0 ) - $dr + $cr;
376             push @details, [ $date, $details, $dr, $cr, $balance ];
377             }
378             }
379             last;
380             }
381             }
382              
383             return \@headings, @details;
384             }
385              
386             =item * $self->list_beneficiaries( account )
387              
388             List beneficiaries of C
389              
390             =cut
391             sub list_beneficiaries {
392             my $self = shift;
393             my $account_from = shift;
394             my $confref = shift;
395              
396             $self->_dprintf( "Fetching beneficiaries for %s\n", ref $account_from ? $account_from->{nick} : $account_from );
397              
398             $confref ||= $self->cached_config();
399              
400             # allow passing in of account objects
401             if ( ref $account_from eq "Finance::Bank::IE::BankOfIreland::Account" ) {
402             $account_from = $account_from->{nick};
403             }
404              
405             my $res =
406             $self->_get( $pages{manageaccounts}->{url}, $confref );
407             $self->_save_page();
408             if ( !$res ) {
409             croak( "Failed to get " . $pages{manageaccounts}->{url} );
410             }
411              
412             # now we have to pretend to be javascript again.
413             $self->_agent()->field( "form:managePayees", "form:managePayees" );
414             $res = $self->_agent()->submit_form();
415             $self->_identify_page();
416             $self->_save_page();
417              
418             if ( !$res->is_success ) {
419             croak( "Failed to submit manageAccounts form" );
420             }
421              
422             # it would be nice to have more payees to test this with
423             my $content = $self->_agent()->content;
424             my $parser = new HTML::TokeParser( \$content );
425             my @beneficiaries;
426             while ( my $tag = $parser->get_tag( "table" )) {
427             next unless $tag->[1]{id};
428             next unless $tag->[1]{id} =~ /payee/i;
429             my %beneficiary;
430             my @cols = qw( desc account_no nsc ref currency nick limit );
431             while ( $tag = $parser->get_tag( "td", "/tr", "/table" )) {
432             if ( $tag->[0] eq "/table" ) {
433             last;
434             }
435             if ( $tag->[0] eq "/tr" and %beneficiary ) {
436             # I don't currently know what an inactive beneficiary
437             # looks like, so I'm flagging them all as Active.
438             push @beneficiaries, bless {
439             type => 'Beneficiary',
440             status => 'Active',
441             }, "Finance::Bank::IE::BankOfIreland::Account";
442             for my $k ( keys %beneficiary ) {
443             $beneficiaries[-1]->{$k} = $beneficiary{$k};
444             }
445              
446             %beneficiary = ();
447             } else {
448             while ( my $token = $parser->get_token()) {
449             if ( $token->[0] eq "E" and $token->[1] eq "td" ) {
450             last;
451             } elsif ( $token->[0] eq "S" and $token->[1] eq "label" ) {
452             $beneficiary{$cols[0]} = $parser->get_trimmed_text( "/label" );
453             } elsif ( $token->[0] eq "T" ) {
454             my $idx = scalar( keys %beneficiary ) - 1;
455             $beneficiary{$cols[$idx]} = $token->[1];
456             } elsif ( $token->[0] eq "S" and $token->[1] eq 'input' ) {
457             $beneficiary{input} = [ @{$token} ];
458             }
459             }
460             }
461             }
462             }
463              
464             \@beneficiaries;
465             }
466              
467             =item * $self->funds_transfer( from, to, amount [,config] )
468              
469             Transfer C from C to C, optionally using C as the config data.
470              
471             =cut
472              
473             sub funds_transfer {
474             my $self = shift;
475             my $account_from = shift;
476             my $account_to = shift;
477             my $amount = shift;
478             my $confref = shift;
479              
480             $self->_dprintf( "Funds transfer of %s from %s to %s\n", $amount,
481             ref $account_from ? $account_from->{nick} : $account_from,
482             ref $account_to ? $account_to->{nick} : $account_to );
483              
484             $confref ||= $self->cached_config();
485              
486             # allow passing in of account objects
487             if ( ref $account_from eq "Finance::Bank::IE::BankOfIreland::Account" ) {
488             $account_from = $account_from->{nick};
489             }
490              
491             if ( ref $account_to eq "Finance::Bank::IE::BankOfIreland::Account" ) {
492             $account_to = $account_to->{nick};
493             }
494              
495             my $beneficiaries = $self->list_beneficiaries( $account_from, $confref );
496              
497             my $acct;
498             for my $bene ( @{$beneficiaries} ) {
499             if ((( $bene->{account_no} ||'' ) eq $account_to ) or
500             (( $bene->{nick} ||'' ) eq $account_to )) {
501             croak "Ambiguous destination account $account_to"
502             if $acct;
503             $acct = $bene;
504             }
505             }
506              
507             if ( !defined( $acct )) {
508             croak( "Unable to find $account_to in list of accounts" );
509             }
510              
511             if ( $acct->{status} eq "Inactive" ) {
512             croak( "Inactive beneficiary" );
513             }
514              
515             # now get the funds transfer page
516             my $res = $self->_agent()->get( $pages{moneyTransfer}->{url} );
517             $self->_save_page();
518             if ( !$res->is_success ) {
519             croak( "Failed to get funds transfer page." );
520             }
521              
522             # fiddly bit. there are different types of transfer, and I don't
523             # have test accounts to support all of them.
524             # billPayment, ownAccountPayment, domesticPayment, internationalPayment
525             # So, testing the bit I can test.
526             $self->_agent()->field( "form:domesticPayment", "form:domesticPayment" );
527             $res = $self->_agent()->submit_form();
528             $self->_identify_page();
529             $self->_save_page();
530             croak( 'not on Origin page' ) unless $self->_agent()->content() =~ m@Domestic Transfer@;
531              
532             # select the origin account:
533             # select > option > id='form:dt_select_acc_from'
534             # defaults to the right account for me...
535              
536             # click on the continue button
537             $res = $self->_agent()->submit_form( button => 'form:formActions:continue' );
538             $self->_identify_page();
539             $self->_save_page();
540             croak( 'not on Details page' ) unless $self->_agent()->content() =~ m@Enter Details@;
541              
542             # on this page, there's a single_line_div containing the account name/no
543             # then another one containing the available funds
544             # format is
labeldata
545             my $account_selector = '';
546             my $content = $self->_agent()->content();
547             my $parser = new HTML::TokeParser( \$content );
548             my @valid_accounts;
549             while ( my $selector = $parser->get_tag( "select" )) {
550             if ( $self->_streq( $selector->[1]{id}, 'form:selectPayeeDomestic')) {
551             while ( my $option = $parser->get_tag( "option", "/select" )) {
552             last if $option->[0] eq '/select';
553             my $accountname = $parser->get_trimmed_text( "/option" );
554             next if $accountname =~ /Select Payee/;
555             push @valid_accounts, $accountname;
556              
557             my ( $nick, $number ) = split( /\s*~\s*/, $accountname );
558             if ( $account_to eq $nick or $account_to eq $number or
559             substr( $account_to, -4 ) eq $number ) {
560             $account_selector = $option->[1]{value};
561             last;
562             }
563             }
564             last;
565             }
566             }
567              
568             if ( $account_selector eq '' ) {
569             croak( sprintf( "Couldn't find payee '%s', valid accounts are '%s'", $account_to, join( "', '", @valid_accounts )));
570             }
571              
572             $res = $self->_agent()->submit_form(
573             fields => {
574             'form:selectPayeeDomestic' => $account_selector,
575             'form:amount' => $amount,
576             },
577             button => 'form:formActions:continue',
578             );
579             $self->_identify_page();
580             # also, the destination account number appears in full on this page.
581             $self->_save_page();
582             croak( 'not on PIN page' ) unless $self->_agent()->content() =~ m@Enter your PIN@;
583              
584             $self->_set_creds_fields( $confref );
585              
586             $res = $self->_agent()->submit_form( button => 'form:formActions:continue' );
587             $self->_identify_page();
588             $self->_save_page();
589             croak( 'not on Confirmation page' ) unless $self->_agent()->content() =~ m@Confirmation@s;
590              
591             # return the 'receipt'
592             # extraction:
593             #

Confirmation

594             #

eur AMOUNT

595             #
596             # has been paid from SOURCE to
597             # DEST
598             #

599             return $self->_agent()->content;
600             }
601              
602              
603             =item * $self->_set_creds_fields( $config )
604              
605             Parse the last received page for credentials entry fields, and populate them with the data from C<$config>. Also injects the missing 'form:continue' hidden field.
606              
607             =cut
608             sub _set_creds_fields {
609             my $self = shift;
610             my $confref = shift;
611              
612             my $form = $self->_agent()->current_form();
613             # avoid having to restructure old config
614             my @dob = split( '/', $confref->{dob} );
615             my %fieldmapping = (
616             'form:dateOfBirth_year' => $dob[2],
617             'form:dateOfBirth_month' => $dob[1],
618             'form:dateOfBirth_date' => $dob[0],
619             'form:phoneNumber' => $confref->{contact},
620             );
621             for my $i ( 1..6 ) {
622             $fieldmapping{"form:security_number_digit$i"} = substr( $confref->{pin}, $i - 1, 1 );
623             $fieldmapping{"form:pinFragment:security_number_digit$i"} = substr( $confref->{pin}, $i - 1, 1 );
624             }
625              
626             for my $id ( keys %fieldmapping ) {
627             my $field = $form->find_input( $id );
628             if ( $field ) {
629             $self->_agent()->field( $id, $fieldmapping{$id});
630             }
631             }
632              
633             # LOSERS.
634             my $input = new HTML::Form::Input( type => 'hidden',
635             name => 'form:continue',
636             value => 'form:continue',
637             );
638             $input->add_to_form( $form );
639             }
640              
641             =item * $scrubbed = $self->_scrub_page( $content )
642              
643             Scrub the supplied content for PII.
644              
645             =cut
646             sub _scrub_page {
647             my ( $self, $content ) = @_;
648              
649             my $output = "";
650              
651             my $parser = new HTML::TokeParser( \$content );
652             my $page = $self->_identify_page( $content );
653             my $payee_acct = 0;
654              
655             while ( my $token = $parser->get_token()) {
656             my $token_string = $token->[0] eq 'T' ? $token->[1] : $token->[-1];
657              
658             if ( $token->[0] eq 'T') {
659             $token_string =~ s@(Last Login.*?)\d+/\d+/\d+ \d+:\d+@${1}01/01/1970 00:00@;
660             $token_string =~ s@(Last Payee Added.*?)\d+/\d+/\d+@${1}01/01/1970@;
661             }
662              
663             if ( $token->[0] eq 'S' ) {
664             if ( $token->[1] eq 'h2' ) {
665             my $tpage = "";
666             while ( my $h2_token = $parser->get_token()) {
667             my $h2_string = $h2_token->[0] eq 'T' ? $h2_token->[1] : $h2_token->[-1];
668             $token_string .= $h2_string;
669             if ( $h2_token->[0] eq 'E' and $h2_token->[1] eq 'h2' ) {
670             last;
671             }
672             $tpage .= $h2_string;
673             }
674             if ( $tpage ne $page ) {
675             $page = $tpage;
676             }
677              
678             # XXX these should use sentinels from %pages
679             if ( $page eq 'Enter your PIN' or $page eq 'Confirmation' ) {
680             # first para contains all the PII, so just nuke it outright
681             $parser->get_tag( "/p" );
682             } elsif ( $page eq 'Enter Details' ) {
683             # now process until we get past the PII
684             my @replacements = ( 'Nickname ~ 9999', 'eur 99.99' );
685             while ( @replacements and my $innertoken = $parser->get_token()) {
686             my $itstring = $innertoken->[0] eq 'T' ? $innertoken->[1] : $innertoken->[-1];
687             if ( $innertoken->[0] eq 'S' and
688             $innertoken->[1] eq 'span' and
689             $self->_streq( $innertoken->[2]{class},
690             'pad_txt' )) {
691             my $tag = $parser->get_tag( '/span' );
692             $itstring .= $tag->[-1];
693             $parser->get_trimmed_text( '/div' );
694             $itstring .= shift @replacements;
695             $itstring .= "";
696             }
697             $token_string .= $itstring;
698             }
699             }
700             }
701              
702             if ( $token->[1] eq 'span' ) {
703             if (( $token->[2]{id}||"") =~ /:detailsColumn$/ or
704             ( $self->_streq( $token->[2]{class}, "acc_name" ))) {
705             while ( my $account_token = $parser->get_token()) {
706             if ( $account_token->[0] eq 'T' ) {
707             $token_string .= "Nickname";
708             last;
709             } else {
710             $token_string .= $account_token->[-1];
711             }
712             }
713             $parser->get_trimmed_text( "/span");
714             while ( my $account_token = $parser->get_token()) {
715             if ( $account_token->[0] eq 'T' ) {
716             if ( $account_token->[1] =~ /^(~ *)/ ) {
717             $token_string .= $1;
718             }
719             $token_string .= '9999';
720             last;
721             } else {
722             $token_string .= $account_token->[-1];
723             }
724             }
725             $parser->get_trimmed_text( "/span" );
726             }
727             } elsif ( $self->_streq( $token->[2]{class}, "acc_value" )) {
728             # a bit more destructive than I'd like...
729             $token_string .= "" . $parser->get_trimmed_text( "/span" ) . "";
730             $token_string .= "";
731             $parser->get_tag( "span" );
732             $token_string .= "99999.99";
733             $parser->get_trimmed_text;
734             $token_string .= "";
735             }
736              
737             # manage payees
738             if ( $token->[1] eq 'td' and
739             $token->[2]{id} and
740             $token->[2]{id} =~ /payee/i ) {
741             if ( $token->[2]{id} =~ /id109/ ) {
742             $token_string .= "reference $payee_acct";
743             } elsif ( $token->[2]{id} =~ /id105/ ) {
744             $token_string .= "account_no $payee_acct";
745             } elsif ( $token->[2]{id} =~ /id113/ ) {
746             $token_string .= "nick $payee_acct";
747             } elsif ( $token->[2]{id} =~ /id107/) {
748             $token_string .= "nsc $payee_acct";
749             } elsif ( $token->[2]{id} =~ /id111/) {
750             $token_string .= "currency $payee_acct";
751             } elsif ( $token->[2]{id} =~ /radiobutton/i ) {
752             $payee_acct++;
753             $token_string .= "";
754             }
755             $parser->get_tag( "/td" );
756             $token_string .= "
757             }
758              
759             if ( $token->[1] eq 'select' and
760             ( $self->_streq( $token->[2]{class}, "acc_select" ) or
761             ( $self->_streq( $token->[2]{id}, "form:selectAccountDropDown")))) {
762             my $added_nickname = 0;
763              
764             while ( my $account_token = $parser->get_token()) {
765             my $string = $account_token->[0] eq 'T' ? $account_token->[1] : $account_token->[-1];
766             if ( $account_token->[0] eq 'S' and $account_token->[1] eq 'option' ) {
767             my $val = $account_token->[2]{value};
768             if ( $val ne 'From Account..' and
769             $val ne 'defaultItem' and
770             $val !~ /^\d+$/ ) {
771             $account_token->[2]{value} = "0";
772             $string = $self->_rebuild_tag( $account_token );
773             $val = '0';
774             }
775             if ( $val ne 'From Account..' and
776             $val ne 'defaultItem' ) {
777             $added_nickname++;
778             if ( $added_nickname == 1 ) {
779             $string .= "Nickname ~ 9999";
780             } else {
781             $string .= "nick $added_nickname ~ $val";
782             }
783             } else {
784             $string .= $parser->get_trimmed_text();
785             }
786             my $tag = $parser->get_tag( "/option" );
787             if ( $tag ) {
788             $string .= $tag->[-1];
789             }
790             }
791             $token_string .= $string;
792             last if ( $account_token->[0] eq 'E' and $account_token->[1] eq 'select' );
793             }
794             }
795             }
796              
797             $output .= $token_string;
798             }
799              
800             return $output;
801             }
802              
803             =back
804              
805             =cut
806              
807             package Finance::Bank::IE::BankOfIreland::Account;
808              
809             # magic (pulled directly from other code, which I now understand)
810             no strict;
811             sub AUTOLOAD { my $self=shift; $AUTOLOAD =~ s/.*:://; $self->{$AUTOLOAD} }
812              
813             1;