1 package Business::OnlinePayment::TransFirsteLink;
4 use vars qw($VERSION $DEBUG %error_messages);
5 use Carp qw(carp croak);
7 use base qw(Business::OnlinePayment::HTTPS);
10 $VERSION = eval $VERSION;
15 '001' => 'Call Issuer',
16 '002' => 'Referral special',
17 '003' => 'Invalid merchant number',
20 '006' => 'General error',
21 '007' => 'Pick up special',
22 '008' => 'Honor with ID',
23 '009' => 'General Decline',
24 '010' => 'Network Error',
26 '012' => 'Invalid transaction type',
27 '013' => 'Invalid amount field',
28 '014' => 'Invalid card number',
29 '015' => 'Invalid issuer',
30 '016' => 'General Decline',
31 '017' => 'General Decline',
32 '018' => 'General Decline',
34 '020' => 'General Decline',
35 '021' => 'No action taken',
36 '022' => 'General Decline',
37 '023' => 'General Decline',
38 '024' => 'General Decline',
39 '025' => 'Acct num miss',
40 '026' => 'General Decline',
41 '027' => 'General Decline',
42 '028' => 'File unavailable',
43 '029' => 'General Decline',
44 '030' => 'Format Error - Decline',
45 '031' => 'General Decline',
46 '032' => 'General Decline',
47 '033' => 'General Decline',
48 '034' => 'General Decline',
49 '036' => 'General Decline',
50 '037' => 'General Decline',
51 '038' => 'General Decline',
52 '039' => 'No card acct',
53 '040' => 'General Decline',
55 '042' => 'General Decline',
56 '043' => 'Stolen card',
57 '044' => 'General Decline',
58 '045' => 'General Decline',
59 '046' => 'General Decline',
60 '048' => 'General Decline',
61 '049' => 'General Decline',
62 '050' => 'General Decline',
63 '051' => 'Over limit',
64 '052' => 'No checking acct',
65 '053' => 'No saving acct',
66 '054' => 'Expired card',
67 '055' => 'Invalid pin',
68 '056' => 'General Decline',
69 '057' => 'TXN not allowed',
70 '058' => 'TXN not allowed term',
71 '059' => 'TXN not allowed - Merchant',
72 '060' => 'General Decline',
73 '061' => 'Over cash limit',
74 '062' => 'Restricted card',
75 '063' => 'Security violate',
76 '064' => 'General Decline',
77 '065' => 'Excessive authorizations',
78 '066' => 'General Decline',
79 '067' => 'General Decline',
80 '069' => 'General Decline',
81 '070' => 'General Decline',
82 '071' => 'General Decline',
83 '072' => 'General Decline',
84 '073' => 'General Decline',
85 '074' => 'General Decline',
86 '075' => 'Excessive pin entry tries',
87 '076' => 'Unable locate previous msg (ref# not found)',
88 '077' => 'Mismatched info',
89 '078' => 'No account',
90 '079' => 'Already reversed',
91 '080' => 'Invalid date',
92 '081' => 'Crypto error',
93 '082' => 'CVV failure',
94 '083' => 'Unable verify pin',
95 '084' => 'Duplicate trans',
96 '085' => 'No reason 2 decline',
97 '086' => 'Cannot verify pin',
98 '088' => 'General Decline',
99 '089' => 'General Decline',
100 '090' => 'General Decline',
101 '091' => 'Issuer unavailable',
102 '092' => 'Destination route not found',
103 '093' => 'Law violation',
104 '094' => 'Duplicate trans',
105 '096' => 'System malfunction',
106 '098' => 'General Decline',
107 '099' => 'General Decline',
108 '0B1' => 'Surcharge amount not permitted on Visa cards or EBT food stamps',
109 '0B2' => 'Surcharge amount not supported by debit network issuer',
110 '0EB' => 'Check digit error',
111 '0EC' => 'Cid format error',
112 '0N0' => 'FORCE STIP',
113 '0N3' => 'Service not available',
114 '0N4' => 'Exceeds limit issuer',
115 '0N5' => 'Ineligible for resubmission',
116 '0N7' => 'CVV2 failure',
117 '0N8' => 'Trans amount exceeds preauth amt',
118 '0P0' => 'Approved pvid miss',
119 '0P1' => 'Declined pvid miss',
120 '0P2' => 'Invalid bill info',
121 '0Q1' => 'Card auth failed',
122 '0R0' => 'Multipay stopped',
123 '0R1' => 'Multipay stopped merch',
124 '0R3' => 'Revocation of all authorizations order',
125 '0XA' => 'Forward to issue1',
126 '0XD' => 'Forward to issue2',
127 '0VD' => 'General Decline',
128 '0T0' => 'First Time Check',
129 '0T1' => 'Check is OK, but cannot be converted',
130 '0T2' => 'Invalid routing transit number or check belongs to a category that is not eligible for conversion',
131 '0T3' => 'Amount greater than established service limit',
132 '0T4' => 'Unpaid items, failed negative check',
133 '0T5' => 'Duplicate check number',
134 '0T6' => 'MICR Error',
135 '0T7' => 'Too many checks (over merchant or bank limit)',
136 '203' => 'Invalid merchant number',
137 '212' => 'Invalid transaction type',
138 '213' => 'Invalid amount field',
139 '214' => 'Invalid card number',
140 '254' => 'Expired card',
141 '257' => 'Txn not allowed',
142 '276' => 'Unable to locate prvious msg (ref # not found)',
143 '278' => 'No account',
144 '284' => 'General Decline',
145 '296' => 'System malfunction',
146 '2Q1' => 'Card authorization failed',
147 '300' => 'Invalid request format',
148 '301' => 'Missing file header',
149 '303' => 'Invalid sender ID',
150 '306' => 'Duplicate file number',
151 '307' => 'General Decline',
152 '309' => 'Comm link down',
153 '310' => 'Missing batch header',
154 '317' => 'Invalid MOTO ID',
155 '338' => 'General Decline',
156 '380' => 'Missing batch trailer',
157 '382' => 'Record count does not match number records in batch',
158 '383' => 'Net amount does not match file amount',
159 '384' => 'Duplicate transaction',
160 '385' => 'Invalid request format',
161 '394' => 'Record count does not match records in file',
162 '395' => 'Net amount does not match file amount',
163 '396' => 'Declined post - reauthorization attempt',
164 '318' => 'Invalid account data source',
165 '319' => 'Invalid POS entry mode',
166 '320' => 'Auth date invalid (transaction date)',
167 '321' => 'Invalid auth source code',
168 '322' => 'Invalid ACI code',
169 'REJ' => 'Rejected transaction that has been re-keyed',
170 '3AC' => 'Invalid authorization code (must be uppercase, no special chars)',
171 '3TI' => 'Invalid tax indicator',
172 '3VD' => 'Voided transaction',
173 '3AD' => 'AVS response code declined',
174 '3AR' => 'AVS required/address information not provided',
175 '3BD' => 'AVS and CVV2 response Code Declined',
176 '3BR' => 'AVS and CVV2 required/information not provided',
177 '3CD' => 'CVV2 response code declined',
178 '3CR' => 'CVV2 required/inrormation not provided',
179 '3L5' => 'No data sent',
180 '3L6' => 'Order number missing',
181 '3M1' => 'Auth date blank',
182 '3M2' => 'Auth amount blank',
183 '3MT' => 'Managed transaction',
184 '3RV' => 'Reversed transaction',
186 '600' => 'General Decline',
197 'XXX' => 'General Decline',
204 my $level = shift || 0;
206 $self->{"__DEBUG"} = $level;
211 $Business::OnlinePayment::HTTPS::DEBUG = $level;
213 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
220 # standard B::OP methods/data
221 $self->server("epaysecure1.transfirst.com");
225 $self->build_subs(qw(
227 order_number avs_code cvv2_response
228 response_page response_code response_headers
232 # module specific data
233 if ( $opts{debug} ) {
234 $self->debug( $opts{debug} );
238 if ( $opts{merchantcustservnum} ) {
239 $self->merchantcustservnum( $opts{merchantcustservnum} );
240 delete $opts{merchantcustservnum};
248 my %content = $self->content();
252 'normal authorization' => 32, # Authorization/Settle transaction
253 'credit' => 20, # Credit (refund)
254 'authorization only' => 30, # Authorization only
255 'post authorization' => 40, # Settlement
259 $content{'TransactionCode'} = $actions{ lc( $content{'action'} ) }
260 || $content{'action'};
265 'mastercard' => 'CC',
266 'american express' => 'CC',
273 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
275 $self->transaction_type( $content{'type'} );
277 # stuff it back into %content
278 $self->content(%content);
282 my ( $self, %map ) = @_;
283 my %content = $self->content();
284 foreach ( keys %map ) {
288 : $content{ $map{$_} };
290 $self->content(%content);
295 my $expiration = shift;
297 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
298 my ( $month, $year ) = ( $1, $2 );
299 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
301 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
304 sub required_fields {
305 my($self,@fields) = @_;
308 my %content = $self->content();
311 if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
315 Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
323 $self->_map_fields();
325 my %content = $self->content;
328 $required{CC_20} = [ qw( ePayAccountNum Password OrderNumber
329 TransactionAmount AccountNumber ExpirationDate
330 MerchantCustServNum ) ];
331 $required{CC_30} = [ qw( ePayAccountNum Password TransactionCode OrderNum
332 TransactionAmount CardAccountNum ExpirationDate
333 CardHolderZip MerchantCustServNum ) ];
334 $required{CC_32} = $required{CC_30};
335 $required{CC_61} = [ qw( ePayAccountNum Password TransactionCode
337 $required{ECHECK_20} = [ qw( ePayAccountNum Password AccountNumber
338 RoutingNumber DollarAmount OrderNumber
340 $required{ECHECK_32} = [ qw( ePayAccountNum Password OrderNumber
341 AccountNumber RoutingNumber CheckNumber
342 DollarAmount CustomerName CustomerAddress
343 CustomerCity CustomerState CustomerZip
347 $optional{CC_20} = [ qw( CardHolderName CardHolderAddress CardHolderCity
348 CardHolderState CardHolderZip CardHolderEmail
349 CardHolderPhone CustomerNum Misc1 Misc2 CVV2
350 Ecommerce DuplicateChecking AuthorizedAmount
351 AutorizedDate AuthorizedTime FulfillmentDate
352 CardHolderCountry POSEntryMode MerchantStoreNum
353 CardHolderIDSource SICCATCode MerchantZipCode
354 AccountDataSource AuthResponseCode AuthSourceCode
355 AuthACICode AuthValidationCode AuthAVSResponse
356 MerchantCustServNum CrossReferenceNum
357 PaymentDescription ReferenceNum ) ];
358 $optional{CC_32} = $optional{CC_30};
359 $optional{CC_30} = [ qw( CardHolderName CardHolderAddress CardHolderCity
360 CardHolderState CardHolderEmail CardHolderPhone
361 CustomerNum Misc1 Misc2 CVV2 Ecommerce
362 DuplicateChecking MessageSequenceNum
363 CardHolderCountry POSEntryMode MerchantStoreNum
364 CardHolderIDSource SICCATCode MerchantZipCode
365 PaymenntDiscriptor CAVVCode ECIValue XID
366 TaxIndicator TotalTaxAmount ) ];
367 $optional{CC_32} = $optional{CC_30};
368 $optional{CC_61} = [ qw( MessageSequenceNum CrossReferenceNum OrderNum
370 $optional{ECHECK_20} = ();
371 $optional{ECHECK_32} = [ qw( CustomerNumber Misc1 Misc2 CustomerEmail
372 DriversLicense DriversLicenseState
373 BirthDate SocSecNum ) ];
375 my $type_action = $self->transaction_type(). '_'. $content{TransactionCode};
376 unless ( exists($required{$type_action}) ) {
377 croak( "TransFirst eLink can't (yet?) handle transaction type: ".
378 "$content{action} on " . $self->transaction_type() );
381 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
382 my $zip = $content{'zip'};
383 $zip =~ s/[^[:alnum:]]//g;
385 my $merchantcustservnum = $self->merchantcustservnum;
386 my $account_number = $self->transaction_type() eq 'CC'
387 ? $content{card_number}
388 : $content{account_number} ;
390 my $invoice_number = $content{invoice_number} || "PAYMENT"; # make one up
391 my $check_number = $content{check_number} || "100"; # make one up
393 $self->_revmap_fields(
395 ePayAccountNum => 'login',
396 Password => 'password',
397 OrderNum => \$invoice_number,
398 OrderNumber => \$invoice_number,
399 MerchantCustServNum => \$merchantcustservnum,
401 TransactionAmount => 'amount',
402 DollarAmount => 'amount',
403 CardAccountNum => 'card_number',
404 ExpirationDate => \$expdate_mmyy, # MMYY from 'expiration'
407 RoutingNumber => 'routing_code',
408 AccountNumber => \$account_number,
409 CheckNumber => \$check_number,
411 CardHolderName => 'name',
412 CustomerName => 'account_name',
413 CardHolderAddress => 'address',
414 CustomerAddress => 'address',
415 CardHolderCity => 'city',
416 CustomerCity => 'city',
417 CardHolderState => 'state',
418 CustomerState => 'state',
419 CardHolderZip => \$zip, # 'zip' with non-alnums removed
420 CustomerZip => \$zip, # 'zip' with non-alnums removed
421 CardHolderEmail => 'email',
422 CustomerEmail => 'email',
423 CardHolderPhone => 'phone',
424 CustomerPhone => 'phone',
425 CustomerNum => 'customer_id',
426 CustomerNumber => 'customer_id',
427 CardHolderCountry => 'country',
429 PaymentDescriptor => 'description',
431 ReferenceNum => 'order_number'
434 my %params = $self->get_fields( @{$required{$type_action}},
435 @{$optional{$type_action}},
438 $params{TestTransaction}='Y' if $self->test_transaction;
440 $params{InstallmentNum} = $params{InstallmentOf} = '01'
441 unless ($params{InstallmentNum} && $params{InstallmentOf});
443 if ( $type_action eq "CC_30" || $type_action eq "CC_32" ) {
444 $self->path($self->path."elink/authpd.asp");
445 } elsif ( $type_action eq "CC_61" ) {
446 $self->path($self->path."eLink/voidpd.asp");
447 } elsif ( $type_action eq "CC_20" ) {
448 $self->path($self->path."eLink/creditpd.asp");
449 } elsif ( $type_action eq "ECHECK_32" ) {
450 $self->path($self->path."eLink/checkPD.asp");
451 } elsif ( $type_action eq "ECHECK_20" ) {
452 $self->path($self->path."eLink/checkcreditPD.asp");
454 croak "don't know path for unexpected type and action $type_action";
457 warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $DEBUG > 1;
458 my ( $page, $resp, %resp_headers ) =
459 $self->https_post( %params );
461 $self->response_code( $resp );
462 $self->response_page( $page );
463 $self->response_headers( \%resp_headers );
465 warn "$page\n" if $DEBUG > 1;
466 # $page should contain | separated values
468 $self->required_fields(@{$required{$type_action}});
472 if ( $type_action eq "CC_30" || $type_action eq "CC_32" ) {
473 my ($format,$account,$tcode,$seq,$moi,$cardnum,$exp,$authamt,$authdate,
474 $authtime,$tstat,$custnum,$ordernum,$refnum,$rcode,$authsrc,$achar,
475 $transid,$vcode,$sic,$country,$avscode,$storenum,$cvv2resp,$cavvcode,
476 $crossrefnum,$etstat,$cavvresponse,$xid,$eci,@junk)
479 # AVS and CVS values may be set on success or failure
480 $self->avs_code($avscode);
481 $self->cvv2_response( $cvv2resp );
482 $self->result_code( $status = $etstat );
483 $self->order_number( $refnum );
484 $self->authorization( $rcode );
485 $self->junk( \@junk );
486 $self->error_message($error_messages{$status});
488 } elsif ( $type_action eq "CC_61" ) {
489 my ($format,$account,$tcode,$voidamt,$seq,$voiddate,$voidtime,$tstat,
490 $refnum,$filler1,$filler2,$filler3,$etstat,@junk)
492 $self->result_code( $status = $etstat );
493 $self->order_number( $refnum );
494 $self->junk( \@junk );
495 $self->error_message($error_messages{$status});
497 } elsif ( $type_action eq "CC_20" ) {
498 my ($format,$account,$tcode,$seq,$moi,$authamt,$authdate,$authtime,
499 $tstat,$refnum,$crossrefnum,$custnum,$ordernum,$etstat,@junk)
501 $self->result_code( $status = $etstat );
502 $self->order_number( $refnum );
503 $self->junk( \@junk );
504 $self->error_message($error_messages{$status});
506 } elsif ( $type_action eq "ECHECK_32" || $type_action eq "ECHECK_20" ) {
507 my ($responsecode,$response,$transactionid,$note,$errors,@junk)
509 # AVS and CVS values may be set on success or failure
510 $self->result_code( $status = $responsecode );
511 $self->order_number( $transactionid );
512 $self->error_message("$response $errors");
513 $self->junk( \@junk );
516 croak "can't interpret response for unexpected type and action $type_action";
519 if ( $resp eq "200" && ($status eq "000" || $status eq "011" || $status eq "085" || $status eq "0P0" || $status eq "P00") ) {
520 $self->is_success(1);
523 $self->is_success(0);
533 Business::OnlinePayment::TransFirsteLink - Transfirst eLink backend for Business::OnlinePayment
537 use Business::OnlinePayment;
539 my $tx = new Business::OnlinePayment(
541 'merchantcustservnum' => "8005551212",
544 # See the module documentation for details of content()
547 action => 'Normal Authorization',
548 description => 'Business::OnlinePayment::TransFirsteLink test',
550 invoice_number => '100100',
551 customer_id => 'jef',
552 name => 'Jeff Finucane',
553 address => '123 Anystreet',
557 email => 'transfirst@weasellips.com',
558 card_number => '4111111111111111',
559 expiration => '12/09',
561 order_number => 'string',
566 if ( $tx->is_success() ) {
568 "Card processed successfully: ", $tx->authorization, "\n",
569 "order number: ", $tx->order_number, "\n",
570 "CVV2 response: ", $tx->cvv2_response, "\n",
571 "AVS code: ", $tx->avs_code, "\n",
576 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
579 "Card was rejected: ", $tx->error_message, $info, "\n",
580 "order number: ", $tx->order_number, "\n",
586 This module is a back end driver that implements the interface
587 specified by L<Business::OnlinePayment> to support payment handling
588 via TransFirst's eLink Internet payment solution.
590 See L<Business::OnlinePayment> for details on the interface this
593 =head1 Standard methods
599 This method sets the 'server' attribute to 'epaysecure1.transfirst.com' and
600 the port attribute to '443'. This method also sets up the
601 L</Module specific methods> described below.
607 =head1 Unofficial methods
609 This module provides the following methods which are not officially part of the
610 standard Business::OnlinePayment interface (as of 3.00_06) but are nevertheless
611 supported by multiple gateways modules and expected to be standardized soon:
615 =item L<order_number()|/order_number()>
617 =item L<avs_code()|/avs_code()>
619 =item L<cvv2_response()|/cvv2_response()>
623 =head1 Module specific methods
625 This module provides the following methods which are not currently
626 part of the standard Business::OnlinePayment interface:
630 =item L<expdate_mmyy()|/expdate_mmyy()>
632 =item L<debug()|/debug()>
638 The following default settings exist:
644 epaysecure1.transfirst.com
652 =head1 Handling of content(%content)
654 The following rules apply to content(%content) data:
658 If 'type' matches one of the following keys it is replaced by the
659 right hand side value:
662 'mastercard' => 'CC',
663 'american express' => 'CC',
667 The value of 'type' is used to set transaction_type(). Currently this
668 module only supports the above values.
670 =head1 Setting TransFirst eLink parameters from content(%content)
672 The following rules are applied to map data to TransFirst eLink parameters
673 from content(%content):
675 # eLink param => $content{<key>}
676 ePayAccountNum => 'login',
677 Password => 'password',
678 OrderNum => 'invoice_number',
679 OrderNumber => 'invoice_number',
681 TransactionAmount => 'amount',
682 DollarAmount => 'amount',
683 CardAccountNum => 'card_number',
684 ExpirationDate => \( $month.$year ), # MM/YY from 'expiration'
687 RoutingNumber => 'routing_code',
688 AccountNumber => \( $type eq 'CC' ? $card_number : $account_number ),
689 CheckNumber => 'check_number',
691 CardHolderName => 'name',
692 CardHolderAddress => 'address',
693 CardHolderCity => 'city',
694 CardHolderState => 'state',
695 CardHolderZip => \$zip, # 'zip' with non-alphanumerics removed
696 CardHolderEmail => 'email',
697 CardHolderPhone => 'phone',
698 CardHolderCountry => 'country',
700 CustomerName => 'name',
701 CustomerAddress => 'address',
702 CustomerCity => 'city',
703 CustomerState => 'state',
704 CustomerZip => \$zip, # 'zip' with non-alphanumerics removed
705 CustomerEmail => 'email',
706 CustomerPhone => 'phone',
708 PaymentDescriptor => 'description',
710 =head1 Mapping TransFirst eLink transaction responses to object methods
712 The following methods provides access to the transaction response data
713 resulting from a Payflow Pro request (after submit()) is called:
715 =head2 order_number()
717 This order_number() method returns the ReferenceNum field for card transactions
718 and TransactionId for check transactions to uniquely identify the transaction.
722 The result_code() method returns the Extended Transaction Status field for
723 card transactions and the Result Code field for check transactions. It is the
724 numeric return code indicating the outcome of the attempted
727 =head2 error_message()
729 The error_message() method returns the Errors field for check
730 transactions. This provides more details about the transaction result.
732 =head2 authorization()
734 The authorization() method returns the Authorization Response Code field,
735 which is the approval code obtained from the card processing network.
739 The avs_code() method returns the AVS Response Code field from the
742 =head2 cvv2_response()
744 The cvv2_response() method returns the CVV2 Response Code field, which is a
745 response message returned with the transaction result.
747 =head2 expdate_mmyy()
749 The expdate_mmyy() method takes a single scalar argument (typically
750 the value in $content{expiration}) and attempts to parse and format
751 and put the date in MMYY format as required by PayflowPro
752 specification. If unable to parse the expiration date simply leave it
753 as is and let the PayflowPro system attempt to handle it as-is.
757 Enable or disble debugging. The value specified here will also set
758 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
759 troubleshooting problems.
763 This module implements an interface to the TransFirst eLink API version
768 Jeff Finucane <transfirst@weasellips.com>
770 Based on Business::OnlinePayment::PayflowPro written by Ivan Kohler
775 perl(1), L<Business::OnlinePayment>, L<Carp>, and the TransFirst
776 e Payment Services Card Not Present eLink User Guide.