package Business::OnlinePayment::MerchantCommerce;
use strict;
use Carp;
use Net::SSLeay qw/make_form post_https/;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
require Exporter;
@ISA = qw(Exporter AutoLoader Business::OnlinePayment);
@EXPORT = qw();
@EXPORT_OK = qw();
$VERSION = 0.01;
sub set_defaults {
my ($self) = @_;
$self->server('trans.atsbank.com');
$self->port('443');
$self->path('/cgi-bin/ats.cgi');
}
sub _action {
my ($self, $action) = @_;
my %content = $self->content();
$content{_action} = $content{action};
delete $content{action};
for
(
'normal authorization','authorization only',
'post authorization','ns_quicksale_cc',
'visa','mastercard','american express','discover'
)
{
$content{action} = 'ns_quicksale_cc'
if $content{_action} eq $_
or $content{type} eq $_;
}
unless ( $content{action} ) {
for ('check','ns_quicksale_check') {
$content{action} = 'ns_quicksale_check'
if $content{_action} eq $_
or $content{type} eq $_;
}
}
if ( $content{action} eq 'ns_quicksale_cc' ) {
if ( ! $content{_action} ) {
if ( $content{type} =~ /^(post authorization|authorization only)$/ ) {
$content{_action} = $content{type};
}
else {
$content{_action} = 'normal authorization';
}
}
}
elsif ( $content{action} eq 'ns_quicksale_check' ) {
$content{_action} = 'check';
}
Carp::croak('Specified action or type not supported by Merchant Commerce')
unless $content{action};
$self->content(%content);
}
sub _map {
my ($self) = @_;
my %content = $self->content;
my %map = (name => $content{action} eq 'ns_quicksale_cc' ? 'ccname' : 'ckname',
login => 'acctid',
description => 'ci_memo',
address => 'ci_billaddr1',
city => 'ci_billcity',
state => 'ci_billstate',
zip => 'ci_billzip',
country => 'ci_billcountry',
phone => 'ci_phone',
email => 'ci_email',
card_number => 'ccnum',
account_number => 'ckacct',
routing_code => 'ckaba');
while ( my($base_identifier, $mc_identifier) = each (%map) ) {
if (defined $content{$base_identifier}) {
$content{$mc_identifier} = $content{$base_identifier};
}
}
$self->content(%content);
}
sub _required {
my ($self) = @_;
Carp::croak('require_avs function not supported by Merchant Commerce')
if $self->require_avs;
my %content = $self->content;
my $test = $self->test_transaction;
if ( $test ) {
$content{acctid} = 'TEST0';
}
if ( $content{action} eq 'ns_quicksale_cc' ) {
($content{expmon}, $content{expyear}) = $self->_date(\%content);
if ( $test ) {
$self->required_fields(qw~ccname~);
$content{'ccnum'} = '5454545454545454';
}
else {
$self->required_fields(qw~ccname ccnum~);
}
}
if ( $content{_action} eq 'authorization only' ) {
$self->build_subs('authrefcode');
$content{authonly} = 1;
}
elsif ( $content{_action} eq 'post authorization' ) {
$self->build_subs('authrefcode');
$self->required_fields(qw~authrefcode~);
}
elsif ( $content{_action} eq 'check' ) {
$self->required_fields(qw~ckname ckacct ckaba~);
}
$self->content(%content);
$self->required_fields(qw~acctid amount~);
}
sub _date {
my ($self, $content) = @_;
if ( !($content->{expmon} and $content->{expyear}) and $content->{exp_date} ) {
($content->{expmon}, $content->{expyear}) = split /\//, $content->{exp_date};
}
Carp::croak('Merchant Commerce requires exp_date(mm/yyyy) or expmon(mm) and expyear(yyyy)')
unless $content->{expmon} and $content->{expyear};
$content->{expmon}, $content->{expyear};
}
sub submit {
my($self) = @_;
$self->_action;
$self->_map;
$self->_required;
my %post_data = $self->get_fields(qw~ckname ckacct ckaba
ccname ccnum expmon expyear authonly authrefcode
acctid action amount subid usepost
ci_companyname ci_phone ci_email ci_memo ci_dlnum ci_ssnum
ci_billaddr1 ci_billaddr2 ci_billcity ci_billstate ci_billzip ci_billcountry
ci_shipaddr1 ci_shipaddr2 ci_shipcity ci_shipstate ci_shipzip ci_shipcountry
emailto emailfrom emailsubject emailtext
recur_create recur_billingcycle recur_billingmax recur_start recur_amount~);
while (my ($key,$value) = each (%post_data)) {
delete $post_data{$key} unless defined $post_data{$key};
}
my $query = make_form(%post_data);
my $server = $self->server;
my $port = $self->port;
my $path = $self->path;
$self->build_subs(qw/response_headers server_status accepted
historyid orderid refcode unknown_result_code
unknown_response_format unknown_methods
declined error post_data conversation
server_response/);
my ($content,$server_status,%headers) = post_https($server,$port,$path,'',$query);
$self->post_data(\%post_data);
$self->server_status($server_status);
$self->server_response($content);
$self->response_headers(\%headers);
my @post_data = map { "$_ => $post_data{$_}" } keys %post_data;
my @headers = map { "$_: $headers{$_}" } keys %headers;
my $conversation = join "\n", @post_data,
$server_status,
@headers,
$content;
$self->conversation($conversation);
my $response_format = $content =~ s/^
//;
chomp $content;
if ( $response_format ) {
my @unknown_methods = ();
for ( split /\n/, $content ) {
my ($meth, $val) = split /=/, lc $_;
unless ( $self->can($meth) ) {
$self->build_subs($meth);
push @unknown_methods, $meth;
}
$self->$meth($val);
}
$self->unknown_functions(\@unknown_methods)
if defined $unknown_methods[0];
$self->result_code($self->accepted || $self->declined || $self->error);
if ( $self->accepted ) {
$self->is_success(1);
$self->authorization($self->accepted);
}
elsif ( $self->declined || $self->error ) {
$self->error_message($self->declined || $self->error);
}
else {
$self->unknown_result_code(1);
$self->error_message($conversation);
};
}
else {
$self->unknown_response_format(1);
$self->error_message($conversation);
}
}
1;
__END__
=head1 NAME
Business::OnlinePayment::MerchantCommerce
=head1 SYNOPSIS
use Business::OnlinePayment;
my $transaction = new Business::OnlinePayment("MerchantCommerce");
$transaction->content(type => 'VISA',
login => '12345',
action => 'Normal Authorization',
amount => '5.00',
name => 'Tim McIntyre',
card_number => '5454545454545454',
exp_date => '12/2005');
$transacton->submit;
if($transaction->is_success) {
print "Success! Authorization Code: " . $transaction->authorization."\n";
} else {
print "Failure!: " . $transaction->error_message."\n";
}
=head1 DESCRIPTION
Merchant Commerce backend for Business::OnlinePayment. Please see Buisness::OnlinePayment for details.
=head2 Supported transaction types
Visa, MasterCard, American Express, Discover, Check
=head2 Required content for all transactions
=over 4
=item * login or acctid
These keys are synonamous
=item * name
Alternatively you may use ccname for credit cards or ckname for checking withhdrawls.
=item * action or type
One of authorization only, post authorization, check, ns_quicksale_check, normal authorization, ns_quicksale_cc, visa, mastercard, american express, or discover.
If set to authorization only MerchantCommerce will create a method(authrefcode) that will return an authrefcode for use in a post authorization.
If set to post authorization then authrefcode becomes a required field.
Use check or ns_quicksale_check for checking withdrawls.
The last six possibilities, normal authorization, ns_quicksale_cc, visa, mastercard, american express, and discover are all synonymous as far as MerchantCommerce is concerned.
If for some reason type and action are both set action will take precidence.
=item * amount
=back
=head2 Additionally required content for credit cards
=over 4
=item * card_number or ccnum:
These keys are synonamous
=item * exp_date(mm/yyyy) or expmon(mm) and expyear(yyyy)
=back
=head2 Optional content for credit cards
=over 4
=item * authrefcode
A reference code returned from a previous authorization only action. Only used with an action of post authorization.
=back
=head2 Additionally required content for checks
=over 4
=item * type or action
Must be set to check, or ns_quicksale_check.
=item * account_number or ckacct
=item * routing_code or ckaba
=back
=head2 Optional content for all transactons
=over 4
=item * address or ci_billaddr1
=item * ci_billaddr2
=item * city or ci_billcity
=item * state or ci_billstate
=item * zip or ci_billzip
=item * country or ci_billcountry
=item * description or ci_memo
=item * subid
=item * usepost
=item * ci_companyname
=item * ci_phone
=item * ci_email
=item * ci_dlnum
=item * ci_ssnum
=item * ci_shipaddr1
=item * ci_shipaddr2
=item * ci_shipcity
=item * ci_shipstate
=item * ci_shipzip
=item * ci_shipcountry
=item * emailto
=item * emailfrom
=item * emailsubject
=item * emailtext
=item * recur_create
=item * recur_billingcycle
=item * recur_billingmax
=item * recur_start
=item * recur_amount
=back
=head2 Content not supported
=over 4
=item * password
=item * bank_name
=item * invoice_number
=item * customer_id
=item * require_avs
=item * fax
=item * action => credit
=back
=head2 Methods
=over 4
=item * orderid
Can be called on any successful authorization.
=item * authrefcode
Can be called on successful authorization only. Returns a reference code to be used in a later post authorization.
=item * historyid
Can be called on success or failure. Usually but not always returned for unknown reasons. Seems to be of dubious usefullness but is provided just in case.
=item * conversation
Returns a string showing the (almost) entire transaction. The first lines are the content that was posted in the traditional hash name => value format. After that is the server status line hopefully HTTP/1.1 200 OK. Next come the response headers in their traditional NAME: value format. Last but certainly not least is content of the response in the format of name=value. The first line of the content part of the string should also contain the string .
=item * post_data
Returns a reference to a hash of the data that was posted to the server.
=item * server_response
Returns a string showing the content of the response from the server. These will be in the form of name=value deliminated by \n.
=item * response_headers
Returns a reference to a hash containing the response headers.
=item * server_status
Returns the server status from the server response.
=item * accepted
On a successful transaction this method returns the same value as the authorization method. This would also mean that is_success is true.
=item * declined
On a failed transaction this method will return the same value as error_message. This would also mean that is_success is false.
=item * error
On a failed transaction this method will return the same value as error_message. This would also mean that is_success is false.
=back
=head2 Please Note
If any of the following three methods are ever set it essentially means that you have found a bug in this module. If you should decide to use them and you find that they are set please email me the string returned from the conversation method so that I can determine the problem. Better yet fix it and send me that. If you decide not to use them you should be fine.
=over 4
=item * unknown_result_code
Merchant Commerce has three known possible result codes, accepted, declined, and error. If for some reason a different result code is returned this method will be set to true and error_message will contain the same value as the conversation method described above. The result code should be contained on the first line of the content of the response. That is the one containing .
=item * unknown_response_format
If the content part of the server response does not contain the as part of its first line this method will be set to true and error_message will contain the same value as the conversation method described above.
=item * unknown_methods
The Merchant Commerce server returns the content of its response as name=value pairs. This MerchantCommerce module parses the content and creates a method named name and sets it's value to value. The methods orderid, refcode, and historyid are known content responses. This method returns a reference to an array of method names that are unknown. You could then call these methods to get the corresponding value.
=back
=head1 AUTHOR
Tim McIntyre, tmac@transport.com
=head1 DISCLAIMER
THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
=head1 SEE ALSO
perl(1) L
=cut