passing options to set_default required
[Business-OnlinePayment-viaKLIX.git] / viaKLIX.pm
1 package Business::OnlinePayment::viaKLIX;
2
3 use strict;
4 use vars qw($VERSION $DEBUG);
5 use Carp qw(carp croak);
6
7 use base qw(Business::OnlinePayment::HTTPS);
8
9 $VERSION = '0.01';
10 $VERSION = eval $VERSION;
11 $DEBUG   = 0;
12
13 sub debug {
14     my $self = shift;
15
16     if (@_) {
17         my $level = shift || 0;
18         if ( ref($self) ) {
19             $self->{"__DEBUG"} = $level;
20         }
21         else {
22             $DEBUG = $level;
23         }
24         $Business::OnlinePayment::HTTPS::DEBUG = $level;
25     }
26     return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
27 }
28
29 sub set_defaults {
30     my $self = shift;
31     my %opts = @_;
32
33     # standard B::OP methods/data
34     $self->server("www.viaKLIX.com");
35     $self->port("443");
36     $self->path("/process.asp");
37
38     $self->build_subs(qw( 
39                           order_number avs_code cvv2_response
40                           response_page response_code response_headers
41                      ));
42
43     # module specific data
44     if ( $opts{debug} ) {
45         $self->debug( $opts{debug} );
46         delete $opts{debug};
47     }
48
49     my %_defaults = ();
50     foreach my $key (keys %opts) {
51       $key =~ /^default_(\w*)$/ or next;
52       $_defaults{$1} = $opts{$key};
53       delete $opts{$key};
54     }
55     $self->{_defaults} = \%_defaults;
56
57 }
58
59 sub _map_fields {
60     my ($self) = @_;
61
62     my %content = $self->content();
63
64     #ACTION MAP
65     my %actions = (
66         'normal authorization' => 'SALE',  # Authorization/Settle transaction
67         'credit'               => 'CREDIT', # Credit (refund)
68     );
69
70     $content{'ssl_transaction_type'} = $actions{ lc( $content{'action'} ) }
71       || $content{'action'};
72
73     # TYPE MAP
74     my %types = (
75         'visa'             => 'CC',
76         'mastercard'       => 'CC',
77         'american express' => 'CC',
78         'discover'         => 'CC',
79         'cc'               => 'CC',
80     );
81
82     $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
83
84     $self->transaction_type( $content{'type'} );
85
86     # stuff it back into %content
87     $self->content(%content);
88 }
89
90 sub _revmap_fields {
91     my ( $self, %map ) = @_;
92     my %content = $self->content();
93     foreach ( keys %map ) {
94         $content{$_} =
95           ref( $map{$_} )
96           ? ${ $map{$_} }
97           : $content{ $map{$_} };
98     }
99     $self->content(%content);
100 }
101
102 sub expdate_mmyy {
103     my $self       = shift;
104     my $expiration = shift;
105     my $expdate_mmyy;
106     if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
107         my ( $month, $year ) = ( $1, $2 );
108         $expdate_mmyy = sprintf( "%02d", $month ) . $year;
109     }
110     return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
111 }
112
113 sub required_fields {
114     my($self,@fields) = @_;
115
116     my @missing;
117     my %content = $self->content();
118     foreach(@fields) {
119       next
120         if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
121       push(@missing, $_);
122     }
123
124     Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
125       if(@missing);
126
127 }
128
129 sub submit {
130     my ($self) = @_;
131
132     $self->_map_fields();
133
134     my %content = $self->content;
135
136     my %required;
137     $required{CC_SALE} =  [ qw( ssl_transaction_type ssl_merchant_id ssl_pin
138                                 ssl_amount ssl_card_number ssl_exp_date
139                               ) ];
140     $required{CC_CREDIT} = $required{CC_SALE};
141     my %optional;
142     $optional{CC_SALE} =  [ qw( ssl_user_id ssl_salestax ssl_cvv2 ssl_cvv2cvc2
143                                 ssl_description ssl_invoice_number
144                                 ssl_customer_code ssl_company ssl_first_name
145                                 ssl_last_name ssl_avs_address ssl_address2
146                                 ssl_city ssl_state ssl_avs_zip ssl_country
147                                 ssl_phone ssl_email ssl_ship_to_company
148                                 ssl_ship_to_first_name ssl_ship_to_last_name
149                                 ssl_ship_to_address ssl_ship_to_city
150                                 ssl_ship_to_state ssl_ship_to_zip
151                                 ssl_ship_to_country
152                               ) ];
153     $optional{CC_CREDIT} = $optional{CC_SALE};
154
155     my $type_action = $self->transaction_type(). '_'. $content{ssl_transaction_type};
156     unless ( exists($required{$type_action}) ) {
157       $self->error_message("viaKLIX can't handle transaction type: ".
158         "$content{action} on " . $self->transaction_type() );
159       $self->is_success(0);
160       return;
161     }
162
163     my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
164     my $zip          = $content{'zip'};
165     $zip =~ s/[^[:alnum:]]//g;
166
167     my $cvv2indicator = 'present' if ( $content{"cvv2"} ); # visa only
168
169     $self->_revmap_fields(
170
171         ssl_merchant_id     => 'login',
172         ssl_pin             => 'password',
173
174         ssl_amount          => 'amount',
175         ssl_card_number     => 'card_number',
176         ssl_exp_date        => \$expdate_mmyy,    # MMYY from 'expiration'
177         ssl_cvv2            => \$cvv2indicator,
178         ssl_cvv2cvc2        => 'cvv2',
179         ssl_description     => 'description',
180         ssl_invoice_number  => 'invoice_number',
181         ssl_customer_code   => 'customer_id',
182
183         ssl_first_name      => 'first_name',
184         ssl_last_name       => 'last_name',
185         ssl_avs_address     => 'address',
186         ssl_city            => 'city',
187         ssl_state           => 'state',
188         ssl_avs_zip         => \$zip,          # 'zip' with non-alnums removed
189         ssl_country         => 'country',
190         ssl_phone           => 'phone',
191         ssl_email           => 'email',
192
193     );
194
195     my %params = $self->get_fields( @{$required{$type_action}},
196                                     @{$optional{$type_action}},
197                                   );
198
199     foreach ( keys ( %{($self->{_defaults})} ) ) {
200       $params{$_} = $self->{_defaults}->{$_} unless exists($params{$_});
201     }
202
203     $params{ssl_test_mode}='true' if $self->test_transaction;
204     
205     $params{ssl_show_form}='false';
206     $params{ssl_result_format}='ASCII';
207
208     $self->required_fields(@{$required{$type_action}});
209     
210     warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $DEBUG > 1;
211     my ( $page, $resp, %resp_headers ) = 
212       $self->https_post( %params );
213
214     $self->response_code( $resp );
215     $self->response_page( $page );
216     $self->response_headers( \%resp_headers );
217
218     warn "$page\n" if $DEBUG > 1;
219     # $page should contain key/value pairs
220
221     my $status ='';
222     my %results = map { s/\s*$//; split '=', $_, 2 } split '^', $page;
223
224     # AVS and CVS values may be set on success or failure
225     $self->avs_code( $results{ssl_avs_response} );
226     $self->cvv2_response( $results{ ssl_cvv2_response } );
227     $self->result_code( $status = $results{ ssl_result } );
228     $self->order_number( $results{ ssl_txn_id } );
229     $self->authorization( $results{ ssl_approval_code } );
230     $self->error_message( $results{ ssl_result_message } );
231
232
233     if ( $resp =~ /^(HTTP\S+ )?200/ && $status eq "0" ) {
234         $self->is_success(1);
235     } else {
236         $self->is_success(0);
237     }
238 }
239
240 1;
241
242 __END__
243
244 =head1 NAME
245
246 Business::OnlinePayment::viaKLIX - viaKLIX backend for Business::OnlinePayment
247
248 =head1 SYNOPSIS
249
250   use Business::OnlinePayment;
251   
252   my $tx = new Business::OnlinePayment(
253       'viaKLIX', 'default_ssl_user_id' => 'webuser',
254   );
255   
256   # See the module documentation for details of content()
257   $tx->content(
258       type           => 'CC',
259       action         => 'Normal Authorization',
260       description    => 'Business::OnlinePayment::viaKLIX test',
261       amount         => '49.95',
262       invoice_number => '100100',
263       customer_id    => 'jef',
264       name           => 'Jeff Finucane',
265       address        => '123 Anystreet',
266       city           => 'Anywhere',
267       state          => 'GA',
268       zip            => '30004',
269       email          => 'viaklix@weasellips.com',
270       card_number    => '4111111111111111',
271       expiration     => '12/09',
272       cvv2           => '123',
273       order_number   => 'string',
274   );
275   
276   $tx->submit();
277   
278   if ( $tx->is_success() ) {
279       print(
280           "Card processed successfully: ", $tx->authorization, "\n",
281           "order number: ",                $tx->order_number,  "\n",
282           "CVV2 response: ",               $tx->cvv2_response, "\n",
283           "AVS code: ",                    $tx->avs_code,      "\n",
284       );
285   }
286   else {
287       print(
288           "Card was rejected: ", $tx->error_message, "\n",
289           "order number: ",      $tx->order_number,  "\n",
290       );
291   }
292
293 =head1 DESCRIPTION
294
295 This module is a back end driver that implements the interface
296 specified by L<Business::OnlinePayment> to support payment handling
297 via viaKLIX's Internet payment solution.
298
299 See L<Business::OnlinePayment> for details on the interface this
300 modules supports.
301
302 =head1 Standard methods
303
304 =over 4
305
306 =item set_defaults()
307
308 This method sets the 'server' attribute to 'www.viaklix.com' and
309 the port attribute to '443'.  This method also sets up the
310 L</Module specific methods> described below.
311
312 =item submit()
313
314 =back
315
316 =head1 Unofficial methods
317
318 This module provides the following methods which are not officially part of the
319 standard Business::OnlinePayment interface (as of 3.00_06) but are nevertheless
320 supported by multiple gateways modules and expected to be standardized soon:
321
322 =over 4
323
324 =item L<order_number()|/order_number()>
325
326 =item L<avs_code()|/avs_code()>
327
328 =item L<cvv2_response()|/cvv2_response()>
329
330 =back
331
332 =head1 Module specific methods
333
334 This module provides the following methods which are not currently
335 part of the standard Business::OnlinePayment interface:
336
337 =over 4
338
339 =item L<expdate_mmyy()|/expdate_mmyy()>
340
341 =item L<debug()|/debug()>
342
343 =back
344
345 =head1 Settings
346
347 The following default settings exist:
348
349 =over 4
350
351 =item server
352
353 www.viaklix.com
354
355 =item port
356
357 443
358
359 =item path
360
361 /process.asp
362
363 =back
364
365 =head1 Parameters passed to constructor
366
367 If any of the key/value pairs passed to the constructor have a key
368 beginning with "default_" then those values are passed to viaKLIX as
369 a the corresponding form field (without the "default_") whenever
370 content(%content) lacks that key.
371
372 =head1 Handling of content(%content)
373
374 The following rules apply to content(%content) data:
375
376 =head2 type
377
378 If 'type' matches one of the following keys it is replaced by the
379 right hand side value:
380
381   'visa'               => 'CC',
382   'mastercard'         => 'CC',
383   'american express'   => 'CC',
384   'discover'           => 'CC',
385
386 The value of 'type' is used to set transaction_type().  Currently this
387 module only supports the above values.
388
389 =head1 Setting viaKLIX parameters from content(%content)
390
391 The following rules are applied to map data to viaKLIX parameters
392 from content(%content):
393
394     # viaKLIX param     => $content{<key>}
395       ssl_merchant_id   => 'login',
396       ssl_pin           => 'password',
397
398       ssl_amount        => 'amount',
399       ssl_card_number   => 'card_number',
400       ssl_exp_date      => \( $month.$year ), # MM/YY from 'expiration'
401       ssl_cvv2          => 'present' whenever cvv2 data is provided
402       ssl_cvv2cvc2      => 'cvv2',
403       ssl_description   => 'description',
404       ssl_invoice_number=> 'invoice_number',
405       ssl_customer_code   => 'customer_id',
406
407       ssl_first_name    => 'first_name',
408       ssl_last_name     => 'last_name',
409       ssl_avs_address   => 'address',
410       ssl_city          => 'city',
411       ssl_state         => 'state',
412       ssl_zip           => \$zip,       # 'zip' with non-alphanumerics removed
413       ssl_country       => 'country',
414       ssl_phone         => 'phone',
415       ssl_email         => 'email',
416
417       CardHolderName    => 'name',
418       CustomerName      => 'account_name',
419
420
421 =head1 Mapping viaKLIX transaction responses to object methods
422
423 The following methods provides access to the transaction response data
424 resulting from a viaKLIX request (after submit()) is called:
425
426 =head2 order_number()
427
428 This order_number() method returns the ssl_txn_id field for card transactions
429 to uniquely identify the transaction.
430
431 =head2 result_code()
432
433 The result_code() method returns the ssl_result field for card transactions.
434 It is the numeric return code indicating the outcome of the attempted
435 transaction.
436
437 =head2 error_message()
438
439 The error_message() method returns the ssl_result_message field for
440 transactions.  This provides more details about the transaction result.
441
442 =head2 authorization()
443
444 The authorization() method returns the ssl_approval_code field,
445 which is the approval code obtained from the card processing network.
446
447 =head2 avs_code()
448
449 The avs_code() method returns the ssl_avs_response field from the
450 transaction result.
451
452 =head2 cvv2_response()
453
454 The cvv2_response() method returns the ssl_cvvw_response field, which is a
455 response message returned with the transaction result.
456
457 =head2 expdate_mmyy()
458
459 The expdate_mmyy() method takes a single scalar argument (typically
460 the value in $content{expiration}) and attempts to parse and format
461 and put the date in MMYY format as required by PayflowPro
462 specification.  If unable to parse the expiration date simply leave it
463 as is and let the PayflowPro system attempt to handle it as-is.
464
465 =head2 debug()
466
467 Enable or disble debugging.  The value specified here will also set
468 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
469 troubleshooting problems.
470
471 =head1 COMPATIBILITY
472
473 This module implements an interface to the viaKLIX API version 2.0
474
475 =head1 AUTHORS
476
477 Jeff Finucane <viaklix@weasellips.com>
478
479 Based on Business::OnlinePayment::PayflowPro written by Ivan Kohler
480 and Phil Lobbes.
481
482 =head1 SEE ALSO
483
484 perl(1), L<Business::OnlinePayment>, L<Carp>, and the Developer Guide to the
485 viaKLIX Virtual Terminal.
486
487 =cut