23ca8c9cc9b43c44b6bdd95bdbc8d4cfc4c15632
[Business-OnlinePayment-BankOfAmerica.git] / BankOfAmerica.pm
1 package Business::OnlinePayment::BankOfAmerica;
2
3 # $Id: BankOfAmerica.pm,v 1.2 2002-08-14 01:01:16 ivan Exp $
4
5 use strict;
6 use Carp qw(croak);
7 use Business::OnlinePayment;
8 use Net::SSLeay qw/make_form post_https make_headers/;
9 #use Text::CSV;
10 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
11
12 require Exporter;
13
14 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
15 @EXPORT = qw();
16 @EXPORT_OK = qw();
17 $VERSION = '1.01';
18
19 sub set_defaults {
20     my $self = shift;
21
22     $self->server('cart.bamart.com');
23     $self->port('443');
24
25 }
26
27 sub revmap_fields {
28     my($self, %map) = @_;
29     my %content = $self->content();
30     foreach(keys %map) {
31 #    warn "$_ = ". ( ref($map{$_})
32 #                         ? ${ $map{$_} }
33 #                         : $content{$map{$_}} ). "\n";
34         $content{$_} = ref($map{$_})
35                          ? ${ $map{$_} }
36                          : $content{$map{$_}};
37     }
38     $self->content(%content);
39 }
40
41 sub get_fields {
42     my($self,@fields) = @_;
43
44     my %content = $self->content();
45     my %new = ();
46     foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
47     return %new;
48 }
49
50 sub order_number {
51     my $self = shift;
52     if (@_) {
53       $self->{order_number} = shift;
54     }
55     return $self->{order_number};
56 }
57
58 sub submit {
59     my($self) = @_;
60
61     my %content = $self->content;
62
63     my $action = lc($content{'action'});
64
65     die 'Normal Authorization not supported'
66       if $action eq 'normal authorization';
67
68     my @fields;
69     my $ioc_indicator = '';
70
71     if ( $action eq 'authorization only' ) {
72       $self->path('/payment.mart');
73       @fields = qw(
74           ioc_merchant_id ioc_order_total_amount ioc_merchant_shopper_id
75           ioc_merchant_order_id ecom_billto_postal_name_first
76           ecom_billto_postal_name_last ecom_billto_postal_street_line1
77           ecom_billto_postal_street_line2 ecom_billto_postal_city
78           ecom_billto_postal_stateprov ecom_billto_postal_postalcode
79           ecom_billto_postal_countrycode ecom_billto_telecom_phone_number
80           ecom_billto_online_email ecom_payment_card_name
81           ecom_payment_card_number ecom_payment_card_expdate_month
82           ecom_payment_card_expdate_year
83       );
84     } elsif ( $action eq 'credit' ) {
85       $self->path('/Settlement.mart');
86       $ioc_indicator = 'R';
87       @fields = qw(
88           ioc_handshake_id ioc_merchant_id ioc_user_name ioc_password
89           ioc_order_number ioc_indicator ioc_settlement_amount
90           ioc_authorization_code ioc_email_flag
91       );
92       #    ioc_email_flag ioc_close_flag ioc_invoice_notes ioc_email_notes_flag
93     } elsif ( $action eq 'post authorization' ) {
94       $self->path('/Settlement.mart');
95       $ioc_indicator = 'S';
96       @fields = qw(
97           ioc_handshake_id ioc_merchant_id ioc_user_name ioc_password
98           ioc_order_number ioc_indicator ioc_settlement_amount
99           ioc_authorization_code ioc_email_flag
100       );
101       #    ioc_email_flag ioc_close_flag ioc_invoice_notes ioc_email_notes_flag
102     } else {
103       die "unknown action $action";
104     }
105
106 #        $self->required_fields(qw/type login password action amount last_name
107 #                                  first_name card_number expiration/);
108
109     my($month, $year);
110     unless ( $action eq 'post authorization' ) {
111
112         if (  $self->transaction_type() =~
113                 /^(cc|visa|mastercard|american express|discover)$/i
114            ) {
115         } else {
116             Carp::croak("BankOfAmerica can't handle transaction type: ".
117                         $self->transaction_type());
118         }
119
120       $content{'expiration'} =~ /^(\d+)\D+(\d+)$/
121         or croak "unparsable expiration $content{expiration}";
122
123       ( $month, $year ) = ( $1, $2 );
124       $year += 2000 if $year < 2000; #not y4k safe, oh shit
125
126     }
127
128     $self->revmap_fields(
129         ioc_merchant_id                  => \($self->merchant_id()),
130         ioc_user_name                    => 'login',
131         ioc_password                     => 'password',
132         ioc_invoice_notes                => 'description',
133         ioc_order_total_amount           => 'amount',
134         ioc_settlement_amount            => 'amount',
135         ioc_merchant_order_id            => 'invoice_number',
136         ioc_order_number                 => 'order_number',
137         ioc_merchant_shopper_id          => 'customer_id',
138         ecom_billto_postal_name_last     => 'last_name',
139         ecom_billto_postal_name_first    => 'first_name',
140         ecom_billto_postal_street_line1  => 'address',
141 #!!!        ecom_billto_postal_street_line2 => 'address',
142         ecom_billto_postal_city          => 'city',
143         ecom_billto_postal_stateprov     => 'state',
144         ecom_billto_postal_postalcode    => 'zip',
145         ecom_payment_card_number         => 'card_number',
146         ecom_billto_postal_countrycode   => 'country',
147         ecom_billto_telecom_phone_number => 'phone',
148         ecom_billto_online_email         => 'email',
149
150         ecom_payment_card_name =>
151           \( $content{'name'} || "$content{first_name} $content{last_name}" ),
152
153         ecom_payment_card_expdate_month => \$month,
154         ecom_payment_card_expdate_year  => \$year,
155
156         ioc_authorization_code           => 'authorization',
157         ioc_indicator                    => \$ioc_indicator,
158         ioc_handshake_id                 => 'order_number',
159         ioc_email_flag                   => \'No',
160
161     );
162
163     my %post_data = $self->get_fields( @fields );
164
165     warn "$_ => $post_data{$_}\n" for keys %post_data;
166     warn "\n";
167     my $pd = make_form(%post_data);
168     my $s = $self->server();
169     my $p = $self->port();
170     my $t = $self->path();
171     my $headers = make_headers('Referer' => $content{'referer'} )
172       unless $action eq 'post authorization';
173     my($page,$server_response,%headers) = post_https($s,$p,$t,$headers,$pd);
174
175     my %response;
176     if ( $action eq 'post authorization' ) {
177      $page =~ s/<HTML>.*//s;
178      $page =~ s/\n+$//;
179      %response =
180         map { /^(\w+)\=(.*)$/ or /^()()$/ or die $_; lc($1) => $2 }
181           split(/\r/, $page);
182     } else {
183       %response =
184         map { /^(\w+)\=(.*)$/ or die $_; lc($1) => $2 } split(/\<BR\>/i, $page);
185     }
186
187     warn "$_ => $response{$_}\n" for keys %response;
188
189     $self->server_response($page);
190
191     if ( $response{'ioc_response_code'} eq '0' ) {
192         $self->is_success(1);
193         $self->result_code($response{'ioc_response_code'});
194         $self->authorization($response{'ioc_authorization_code'});
195         $self->order_number($response{'ioc_order_id'});
196     } else {
197         $self->is_success(0);
198         $self->result_code($response{'ioc_response_code'});
199         my $error =
200           $action eq 'post authorization'
201             ? $response{'ioc_response_desc'}
202             : $response{'ioc_reject_description'};
203         $error .= ': '. $response{'ioc_missing_fields'}
204           if $response{'ioc_missing_fields'};
205         $error .= ': '. $response{'ioc_invalid_fields'}
206           if $response{'ioc_invalid_fields'};
207         $self->error_message($error);
208     }
209
210 }
211
212 1;
213 __END__
214
215 =head1 NAME
216
217 Business::OnlinePayment::BankOfAmerica - Bank of America backend for Business::OnlinePayment
218
219 =head1 SYNOPSIS
220
221   use Business::OnlinePayment;
222
223   my $tx = new Business::OnlinePayment("BankOfAmerica", 'merchant_id' => 'YOURMERCHANTID');
224   $tx->content(
225       type           => 'VISA',
226       action         => 'Authorization Only',
227       description    => 'Business::OnlinePayment test',
228       amount         => '49.95',
229       invoice_number => '100100',
230       customer_id    => 'jsk',
231       first_name     => 'Jason',
232       last_name      => 'Kohles',
233       address        => '123 Anystreet',
234       city           => 'Anywhere',
235       state          => 'UT',
236       zip            => '84058',
237       email          => 'ivan-bofa@420.am',
238       card_number    => '4007000000027',
239       expiration     => '09/99',
240       referer        => 'http://cleanwhisker.420.am/',
241   );
242   $tx->submit();
243
244   if($tx->is_success()) {
245       print "Card processed successfully: ".$tx->authorization."\n";
246   } else {
247       print "Card was rejected: ".$tx->error_message."\n";
248   }
249
250  if($tx->is_success()) {
251
252       $auth = $tx->authorization;
253       $ordernum = $tx->order_number;
254
255       my $capture = new Business::OnlinePayment("BankOfAmerica", 'merchant_id' => 'YOURMERCHANTID' );
256
257       $capture->content(
258           action         => 'Post Authorization',
259           login          => 'YOURLOGIN
260           password       => 'YOURPASSWORD',
261           order_number   => $ordernum,
262           amount         => '0.01',
263           authorization  => $auth,
264           description    => 'Business::OnlinePayment::BankOfAmerica visa test',
265       );
266
267       $capture->submit();
268
269       if($capture->is_success()) { 
270           print "Card captured successfully: ".$capture->authorization."\n";
271       } else {
272           print "Card was rejected: ".$capture->error_message."\n";
273       }
274
275   }
276
277 =head1 SUPPORTED TRANSACTION TYPES
278
279 =head2 Visa, MasterCard, American Express, JCB, Discover/Novus, Carte blanche/Diners Club
280
281 Content required for `Authorization Only': type, action, amount,
282 invoice_number, customer_id, first_name, last_name, address, city, state, zip,
283 email, card_number, expiration, referer
284
285 Content required for `Post Authorization': action, login, password,
286 order_number, amount, authorization, description
287
288 `Normal Authorization' is not supported by the Bank of America gateway.
289
290 `Credit' is untested.
291
292 =head1 DESCRIPTION
293
294 For detailed information see L<Business::OnlinePayment>.
295
296 =head1 NOTE
297
298 Unlike Business::OnlinePayment or early verisons of
299 Business::OnlinePayment::AuthorizeNet, Business::OnlinePayment::BankOfAmerica
300 requires separate I<first_name> and I<last_name> fields.
301
302 An additional I<name> field is optional.  By default the I<first_name> and
303 I<last_name> fields will be concatenated.
304
305 =head1 NOTE
306
307 Business::OnlinePayment::BankOfAmerica does not support the
308 B<Normal Authorization> mode which combines authorization and capture into a
309 single tranaction.  You must use the B<Authorization Only> mode followed by the
310 B<Post Authorization> mode.  The B<Credit> mode is supported.
311
312 =head1 COMPATIBILITY
313
314 This module implements the interface documented at
315 http://www.bankofamerica.com/merchantservices/index.cfm?template=merch_ic_estores_developer.cfm
316
317 The settlement API is documented at
318 https://manager.bamart.com/welcome/SettlementAPI.pdf
319
320 =head1 BUGS
321
322 No login and password are required for B<Authorization Only> mode.  Access
323 is restricted only by the merchant id (available in any public store webpage
324 which passes off to the backend system) and HTTP referer header.
325
326 There is no way to run test transactions against the settlement API.
327
328 =head1 AUTHOR
329
330 Ivan Kohler <ivan-bofa@420.am>
331
332 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
333
334 =head1 SEE ALSO
335
336 perl(1). L<Business::OnlinePayment>.
337
338 =cut
339