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