set default server and default shipping address to billing address for card transactions
[Business-OnlinePayment-IPPay.git] / IPPay.pm
1 package Business::OnlinePayment::IPPay;
2
3 use strict;
4 use Carp;
5 use Tie::IxHash;
6 use XML::Simple;
7 use XML::Writer;
8 use LWP::UserAgent;
9 use HTTP::Request;
10 use HTTP::Request::Common qw (POST);
11 use Date::Calc qw(Add_Delta_YM Add_Delta_Days);
12 use Business::OnlinePayment;
13 use Business::OnlinePayment::HTTPS;
14 use vars qw($VERSION $DEBUG @ISA $me);
15
16 @ISA = qw(Business::OnlinePayment::HTTPS);
17 $VERSION = '0.02';
18 $DEBUG = 0;
19 $me = 'Business::OnlinePayment::IPPay';
20
21 sub set_defaults {
22     my $self = shift;
23     my %opts = @_;
24
25     # standard B::OP methods/data
26     $self->server('gateway17.jetpay.com') unless $self->server;
27     $self->port('443') unless $self->port;
28     $self->path('/jetpay') unless $self->path;
29
30     $self->build_subs(qw( order_number avs_code cvv2_response
31                           response_page response_code response_headers
32                      ));
33
34     # module specific data
35     if ( $opts{debug} ) {
36         $self->debug( $opts{debug} );
37         delete $opts{debug};
38     }
39
40     my %_defaults = ();
41     foreach my $key (keys %opts) {
42       $key =~ /^default_(\w*)$/ or next;
43       $_defaults{$1} = $opts{$key};
44       delete $opts{$key};
45     }
46     $self->{_defaults} = \%_defaults;
47 }
48
49 sub map_fields {
50     my($self) = @_;
51
52     my %content = $self->content();
53
54     # TYPE MAP
55     my %types = ( 'visa'               => 'CC',
56                   'mastercard'         => 'CC',
57                   'american express'   => 'CC',
58                   'discover'           => 'CC',
59                   'check'              => 'ECHECK',
60                 );
61     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
62     $self->transaction_type($content{'type'});
63     
64     # ACTION MAP 
65     my $action = lc($content{'action'});
66     my %actions =
67       ( 'normal authorization'            => 'SALE',
68         'authorization only'              => 'AUTHONLY',
69         'post authorization'              => 'CAPT',
70         'void'                            => 'VOID',
71         'credit'                          => 'CREDIT',
72       );
73     my %check_actions =
74       ( 'normal authorization'            => 'CHECK',
75         'void'                            => 'VOIDACH',
76         'credit'                          => 'REVERSAL',
77       );
78     if ($self->transaction_type eq 'CC') {
79       $content{'TransactionType'} = $actions{$action} || $action;
80     }elsif ($self->transaction_type eq 'ECHECK') {
81       $content{'TransactionType'} = $check_actions{$action} || $action;
82     }
83
84
85     # ACCOUNT TYPE MAP
86     my %account_types = ('personal checking'   => 'Checking',
87                          'personal savings'    => 'Savings',
88                          'business checking'   => 'BusinessCk',
89                         );
90     $content{'account_type'} = $account_types{lc($content{'account_type'})}
91                                || $content{'account_type'};
92
93     $content{Origin} = 'RECURRING' 
94       if ($content{recurring_billing} &&$content{recurring_billing} eq 'YES' );
95
96     # stuff it back into %content
97     $self->content(%content);
98
99 }
100
101 sub expdate_month {
102   my ($self, $exp) = (shift, shift);
103   my $month;
104   if ( defined($exp) and $exp =~ /^(\d+)\D+\d*\d{2}$/ ) {
105     $month  = sprintf( "%02d", $1 );
106   }elsif ( defined($exp) and $exp =~ /^(\d{2})\d{2}$/ ) {
107     $month  = sprintf( "%02d", $1 );
108   }
109   return $month;
110 }
111
112 sub expdate_year {
113   my ($self, $exp) = (shift, shift);
114   my $year;
115   if ( defined($exp) and $exp =~ /^\d+\D+\d*(\d{2})$/ ) {
116     $year  = sprintf( "%02d", $1 );
117   }elsif ( defined($exp) and $exp =~ /^\d{2}(\d{2})$/ ) {
118     $year  = sprintf( "%02d", $1 );
119   }
120   return $year;
121 }
122
123 sub revmap_fields {
124   my $self = shift;
125   tie my(%map), 'Tie::IxHash', @_;
126   my %content = $self->content();
127   map {
128         my $value;
129         if ( ref( $map{$_} ) eq 'HASH' ) {
130           $value = $map{$_} if ( keys %{ $map{$_} } );
131         }elsif( ref( $map{$_} ) ) {
132           $value = ${ $map{$_} };
133         }elsif( exists( $content{ $map{$_} } ) ) {
134           $value = $content{ $map{$_} };
135         }
136
137         if (defined($value)) {
138           ($_ => $value);
139         }else{
140           ();
141         }
142       } (keys %map);
143 }
144
145 sub submit {
146   my($self) = @_;
147
148   $self->is_success(0);
149   $self->map_fields();
150
151   my @required_fields = qw(action login type);
152
153   my $action = lc($self->{_content}->{action});
154   my $type = $self->transaction_type();
155   if ( $action eq 'normal authorization'
156     || $action eq 'credit'
157     || $action eq 'authorization only' && $type eq 'CC')
158   {
159     push @required_fields, qw( amount );
160
161     push @required_fields, qw( card_number expiration )
162       if ($type eq "CC"); 
163         
164     push @required_fields,
165       qw( routing_code account_number name ) # account_type
166       if ($type eq "ECHECK");
167         
168   }elsif ( $action eq 'post authorization' && $type eq 'CC') {
169     push @required_fields, qw( order_number );
170   }elsif ( $action eq 'void') {
171     push @required_fields, qw( order_number amount );
172
173     push @required_fields, qw( authorization card_number )
174       if ($type eq "CC");
175
176     push @required_fields,
177       qw( routing_code account_number name ) # account_type
178       if ($type eq "ECHECK");
179
180   }else{
181     croak "$me can't handle transaction type: ".
182       $self->{_content}->{action}. " for ".
183       $self->transaction_type();
184   }
185
186   my %content = $self->content();
187   foreach ( keys ( %{($self->{_defaults})} ) ) {
188     $content{$_} = $self->{_defaults}->{$_} unless exists($content{$_});
189   }
190   $self->content(%content);
191
192   $self->required_fields(@required_fields);
193
194   my $transaction_id = $content{'order_number'};
195   unless ($transaction_id) {
196     my ($page, $server_response, %headers) = $self->https_get('dummy' => 1);
197     return unless $server_response=~ /^200/;
198     $transaction_id = $page;
199   }
200
201   my $cardexpmonth = $self->expdate_month($content{expiration});
202   my $cardexpyear  = $self->expdate_year($content{expiration});
203   my $cardstartmonth = $self->expdate_month($content{card_start});
204   my $cardstartyear  = $self->expdate_year($content{card_start});
205  
206   my $amount;
207   if (defined($content{amount})) {
208     $amount = sprintf("%.2f", $content{amount});
209     $amount =~ s/\.//;
210   }
211
212   my $check_number = $content{check_number} || "100"  # make one up
213     if($content{account_number});
214
215   my $terminalid = $content{login} if $type eq 'CC';
216   my $merchantid = $content{login} if $type eq 'ECHECK';
217
218   tie my %ach, 'Tie::IxHash',
219     $self->revmap_fields(
220                           #AccountType         => 'account_type',
221                           AccountNumber       => 'account_number',
222                           ABA                 => 'routing_code',
223                           CheckNumber         => \$check_number,
224                         );
225
226   tie my %industryinfo, 'Tie::IxHash',
227     $self->revmap_fields(
228                           Type                => 'IndustryInfo',
229                         );
230
231   tie my %shippingaddr, 'Tie::IxHash',
232     $self->revmap_fields(
233                           Address             => 'ship_address',
234                           City                => 'ship_city',
235                           StateProv           => 'ship_state',
236                           Country             => 'ship_country',
237                           Phone               => 'ship_phone',
238                         );
239
240   unless ( $type ne 'CC' || keys %shippingaddr ) {
241     tie %shippingaddr, 'Tie::IxHash',
242       $self->revmap_fields(
243                             Address             => 'address',
244                             City                => 'city',
245                             StateProv           => 'state',
246                             Country             => 'country',
247                             Phone               => 'phone',
248                           );
249   }
250
251   tie my %shippinginfo, 'Tie::IxHash',
252     $self->revmap_fields(
253                           CustomerPO          => 'CustomerPO',
254                           ShippingMethod      => 'ShippingMethod',
255                           ShippingName        => 'ship_name',
256                           ShippingAddr        => \%shippingaddr,
257                         );
258
259   tie my %req, 'Tie::IxHash',
260     $self->revmap_fields(
261                           TransactionType     => 'TransactionType',
262                           TerminalID          => 'login',
263 #                          TerminalID          => \$terminalid,
264 #                          MerchantID          => \$merchantid,
265                           TransactionID       => \$transaction_id,
266                           RoutingCode         => 'RoutingCode',
267                           Approval            => 'authorization',
268                           BatchID             => 'BatchID',
269                           Origin              => 'Origin',
270                           Password            => 'password',
271                           OrderNumber         => 'invoice_number',
272                           CardNum             => 'card_number',
273                           CVV2                => 'cvv2',
274                           Issue               => 'issue_number',
275                           CardExpMonth        => \$cardexpmonth,
276                           CardExpYear         => \$cardexpyear,
277                           CardStartMonth      => \$cardstartmonth,
278                           CardStartYear       => \$cardstartyear,
279                           Track1              => 'track1',
280                           Track2              => 'track2',
281                           ACH                 => \%ach,
282                           CardName            => 'name',
283                           DispositionType     => 'DispositionType',
284                           TotalAmount         => \$amount,
285                           FeeAmount           => 'FeeAmount',
286                           TaxAmount           => 'TaxAmount',
287                           BillingAddress      => 'address',
288                           BillingCity         => 'city',
289                           BillingStateProv    => 'state',
290                           BillingPostalCode   => 'zip',
291                           BillingCountry      => 'country',
292                           BillingPhone        => 'phone',
293                           Email               => 'email',
294                           UserIPAddr          => 'customer_ip',
295                           UserHost            => 'UserHost',
296                           UDField1            => 'UDField1',
297                           UDField2            => 'UDField2',
298                           UDField3            => 'UDField3',
299                           ActionCode          => 'ActionCode',
300                           IndustryInfo        => \%industryinfo,
301                           ShippingInfo        => \%shippinginfo,
302                         );
303
304   my $post_data;
305   my $writer = new XML::Writer( OUTPUT      => \$post_data,
306                                 DATA_MODE   => 1,
307                                 DATA_INDENT => 1,
308                                 ENCODING    => 'us-ascii',
309                               );
310   $writer->xmlDecl();
311   $writer->startTag('JetPay');
312   foreach ( keys ( %req ) ) {
313     $self->_xmlwrite($writer, $_, $req{$_});
314   }
315   $writer->endTag('JetPay');
316   $writer->end();
317
318   if ($self->test_transaction()) {
319     $self->server('test1.jetpay.com');
320     $self->port('443');
321     $self->path('/jetpay');
322   }
323
324   warn "$post_data\n" if $DEBUG;
325
326   my ($page,$server_response,%headers) = $self->https_post($post_data);
327
328   warn "$page\n" if $DEBUG;
329
330   my $response = {};
331   if ($server_response =~ /^200/){
332     $response = XMLin($page);
333     if (  exists($response->{ActionCode}) && !exists($response->{ErrMsg})) {
334       $self->error_message($response->{ResponseText});
335     }else{
336       $self->error_message($response->{Errmsg});
337     }
338 #  }else{
339 #    $self->error_message("Server Failed");
340   }
341
342   $self->result_code($response->{ActionCode} || '');
343   $self->order_number($response->{TransactionID} || '');
344   $self->authorization($response->{Approval} || '');
345   $self->cvv2_response($response->{CVV2} || '');
346   $self->avs_code($response->{AVS} || '');
347
348   $self->is_success($self->result_code() eq '000' ? 1 : 0);
349
350   unless ($self->is_success()) {
351     unless ( $self->error_message() ) { #additional logging information
352       $self->error_message(
353         "(HTTPS response: $server_response) ".
354         "(HTTPS headers: ".
355           join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
356         "(Raw HTTPS content: $page)"
357       );
358     }
359   }
360
361 }
362
363 sub _xmlwrite {
364   my ($self, $writer, $item, $value) = @_;
365   $writer->startTag($item);
366   if ( ref( $value ) eq 'HASH' ) {
367     foreach ( keys ( %$value ) ) {
368       $self->_xmlwrite($writer, $_, $value->{$_});
369     }
370   }else{
371     $writer->characters($value);
372   }
373   $writer->endTag($item);
374 }
375
376 1;
377 __END__
378
379 =head1 NAME
380
381 Business::OnlinePayment::IPPay - IPPay backend for Business::OnlinePayment
382
383 =head1 SYNOPSIS
384
385   use Business::OnlinePayment;
386
387   my $tx =
388     new Business::OnlinePayment( "IPPay",
389                                  'default_Origin' => 'PHONE ORDER',
390                                );
391   $tx->content(
392       type           => 'VISA',
393       login          => 'testdrive',
394       password       => '', #password 
395       action         => 'Normal Authorization',
396       description    => 'Business::OnlinePayment test',
397       amount         => '49.95',
398       customer_id    => 'tfb',
399       name           => 'Tofu Beast',
400       address        => '123 Anystreet',
401       city           => 'Anywhere',
402       state          => 'UT',
403       zip            => '84058',
404       card_number    => '4007000000027',
405       expiration     => '09/02',
406       cvv2           => '1234', #optional
407   );
408   $tx->submit();
409
410   if($tx->is_success()) {
411       print "Card processed successfully: ".$tx->authorization."\n";
412   } else {
413       print "Card was rejected: ".$tx->error_message."\n";
414   }
415
416 =head1 SUPPORTED TRANSACTION TYPES
417
418 =head2 CC, Visa, MasterCard, American Express, Discover
419
420 Content required: type, login, action, amount, card_number, expiration.
421
422 =head2 Check
423
424 Content required: type, login, action, amount, name, account_number, routing_code.
425
426 =head1 DESCRIPTION
427
428 For detailed information see L<Business::OnlinePayment>.
429
430 =head1 METHODS AND FUNCTIONS
431
432 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
433
434 =head2 result_code
435
436 Returns the response error code.
437
438 =head2 error_message
439
440 Returns the response error description text.
441
442 =head2 server_response
443
444 Returns the complete response from the server.
445
446 =head1 Handling of content(%content) data:
447
448 =head2 action
449
450 The following actions are valid
451
452   normal authorization
453   authorization only
454   post authorization
455   credit
456   void
457
458 =head1 Setting IPPay parameters from content(%content)
459
460 The following rules are applied to map data to IPPay parameters
461 from content(%content):
462
463       # param => $content{<key>}
464       TransactionType     => 'TransactionType',
465       TerminalID          => 'login',
466       TransactionID       => 'order_number',
467       RoutingCode         => 'RoutingCode',
468       Approval            => 'authorization',
469       BatchID             => 'BatchID',
470       Origin              => 'Origin',
471       Password            => 'password',
472       OrderNumber         => 'invoice_number',
473       CardNum             => 'card_number',
474       CVV2                => 'cvv2',
475       Issue               => 'issue_number',
476       CardExpMonth        => \( $month ), # MM from MM(-)YY(YY) of 'expiration'
477       CardExpYear         => \( $year ), # YY from MM(-)YY(YY) of 'expiration'
478       CardStartMonth      => \( $month ), # MM from MM(-)YY(YY) of 'card_start'
479       CardStartYear       => \( $year ), # YY from MM(-)YY(YY) of 'card_start'
480       Track1              => 'track1',
481       Track2              => 'track2',
482       ACH
483         AccountNumber       => 'account_number',
484         ABA                 => 'routing_code',
485         CheckNumber         => 'check_number',
486       CardName            => 'name',
487       DispositionType     => 'DispositionType',
488       TotalAmount         => 'amount' reformatted into cents
489       FeeAmount           => 'FeeAmount',
490       TaxAmount           => 'TaxAmount',
491       BillingAddress      => 'address',
492       BillingCity         => 'city',
493       BillingStateProv    => 'state',
494       BillingPostalCode   => 'zip',
495       BillingCountry      => 'country',
496       BillingPhone        => 'phone',
497       Email               => 'email',
498       UserIPAddr          => 'customer_ip',
499       UserHost            => 'UserHost',
500       UDField1            => 'UDField1',
501       UDField2            => 'UDField2',
502       UDField3            => 'UDField3',
503       ActionCode          => 'ActionCode',
504       IndustryInfo
505         Type                => 'IndustryInfo',
506       ShippingInfo
507         CustomerPO          => 'CustomerPO',
508         ShippingMethod      => 'ShippingMethod',
509         ShippingName        => 'ship_name',
510         ShippingAddr
511           Address             => 'ship_address',
512           City                => 'ship_city',
513           StateProv           => 'ship_state',
514           Country             => 'ship_country',
515           Phone               => 'ship_phone',
516
517 =head1 NOTE
518
519 =head1 COMPATIBILITY
520
521 Business::OnlinePayment::IPPay uses IPPay XML Product Specifications version
522 1.1.2.
523
524 See http://www.ippay.com/ for more information.
525
526 =head1 AUTHOR
527
528 Jeff Finucane, ippay@weasellips.com
529
530 =head1 SEE ALSO
531
532 perl(1). L<Business::OnlinePayment>.
533
534 =cut
535