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