1 package Business::OnlinePayment::eSelectPlus;
6 use Business::OnlinePayment 3;
7 use Business::OnlinePayment::HTTPS 0.03;
8 use vars qw($VERSION $DEBUG @ISA);
10 @ISA = qw(Business::OnlinePayment::HTTPS);
18 #$self->server('esplusqa.moneris.com'); # development
19 $self->server('esplus.moneris.com'); # production
20 $self->path('/gateway_us/servlet/MpgRequest');
23 ##$self->server('esqa.moneris.com'); # development
24 #$self->server('www3.moneris.com'); # production
25 #$self->path('/gateway2/servlet/MpgRequest');
29 $self->build_subs(qw( order_number avs_code ));
30 # avs_code order_type md5 cvv2_response cavv_response
36 if ( defined( $self->{_content}{'currency'} )
37 && $self->{_content}{'currency'} eq 'CAD' ) {
38 $self->server('www3.moneris.com');
39 $self->path('/gateway2/servlet/MpgRequest');
40 } else { #sorry, default to USD
41 $self->server('esplus.moneris.com');
42 $self->path('/gateway_us/servlet/MpgRequest');
45 if ($self->test_transaction) {
46 if ( defined( $self->{_content}{'currency'} )
47 && $self->{_content}{'currency'} eq 'CAD' ) {
48 $self->server('esqa.moneris.com');
49 $self->{_content}{'login'} = 'store2'; # store[123]
50 $self->{_content}{'password'} = 'yesguy';
51 } else { #sorry, default to USD
52 $self->server('esplusqa.moneris.com');
53 $self->{_content}{'login'} = 'monusqa002'; # monusqa00[123]
54 $self->{_content}{'password'} = 'qatoken';
58 my %cust_id = ( 'invoice_number' => 'cust_id' );
60 my $invoice_number = $self->{_content}{invoice_number};
62 # BOP field => eSelectPlus field
66 # => 'transaction_type',
68 #password => 'api_token',
75 #address => 'avs_street_number'/'avs_street_name' handled below
80 phone => 'avs_custphone',
83 customer_ip => 'avs_custip',
86 #expiration => 'expdate', #handled below
90 customer_id => 'cust_id',
91 order_number => 'order_id', # must be unique number
92 authorization => 'txn_number', # reference to previous trans
95 my $action = $self->{_content}{'action'};
96 if ( $self->{_content}{'action'} =~ /^\s*normal\s*authorization\s*$/i ) {
98 } elsif ( $self->{_content}{'action'} =~ /^\s*authorization\s*only\s*$/i ) {
100 } elsif ( $self->{_content}{'action'} =~ /^\s*post\s*authorization\s*$/i ) {
101 $action = 'completion';
102 } elsif ( $self->{_content}{'action'} =~ /^\s*void\s*$/i ) {
103 $action = 'purchasecorrection';
104 } elsif ( $self->{_content}{'action'} =~ /^\s*credit\s*$/i ) {
105 if ( $self->{_content}{'authorization'} ) {
108 $action = 'ind_refund';
112 if ( $action =~ /^(purchase|preauth|ind_refund)$/ ) {
114 $self->required_fields(qw(
115 login password amount card_number expiration
118 #cardexpiremonth & cardexpireyear
119 $self->{_content}{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
120 or croak "unparsable expiration ". $self->{_content}{expiration};
121 my( $month, $year ) = ( $1, $2 );
122 $month = '0'. $month if $month =~ /^\d$/;
123 $self->{_content}{expdate} = $year.$month;
126 #0 = CVD value is deliberately bypassed or is not provided by the merchant
127 #1 = CVD value is present.
128 #2 = CVD value is on the card, but is illegible.
129 #9 = Cardholder states that the card has no CVD imprint.
130 $self->{_content}{cvd_indicator} = $self->{_content}{cvd_value} ? 1 : 0;
132 $self->generate_order_id;
134 $self->{_content}{order_id} .= '-'. ($invoice_number || 0);
136 $self->{_content}{amount} = sprintf('%.2f', $self->{_content}{amount} );
138 } elsif ( $action =~ /^(completion|purchasecorrection|refund)$/ ) {
140 $self->required_fields(qw(
141 login password order_number authorization
144 if ( $action eq 'completion' ) {
145 $self->{_content}{comp_amount} = delete $self->{_content}{amount};
146 } elsif ( $action eq 'purchasecorrection' ) {
147 delete $self->{_content}{amount};
148 #} elsif ( $action eq 'refund' ) {
153 if ( $self->{_content}{address} ) {
155 my $name = $self->{_content}{address};
156 if ( $name =~ s/^\s*(\d+)\w\s+// ) {
159 $name = substr( $name, 0, 19 - length($number) );
160 $self->{_content}{avs_street_number} = $number;
161 $self->{_content}{avs_street_name} = $name;
164 $self->{_content}{avs_zipcode} =~ s/\W//g
165 if defined $self->{_content}{avs_zipcode};
167 # E-Commerce Indicator (see eSelectPlus docs)
168 $self->{_content}{'crypt_type'} ||= 7;
170 $action = "us_$action"
171 unless defined( $self->{_content}{'currency'} )
172 && $self->{_content}{'currency'} eq 'CAD';
174 #no, values aren't escaped for XML. their "mpgClasses.pl" example doesn't
175 #appear to do so, i dunno
176 tie my %fields, 'Tie::IxHash', $self->get_fields( $self->fields );
178 '<?xml version="1.0"?>'.
180 '<store_id>'. $self->{_content}{'login'}. '</store_id>'.
181 '<api_token>'. $self->{_content}{'password'}. '</api_token>'.
183 join('', map "<$_>$fields{$_}</$_>", keys %fields );
185 if ( $action =~ /^(purchase|preauth|ind_refund)$/ ) {
186 tie my %avs_fields, 'Tie::IxHash', $self->get_fields( $self->avs_fields );
189 join('', map "<$_>$avs_fields{$_}</$_>", keys %avs_fields ).
191 if grep $_, values %avs_fields;
193 tie my %cvd_fields, 'Tie::IxHash', $self->get_fields( $self->cvd_fields );
196 join('', map "<$_>$cvd_fields{$_}</$_>", keys %cvd_fields ).
198 if grep $_, values %cvd_fields;
205 warn "POSTING: ".$post_data if $DEBUG > 1;
207 my( $page, $response, @reply_headers) = $self->https_post( $post_data );
210 my %reply_headers = @reply_headers;
211 warn join('', map { " $_ => $reply_headers{$_}\n" } keys %reply_headers)
214 if ($response !~ /^200/) {
216 $response =~ s/[\r\n]+/ /g; # ensure single line
217 $self->is_success(0);
218 my $diag_message = $response || "connection error";
222 # avs_code - eSELECTplus_Perl_IG.pdf Appendix F
223 my %avsTable = ('A' => 'A',
240 my $AvsResultCode = $self->GetXMLProp($page, 'AvsResultCode');
241 $self->avs_code( defined($AvsResultCode) && exists $avsTable{$AvsResultCode}
242 ? $avsTable{$AvsResultCode}
246 #md5 cvv2_response cavv_response ...?
248 $self->server_response($page);
250 my $result = $self->GetXMLProp($page, 'ResponseCode');
252 die "gateway error: ". $self->GetXMLProp( $page, 'Message' )
253 if $result =~ /^null$/i;
255 # Original order_id supplied to the gateway
256 $self->order_number($self->GetXMLProp($page, 'ReceiptId'));
258 # We (Whizman & DonorWare) do not have enough info about "ISO"
259 # response codes to make use of them.
260 # There may be good reasons why the ISO codes could be preferable,
261 # but we would need more information. For now, the ResponseCode.
262 # $self->result_code( $self->GetXMLProp( $page, 'ISO' ) );
263 $self->result_code( $result );
265 if ( $result =~ /^\d+$/ && $result < 50 ) {
266 $self->is_success(1);
267 $self->authorization($self->GetXMLProp($page, 'TransID'));
268 } elsif ( $result =~ /^\d+$/ ) {
269 $self->is_success(0);
270 my $tmp_msg = $self->GetXMLProp( $page, 'Message' );
271 $tmp_msg =~ s/\s{2,}//g;
272 $tmp_msg =~ s/[\*\=]//g;
273 $self->error_message( $tmp_msg );
275 die "unparsable response received from gateway (response $result)".
276 ( $DEBUG ? ": $page" : '' );
281 use vars qw(@oidset);
282 @oidset = ( 'A'..'Z', '0'..'9' );
283 sub generate_order_id {
285 #generate an order_id if order_number not passed
286 unless ( exists ($self->{_content}{order_id})
287 && defined($self->{_content}{order_id})
288 && length ($self->{_content}{order_id})
290 $self->{_content}{'order_id'} =
291 join('', map { $oidset[int(rand(scalar(@oidset)))] } (1..23) );
298 #order is important to this processor
315 #order is important to this processor
334 #order is important to this processor
342 my( $self, $raw, $prop ) = @_;
346 ($data) = $raw =~ m"<$prop>(.*?)</$prop>"gsi;
347 #$data =~ s/<.*?>/ /gs;
358 Business::OnlinePayment::eSelectPlus - Moneris eSelect Plus backend module for Business::OnlinePayment
362 use Business::OnlinePayment;
365 # One step transaction, the simple case.
368 my $tx = new Business::OnlinePayment("eSelectPlus");
371 login => 'eSelect Store ID,
372 password => 'eSelect API Token',
373 action => 'Normal Authorization',
374 description => 'Business::OnlinePayment test',
376 currency => 'USD', #or CAD for compatibility with previous releases
377 name => 'Tofu Beast',
378 address => '123 Anystreet',
382 phone => '420-867-5309',
383 email => 'tofu.beast@example.com',
384 card_number => '4005550000000019',
385 expiration => '08/06',
386 cvv2 => '1234', #optional
390 if($tx->is_success()) {
391 print "Card processed successfully: ".$tx->authorization."\n";
393 print "Card was rejected: ".$tx->error_message."\n";
395 print "AVS code: ". $tx->avs_code. "\n"; # Y - Address and ZIP match
396 # A - Address matches but not ZIP
397 # Z - ZIP matches but not address
399 # E - AVS error or unsupported
400 # R - Retry (timeout)
401 # (empty) - not verified
403 =head1 SUPPORTED TRANSACTION TYPES
405 =head2 CC, Visa, MasterCard, American Express, Discover
407 Content required: type, login, password, action, amount, card_number, expiration.
414 Net::SSLeay _or_ ( Crypt::SSLeay and LWP )
418 For detailed information see L<Business::OnlinePayment>.
422 =head2 Note for Canadian merchants upgrading to 0.03
424 As of version 0.03, this module now defaults to the US Moneris. Make sure to
425 pass currency=>'CAD' for Canadian transactions.
427 =head2 Note for upgrading to 0.05
429 As of version 0.05, the bank authorization code is discarded (AuthCode),
430 so that authorization() and order_number() can return the 2 fields needed
431 for capture. See also
432 cpansearch.perl.org/src/IVAN/Business-OnlinePayment-3.02/notes_for_module_writers_v3
436 Ivan Kohler <ivan-eselectplus@420.am>
437 Randall Whitman L<whizman.com|http://whizman.com>
441 perl(1). L<Business::OnlinePayment>.