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