-package Business::OnlinePayment::eSelectPlus;\r
-\r
-use strict;\r
-use Carp;\r
-use Tie::IxHash;\r
-use Business::OnlinePayment 3;\r
-use Business::OnlinePayment::HTTPS 0.03;\r
-use vars qw($VERSION $DEBUG @ISA);\r
-\r
-@ISA = qw(Business::OnlinePayment::HTTPS);\r
-$VERSION = '0.01';\r
-$DEBUG = 0;\r
-\r
-sub set_defaults {\r
- my $self = shift;\r
-\r
- $self->server('esqa.moneris.com');\r
- $self->port('443');\r
- $self->path('/gateway2/servlet/MpgRequest');\r
-\r
- $self->build_subs(qw( order_number ));\r
- # avs_code order_type md5 cvv2_response cavv_response\r
-}\r
-\r
-sub submit {\r
- my($self) = @_;\r
-\r
- #$self->map_fields();\r
- $self->remap_fields(\r
- # => 'order_type',\r
- # => 'transaction_type',\r
- #login => 'store_id',\r
- #password => 'api_token',\r
- #authorization => \r
- #customer_ip =>\r
- #name =>\r
- #first_name =>\r
- #last_name =>\r
- #company =>\r
- #address => \r
- #city => \r
- #state => \r
- #zip => \r
- #country =>\r
- phone => \r
- #fax =>\r
- email =>\r
- card_number => 'pan',\r
- #expiration =>\r
- # => 'expdate',\r
-\r
- 'amount' => 'amount',\r
- #invoice_number =>\r
- #customer_id =>\r
- order_number => 'order_id',\r
- authorization => 'txn_number'\r
-\r
- #cvv2 =>\r
- );\r
-\r
- my $action = $self->{_content}{'action'};\r
- if ( $self->{_content}{'action'} =~ /^\s*normal\s*authorization\s*$/i ) {\r
- $action = 'purchase';\r
- } elsif ( $self->{_content}{'action'} =~ /^\s*authorization\s*only\s*$/i ) {\r
- $action = 'preauth';\r
- } elsif ( $self->{_content}{'action'} =~ /^\s*post\s*authorization\s*$/i ) {\r
- $action = 'completion';\r
- } elsif ( $self->{_content}{'action'} =~ /^\s*void\s*$/i ) {\r
- $action = 'void';\r
- } elsif ( $self->{_content}{'action'} =~ /^\s*credit\s*$/i ) {\r
- if ( $self->{_content}{'authorization'} ) {\r
- $action = 'refund';\r
- } else {\r
- $action = 'ind_refund';\r
- }\r
- }\r
-\r
- if ( $action =~ /^(purchase|preauth|ind_refund)$/ ) {\r
-\r
- $self->required_fields(\r
- qw( login password amount card_number expiration )\r
- );\r
-\r
- #cardexpiremonth & cardexpireyear\r
- $self->{_content}{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/\r
- or croak "unparsable expiration ". $self->{_content}{expiration};\r
- my( $month, $year ) = ( $1, $2 );\r
- $month = '0'. $month if $month =~ /^\d$/;\r
- $self->{_content}{expdate} = $year.$month;\r
-\r
- $self->generate_order_id;\r
-\r
- $self->{_content}{amount} = sprintf('%.2f', $self->{_content}{amount} );\r
-\r
- } elsif ( $action eq 'completion' || $action eq 'void' ) {\r
-\r
- $self->required_fields( qw( login password order_number authorization ) );\r
-\r
- } elsif ( $action eq 'refund' ) {\r
-\r
- $self->required_fields(\r
- qw( login passowrd order_number authorization )\r
- );\r
-\r
- }\r
-\r
- $self->{_content}{'crypt_type'} ||= 7;\r
-\r
- #no, values aren't escaped for XML. their "mpgClasses.pl" example doesn't\r
- #appear to do so, i dunno\r
- tie my %fields, 'Tie::IxHash', $self->get_fields( $self->fields );\r
- my $post_data =\r
- '<?xml version="1.0"?>'.\r
- '<request>'.\r
- '<store_id>'. $self->{_content}{'login'}. '</store_id>'.\r
- '<api_token>'. $self->{_content}{'password'}. '</api_token>'.\r
- "<$action>".\r
- join('', map "<$_>$fields{$_}</$_>", keys %fields ).\r
- "</$action>".\r
- '</request>';\r
-\r
- warn $post_data if $DEBUG > 1;\r
-\r
- my( $page, $response, @reply_headers) = $self->https_post( $post_data );\r
-\r
- #my %reply_headers = @reply_headers;\r
- #warn join('', map { " $_ => $reply_headers{$_}\n" } keys %reply_headers )\r
- # if $DEBUG;\r
-\r
- #XXX check $response and die if not 200?\r
-\r
- # avs_code\r
- # is_success\r
- # result_code\r
- # authorization\r
- #md5 cvv2_response cavv_response ...?\r
-\r
- $self->server_response($page);\r
-\r
- my $result = $self->GetXMLProp($page, 'ResponseCode');\r
-\r
- die "gateway error: ". $self->GetXMLProp( $page, 'Message' )\r
- if $result =~ /^null$/i;\r
-\r
- if ( $result =~ /^\d+$/ && $result < 50 ) {\r
- $self->is_success(1);\r
- $self->result_code( $self->GetXMLProp( $page, 'ISO' ) );\r
- $self->authorization( $self->GetXMLProp( $page, 'Txn_number' ) );\r
- $self->order_number( $self->GetXMLProp( $page, 'order_id') );\r
- } elsif ( $result =~ /^\d+$/ ) {\r
- $self->is_success(0);\r
- $self->error_message( $self->GetXMLProp( $page, 'Message' ) );\r
- } else {\r
- die "unparsable response received from gateway (response $result)".\r
- ( $DEBUG ? ": $page" : '' );\r
- }\r
-\r
-}\r
-\r
-use vars qw(@oidset);\r
-@oidset = ( 'A'..'Z', '0'..'9' );\r
-sub generate_order_id {\r
- my $self = shift;\r
- #generate an order_id if order_number not passed\r
- unless ( exists ($self->{_content}{order_id})\r
- && defined($self->{_content}{order_id})\r
- && length ($self->{_content}{order_id})\r
- ) {\r
- $self->{_content}{'order_id'} =\r
- join('', map { $oidset[int(rand(scalar(@oidset)))] } (1..23) );\r
- }\r
-}\r
-\r
-sub fields {\r
- my $self = shift;\r
-\r
- #order is important to this processor\r
- qw(\r
- order_id\r
- cust_id\r
- amount\r
- comp_amount\r
- txn_number\r
- pan\r
- expdate\r
- crypt_type\r
- cavv\r
- );\r
-}\r
-\r
-sub GetXMLProp {\r
- my( $self, $raw, $prop ) = @_;\r
- local $^W=0;\r
-\r
- my $data;\r
- ($data) = $raw =~ m"<$prop>(.*?)</$prop>"gsi;\r
- #$data =~ s/<.*?>/ /gs;\r
- chomp $data;\r
- return $data;\r
-}\r
-\r
-1;\r
-\r
-__END__\r
-\r
-=head1 NAME\r
-\r
-Business::OnlinePayment::eSelectPlus - Moneris eSelect Plus backend module for Business::OnlinePayment\r
-\r
-=head1 SYNOPSIS\r
-\r
- use Business::OnlinePayment;\r
-\r
- ####\r
- # One step transaction, the simple case.\r
- ####\r
-\r
- my $tx = new Business::OnlinePayment("eSelectPlus");\r
- $tx->content(\r
- type => 'VISA',\r
- login => 'eSelect Store ID,\r
- password => 'eSelect API Token',\r
- action => 'Normal Authorization',\r
- description => 'Business::OnlinePayment test',\r
- amount => '49.95',\r
- name => 'Tofu Beast',\r
- address => '123 Anystreet',\r
- city => 'Anywhere',\r
- state => 'UT',\r
- zip => '84058',\r
- phone => '420-867-5309',\r
- email => 'tofu.beast@example.com',\r
- card_number => '4005550000000019',\r
- expiration => '08/06',\r
- cvv2 => '1234', #optional\r
- );\r
- $tx->submit();\r
-\r
- if($tx->is_success()) {\r
- print "Card processed successfully: ".$tx->authorization."\n";\r
- } else {\r
- print "Card was rejected: ".$tx->error_message."\n";\r
- }\r
-\r
-=head1 SUPPORTED TRANSACTION TYPES\r
-\r
-=head2 CC, Visa, MasterCard, American Express, Discover\r
-\r
-Content required: type, login, password, action, amount, card_number, expiration.\r
-\r
-=head1 PREREQUISITES\r
-\r
- URI::Escape\r
- Tie::IxHash\r
-\r
- Net::SSLeay _or_ ( Crypt::SSLeay and LWP )\r
-\r
-=head1 DESCRIPTION\r
-\r
-For detailed information see L<Business::OnlinePayment>.\r
-\r
-=head1 NOTE\r
-\r
-=head1 AUTHOR\r
-\r
-Ivan Kohler <ivan-eselectplus@420.am>\r
-\r
-=head1 SEE ALSO\r
-\r
-perl(1). L<Business::OnlinePayment>.\r
-\r
-=cut\r
-\r
+package Business::OnlinePayment::eSelectPlus;
+
+use strict;
+use Carp;
+use Tie::IxHash;
+use Business::OnlinePayment 3;
+use Business::OnlinePayment::HTTPS 0.03;
+use vars qw($VERSION $DEBUG @ISA);
+
+@ISA = qw(Business::OnlinePayment::HTTPS);
+$VERSION = '0.06';
+$DEBUG = 0;
+
+sub set_defaults {
+ my $self = shift;
+
+ #USD
+ #$self->server('esplusqa.moneris.com'); # development
+ $self->server('esplus.moneris.com'); # production
+ $self->path('/gateway_us/servlet/MpgRequest');
+
+ ##CAD
+ ##$self->server('esqa.moneris.com'); # development
+ #$self->server('www3.moneris.com'); # production
+ #$self->path('/gateway2/servlet/MpgRequest');
+
+ $self->port('443');
+
+ $self->build_subs(qw( order_number avs_code ));
+ # avs_code order_type md5 cvv2_response cavv_response
+}
+
+sub submit {
+ my($self) = @_;
+
+ if ( defined( $self->{_content}{'currency'} )
+ && $self->{_content}{'currency'} eq 'CAD' ) {
+ $self->server('www3.moneris.com');
+ $self->path('/gateway2/servlet/MpgRequest');
+ } else { #sorry, default to USD
+ $self->server('esplus.moneris.com');
+ $self->path('/gateway_us/servlet/MpgRequest');
+ }
+
+ if ($self->test_transaction) {
+ if ( defined( $self->{_content}{'currency'} )
+ && $self->{_content}{'currency'} eq 'CAD' ) {
+ $self->server('esqa.moneris.com');
+ $self->{_content}{'login'} = 'store2'; # store[123]
+ $self->{_content}{'password'} = 'yesguy';
+ } else { #sorry, default to USD
+ $self->server('esplusqa.moneris.com');
+ $self->{_content}{'login'} = 'monusqa002'; # monusqa00[123]
+ $self->{_content}{'password'} = 'qatoken';
+ }
+ }
+
+ # BOP field => eSelectPlus field
+ #$self->map_fields();
+ $self->remap_fields(
+ # => 'order_type',
+ # => 'transaction_type',
+ #login => 'store_id',
+ #password => 'api_token',
+ #authorization =>
+ #customer_ip =>
+ #name =>
+ #first_name =>
+ #last_name =>
+ #company =>
+ #address =>
+ #city =>
+ #state =>
+ #zip =>
+ #country =>
+ phone =>
+ #fax =>
+ email =>
+ card_number => 'pan',
+ #expiration =>
+ # => 'expdate',
+
+ 'amount' => 'amount',
+ invoice_number => 'cust_id',
+ #customer_id => 'cust_id',
+ order_number => 'order_id', # must be unique number
+ authorization => 'txn_number' # reference to previous trans
+
+ #cvv2 =>
+ );
+
+ my $action = $self->{_content}{'action'};
+ if ( $self->{_content}{'action'} =~ /^\s*normal\s*authorization\s*$/i ) {
+ $action = 'purchase';
+ } elsif ( $self->{_content}{'action'} =~ /^\s*authorization\s*only\s*$/i ) {
+ $action = 'preauth';
+ } elsif ( $self->{_content}{'action'} =~ /^\s*post\s*authorization\s*$/i ) {
+ $action = 'completion';
+ } elsif ( $self->{_content}{'action'} =~ /^\s*void\s*$/i ) {
+ $action = 'purchasecorrection';
+ } elsif ( $self->{_content}{'action'} =~ /^\s*credit\s*$/i ) {
+ if ( $self->{_content}{'authorization'} ) {
+ $action = 'refund';
+ } else {
+ $action = 'ind_refund';
+ }
+ }
+
+ if ( $action =~ /^(purchase|preauth|ind_refund)$/ ) {
+
+ $self->required_fields(qw(
+ login password amount card_number expiration
+ ));
+
+ #cardexpiremonth & cardexpireyear
+ $self->{_content}{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
+ or croak "unparsable expiration ". $self->{_content}{expiration};
+ my( $month, $year ) = ( $1, $2 );
+ $month = '0'. $month if $month =~ /^\d$/;
+ $self->{_content}{expdate} = $year.$month;
+
+ $self->generate_order_id;
+
+ $self->{_content}{amount} = sprintf('%.2f', $self->{_content}{amount} );
+
+ } elsif ( $action =~ /^(completion|purchasecorrection|refund)$/ ) {
+
+ $self->required_fields(qw(
+ login password order_number authorization
+ ));
+
+ if ( $action eq 'completion' ) {
+ $self->{_content}{comp_amount} = delete $self->{_content}{amount};
+ } elsif ( $action eq 'purchasecorrection' ) {
+ delete $self->{_content}{amount};
+ #} elsif ( $action eq 'refund' ) {
+ }
+
+ }
+
+ # E-Commerce Indicator (see eSelectPlus docs)
+ $self->{_content}{'crypt_type'} ||= 7;
+
+ $action = "us_$action"
+ unless defined( $self->{_content}{'currency'} )
+ && $self->{_content}{'currency'} eq 'CAD';
+
+ #no, values aren't escaped for XML. their "mpgClasses.pl" example doesn't
+ #appear to do so, i dunno
+ tie my %fields, 'Tie::IxHash', $self->get_fields( $self->fields );
+ my $post_data =
+ '<?xml version="1.0"?>'.
+ '<request>'.
+ '<store_id>'. $self->{_content}{'login'}. '</store_id>'.
+ '<api_token>'. $self->{_content}{'password'}. '</api_token>'.
+ "<$action>".
+ join('', map "<$_>$fields{$_}</$_>", keys %fields ).
+ "</$action>".
+ '</request>';
+
+ warn "POSTING: ".$post_data if $DEBUG > 1;
+
+ my( $page, $response, @reply_headers) = $self->https_post( $post_data );
+
+ if ($DEBUG > 1) {
+ my %reply_headers = @reply_headers;
+ warn join('', map { " $_ => $reply_headers{$_}\n" } keys %reply_headers)
+ }
+
+ if ($response !~ /^200/) {
+ # Connection error
+ $response =~ s/[\r\n]+/ /g; # ensure single line
+ $self->is_success(0);
+ my $diag_message = $response || "connection error";
+ die $diag_message;
+ }
+
+ # avs_code - eSELECTplus_Perl_IG.pdf Appendix F
+ my %avsTable = ('A' => 'A',
+ 'B' => 'A',
+ 'C' => 'E',
+ 'D' => 'Y',
+ 'G' => '',
+ 'I' => '',
+ 'M' => 'Y',
+ 'N' => 'N',
+ 'P' => 'Z',
+ 'R' => 'R',
+ 'S' => '',
+ 'U' => 'E',
+ 'W' => 'Z',
+ 'X' => 'Y',
+ 'Y' => 'Y',
+ 'Z' => 'Z',
+ );
+ my $AvsResultCode = $self->GetXMLProp($page, 'AvsResultCode');
+ $self->avs_code( defined($AvsResultCode) && exists $avsTable{$AvsResultCode}
+ ? $avsTable{$AvsResultCode}
+ : $AvsResultCode
+ );
+
+ #md5 cvv2_response cavv_response ...?
+
+ $self->server_response($page);
+
+ my $result = $self->GetXMLProp($page, 'ResponseCode');
+
+ die "gateway error: ". $self->GetXMLProp( $page, 'Message' )
+ if $result =~ /^null$/i;
+
+ # Original order_id supplied to the gateway
+ $self->order_number($self->GetXMLProp($page, 'ReceiptId'));
+
+ # We (Whizman & DonorWare) do not have enough info about "ISO"
+ # response codes to make use of them.
+ # There may be good reasons why the ISO codes could be preferable,
+ # but we would need more information. For now, the ResponseCode.
+ # $self->result_code( $self->GetXMLProp( $page, 'ISO' ) );
+ $self->result_code( $result );
+
+ if ( $result =~ /^\d+$/ && $result < 50 ) {
+ $self->is_success(1);
+ $self->authorization($self->GetXMLProp($page, 'TransID'));
+ } elsif ( $result =~ /^\d+$/ ) {
+ $self->is_success(0);
+ my $tmp_msg = $self->GetXMLProp( $page, 'Message' );
+ $tmp_msg =~ s/\s{2,}//g;
+ $tmp_msg =~ s/[\*\=]//g;
+ $self->error_message( $tmp_msg );
+ } else {
+ die "unparsable response received from gateway (response $result)".
+ ( $DEBUG ? ": $page" : '' );
+ }
+
+}
+
+use vars qw(@oidset);
+@oidset = ( 'A'..'Z', '0'..'9' );
+sub generate_order_id {
+ my $self = shift;
+ #generate an order_id if order_number not passed
+ unless ( exists ($self->{_content}{order_id})
+ && defined($self->{_content}{order_id})
+ && length ($self->{_content}{order_id})
+ ) {
+ $self->{_content}{'order_id'} =
+ join('', map { $oidset[int(rand(scalar(@oidset)))] } (1..23) );
+ }
+}
+
+sub fields {
+ my $self = shift;
+
+ #order is important to this processor
+ qw(
+ order_id
+ cust_id
+ amount
+ comp_amount
+ txn_number
+ pan
+ expdate
+ crypt_type
+ cavv
+ );
+}
+
+sub GetXMLProp {
+ my( $self, $raw, $prop ) = @_;
+ local $^W=0;
+
+ my $data;
+ ($data) = $raw =~ m"<$prop>(.*?)</$prop>"gsi;
+ #$data =~ s/<.*?>/ /gs;
+ chomp $data;
+ return $data;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Business::OnlinePayment::eSelectPlus - Moneris eSelect Plus backend module for Business::OnlinePayment
+
+=head1 SYNOPSIS
+
+ use Business::OnlinePayment;
+
+ ####
+ # One step transaction, the simple case.
+ ####
+
+ my $tx = new Business::OnlinePayment("eSelectPlus");
+ $tx->content(
+ type => 'VISA',
+ login => 'eSelect Store ID,
+ password => 'eSelect API Token',
+ action => 'Normal Authorization',
+ description => 'Business::OnlinePayment test',
+ amount => '49.95',
+ currency => 'USD', #or CAD for compatibility with previous releases
+ name => 'Tofu Beast',
+ address => '123 Anystreet',
+ city => 'Anywhere',
+ state => 'UT',
+ zip => '84058',
+ phone => '420-867-5309',
+ email => 'tofu.beast@example.com',
+ card_number => '4005550000000019',
+ expiration => '08/06',
+ cvv2 => '1234', #optional
+ );
+ $tx->submit();
+
+ if($tx->is_success()) {
+ print "Card processed successfully: ".$tx->authorization."\n";
+ } else {
+ print "Card was rejected: ".$tx->error_message."\n";
+ }
+ print "AVS code: ". $tx->avs_code. "\n"; # Y - Address and ZIP match
+ # A - Address matches but not ZIP
+ # Z - ZIP matches but not address
+ # N - no match
+ # E - AVS error or unsupported
+ # R - Retry (timeout)
+ # (empty) - not verified
+
+=head1 SUPPORTED TRANSACTION TYPES
+
+=head2 CC, Visa, MasterCard, American Express, Discover
+
+Content required: type, login, password, action, amount, card_number, expiration.
+
+=head1 PREREQUISITES
+
+ URI::Escape
+ Tie::IxHash
+
+ Net::SSLeay _or_ ( Crypt::SSLeay and LWP )
+
+=head1 DESCRIPTION
+
+For detailed information see L<Business::OnlinePayment>.
+
+=head1 NOTES
+
+=head2 Note for Canadian merchants upgrading to 0.03
+
+As of version 0.03, this module now defaults to the US Moneris. Make sure to
+pass currency=>'CAD' for Canadian transactions.
+
+=head2 Note for upgrading to 0.05
+
+As of version 0.05, the bank authorization code is discarded (AuthCode),
+so that authorization() and order_number() can return the 2 fields needed
+for capture. See also
+cpansearch.perl.org/src/IVAN/Business-OnlinePayment-3.02/notes_for_module_writers_v3
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-eselectplus@420.am>
+Randall Whitman L<whizman.com|http://whizman.com>
+
+=head1 SEE ALSO
+
+perl(1). L<Business::OnlinePayment>.
+
+=cut
+