The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Finance::Bank::HSBC;
use strict;
use Carp;
our $VERSION = '1.01';

use WWW::Mechanize;
use HTML::TokeParser;

sub check_balance {
    my ($class, %opts) = @_;
    my @accounts;
    croak "Must provide a security code" unless exists $opts{seccode};
    croak "Must provide a banking id" unless exists $opts{bankingid};

    my $self = bless { %opts }, $class;
    
    my $agent = WWW::Mechanize->new();
    $agent->get("https://www.ebank.hsbc.co.uk/main/loginmain.jsp");

    # Filling in the login form. 
    $agent->form(0);
    $agent->field("internetBankingID", $opts{bankingid});
    $agent->field("telephoneSecurityNumber", $opts{seccode}); 
    $agent->click("ok");

    # We're given a redirect, and then need to navigate a frameset.
    $agent->follow(0);
    $agent->follow("master");
    $agent->follow("main");

    # We need to perform a new GET request on the portfolio page.
    my @links = @{$agent->{links}};
    my $bal   = $links[2];
    $agent->get(@{$bal});

    # More frameset navigation.
    $agent->follow(0);
    $agent->follow("main");

    # Now we have the data, we need to parse it.  This is fragile.
    my $content = $agent->{content};

    my ($split1, $split2)  = split /<!-- bgcolor="#9D0710" -->/, $content;
    my ($content, $split2) = split /portform/, $split2;

    my $stream = HTML::TokeParser->new(\$content) or die "$!";
    
    while (my $token = $stream->get_tag("tr")) {
        $token = $stream->get_tag("td");
        
        if ($token->[1]{height} eq "25") {
            # We have an account!
            my $accountname = $stream->get_trimmed_text("/td");
            $stream->get_tag("td");

            my $accounttype = $stream->get_trimmed_text("/td");
            $stream->get_tag("td");

            my $accountnumber = $stream->get_trimmed_text("/td");
            $stream->get_tag("td");
            $stream->get_tag("td");

            my $accountbalance = $stream->get_trimmed_text("/td");

            # Convert / [CD]$/ to a positive or negative float.
            $accountbalance =~ s/ C$//;
            $accountbalance = "-$accountbalance" if $accountbalance =~ s/ D$//;
                   
            push @accounts, {
                balance    => $accountbalance,
                name       => $accountname,
                type       => $accounttype,
                account    => $accountnumber
            };
        }
    }
    return @accounts;
}

1;
__END__
# Below is stub documentation for your module. You better edit it!

=head1 NAME

Finance::Bank::HSBC - Check your HSBC bank accounts from Perl

=head1 SYNOPSIS

  use Finance::Bank::HSBC;
  my @accounts = Finance::Bank::HSBC->check_balance(
      bankingid => "IBxxxxxxxxxx",
      seccode   => "xxxxxx"
  );

  foreach (@accounts) {
      printf "%25s : %13s / %18s : GBP %8.2f\n",
        $_->{name}, $_->{type}, $_->{account}, $_->{balance};
  }

=head1 DESCRIPTION

This module provides a rudimentary interface to the HSBC online
banking system at C<https://www.ebank.hsbc.co.uk/>. 

=head1 DEPENDENCIES

You will need either C<Crypt::SSLeay> or C<IO::Socket::SSL> installed 
for HTTPS support to work with LWP.  This module also depends on 
C<WWW::Mechanize> and C<HTML::TokeParser> for screen-scraping.

=head1 CLASS METHODS

    check_balance(bankingid => $u, seccode => $p)

Return an array of account hashes, one for each of your bank accounts.

=head1 ACCOUNT HASH KEYS 

    $ac->name
    $ac->type
    $ac->account
    $ac->balance
 
Return the account owner's name, account type (eg. 'STUDENT A/C'), account
number, and balance as a signed floating point value.

=head1 WARNING

This warning is from Simon Cozens' C<Finance::Bank::LloydsTSB>, and seems
just as apt here.

This is code for B<online banking>, and that means B<your money>, and
that means B<BE CAREFUL>. You are encouraged, nay, expected, to audit
the source of this module yourself to reassure yourself that I am not
doing anything untoward with your banking data. This software is useful
to me, but is provided under B<NO GUARANTEE>, explicit or implied.

=head1 THANKS

Simon Cozens for C<Finance::Bank::LloydsTSB>, upon which most of this code
is based, Andy Lester (and Skud, by continuation) for WWW::Mechanize, Gisle
Aas for HTML::TokeParser.

=head1 AUTHOR

Chris Ball C<chris@cpan.org>

=cut