package Business::BatchPayment::CardFortress; use 5.006; use strict; use warnings; our $VERSION = '0.01'; =head1 NAME Business::BatchPayment::CardFortress =head1 DESCRIPTION Batch payment processing via the CardFortress secure payment proxy service. =head1 USAGE See Business::BatchPayment for general usage information. =head2 PROCESSOR ATTRIBUTES =over 4 =item login, password Your login credentials for CardFortress. =item gateway The L module to use for upstream processing. You don't need to have this module installed, but the proxy server does. =item gateway_* Processor options to pass along to the gateway module. The 'gateway_' prefix will be stripped. =back =cut use Business::BatchPayment; use Moose; with 'Business::BatchPayment::Processor'; with 'Business::BatchPayment::TestMode'; with 'Business::BatchPayment::Debug'; use Data::Serializer; has [ qw(login password private_key gateway) ] => ( is => 'ro', isa => 'Str', required => 1, ); has 'gateway_opts' => ( is => 'ro', isa => 'HashRef', default => sub { {login => ''} }, ); has 'serializer' => ( is => 'ro', lazy => 1, handles => [qw(serialize deserialize)], default => sub { # doesn't have to be Storable, but it must support blessed objects Data::Serializer->new(serializer => 'Storable', encoding => 'b64') }, ); # pull any arg named "gateway_foo" into gateway_opts around 'BUILDARGS' => sub { my ($orig, $class, %args) = @_; foreach (keys %args) { if (/^gateway_(.*)/) { $args{'gateway_opts'}->{$1} = delete $args{$_}; } } $class->$orig(%args); }; sub default_transport { my $self = shift; Business::BatchPayment->create('CardFortress::Transport', login => $self->login, password => $self->password, debug => $self->debug, test_mode => $self->test_mode, private_key => $self->private_key, serializer => $self->serializer, ); } sub format_request { my $self = shift; my $batch = shift; bless $batch, 'Business::BatchPayment::CardFortress::Batch'; # things that get encoded in the batch: $batch->gateway($self->gateway); $batch->gateway_opts($self->gateway_opts); $self->serialize($batch); # that's all folks } sub parse_response { my $self = shift; my $input = shift; my $batch = $self->deserialize($input); } package Business::BatchPayment::CardFortress::Batch; use Moose; extends 'Business::BatchPayment::Batch'; has 'gateway' => ( is => 'rw', isa => 'Str', ); has 'gateway_opts' => ( is => 'rw', isa => 'HashRef', ); package Business::BatchPayment::CardFortress::Transport; use Moose; use Moose::Util::TypeConstraints; extends 'Business::BatchPayment::Transport::HTTPS'; with 'Business::BatchPayment::TestMode'; use File::Slurp; use MIME::Base64; use Crypt::OpenSSL::RSA; has '+host' => ( default => sub { my $self = shift; $self->test_mode ? 'gw.cardfortress.com' : 'test.cardfortress.com' }, lazy => 1, ); has ['login', 'password', 'private_key'] => (isa => 'Str', is => 'rw'); has 'serializer' => ( handles => [qw(serialize deserialize)] ); sub upload { my ($self, $content) = @_; warn "Sending batch...\n" if $self->debug; my ($page, $response, %reply_headers) = $self->https_post('/batch/submit', { login => $self->login, password => $self->password, content => $content, }); $page = $self->deserialize($page); die $page->{error} if $page->{error}; my $batchid = $page->{batchid}; my $private_key = read_file($self->private_key) or die "No private key available"; my $rsa = Crypt::OpenSSL::RSA->new_private_key($private_key); my %answers; foreach my $item (@{ $page->{items} }) { if ( $item->{error} ) { # We have no reliable way to report an error in a specific transaction # at this stage. The server will send the error in the reply batch. # For now do nothing. } elsif ( $item->{challenge} ) { my $challenge = $item->{challenge}; $answers{ $item->{tid} } = $rsa->decrypt( decode_base64($challenge) ); } else { # newly created card--doesn't have a challenge, so do nothing } } # post the response warn "Answering cryptographic challenge...\n" if $self->debug; my $answer_content = $self->serialize({ batchid => $batchid, answers => \%answers }); ($page, $response, %reply_headers) = $self->https_post('/batch/run', { login => $self->login, password => $self->password, content => $answer_content, }); $page = $self->deserialize($page); die $page->{error} if $page->{error}; return; } =head1 AUTHOR Mark Wells, C<< >> =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc Business::BatchPayment::CardFortress Commercial support is available from Freeside Internet Services, Inc. L =head1 ACKNOWLEDGEMENTS =head1 LICENSE AND COPYRIGHT Copyright 2012 Mark Wells. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; # End of Business::BatchPayment::Paymentech