diff options
author | ivan <ivan> | 2010-09-19 21:07:43 +0000 |
---|---|---|
committer | ivan <ivan> | 2010-09-19 21:07:43 +0000 |
commit | 34d2ae2ce4804ddd556e88185bdf36ab5abe2bdc (patch) | |
tree | 9a8c44216f5a753173c68aef57cd22923261d3e6 | |
parent | d86dd0907225727a1d299138e4758e8a77f412c4 (diff) |
0.02 contrib from DonorWare / Whizman
-rw-r--r-- | Changes | 19 | ||||
-rw-r--r-- | MANIFEST | 1 | ||||
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | eSelectPlus.pm | 603 | ||||
-rw-r--r-- | t/bad_card.t | 16 | ||||
-rw-r--r-- | t/credit_card.t | 14 | ||||
-rw-r--r-- | t/crypt_bad_card.t | 12 | ||||
-rw-r--r-- | t/crypt_credit_card.t | 2 |
8 files changed, 373 insertions, 295 deletions
@@ -1,2 +1,21 @@ +Revision history for Perl extension Business::OnlinePayment::eSelectPlus + +0.02 unreleased (2008-04-02) + - Server name of production server + - Honor test_transaction mode in submit, by overriding user/pass, + as well as setting server name to QA server. + - Check HTTP errors. + - Set avs-code. + - Use ResponseCode rather than ISO for result-code. + We 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. + - Use AuthCode rather then Txn-number for authorization. + - Remove extra spaces and other odd characters in Message. + - by Randall Whitman <www.whizman.com> on contract to + DonorWare LLC dba donor.com <cpan @ donor . com>. + - fix tests, die on connction errors per B:OP spec + 0.01 Wed Mar 16 02:39:29 PST 2005 - original version; created by ivan 1.0 @@ -10,3 +10,4 @@ t/crypt_credit_card.t t/bop.t t/bad_card.t t/crypt_bad_card.t +META.yml Module meta-data (added by MakeMaker) @@ -1,4 +1,5 @@ Copyright (c) 2004 Ivan Kohler +Copyright (c) 2008 DonorWare LLC All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. diff --git a/eSelectPlus.pm b/eSelectPlus.pm index 065360e..6223c17 100644 --- a/eSelectPlus.pm +++ b/eSelectPlus.pm @@ -1,273 +1,330 @@ -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.01';
-$DEBUG = 0;
-
-sub set_defaults {
- my $self = shift;
-
- $self->server('esqa.moneris.com');
- $self->port('443');
- $self->path('/gateway2/servlet/MpgRequest');
-
- $self->build_subs(qw( order_number ));
- # avs_code order_type md5 cvv2_response cavv_response
-}
-
-sub submit {
- my($self) = @_;
-
- #$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 =>
- #customer_id =>
- order_number => 'order_id',
- authorization => 'txn_number'
-
- #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 = 'void';
- } 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 eq 'completion' || $action eq 'void' ) {
-
- $self->required_fields( qw( login password order_number authorization ) );
-
- } elsif ( $action eq 'refund' ) {
-
- $self->required_fields(
- qw( login password order_number authorization )
- );
-
- }
-
- $self->{_content}{'crypt_type'} ||= 7;
-
- #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 $post_data if $DEBUG > 1;
-
- my( $page, $response, @reply_headers) = $self->https_post( $post_data );
-
- #my %reply_headers = @reply_headers;
- #warn join('', map { " $_ => $reply_headers{$_}\n" } keys %reply_headers )
- # if $DEBUG;
-
- #XXX check $response and die if not 200?
-
- # avs_code
- # is_success
- # result_code
- # authorization
- #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;
-
- if ( $result =~ /^\d+$/ && $result < 50 ) {
- $self->is_success(1);
- $self->result_code( $self->GetXMLProp( $page, 'ISO' ) );
- $self->authorization( $self->GetXMLProp( $page, 'Txn_number' ) );
- $self->order_number( $self->GetXMLProp( $page, 'order_id') );
- } elsif ( $result =~ /^\d+$/ ) {
- $self->is_success(0);
- $self->error_message( $self->GetXMLProp( $page, 'Message' ) );
- } 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',
- 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";
- }
-
-=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 NOTE
-
-=head1 AUTHOR
-
-Ivan Kohler <ivan-eselectplus@420.am>
-
-=head1 SEE ALSO
-
-perl(1). L<Business::OnlinePayment>.
-
-=cut
-
+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.02'; +$DEBUG = 0; + +sub set_defaults { + my $self = shift; + + #$self->server('esqa.moneris.com'); # development + $self->server('www3.moneris.com'); # production + $self->port('443'); + $self->path('/gateway2/servlet/MpgRequest'); + + $self->build_subs(qw( order_number avs_code )); + # avs_code order_type md5 cvv2_response cavv_response +} + +sub submit { + my($self) = @_; + + if ($self->test_transaction) { + $self->server('esqa.moneris.com'); + $self->{_content}{'login'} = 'store2'; # store[123] + $self->{_content}{'password'} = 'yesguy'; + } + + # 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 = 'void'; + } 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 eq 'completion' || $action eq 'void' ) { + + $self->required_fields( qw( login password order_number authorization ) ); + + } elsif ( $action eq 'refund' ) { + + $self->required_fields( + qw( login password order_number authorization ) + ); + + } + + # E-Commerce Indicator (see eSelectPlus docs) + $self->{_content}{'crypt_type'} ||= 7; + + #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 ); + + #my %reply_headers = @reply_headers; + #warn join('', map { " $_ => $reply_headers{$_}\n" } keys %reply_headers ) + # if $DEBUG; + + 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; + + # New unique reference created by the gateway + $self->order_number($self->GetXMLProp($page, 'ReferenceNum')); + # 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, 'AuthCode')); + } 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', + 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 NOTE + +=head1 AUTHOR + +Ivan Kohler <ivan-eselectplus@420.am> +Randall Whitman <www.whizman.com> + +=head1 SEE ALSO + +perl(1). L<Business::OnlinePayment>. + +=cut + diff --git a/t/bad_card.t b/t/bad_card.t index afd324b..f1fc9d6 100644 --- a/t/bad_card.t +++ b/t/bad_card.t @@ -11,25 +11,25 @@ my $tx = new Business::OnlinePayment("eSelectPlus"); #$Business::OnlinePayment::HTTPS::DEBUG = 1; #$Business::OnlinePayment::HTTPS::DEBUG = 1; -#$Business::OnlinePayment::eSelectPlus::DEBUG = 1; -#$Business::OnlinePayment::eSelectPlus::DEBUG = 1; +$Business::OnlinePayment::eSelectPlus::DEBUG = 1; +$Business::OnlinePayment::eSelectPlus::DEBUG = 1; $tx->content( type => 'VISA', - login => 'store1', - password => 'yesguy', + login => 'moot', + password => 'moot', action => 'Normal Authorization', - amount => '32.32', + amount => '0.54', card_number => '4242424242424242', - expiration => '08/06', + expiration => '08/00', ); $tx->test_transaction(1); # test, dont really charge $tx->submit(); +# warn $tx->server_response."\n"; +# warn $tx->error_message. "\n"; if($tx->is_success()) { print "not ok 1\n"; } else { - #warn $tx->server_response."\n"; - #warn $tx->error_message. "\n"; print "ok 1\n"; } diff --git a/t/credit_card.t b/t/credit_card.t index 7cc40ac..6843155 100644 --- a/t/credit_card.t +++ b/t/credit_card.t @@ -11,17 +11,17 @@ my $tx = new Business::OnlinePayment("eSelectPlus"); #$Business::OnlinePayment::HTTPS::DEBUG = 1; #$Business::OnlinePayment::HTTPS::DEBUG = 1; -#$Business::OnlinePayment::eSelectPlus::DEBUG = 1; -#$Business::OnlinePayment::eSelectPlus::DEBUG = 1; +$Business::OnlinePayment::eSelectPlus::DEBUG = 1; +$Business::OnlinePayment::eSelectPlus::DEBUG = 1; $tx->content( type => 'VISA', - login => 'store1', - password => 'yesguy', + login => 'moot', + password => 'moot', action => 'Normal Authorization', - amount => '54.01', + amount => '0.80', card_number => '4242424242424242', - expiration => '01/06', + expiration => '01/12', ); $tx->test_transaction(1); # test, dont really charge $tx->submit(); @@ -29,7 +29,7 @@ $tx->submit(); if($tx->is_success()) { print "ok 1\n"; } else { - #warn $tx->server_response."\n"; + warn $tx->server_response."\n"; warn $tx->error_message. "\n"; print "not ok 1\n"; } diff --git a/t/crypt_bad_card.t b/t/crypt_bad_card.t index 7089078..1db7c4b 100644 --- a/t/crypt_bad_card.t +++ b/t/crypt_bad_card.t @@ -15,21 +15,21 @@ my $tx = new Business::OnlinePayment("eSelectPlus"); $tx->content( type => 'VISA', - login => 'store1', - password => 'yesguy', + login => 'moot', + password => 'moot', action => 'Normal Authorization', - amount => '32.32', + amount => '0.54', card_number => '4242424242424242', - expiration => '08/06', + expiration => '08/00', ); $tx->test_transaction(1); # test, dont really charge $tx->submit(); +# warn $tx->server_response."\n"; +# warn $tx->error_message. "\n"; if($tx->is_success()) { print "not ok 1\n"; } else { - #warn $tx->server_response."\n"; - #warn $tx->error_message. "\n"; print "ok 1\n"; } diff --git a/t/crypt_credit_card.t b/t/crypt_credit_card.t index ad458c3..a9080c0 100644 --- a/t/crypt_credit_card.t +++ b/t/crypt_credit_card.t @@ -17,7 +17,7 @@ $tx->content( login => 'store1', password => 'yesguy', action => 'Normal Authorization', - amount => '54.01', + amount => '0.80', card_number => '4242424242424242', expiration => '08/06', ); |