File Coverage

blib/lib/Business/OnlinePayment/TransFirsteLink.pm
Criterion Covered Total %
statement 30 169 17.7
branch 4 60 6.6
condition 1 39 2.5
subroutine 7 12 58.3
pod 5 5 100.0
total 47 285 16.4


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::TransFirsteLink;
2              
3 2     2   30214 use strict;
  2         4  
  2         70  
4 2     2   8 use vars qw($VERSION $DEBUG %error_messages);
  2         4  
  2         125  
5 2     2   10 use Carp qw(carp croak);
  2         7  
  2         121  
6 2     2   1588 use Tie::IxHash;
  2         9448  
  2         55  
7              
8 2     2   12 use base qw(Business::OnlinePayment::HTTPS);
  2         3  
  2         2266  
9              
10             $VERSION = '0.05';
11             $VERSION = eval $VERSION;
12             $DEBUG = 0;
13              
14             %error_messages = (
15             '000' => 'Approval',
16             '001' => 'Call Issuer',
17             '002' => 'Referral special',
18             '003' => 'Invalid merchant number',
19             '004' => 'Pick up',
20             '005' => 'Declined',
21             '006' => 'General error',
22             '007' => 'Pick up special',
23             '008' => 'Honor with ID',
24             '009' => 'General Decline',
25             '010' => 'Network Error',
26             '011' => 'Approval',
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',
34             '019' => 'Re-enter',
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',
55             '041' => 'Lost card',
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',
186             '3TO' => 'Timeout',
187             '600' => 'General Decline',
188             '990' => 'Voided',
189             '991' => 'Voided',
190             '992' => 'Voided',
191             '993' => 'Voided',
192             '994' => 'Voided',
193             '995' => 'Voided',
194             '996' => 'Voided',
195             '997' => 'Voided',
196             '998' => 'Voided',
197             '999' => 'Voided',
198             'XXX' => 'General Decline',
199             );
200              
201             sub debug {
202 0     0 1 0 my $self = shift;
203              
204 0 0       0 if (@_) {
205 0   0     0 my $level = shift || 0;
206 0 0       0 if ( ref($self) ) {
207 0         0 $self->{"__DEBUG"} = $level;
208             }
209             else {
210 0         0 $DEBUG = $level;
211             }
212 0         0 $Business::OnlinePayment::HTTPS::DEBUG = $level;
213             }
214 0 0 0     0 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
215             }
216              
217             sub set_defaults {
218 2     2 1 3186 my $self = shift;
219 2         3 my %opts = @_;
220              
221             # standard B::OP methods/data
222 2         52 $self->server("epaysecure1.transfirst.com");
223 2         62 $self->port("443");
224 2         54 $self->path("/");
225              
226 2         19 $self->build_subs(qw(
227             merchantcustservnum
228             order_number avs_code cvv2_response
229             response_page response_code response_headers
230             junk
231             ));
232              
233             # module specific data
234 2 50       268 if ( $opts{debug} ) {
235 0         0 $self->debug( $opts{debug} );
236 0         0 delete $opts{debug};
237             }
238              
239 2 50       8 if ( $opts{merchantcustservnum} ) {
240 0         0 $self->merchantcustservnum( $opts{merchantcustservnum} );
241 0         0 delete $opts{merchantcustservnum};
242             }
243              
244             }
245              
246             sub _map_fields {
247 0     0   0 my ($self) = @_;
248              
249 0         0 my %content = $self->content();
250              
251             #ACTION MAP
252 0         0 my %actions = (
253             'normal authorization' => 32, # Authorization/Settle transaction
254             'credit' => 20, # Credit (refund)
255             'authorization only' => 30, # Authorization only
256             'post authorization' => 40, # Settlement
257             'void' => 61, # Void
258             );
259              
260 0   0     0 $content{'TransactionCode'} = $actions{ lc( $content{'action'} ) }
261             || $content{'action'};
262              
263             # TYPE MAP
264 0         0 my %types = (
265             'visa' => 'CC',
266             'mastercard' => 'CC',
267             'american express' => 'CC',
268             'discover' => 'CC',
269             'cc' => 'CC',
270              
271             'check' => 'ECHECK',
272             );
273              
274 0   0     0 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
275              
276 0         0 $self->transaction_type( $content{'type'} );
277              
278             # stuff it back into %content
279 0         0 $self->content(%content);
280             }
281              
282             sub _revmap_fields {
283 0     0   0 my ( $self, %map ) = @_;
284 0         0 my %content = $self->content();
285 0         0 foreach ( keys %map ) {
286 0         0 $content{$_} =
287             ref( $map{$_} )
288 0 0       0 ? ${ $map{$_} }
289             : $content{ $map{$_} };
290             }
291 0         0 $self->content(%content);
292             }
293              
294             sub expdate_mmyy {
295 4     4 1 1342 my $self = shift;
296 4         5 my $expiration = shift;
297 4         4 my $expdate_mmyy;
298 4 50 33     35 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
299 4         9 my ( $month, $year ) = ( $1, $2 );
300 4         14 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
301             }
302 4 50       22 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
303             }
304              
305             sub required_fields {
306 0     0 1   my($self,@fields) = @_;
307              
308 0           my @missing;
309 0           my %content = $self->content();
310 0           foreach(@fields) {
311             next
312 0 0 0       if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
      0        
313 0           push(@missing, $_);
314             }
315              
316 0 0         Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
317             if(@missing);
318              
319             }
320              
321             sub submit {
322 0     0 1   my ($self) = @_;
323              
324 0           $self->_map_fields();
325              
326 0           my %content = $self->content;
327              
328 0           my %required;
329 0           $required{CC_20} = [ qw( ePayAccountNum Password OrderNum
330             TransactionAmount CardAccountNum ExpirationDate
331             MerchantCustServNum ) ];
332 0           $required{CC_30} = [ qw( ePayAccountNum Password TransactionCode OrderNum
333             TransactionAmount CardAccountNum ExpirationDate
334             CardHolderZip MerchantCustServNum ) ];
335 0           $required{CC_32} = $required{CC_30};
336 0           $required{CC_61} = [ qw( ePayAccountNum Password TransactionCode
337             ReferenceNum ) ];
338 0           $required{ECHECK_20} = [ qw( ePayAccountNum Password AccountNumber
339             RoutingNumber DollarAmount OrderNumber
340             CustomerNumber CustomerName ) ];
341 0           $required{ECHECK_32} = [ qw( ePayAccountNum Password OrderNumber
342             AccountNumber RoutingNumber CheckNumber
343             DollarAmount CustomerName CustomerAddress
344             CustomerCity CustomerState CustomerZip
345             CustomerPhone ) ];
346              
347 0           my %optional;
348 0           $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 0           $optional{CC_32} = $optional{CC_30};
360 0           $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 0           $optional{CC_32} = $optional{CC_30};
369 0           $optional{CC_61} = [ qw( MessageSequenceNum CrossReferenceNum OrderNum
370             CustomerNum ) ];
371 0           $optional{ECHECK_20} = ();
372 0           $optional{ECHECK_32} = [ qw( CustomerNumber Misc1 Misc2 CustomerEmail
373             DriversLicense DriversLicenseState
374             BirthDate SocSecNum ) ];
375              
376 0           my $type_action = $self->transaction_type(). '_'. $content{TransactionCode};
377 0 0         unless ( exists($required{$type_action}) ) {
378             # croak( "TransFirst eLink can't (yet?) handle transaction type: ".
379             # "$content{action} on " . $self->transaction_type() );
380 0           $self->error_message("TransFirst eLink can't handle transaction type: ".
381             "$content{action} on " . $self->transaction_type() );
382 0           $self->is_success(0);
383 0           return;
384             }
385              
386 0           my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
387              
388 0           my $zip = $content{'zip'};
389 0           $zip =~ s/[^[:alnum:]]//g;
390              
391 0           my $phone = $content{'phone'};
392 0           $phone =~ s/\D//g;
393              
394 0           my $merchantcustservnum = $self->merchantcustservnum;
395 0 0         my $account_number = $self->transaction_type() eq 'CC'
396             ? $content{card_number}
397             : $content{account_number} ;
398              
399 0   0       my $invoice_number = $content{invoice_number} || "PAYMENT"; # make one up
400 0   0       my $check_number = $content{check_number} || "100"; # make one up
401              
402 0           $self->_revmap_fields(
403              
404             ePayAccountNum => 'login',
405             Password => 'password',
406             OrderNum => \$invoice_number,
407             OrderNumber => \$invoice_number,
408             MerchantCustServNum => \$merchantcustservnum,
409              
410             TransactionAmount => 'amount',
411             DollarAmount => 'amount',
412             CardAccountNum => 'card_number',
413             ExpirationDate => \$expdate_mmyy, # MMYY from 'expiration'
414             CVV2 => 'cvv2',
415              
416             RoutingNumber => 'routing_code',
417             AccountNumber => \$account_number,
418             AccountNum => \$account_number,
419             CheckNumber => \$check_number,
420              
421             CardHolderName => 'name',
422             CustomerName => 'account_name',
423             CardHolderAddress => 'address',
424             CustomerAddress => 'address',
425             CardHolderCity => 'city',
426             CustomerCity => 'city',
427             CardHolderState => 'state',
428             CustomerState => 'state',
429             CardHolderZip => \$zip, # 'zip' with non-alnums removed
430             CustomerZip => \$zip, # 'zip' with non-alnums removed
431             CardHolderEmail => 'email',
432             CustomerEmail => 'email',
433             CardHolderPhone => \$phone,
434             CustomerPhone => \$phone,
435             CustomerNum => 'customer_id',
436             CustomerNumber => 'customer_id',
437             CardHolderCountry => 'country',
438              
439             PaymentDescriptor => 'description',
440              
441             ReferenceNum => 'order_number'
442             );
443              
444 0           tie my %params, 'Tie::IxHash',
445 0           $self->get_fields( @{$required{$type_action}},
446 0           @{$optional{$type_action}},
447             );
448              
449 0 0         $params{TestTransaction}='Y' if $self->test_transaction;
450              
451 0 0 0       $params{InstallmentNum} = $params{InstallmentOf} = '01'
452             unless ($params{InstallmentNum} && $params{InstallmentOf});
453              
454 0 0         if ($self->transaction_type() eq 'ECHECK') {
455 0           delete $params{InstallmentNum};
456 0           delete $params{InstallmentOf};
457             }
458              
459 0 0 0       if ( $type_action eq "CC_30" || $type_action eq "CC_32" ) {
    0          
    0          
    0          
    0          
460 0           $self->path($self->path."elink/authpd.asp");
461             } elsif ( $type_action eq "CC_61" ) {
462 0           $self->path($self->path."eLink/voidpd.asp");
463             } elsif ( $type_action eq "CC_20" ) {
464 0           $self->path($self->path."eLink/creditpd.asp");
465             } elsif ( $type_action eq "ECHECK_32" ) {
466 0           $self->path($self->path."eLink/checkPD.asp");
467             } elsif ( $type_action eq "ECHECK_20" ) {
468 0           $self->path($self->path."eLink/checkcreditPD.asp");
469             } else {
470 0           croak "don't know path for unexpected type and action $type_action";
471             }
472              
473 0 0         warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $DEBUG > 1;
  0            
474 0           my ( $page, $resp, %resp_headers ) =
475             $self->https_post( %params );
476              
477 0           $self->response_code( $resp );
478 0           $self->response_page( $page );
479 0           $self->response_headers( \%resp_headers );
480              
481 0 0         warn "$page\n" if $DEBUG > 1;
482             # $page should contain | separated values
483              
484 0           $self->required_fields(@{$required{$type_action}});
  0            
485              
486 0           my $status ='';
487 0           my @rarray = ();
488              
489 0 0 0       if ( $type_action eq "CC_30" || $type_action eq "CC_32" ) {
    0          
    0          
    0          
    0          
490 0           my ($format,$account,$tcode,$seq,$moi,$cardnum,$exp,$authamt,$authdate,
491             $authtime,$tstat,$custnum,$ordernum,$refnum,$rcode,$authsrc,$achar,
492             $transid,$vcode,$sic,$country,$avscode,$storenum,$cvv2resp,$cavvcode,
493             $crossrefnum,$etstat,$cavvresponse,$xid,$eci,@junk)
494             = split '\|', $page;
495              
496             # AVS and CVS values may be set on success or failure
497 0           $self->avs_code($avscode);
498 0           $self->cvv2_response( $cvv2resp );
499 0           $self->result_code( $status = $etstat );
500 0           $self->order_number( $refnum );
501 0           $self->authorization( $rcode );
502 0           $self->junk( \@junk );
503 0           $self->error_message($error_messages{$status});
504              
505              
506             } elsif ( $type_action eq "CC_61" ) {
507 0           $self->avs_code('');
508 0           $self->cvv2_response('');
509 0           my ($format,$account,$tcode,$seq,$voiddate,$voidtime,$tstat, # flaky docs
510             $refnum,$filler1,$filler2,$filler3,$etstat,@junk)
511             = split '\|', $page;
512 0           $self->result_code( $status = $etstat );
513 0           $self->order_number( $refnum );
514 0           $self->authorization('');
515 0           $self->junk( \@junk );
516 0           $self->error_message($error_messages{$status});
517              
518             } elsif ( $type_action eq "CC_20" ) {
519 0           $self->avs_code('');
520 0           $self->cvv2_response('');
521 0           my ($format,$account,$tcode,$seq,$moi,$authamt,$authdate,$authtime,
522             $tstat,$refnum,$crossrefnum,$custnum,$ordernum,$etstat,@junk)
523             = split '\|', $page;
524 0           $self->result_code( $status = $etstat );
525 0           $self->order_number( $refnum );
526 0           $self->authorization('');
527 0           $self->junk( \@junk );
528 0           $self->error_message($error_messages{$status});
529              
530             } elsif ( $type_action eq "ECHECK_32" ) {
531 0           my ($responsecode,$response,$transactionid,$note,$errors,@junk)
532             = split '\|', $page;
533 0           $self->avs_code('');
534 0           $self->cvv2_response('');
535 0           $self->result_code( $status = $responsecode );
536 0           $self->order_number( $transactionid );
537 0           $self->authorization('');
538 0 0         $errors = $errors ? $errors : '';
539 0           $self->error_message("$response $errors");
540 0           $self->junk( \@junk );
541              
542             } elsif ( $type_action eq "ECHECK_20" ) {
543 0           my ($response,$transactionid,$note,$errors,@junk) # very flaky docs
544             = split '\|', $page;
545 0           $self->avs_code('');
546 0           $self->cvv2_response('');
547 0           $self->result_code( $status = $response );
548 0           $self->order_number( $transactionid );
549 0           $self->authorization('');
550 0 0         $errors = $errors ? $errors : '';
551 0           $self->error_message("$response $errors");
552 0           $self->junk( \@junk );
553              
554             } else {
555 0           croak "can't interpret response for unexpected type and action $type_action";
556             }
557              
558 0 0 0       if ( $resp =~ /^(HTTP\S+ )?200/ && ($status eq "000" || $status eq "011" || $status eq "085" || $status eq "0P0" || $status eq "P00" || $status eq 'ACCEPTED') ) {
      0        
559 0           $self->is_success(1);
560             }
561             else {
562 0           $self->is_success(0);
563             }
564             }
565              
566             1;
567              
568             __END__