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