0.03
[Business-OnlinePayment-USAePay.git] / USAePay.pm
1 package Business::OnlinePayment::USAePay;
2
3 use strict;
4 use Carp;
5 use Business::OnlinePayment 3;
6 use Business::OnlinePayment::HTTPS;
7 use Digest::MD5 qw(md5_hex);
8 use URI::Escape;
9 use vars qw($VERSION @ISA $DEBUG);
10
11 @ISA = qw(Business::OnlinePayment::HTTPS);
12 $VERSION = '0.03';
13
14 $DEBUG = 0;
15
16 sub _info {
17   {
18     'info_compat'           => '0.01',
19     'gateway_name'          => 'USAePay',
20     'gateway_url'           => 'http://www.usaepay.com',
21     'module_version'        => $VERSION,
22     'supported_types'       => [ 'CC', 'ECHECK' ],
23     'supported_actions'     => {
24                                   CC => [
25                                     'Normal Authorization',
26                                     'Authorization Only',
27                                     'Post Authorization',
28                                     'Credit',
29                                     'Void',
30                                     ],
31                                   ECHECK => [
32                                     'Normal Authorization',
33                                     'Credit',
34                                     ],
35     },
36   };
37 }
38
39 my $default_path = '/gate.php';
40 my $default_cert_path = '/secure/gate.php';
41
42 sub set_defaults {
43     my $self = shift;
44     $self->server('www.usaepay.com');
45     $self->port('443');
46     $self->path($default_path);
47     $self->build_subs(qw(avs_code cvv2_response));
48
49 }
50
51 sub map_fields {
52   my($self) = shift;
53
54   my %content = $self->content();
55
56   my %types = ('visa'             => 'CC',
57                'mastercard'       => 'CC',
58                'american express' => 'CC',
59                'discover'         => 'CC',
60                'check'            => 'ECHECK',
61               );
62   $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
63   $self->transaction_type($content{'type'});
64
65   my %actions;
66   my %cc_actions = ('normal authorization' => 'sale',
67                     'authorization only'   => 'authonly',
68                     'post authorization'   => 'postauth',
69                     'credit'               => 'credit',
70                     'void'                 => 'void',
71                    );
72   my %ec_actions = ('normal authorization' => 'check',
73                     'credit'               => 'checkcredit',
74                    );
75   if ($content{'type'} eq 'CC') {
76     (%actions) = (%cc_actions);
77   }elsif ($content{'type'} eq 'ECHECK') {
78     (%actions) = (%ec_actions);
79   }
80   $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
81                  
82   $content{'expiration'} =~ s/\D//g if exists $content{'expiration'};
83
84   $content{'md5hash'} =
85       md5_hex( join(':', map { defined($content{$_}) ? $content{$_} : '' }
86                              qw(action password amount invoice_number md5key)))
87     if defined $content{'password'};
88
89   $self->content(%content);
90 }
91
92 sub submit {
93     my($self) = @_;
94
95     $self->map_fields();
96
97     $self->remap_fields(
98       login            => 'UMkey',
99       md5key           => 'UMmd5key',
100       md5hash          => 'UMmd5hash',
101       card_number      => 'UMcard',
102       expiration       => 'UMexpir',
103       amount           => 'UMamount',
104       invoice_number   => 'UMinvoice',
105       description      => 'UMdescription',
106       customer_id      => 'UMcustid',
107       cvv2             => 'UMcvv2',
108       email            => 'UMcustemail',
109       name             => 'UMname',
110       address          => 'UMstreet',
111       zip              => 'UMzip',
112       customer_ip      => 'UMip',
113       order_number     => 'UMrefNum',
114       authorization    => 'UMauthCode',
115       routing_code     => 'UMrouting',
116       account_number   => 'UMaccount',
117       customer_ssn     => 'UMssn',
118       action           => 'UMcommand',
119     );
120     my %content = $self->content;
121     if ( $DEBUG ) {
122       warn "content:$_ => $content{$_}\n" foreach keys %content;
123     }
124
125     my @required_fields = qw(type action login);
126
127     my $action = $self->{_content}->{action};
128     if ($self->transaction_type() eq 'CC' ) {
129       if ($action eq 'void' or $action eq 'capture') {
130         push @required_fields, qw/order_number/;
131       }
132       else {
133         # sale, authonly, credit, postauth
134         push @required_fields, qw/card_number expiration amount address zip/;
135         if ($action eq 'postauth') {
136           push @required_fields, qw/authorization/;
137         }
138       }
139     } 
140     elsif ($self->transaction_type() eq 'ECHECK' ) {
141       push @required_fields, qw/routing_code account_number amount name customer_ssn/;
142     } else {
143       croak("USAePay can't handle transaction type: ".
144             $self->transaction_type());
145     }
146
147     $self->required_fields(@required_fields);
148
149     my %post_data = $self->get_fields( map "$_", qw(
150       UMcommand UMkey UMmd5hash UMmd5key UMauthCode UMrefNum UMcard UMexpir
151       UMrouting UMaccount UMamount Umtax UMnontaxable UMtip UMshipping
152       UMdiscount UMsubtotal UMcustid UMinvoice UMorderid UMponum UMdescription
153       UMcvv2 UMcustemail UMcustreceipt UMname UMStreet UMzip UMssn UMdlnum
154       UMdlstate UMclerk UMterminal UMtable UMip UMsoftware UMredir
155       UMredirApproved UMredirDeclined UMechofields UMtestmode
156     ) );
157
158     # test_transaction(0): normal mode
159     #                  1 : test mode (validates formatting only)
160     #                  2 : use sandbox server
161     #                  3 : test mode on sandbox server
162     my $test = $self->test_transaction || 0;
163     $self->server('sandbox.usaepay.com') if ( $test & 2 );
164     $post_data{'UMtestmode'} = ($test & 1) ? 1 : 0;
165
166     $post_data{'UMsoftware'} = __PACKAGE__. " $VERSION";
167     if ( $DEBUG ) {
168       warn "post_data:$_ => $post_data{$_}\n" foreach keys %post_data;
169     }
170
171     my($page,$server_response) = $self->https_post(%post_data);
172     if ( $DEBUG ) {
173       warn "response page: $page\n";
174     }
175
176     my $response;
177     if ($server_response =~ /200/){
178       $response = {map { split '=', $_, 2 } split '&', $page};
179     }else{
180       $response->{UMstatus} = 'Error';
181       $response->{UMerror} = $server_response;
182     }
183
184     $response->{$_} =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg
185       foreach keys %$response;
186
187     if ( $DEBUG ) {
188       warn "response:$_ => $response->{$_}\n" foreach keys %$response;
189     }
190
191     if ( $response->{UMstatus} =~ /^Approved/ ) {
192       $self->is_success(1);
193       $self->authorization($response->{UMauthCode});
194     } else {
195       $self->is_success(0);
196     }
197     $self->order_number($response->{UMrefNum});
198     $self->avs_code($response->{UMavsResultCode});
199     $self->cvv2_response($response->{UMcvv2ResultCode});
200     $self->result_code($response->{UMresult});
201     $self->error_message($response->{UMerror});
202     $self->server_response($response);
203 }
204
205 1;
206 __END__
207
208 =head1 NAME
209
210 Business::OnlinePayment::USAePay - USA ePay backend for Business::OnlinePayment
211
212 =head1 SYNOPSIS
213
214   use Business::OnlinePayment;
215
216   my $tx = new Business::OnlinePayment("USAePay");
217   $tx->content(
218       login          => 'igztOatyqbpd1wsxijl4xnxjodldwdxR', #USAePay source key
219       password       => 'abcdef', #USAePay PIN
220       action         => 'Normal Authorization',
221       description    => 'Business::OnlinePayment test',
222       amount         => '49.95',
223       invoice_number => '100100',
224       name           => 'Tofu Beast',
225       card_number    => '46464646464646',
226       expiration     => '11/08',
227       address        => '1234 Bean Curd Lane, San Francisco',
228       zip            => '94102',
229   );
230   $tx->submit();
231
232   if($tx->is_success()) {
233       print "Card processed successfully: ".$tx->authorization."\n";
234   } else {
235       print "Card was rejected: ".$tx->error_message."\n";
236   }
237
238 =head1 DESCRIPTION
239
240 For detailed information see L<Business::OnlinePayment>.
241
242 =head1 COMPATIBILITY
243
244 This module was developed against USAePay's CGI Gateway API v2.9.5 and
245 also tested against v2.17.1 without problems.  See
246 http://wiki.usaepay.com/developer/transactionapi for details.
247
248 =head1 AUTHOR
249
250 Original author: Jeff Finucane <jeff@cmh.net>
251
252 Current maintainer: Ivan Kohler <ivan-usaepay@freeside.biz>
253
254 =head1 COPYRIGHT & LICENSE
255
256 Copyright (C) 2012 Freeside Internet Services, Inc. (http://freeside.biz/)
257
258 This program is free software; you can redistribute it and/or modify it
259 under the same terms as Perl itself.
260
261 =head1 ADVERTISEMENT
262
263 Need a complete, open-source back-office and customer self-service solution?
264 The Freeside software includes support for credit card and electronic check
265 processing with USAePay and over 50 other gateways, invoicing, integrated
266 trouble ticketing, and customer signup and self-service web interfaces.
267
268 http://freeside.biz/freeside/
269
270 =head1 SEE ALSO
271
272 perl(1). L<Business::OnlinePayment>.
273
274 =cut