19f3e5350bf8512549104d7a26524e3e99f454d6
[Business-OnlinePayment-CardFortress.git] / lib / Business / OnlinePayment / CardFortress.pm
1 package Business::OnlinePayment::CardFortress;
2
3 use base qw( Business::OnlinePayment::HTTPS );
4
5 use warnings;
6 use strict;
7 #use vars qw( $DEBUG $me );
8 use File::Slurp;
9 use MIME::Base64;
10 use Crypt::OpenSSL::RSA;
11
12 our $VERSION = 0.02;
13
14 sub _info {
15   {
16     'info_version'      => '0.01',
17     'module_version'    => $VERSION,
18     'supported_types'   => [ 'CC' ],
19     'supported_actions' => { 'CC' => [
20                                        'Normal Authorization',
21                                        'Authorization Only',
22                                        'Post Authorization',
23                                        'Void',
24                                        'Credit',
25                                      ],
26                            },
27     'token_support'     => 1,
28     #need to figure out how to pass through for gateways that do... an option?
29     #'CC_void_requires_card' => 1,
30   };
31 }
32
33 sub set_defaults {
34   my $self = shift;
35   my %opts = @_;
36   
37   $self->server('gw.cardfortress.com') unless $self->server;
38
39   $self->port('443') unless $self->port;
40   $self->path('/bop/index.html') unless $self->path;
41
42   $self->build_subs(qw( order_number avs_code cvv2_response
43                         response_page response_code response_headers
44                         card_token private_key
45                    ));
46 }
47
48 sub submit {
49   my $self = shift;
50
51   $self->server('test.cardfortress.com') if $self->test_transaction;
52
53   my %content = $self->content;
54   $content{$_} = $self->$_() for qw( gateway gateway_login gateway_password );
55
56   my ($page,$server_response,%headers) = $self->https_post(%content);
57
58   die "$server_response\n" unless $server_response =~ /^200/;
59
60   my %response = ();
61   #this encoding good enough?  wfm... if something's easier for other
62   #languages they can always use a different URL
63   foreach my $line ( grep /^\w+=/, split(/\n/, $page) ) {
64     $line =~ /^(\w+)=(.*)$/ or next;
65     $response{$1} = $2;
66   }
67
68   foreach (qw( is_success error_message failure_status
69                authorization order_number
70                fraud_score fraud_transaction_id
71                result_code avs_code cvv2_response
72                card_token
73              )) {
74     $self->$_($response{$_});
75   }
76
77   #map these to gateway_response_code, etc?
78   # response_code()
79   # response_headers()
80   # response_page()
81
82   #handle the challenge/response handshake
83   if ( $self->error_message eq '_challenge' ) { #XXX infinite loop protection?
84
85     my $private_key = $self->private_key
86       or die "no private key available";
87
88     $private_key = read_file($private_key)
89       if $private_key !~ /-----BEGIN/ && -r $private_key;
90
91     #decrypt the challenge with the private key
92     my $challenge = decode_base64($response{'card_challenge'});
93
94     #here is the hardest part to implement at each client side
95     my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($private_key);
96     my $response = $rsa_priv->decrypt($challenge);
97
98     #try the transaction again with the challenge response
99     # (B:OP could sure use a better way to alter one value)
100     my %content = $self->content;
101     $content{'card_response'} = encode_base64($response, '');
102     $self->content(%content);
103     $self->submit;
104   }
105
106 }
107
108 1;
109
110 __END__
111
112 =head1 NAME
113
114 Business::OnlinePayment::CardFortress - CardFortress backend for Business::OnlinePayment
115
116 =head1 SYNOPSIS
117
118   use Business::OnlinePayment;
119
120   my $tx = new Business::OnlinePayment(
121     'CardFortress',
122       'gateway'          => 'ProcessingGateway',
123       'gateway_login'    => 'gwlogin',
124       'gateway_password' => 'gwpass',
125       #private_key not necessary
126   );
127
128   $tx->content(
129       type           => 'VISA',
130       login          => 'cardfortress_login',
131       password       => 'cardfortress_pass',
132       action         => 'Normal Authorization',
133       description    => 'Business::OnlinePayment test',
134       amount         => '49.95',
135       customer_id    => 'tfb',
136       name           => 'Tofu Beast',
137       address        => '123 Anystreet',
138       city           => 'Anywhere',
139       state          => 'UT',
140       zip            => '84058',
141       card_number    => '4007000000027',
142       expiration     => '09/02',
143       cvv2           => '1234', #optional (not stored)
144   );
145   $tx->submit();
146
147   if($tx->is_success()) {
148       print "Card processed successfully: ".$tx->authorization."\n";
149       $token = $tx->card_token;
150       print "Card token is: $token\n";
151   } else {
152       print "Card was rejected: ".$tx->error_message."\n";
153   }
154
155   # ... time slips by ...
156
157   my $rx = new Business::OnlinePayment(
158     'CardFortress',
159       'gateway'          => 'ProcessingGateway',
160       'gateway_login'    => 'gwlogin',
161       'gateway_password' => 'gwpass',
162       'private_key'      => $private_key_string, #or filename
163   );
164
165   $rx->content(
166       type           => 'VISA',
167       login          => 'cardfortress_login',
168       password       => 'cardfortress_pass',
169       action         => 'Normal Authorization',
170       description    => 'Business::OnlinePayment test',
171       amount         => '49.95',
172       card_token     => $card_token
173       cvv2           => '1234', #optional, typically not necessary w/followup tx
174   );
175   $rx->submit();
176
177 =head1 DESCRIPTION
178
179 This is a Business::OnlinePayment backend module for the gateway-independent
180 CardFortress storage service (http://cardfortress.com/).
181
182 =head1 SUPPORTED TRANSACTION TYPES
183
184 =head2 CC, Visa, MasterCard, American Express, Discover
185
186 Content required: type, login, action, amount, card_number, expiration.
187
188 =head1 METHODS AND FUNCTIONS
189
190 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
191
192 =head2 card_token
193
194 Returns the card token for any transaction.  The card token can be used in
195 a subsequent transaction as a replacement for the card number and expiration
196 (as well as customer/AVS data).
197
198 =head2 result_code
199
200 Returns the response error code.
201
202 =head2 error_message
203
204 Returns the response error description text.
205
206 =head2 server_response
207
208 Returns the complete response from the server.
209
210 =head1 AUTHOR
211
212 Ivan Kohler C<< <ivan-bop-cardfortress at freeside.biz> >>
213
214 =head1 COPYRIGHT & LICENSE
215
216 Copyright 2008-2010 Freeside Internet Services, Inc. (http://freeside.biz/)
217 All rights reserved.
218
219 This program is free software; you can redistribute it and/or modify it
220 under the same terms as Perl itself.
221
222 =cut
223
224 1;