Finance::Bank::Schwab - Check your Charles Schwab accounts from Perl


Finance-Bank-Schwab documentation Contained in the Finance-Bank-Schwab distribution.

Index


Code Index:

NAME

Top

Finance::Bank::Schwab - Check your Charles Schwab accounts from Perl

SYNOPSIS

Top

  use Finance::Bank::Schwab;
  my @accounts = Finance::Bank::Schwab->check_balance(
      username => "xxxxxxxxxxxx",
      password => "12345",
  );

  foreach (@accounts) {
      printf "%20s : %8s / %8s : USD %9.2f\n",
      $_->name, $_->sort_code, $_->account_no, $_->balance;
  }

DESCRIPTION

Top

This module provides a rudimentary interface to the Charles Schwab site. You will need either Crypt::SSLeay or IO::Socket::SSL installed for HTTPS support to work. WWW::Mechanize is required.

CLASS METHODS

Top

check_balance()

  check_balance( usename => $u, password => $p )

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

OBJECT METHODS

Top

  $ac->name
  $ac->sort_code
  $ac->account_no

Return the account name, sort code and the account number. The sort code is just the name in this case, but it has been included for consistency with other Finance::Bank::* modules.

  $ac->balance

Return the account balance as a signed floating point value.

WARNING

Top

This warning is verbatim from Simon Cozens' Finance::Bank::LloydsTSB, and certainly applies to this module as well.

This is code for online banking, and that means your money, and that means 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 NO GUARANTEE, explicit or implied.

THANKS

Top

Simon Cozens for Finance::Bank::LloydsTSB. The interface to this module, some code and the pod were all taken from Simon's module.

AUTHOR

Top

Mark Grimes <mgrimes@cpan.org>

COPYRIGHT AND LICENSE

Top


Finance-Bank-Schwab documentation Contained in the Finance-Bank-Schwab distribution.

package Finance::Bank::Schwab;

###########################################################################
# Finance::Bank::Schwab
# Mark Grimes
#
# Check you account blances at Charles Schwab.
# Copyright (c) 2005-2009 Mark Grimes (mgrimes@cpan.org).
# All rights reserved. This program is free software; you can redistribute
# it and/or modify it under the same terms as Perl itself.
#
# Parts of this package were inspired by:
#   Simon Cozens - Finance::Bank::Lloyds module
# Thanks!
#
###########################################################################
use strict;
use warnings;

use Carp;
use WWW::Mechanize;
use HTML::TableExtract;

our $VERSION = '1.20';

our $ua = WWW::Mechanize->new(
    env_proxy  => 1,
    keep_alive => 1,
    timeout    => 30,
    cookie_jar => {},
);

# Debug logging:
# $ua->default_header( 'Accept-Encoding' => scalar HTTP::Message::decodable() );
# $ua->add_handler( "request_send",  sub { shift->dump; return } );
# $ua->add_handler( "response_done", sub { shift->dump; return } );

sub check_balance {
    my ( $class, %opts ) = @_;
    my $content;

    if ( $opts{content} ) {

        # If we give it a file, use the file rather than downloading
        open my $fh, "<", $opts{content} or confess;
        $content = do { local $/ = undef; <$fh> };
        close $fh;

    } else {

        croak "Must provide a password" unless exists $opts{password};
        croak "Must provide a username" unless exists $opts{username};

        my $self = bless {%opts}, $class;

        # Get the login page
        $ua->get(
            'https://client.schwab.com/Login/SignOn/CustomerCenterLogin.aspx')
          or croak "couldn't load inital page";

        # Find the login form, change the action url, then set the username/
        # password and submit
        my $login_form = $ua->form_name('aspnetForm')
          or croak "Couldn't find the login form";
        $login_form->action(
            'https://client.schwab.com/Login/SignOn/signon.ashx')
          or croak "Couldn't update the action url on login form";
        my $username_field =
          'ctl00$WebPartManager1$CenterLogin$LoginUserControlId$txtLoginID';
        $login_form->value( $username_field => $opts{username} );
        $login_form->value( 'txtPassword'   => $opts{password} );
        $ua->submit() or croak "couldn't sign on to account";

        $content = $ua->content;
    }

    if ( $opts{log} ) {

        # Dump to the filename passed in log
        open( my $fh, ">", $opts{log} ) or confess;
        print $fh $content;
        close $fh;
    }

    my @accounts;

    my $te = HTML::TableExtract->new(
        headers   => [ 'Account', 'Name', '(?:Value|Available\s+Balance)' ],
        keep_html => 1,
        ## decode    => 0,
    );

    {

        # HTML::TableExtract warns about undef value with keep_html option
        $SIG{__WARN__} = sub {
            warn @_ unless $_[0] =~ /uninitialized value in subroutine entry/;
        };
        $te->parse($content);
    }

    for my $ts ( $te->tables ) {

        # print "Table (", join( ',', $ts->coords ), "):\n";

        for my $row ( $ts->rows ) {
            next if $row->[0] =~ /Totals/;    # Skip total rows

            # Pull relevant info from link or span tags:
            $row->[0] =~ s{^.*<a[^>]*>(.*)</a>.*$}{$1}m;
            $row->[1] =~ s{^.*<span[^>]*>(.*)</span>.*$}{$1}m;
            $row->[2] =~ s{^.*<span[^>]*>(.*)</span>.*$}{$1}m;

            $_ =~ s{^\s*|\s*$}{}g for @$row;    # Trim whitespace
            $row->[0] =~ s{^([\d.-]+).*$}{$1}s; # Strip all but num from name
            $row->[2] =~ s/[\$,]//xg;           # Remove $ and , from value

            push @accounts, (
                bless {
                    balance    => $row->[2],
                    name       => $row->[1],
                    sort_code  => $row->[1],
                    account_no => $row->[0],
                    ## parent       => $self,
                    statement => undef,
                },
                "Finance::Bank::Schwab::Account"
            );

            # print join( ',', @$row ), "\n";
        }
    }

    return @accounts;
}

package Finance::Bank::Schwab::Account;

# Basic OO smoke-and-mirrors Thingy
no strict;

sub AUTOLOAD {
    my $self = shift;
    $AUTOLOAD =~ s/.*:://x;
    return $self->{$AUTOLOAD};
}

1;

__END__

# Below is stub documentation for your module. You'd better edit it!