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