16fddaee8ded1d64c201097e1cdb59c3d61e34da
[Business-OnlinePayment-AuthorizeNet.git] / AuthorizeNet / ARB.pm
1 package Business::OnlinePayment::AuthorizeNet::ARB;
2
3 use strict;
4 use Carp;
5 use Business::OnlinePayment::AuthorizeNet;
6 use Business::OnlinePayment::HTTPS;
7 use XML::Simple;
8 use XML::Writer;
9 use Tie::IxHash;
10 use vars qw($VERSION $DEBUG @ISA $me);
11
12 @ISA = qw(Business::OnlinePayment::AuthorizeNet Business::OnlinePayment::HTTPS);
13 $VERSION = '0.01';
14 $DEBUG = 0;
15 $me='Business::OnlinePayment::AuthorizeNet::ARB';
16
17 sub set_defaults {
18     my $self = shift;
19
20     $self->server('api.authorize.net') unless $self->server;
21     $self->port('443') unless $self->port;
22     $self->path('/xml/v1/request.api') unless $self->path;
23
24     $self->build_subs(qw( order_number md5 avs_code cvv2_response
25                           cavv_response
26                      ));
27 }
28
29 sub map_fields {
30     my($self) = @_;
31
32     my %content = $self->content();
33
34     # ACTION MAP
35     my %actions = ('recurring authorization'
36                       => 'ARBCreateSubscriptionRequest',
37                    'modify recurring authorization'
38                       => 'ARBUpdateSubscriptionRequest',
39                    'cancel recurring authorization'
40                       => 'ARBCancelSubscriptionRequest',
41                   );
42     $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
43
44     # TYPE MAP
45     my %types = ('visa'               => 'CC',
46                  'mastercard'         => 'CC',
47                  'american express'   => 'CC',
48                  'discover'           => 'CC',
49                  'check'              => 'ECHECK',
50                 );
51     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
52     $self->transaction_type($content{'type'});
53
54     # ACCOUNT TYPE MAP
55     my %account_types = ('personal checking'   => 'checking',
56                          'personal savings'    => 'savings',
57                          'business checking'   => 'businessChecking',
58                          'business savings'    => 'savings',
59                         );
60     $content{'account_type'} = $account_types{lc($content{'account_type'})}
61                                || $content{'account_type'};
62
63     # MASSAGE EXPIRATION 
64     $content{'expdate_yyyymm'} = $self->expdate_yyyymm($content{'expiration'});
65
66     # stuff it back into %content
67     $self->content(%content);
68
69 }
70
71 sub revmap_fields {
72   my $self = shift;
73   tie my(%map), 'Tie::IxHash', @_;
74   my %content = $self->content();
75   map {
76         my $value;
77         if ( ref( $map{$_} ) eq 'HASH' ) {
78           $value = $map{$_} if ( keys %{ $map{$_} } );
79         }elsif( exists( $content{ $map{$_} } ) ) {
80           $value = $content{ $map{$_} };
81         }
82
83         if (defined($value)) {
84           ($_ => $value);
85         }else{
86           ();
87         }
88       } (keys %map);
89 }
90
91 sub expdate_yyyymm {
92   my $self = shift;
93   my $expiration = shift;
94   my $expdate_yyyymm;
95   if ( defined($expiration) and $expiration =~ /^(\d{1,2})\D+(\d{2})$/ ) {
96     my ( $month, $year ) = ( $1, $2 );
97    $expdate_yyyymm = sprintf( "20%02d-%02d", $year, $month );
98   }
99   return defined($expdate_yyyymm) ? $expdate_yyyymm : $expiration;
100 };
101
102 sub _xmlwrite {
103   my ($self, $writer, $item, $value) = @_;
104   $writer->startTag($item);
105   if ( ref( $value ) eq 'HASH' ) {
106     foreach ( keys ( %$value ) ) {
107       $self->_xmlwrite($writer, $_, $value->{$_});
108     }
109   }else{
110     $writer->characters($value);
111   }
112   $writer->endTag($item);
113 }
114
115 sub submit {
116   my($self) = @_;
117
118   $self->map_fields();
119
120   my @required_fields = qw(action login password);
121
122   if ( $self->{_content}->{action} eq 'ARBCreateSubscriptionRequest' ) {
123     push @required_fields,
124       qw( type interval start periods amount first_name last_name );
125
126     if ($self->transaction_type() eq "ECHECK") {
127       push @required_fields,
128         qw( amount routing_code account_number account_type account_name
129             check_type
130           );
131     } elsif ($self->transaction_type() eq 'CC' ) {
132       push @required_fields, qw( card_number expiration );
133     }
134   }elsif ( $self->{_content}->{action} eq 'ARBUpdateSubscriptionRequest' ) {
135     push @required_fields, qw( subscription );
136   }elsif ( $self->{_content}->{action} eq 'ARBCancelSubscriptionRequest' ) {
137     push @required_fields, qw( subscription );
138   }else{
139     croak "$me can't handle transaction type: ".
140       $self->{_content}->{action}. " for ". 
141       $self->transaction_type();
142   }
143
144   $self->required_fields(@required_fields);
145
146   tie my %merchant, 'Tie::IxHash',
147     $self->revmap_fields(
148                           name           => 'login',
149                           transactionKey => 'password',
150                         );
151
152   my ($length,$unit) =
153     ($self->{_content}->{interval} or '') =~ /^\s*(\d+)\s+(day|month)s?\s*$/;
154   tie my %interval, 'Tie::IxHash', (
155                                      ($length ? (length => $length)   : () ),
156                                      ($unit   ? (unit   => $unit.'s') : () ),
157                                    );
158
159   tie my %schedule, 'Tie::IxHash',
160     $self->revmap_fields(
161                           interval         => \%interval,
162                           startDate        => 'start',
163                           totalOccurrences => 'periods',
164                           trialOccurrences => 'trialperiods',
165                           phoneNumber      => 'phone',
166                         );
167
168   tie my %account, 'Tie::IxHash', ( 
169     ( defined($self->transaction_type())
170       && $self->transaction_type() eq 'CC'
171     ) ? $self->revmap_fields(
172                               cardNumber     => 'card_number',
173                               expirationDate => 'expdate_yyyymm',
174                             )
175       : $self->revmap_fields(
176                               accountType    => 'account_type',
177                               routingNumber  => 'routing_code',
178                               accountNumber  => 'account_number',
179                               nameOnAccount  => 'account_name',
180                               echeckType     => 'check_type',
181                               bankName       => 'bank_name',
182                             )
183   );
184
185   tie my %payment, 'Tie::IxHash',
186     $self->revmap_fields(
187                            ( ( defined($self->transaction_type()) && # require?
188                                $self->transaction_type() eq 'CC'
189                              ) ?  'creditCard'
190                                : 'bankAccount'
191                            )  => \%account,
192                          );
193
194   tie my %order, 'Tie::IxHash',
195     $self->revmap_fields(
196                           invoiceNumber => 'invoice_number',
197                           description   => 'description',
198                         );
199
200   tie my %drivers, 'Tie::IxHash',
201     $self->revmap_fields(
202                           number      => 'license_num',
203                           state       => 'license_state',
204                           dateOfBirth => 'license_dob',
205                         );
206
207   tie my %billto, 'Tie::IxHash',
208     $self->revmap_fields(
209                           firstName => 'first_name',
210                           lastName  => 'last_name',
211                           company   => 'company',
212                           address   => 'address',
213                           city      => 'city',
214                           state     => 'state',
215                           zip       => 'zip',
216                           country   => 'country',
217                         );
218
219   tie my %shipto, 'Tie::IxHash',
220     $self->revmap_fields(
221                           firstName => 'ship_first_name',
222                           lastName  => 'ship_last_name',
223                           company   => 'ship_company',
224                           address   => 'ship_address',
225                           city      => 'ship_city',
226                           state     => 'ship_state',
227                           zip       => 'ship_zip',
228                           country   => 'ship_country',
229                         );
230
231   tie my %customer, 'Tie::IxHash',
232     $self->revmap_fields(
233                           type           => 'customer_org',
234                           id             => 'customer_id',
235                           email          => 'email',
236                           phoneNumber    => 'phone',
237                           faxNumber      => 'fax',
238                           driversLicense => \%drivers,
239                           taxid          => 'customer_ssn',
240                         );
241
242   tie my %sub, 'Tie::IxHash',
243     $self->revmap_fields(
244                           paymentSchedule => \%schedule,
245                           amount          => 'amount',
246                           trialAmount     => 'trialamount',
247                           payment         => \%payment,
248                           order           => \%order,
249                           customer        => \%customer,
250                           billTo          => \%billto,
251                           shipTo          => \%shipto,
252                         );
253
254
255   tie my %req, 'Tie::IxHash',
256     $self->revmap_fields (
257                            merchantAuthentication => \%merchant,
258                            subscriptionId         => 'subscription',
259                            subscription           => \%sub,
260                          );
261
262   my $ns = "AnetApi/xml/v1/schema/AnetApiSchema.xsd";
263   my $post_data;
264   my $writer = new XML::Writer( OUTPUT      => \$post_data,
265                                 DATA_MODE   => 1,
266                                 DATA_INDENT => 1,
267                                 ENCODING    => 'utf-8',
268                               );
269   $writer->xmlDecl();
270   $writer->startTag($self->{_content}->{action}, 'xmlns', $ns);
271   foreach ( keys ( %req ) ) {
272     $self->_xmlwrite($writer, $_, $req{$_});
273   }
274   $writer->endTag($self->{_content}->{action});
275   $writer->end();
276
277   if ($self->test_transaction()) {
278     $self->server('apitest.authorize.net');
279   }
280
281   warn $post_data if $DEBUG;
282   my($page,$server_response,%headers) =
283     $self->https_post( { 'Content-Type' => 'text/xml' }, $post_data);
284
285   #trim leading (4?) characters of unknown origin not in spec
286   $page =~ s/^(.*?)</</;
287   my $garbage=$1;
288   warn "Trimmed $garbage from response page.\n" if $DEBUG;
289
290   warn $page if $DEBUG;
291
292   my $response;
293   my $message;
294   if ($server_response =~ /200/){
295     $response = XMLin($page);
296     if (ref($response->{messages}->{message}) eq 'ARRAY') {
297       $message = $response->{messages}->{message}->[0];
298     }else{
299       $message = $response->{messages}->{message};
300     }
301   }else{
302     $response->{messages}->{resultCode} = "Server Failed";
303     $message->{code} = $server_response;
304   }
305
306   $self->server_response($page);
307   $self->order_number($response->{subscriptionId});
308   $self->result_code($message->{code});
309   $self->error_message($message->{text});
310
311   if($response->{messages}->{resultCode} eq "Ok" ) {
312       $self->is_success(1);
313   } else {
314       $self->is_success(0);
315       unless ( $self->error_message() ) { #additional logging information
316         $self->error_message(
317           "(HTTPS response: $server_response) ".
318           "(HTTPS headers: ".
319             join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
320           "(Raw HTTPS content: $page)"
321         );
322       }
323   }
324 }
325
326 1;
327 __END__
328
329 =head1 NAME
330
331 Business::OnlinePayment::AuthorizeNet::ARB - AuthorizeNet ARB backend for Business::OnlinePayment
332
333 =head1 AUTHOR
334
335 Jeff Finucane, authorizenetarb@weasellips.com
336
337 =head1 SEE ALSO
338
339 perl(1). L<Business::OnlinePayment>.
340
341 =cut
342