unbreak this code
[Business-OnlinePayment-vSecureProcessing.git] / vSecureProcessing.pm
1 package Business::OnlinePayment::vSecureProcessing;
2
3 use strict;
4 use URI::Escape;
5 use Carp;
6 use Template;
7 use XML::Simple;
8 use Data::Dumper;
9 use MIME::Entity;
10
11 use Business::OnlinePayment;
12 use Business::OnlinePayment::HTTPS;
13 #use Net::SSLeay qw(post_http post_https make_headers make_form);
14 use vars qw($VERSION $DEBUG @ISA $me);
15
16 @ISA = qw(Business::OnlinePayment::HTTPS);
17 $DEBUG = 3;
18 $VERSION = '0.01';
19 $me = 'Business::OnlinePayment::vSecureProcessing';
20
21
22 # $server: http://dvrotsos2.kattare.com
23
24 # mapping out all possible endpoints
25 # but this version will only be building out "charge", "void", & "credit"
26 my %payment_actions = (
27     'charge' => {
28         path      => '/vsg2/processpayment',
29     },
30     'void' => {
31         path      => '/vsg2/processvoid',
32     },
33     'refund' => {
34         path      => '/vsg2/processrefund',
35     },
36     'authorize' => {
37         path      => '/vsg2/processauth',
38     },
39     'authorize_cancel' => {
40         path      => '/vsg2/processauthcancel',
41     },
42     'capture' => {
43         path      => '/vsg2/processcaptureonly',
44     },
45     'create_token' => {
46         path      => '/vsg2/createtoken',
47     },
48     'delete_token' => {
49         path      => '/vsg2/deletetoken',
50     },
51     'query_token' => {
52         path      => '/vsg2/querytoken',
53     },
54     'update_exp_date' => {
55         path      => '/vsg2/updateexpiration',
56     },
57     'update_token' => {
58         path      => '/vsg2/updatetoken',
59     },
60
61 );
62
63 my %action_mapping = (
64     'normal authorization'    => 'charge',
65     'credit'                => 'refund',
66     'authorization only'    => 'authorize',
67     'post authorization'    => 'capture',
68     'reverse authorization' => 'authorize_cancel'
69     # void => void
70 );
71
72 #BEGIN{
73 #eval 'use bytes; sub blength ($) { length $_[0] }';
74 #$@ and eval '    sub blength ($) { length $_[0] }' ;
75 #}
76 #sub Net::SSLeay::do_httpx3 {
77 #  $DB::single = 1;
78 #
79 #    my ($method, $usessl, $site, $port, $path, $headers,
80 #        $content, $mime_type, $crt_path, $key_path) = @_;
81 #    my ($response, $page, $h,$v);
82 #    my $CRLF = "\x0d\x0a";  # because \r\n is not fully portable
83 #    if ($content) {
84 #        $mime_type = "";#application/x-www-form-urlencoded" unless $mime_type;
85 #        my $len = length($content);
86 #        #$content = "$mime_type${CRLF}Content-Length: $len$CRLF$CRLF$content";
87 #        $content = "Cache-Control: no-cache$CRLF"
88 #            . "Content-Type: multipart/form-data; boundary=--FormBoundaryE19zNvXGzXaLvS5C$CRLF"
89 #            . "Accept: */*$CRLF"
90 #            . "Content-Length: $len$CRLF$CRLF$content";
91 #    } else {
92 #        $content = "$CRLF$CRLF";
93 #    }
94 #    my $req = "$method $path HTTP/1.1$CRLF";
95 #    unless (defined $headers && $headers =~ /^Host:/m) {
96 #        $req .= "Host: $site";
97 #        unless (($port == 80 && !$usessl) || ($port == 443 && $usessl)) {
98 #            $req .= ":$port";
99 #        }
100 #        $req .= $CRLF;
101 #    }
102 #    $req .= (defined $headers ? $headers : '') . "$content";
103 #
104 #    warn "do_httpx3($method,$usessl,$site:$port)" if $DEBUG;
105 #    my ($http, $errs, $server_cert)
106 #        = Net::SSLeay::httpx_cat($usessl, $site, $port, $req, $crt_path, $key_path);
107 #    return (undef, "HTTP/1.0 900 NET OR SSL ERROR$CRLF$CRLF$errs") if $errs;
108 #
109 #    $http = '' if !defined $http;
110 #    ($headers, $page) = split /\s?\n\s?\n/, $http, 2;
111 #    warn "headers >$headers< page >>$page<< http >>>$http<<<" if $DEBUG>1;
112 #    ($response, $headers) = split /\s?\n/, $headers, 2;
113 #    return ($page, $response, $headers, $server_cert);
114 #};
115
116 #sub Net::SSLeay::make_form {
117 #    my (@fields) = @_;
118 #    my $form;
119 #    while (@fields) {
120 #        my ($name, $data) = (shift(@fields), shift(@fields));
121 ##       $data =~ s/([^\w\-.\@\$ ])/sprintf("%%%2.2x",ord($1))/gse;
122 ##       $data =~ tr[ ][+];
123 #        $form .= "$name=$data&";
124 #    }
125 #    chop $form;
126 #    return $form;
127 #}
128
129 sub set_defaults {
130     my $self = shift;
131     my %options = @_;
132     
133     # inistialize standard B::OP attributes
134     $self->is_success(0);
135     $self->$_( '' ) for qw/authorization
136                            result_code
137                            error_message
138                            server
139                            port
140                            path
141                            server_response/;
142                            
143     # B::OP creates the following accessors:
144     #     server, port, path, test_transaction, transaction_type,
145     #     server_response, is_success, authorization,
146     #     result_code, error_message,
147     
148     $self->build_subs(qw/
149             env platform userid gid tid appid action
150             cvv_response avs_response risk_score
151     /);
152     
153     $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG;
154     
155     
156     
157     $self->server($options{'server'});
158     
159     $self->gid($options{'gid'});
160     
161     $self->tid($options{'tid'});
162     
163     $self->platform($options{'platform'});
164     
165     $self->appid($options{'appid'});
166     
167     $self->env((defined($options{'env'})) ? $options{'env'} : 'live'); # 'live'/'test'
168     
169 #     $self->port(($options{'env'} eq 'test') ? 80 : 443);
170     $self->port(443);
171 }
172
173
174
175 sub clean_content {
176     my ($self,$content) = @_;
177     my %content = $self->content();
178     
179     {
180         no warnings 'uninitialized';
181         
182         # strip non-digits from card number
183         my $card_number = '';
184         if ( $content{card_number} ) {
185             $content{card_number} =~ s/\D//g;
186         }
187         
188         # separate month and year values for expiry_date
189         if ( $content{expiration} ) {
190             ($content{exp_month}, $content{exp_year}) = split /\//, $content{expiration};
191             $content{exp_month} = sprintf "%02d", $content{exp_month};
192             $content{exp_year}  = substr($content{exp_year},0,2) if ($content{exp_year} > 99);
193         }
194         
195         if (!$content{'first_name'} || !$content{'last_name'} && $content{'name'}) {
196             ($content{'first_name'}, $content{'last_name'}) = split(' ', $content{'name'}, 2);
197         }
198         
199         if ($content{'address'} =~ m/[\D ]*(\d+)\D/) {
200             $content{'street_number'} = $1;
201         }
202     }
203     warn "Content after cleaning:\n".Dumper(\%content)."\n" if ($DEBUG >2);
204     $self->content(%content);
205 }
206
207 sub process_content {
208     my $self = shift;
209     $self->clean_content();
210     my %content = $self->content();
211     $self->action(($action_mapping{lc $content{'action'}}) ? $action_mapping{lc $content{'action'}} : lc $content{'action'});
212     $self->path($payment_actions{ $self->action }{path})
213       unless length($self->path);
214     $self->appid($content{appid}) if (!$self->appid && $content{appid});
215 }
216
217 sub submit {
218     my $self = shift;
219     
220     # inistialize standard B::OP attributes
221     $self->is_success(0);
222     $self->$_( '' ) for qw/authorization
223                            result_code
224                            error_message
225                            server_response/;
226                            
227     # clean and process the $self->content info
228     $self->process_content();
229     my %content = $self->content;
230     my $action = $self->action();
231     
232     my @acceptable_actions = ('charge', 'refund', 'void');
233     
234     unless ( grep { $action eq $_ } @acceptable_actions ) {
235         croak "'$action' is not supported at this time.";
236     }
237     
238     # fill out the template vars
239     my $template_vars = {
240         
241         auth => {
242             platform    => $self->platform,
243             userid        => $self->userid,
244             gid            => $self->gid,
245             tid            => $self->tid
246         },
247         
248         payment => {
249             amount            => $content{'amount'},
250             track1            => ($content{'track1'}) ? $content{'track1'} : '',
251             track2            => ($content{'track2'}) ? $content{'track2'} : '',
252             type            => ($content{'description'}) ? $content{'description'} : '',
253             cf1                => ($content{'UDField1'}) ? $content{'UDField1'} : '',
254             cf2                => ($content{'UDField2'}) ? $content{'UDField2'} : '',
255             cf3                => '',
256             account_number    => ($content{'card_number'}) ? $content{'card_number'} : '',
257             exp_month        => $content{'exp_month'},
258             exp_year        => $content{'exp_year'},
259             cvv                => ($content{'cvv'}) ? $content{'cvv'} : ($content{'cvv2'}) ? $content{'cvv2'} : '',
260             first_name        => ($content{'first_name'}) ? $content{'first_name'} : '',
261             last_name        => ($content{'last_name'}) ? $content{'last_name'} : '',
262             postal_code        => ($content{'zip'}) ? $content{'zip'} : '',
263             street_address    => ($content{'street_number'}) ? $content{'street_number'} : '',
264             industry_type    => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '',
265             invoice_num        => ($content{'invoice_number'}) ? $content{'invoice_number'} : '',
266             appid            => $self->appid(),
267             recurring        => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0,
268             response_code   => ($content{'response_code'}) ? $content{'response_code'} : '',
269             reference_number=> ($content{'ref_num'}) ? $content{'ref_num'} : '',
270             token           => ($content{'token'}) ? $content{'token'} : '',
271             receipt         => ($content{'receipt'}) ? $content{'receipt'} : '',
272             transaction_date=> ($content{'txn_date'}) ? $content{'txn_date'} : '',
273             merchant_data   => ($content{'merchant_data'}) ? $content{'merchant_data'} : '',
274         },
275         
276         # we won't be using level2 nor level3.  So I'm leaving them blank for now.
277         level2 => {
278             card_type                => '',
279             purchase_code            => '',
280             country_code            => '',
281             ship_tp_postal_code        => '',
282             ship_from_postal_code    => '',
283             sales_tax                => '',
284             product_description1    => '',
285             product_description2    => '',
286             product_description3    => '',
287             product_description4    => ''
288         },
289         
290         level3 => {
291             purchase_order_num    => '',
292             order_date            => '',
293             duty_amount            => '',
294             alt_tax_amount        => '',
295             discount_amount        => '',
296             freight_amount        => '',
297             tax_exempt            => '',
298             line_item_count        => '',
299             purchase_items        => $self->_parse_line_items()
300         }
301     };
302     
303   
304     # create the list of required fields based on the action
305     my @required_fields = qw/ amount /;
306     if ($action eq 'charge') {
307         push(@required_fields, $_) foreach (qw/ account_number cvv exp_month exp_year /);
308     }elsif ($action eq 'void') {
309         push(@required_fields, $_) foreach (qw/ response_code reference_number receipt token transaction_date exp_month exp_year /);
310     }elsif ($action eq 'refund') {
311         push(@required_fields, $_) foreach (qw/ merchant_data token account_number exp_month exp_year /);
312     }
313     
314     # check the requirements are met.
315     my @missing_fields;
316     foreach my $field (@required_fields) {
317         push(@missing_fields, $field) if (!$template_vars->{payment}{$field});
318     }
319     if (scalar(@missing_fields)) {
320         croak "Missing required fields: ".join(', ', @missing_fields);
321     }
322     
323     # read in the appropriate xml template
324     my $xml_template = _get_xml_template( $action );
325     # create a template object.
326     my $tt = Template->new();
327     # populate the XML template.
328     my $xml_data;
329     $tt->process( \$xml_template, $template_vars, \$xml_data ) || croak $tt->error();
330     
331     warn "XML:\n$xml_data\n" if $DEBUG > 2;
332     
333     my $boundary = sprintf('FormBoundary%06d', int(rand(1000000)));
334     # opts for B:OP:HTTPS::https_post
335     my $opts = { headers => {}, debug => $DEBUG };
336     $opts->{'Content-Type'} =
337     $opts->{headers}->{'Content-Type'} =
338         "multipart/form-data, boundary=$boundary";
339
340     my $content =
341       "--$boundary\n".
342      "Content-Disposition: form-data; name=\"param\"\n\n".
343      $xml_data."\n".
344      "--$boundary--\n";
345
346     # conform to RFC standards
347     $content =~ s/\n/\r\n/gs;
348
349     my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content );
350   
351     # store the server response.
352     $self->server_response($server_response);
353     # parse the result page.
354     $self->parse_response($page);
355     
356     if (!$self->is_success() && !$self->error_message() ) {
357         if ( $DEBUG ) {
358             #additional logging information, possibly too sensitive for an error msg
359             # (vSecureProcessing seems to have a failure mode where they return the full
360             #  original request including card number)
361             $self->error_message(
362               "(HTTPS response: ".$server_response.") ".
363               "(HTTPS headers: ".
364             join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
365               "(Raw HTTPS content: ".$page.")"
366             );
367         } else {
368             $self->error_message('No error information was returned by vSecureProcessing (enable debugging for raw HTTPS response)');
369         }
370     }
371     
372 }
373
374 # read $self->server_response and decipher any errors
375 sub parse_response {
376     my $self = shift;
377     my $page = shift;
378
379     if ($self->server_response =~ /^200/) {
380         my $response = XMLin($page);
381         $self->result_code($response->{Status});
382         $self->avs_response($response->{AvsResponse});
383         $self->cvv_response($response->{CvvResponse});
384         $self->is_success($self->result_code() eq '0' ? 1 : 0);
385         if ($self->is_success()) {
386             $self->authorization($response->{AuthIdentificationResponse});
387         }
388         # fill in error_message if there is is an error
389         if ( !$self->is_success && exists($response->{ResultCode})) {
390             $self->error_message('Error '.$response->{ResponseCode}.': '.$response->{ResultCode});
391         }elsif ( !$self->is_success && exists($response->{Receipt}) ) {
392             $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : '');
393         }
394         
395     }else {
396         $self->is_success(0);
397         $self->error_message('Error communicating with vSecureProcessing server');
398         return;
399     }
400     
401     
402 }
403
404 sub _get_xml_template {
405     my $action = shift;
406     
407     my $xml_template = q|<Request >
408     <MerchantData> 
409         <Platform>[% auth.platform %]</Platform>
410         <UserId>[% auth.userid %]</UserId> 
411         <GID>[% auth.gid %]</GID>
412         <Tid>[% auth.tid %]</Tid>
413     </MerchantData>
414     |;
415     
416     if ($action eq 'charge') {
417         $xml_template .= _get_xml_template_charge();
418     }elsif($action eq 'void') {
419         $xml_template .= _get_xml_template_void();
420     }elsif($action eq 'authorize') {
421         $xml_template .= _get_xml_template_auth();
422     }elsif($action eq 'authorize_cancel') {
423         $xml_template .= _get_xml_template_auth_cancel();
424     }elsif($action eq 'refund') {
425         $xml_template .= _get_xml_template_refund();
426     }elsif($action eq 'capture') {
427         $xml_template .= _get_xml_template_capture();
428     }elsif($action eq 'create_token') {
429         $xml_template .= _get_xml_template_create_token();
430     }elsif($action eq 'delete_token') {
431         $xml_template .= _get_xml_template_delete_token();
432     }elsif($action eq 'query_token') {
433         $xml_template .= _get_xml_template_query_token();
434     }elsif($action eq 'update_exp_date') {
435         $xml_template .= _get_xml_template_update_exp_date();
436     }elsif($action eq 'update_token') {
437         $xml_template .= _get_xml_template_update_token();
438     }
439     
440     $xml_template .= "</Request>";
441     $xml_template =~ s/[\n\t\s]*//g;
442     
443     return $xml_template;
444 }
445
446 sub _get_xml_template_charge {
447     my $xml_template = q|<ProcessPayment>
448         <Amount>[% payment.amount %]</Amount>
449         <Trk1>[% payment.track1 %]</Trk1>
450         <Trk2>[% payment.track2 %]</Trk2>
451         <TypeOfSale>[% payment.type %]</TypeOfSale>
452         <Cf1>[% payment.cf1 %]</Cf1>
453         <Cf2>[% payment.cf2 %]</Cf2>
454         <Cf3>[% payment.cf3 %]</Cf3>
455         <AccountNumber>[% payment.account_number %]</AccountNumber>
456         <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
457         <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
458         <Cvv>[% payment.cvv %]</Cvv>
459         <CardHolderFirstName>[% payment.first_name %]</CardHolderFirstName>
460         <CardHolderLastName>[% payment.last_name %]</CardHolderLastName>
461         <AvsZip>[% payment.postal_code %]</AvsZip>
462         <AvsStreet>[% payment.street_address %]</AvsStreet>
463         <IndustryType>
464             <IndType >[% payment.industry_type %]</IndType >
465             <IndInvoice>[% payment.invoice_num %]</IndInvoice>
466         </IndustryType>
467         <ApplicationId>[% payment.appid %]</ApplicationId>
468         <Recurring>[% payment.recurring %]</Recurring>
469     </ProcessPayment>|;
470     
471     # other options (that we are not using right now):    
472 #     <Level2PurchaseInfo>
473 #         <Level2CardType>[% level2.card_type %]</Level2CardType >
474 #         <PurchaseCode>[% level2.purchase_code %]</PurchaseCode>
475 #         <ShipToCountryCode>[% level2.country_code %]</ShipToCountryCode>
476 #         <ShipToPostalCode>[% level2.ship_tp_postal_code %]</ShipToPostalCode>
477 #         <ShipFromPostalCode>[% level2.ship_from_postal_code %]</ShipFromPostalCode>
478 #         <SalesTax>[% level2.sales_tax %]</SalesTax>
479 #         <ProductDescription1>[% level2.product_description1 %]</ProductDescription1>
480 #         <ProductDescription2>[% level2.product_description2 %]</ProductDescription2>
481 #         <ProductDescription3>[% level2.product_description3 %]</ProductDescription3>
482 #         <ProductDescription4>[% level2.product_description4 %]</ProductDescription4>
483 #     </Level2PurchaseInfo>
484 #     <Level3PurchaseInfo>
485 #         <PurchaseOrderNumber>[% level3.purchase_order_num %]</PurchaseOrderNumber>
486 #         <OrderDate>[% level3.order_date %]</OrderDate>
487 #         <DutyAmount>[% level3.duty_amount %]</DutyAmount>
488 #         <AlternateTaxAmount>[% level3.alt_tax_amount %]</AlternateTaxAmount>
489 #         <DiscountAmount>[% level3.discount_amount %]</DiscountAmount>
490 #         <FreightAmount>[% level3.freight_amount %]</FreightAmount>
491 #         <TaxExemptFlag>[% level3.tax_exempt %]</TaxExemptFlag>
492 #         <LineItemCount>[% level3.line_item_count %]</LineItemCount>
493 #         <PurchaseItems>
494 #             [% level3.purchase_items %]
495 #         </PurchaseItems>
496 #     </Level3PurchaseInfo>
497
498     return $xml_template;
499 }
500
501 sub _parse_line_items {
502     my $self = shift;
503     my %content = $self->content();
504     
505     return '' if (!$content{'items'});
506     
507     my @line_items;
508     my $template = q|            <LineItem>
509                 <ItemSequenceNumber>[% seq_num %]</ItemSequenceNumber>
510                 <ItemCode>[% code %]</ItemCode>
511                 <ItemDescription>[% desc %]</ItemDescription>
512                 <ItemQuantity>[% qty %]</ItemQuantity>
513                 <ItemUnitOfMeasure>[% unit %]</ItemUnitOfMeasure>
514                 <ItemUnitCost>[% unit_cost %]</ItemUnitCost>
515                 <ItemAmount>[% amount %]</ItemAmount>
516                 <ItemDiscountAmount>[% discount_amount %]</ItemDiscountAmount>
517                 <ItemTaxAmount>[% tax_amount %]</ItemTaxAmount>
518                 <ItemTaxRate>[% tax_rate %]</ItemTaxRate>
519             </LineItem>|;
520     
521     
522     my @items = $content{'items'};
523     foreach my $item (@items) {
524         # fille in the slots from $template with details in $item
525         # push to @line_items
526     }
527     
528     return join("\n", @line_items);
529 }
530
531 sub _get_xml_template_void {
532     my $xml_template = q|<ProcessVoid>
533         <Amount>[% payment.amount %]</Amount>
534         <AccountNumber>[% payment.account_number %]</AccountNumber>
535         <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
536         <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
537         <ReferenceNumber>[% payment.reference_number %]</ReferenceNumber>
538         <TransactionDate/>
539         <IndustryType1>[% payment.industry_type %]</IndustryType1>
540         <ApplicationId>[% payment.appid %]</ApplicationId>
541     </ProcessVoid>|;
542
543     return $xml_template;
544 }
545
546 sub _get_xml_template_refund {
547     my $xml_template = q|<ProcessRefund>
548         <Amount>[% payment.amount %]</Amount>
549         <AccountNumber>[% payment.account_number %]</AccountNumber>
550         <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
551         <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
552         <ApplicationId>[% payment.appid %]</ApplicationId>
553     </ProcessRefund>|;
554
555     return $xml_template;
556 }
557
558 sub _get_xml_template_auth {
559     my $xml_template = '';
560
561     return $xml_template;
562 }
563
564 sub _get_xml_template_auth_cancel {
565     my $xml_template = '';
566
567     return $xml_template;
568 }
569
570 sub _get_xml_template_capture {
571     my $xml_template = '';
572
573     return $xml_template;
574 }
575
576 sub _get_xml_template_create_token {
577     my $xml_template = '';
578
579     return $xml_template;
580 }
581
582 sub _get_xml_template_delete_token {
583     my $xml_template = '';
584
585     return $xml_template;
586 }
587
588 sub _get_xml_template_query_token {
589     my $xml_template = '';
590
591     return $xml_template;
592 }
593
594 sub _get_xml_template_update_exp_date {
595     my $xml_template = '';
596
597     return $xml_template;
598 }
599
600 sub _get_xml_template_update_token {
601     my $xml_template = '';
602
603     return $xml_template;
604 }
605
606
607 1;
608 __END__
609
610
611 =head1 NAME
612
613 Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Business::OnlinePayment
614
615 =head1 SYNOPSIS
616
617   use Business::OnlinePayment;
618   my %processor_info = (
619     platform    => '####',
620     gid         => 12345678901234567890,
621     tid         => 01,
622     user_id     => '####',
623     url         => 'www.####.com'
624   );
625   my $tx =
626     new Business::OnlinePayment( "vSecureProcessing", %processor_info);
627   $tx->content(
628       appid          => '######',
629       type           => 'VISA',
630       action         => 'Normal Authorization',
631       description    => 'Business::OnlinePayment test',
632       amount         => '49.95',
633       customer_id    => 'tfb',
634       name           => 'Tofu Beast',
635       address        => '123 Anystreet',
636       city           => 'Anywhere',
637       state          => 'UT',
638       zip            => '84058',
639       card_number    => '4007000000027',
640       expiration     => '09/02',
641       cvv2           => '1234', #optional
642   );
643   $tx->submit();
644
645   if($tx->is_success()) {
646       print "Card processed successfully: ".$tx->authorization."\n";
647   } else {
648       print "Card was rejected: ".$tx->error_message."\n";
649   }
650
651 =head1 DESCRIPTION
652
653 For detailed information see L<Business::OnlinePayment>.
654
655 =head1 METHODS AND FUNCTIONS
656
657 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
658
659 =head2 result_code
660
661 Returns the response error code.
662
663 =head2 error_message
664
665 Returns the response error description text.
666
667 =head2 server_response
668
669 Returns the complete response from the server.
670
671 =head1 Handling of content(%content) data:
672
673 =head2 action
674
675 The following actions are valid
676
677   normal authorization
678   credit
679   void
680
681 =head1 Setting vSecureProcessing parameters from content(%content)
682
683 The following rules are applied to map data to vSecureProcessing parameters
684 from content(%content):
685
686       # param => $content{<key>}
687       AccountNumber       => 'card_number',
688       Cvv                 => 'cvv2',
689       ExpirationMonth     => \( $month ), # MM from MM/YY of 'expiration'
690       ExpirationYear      => \( $year ), # YY from MM/YY of 'expiration'
691       Trk1                => 'track1',
692       Trk2                => 'track2',
693       CardHolderFirstName => 'first_name',
694       CardHolderLastName  => 'last_name',
695       Amount              => 'amount'
696       AvsStreet           => 'address',
697       AvsZip              => 'zip',
698       Cf1                 => 'UDField1',
699       Cf2                 => 'UDField2',
700       IndustryType        => 'IndustryInfo',
701
702 =head1 NOTE
703
704 =head1 COMPATIBILITY
705
706 Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014).
707
708 See http://www.vsecureprocessing.com/ for more information.
709
710 =head1 AUTHORS
711
712 Original author: Alex Brelsfoard
713
714 Current maintainer: Alex Brelsfoard
715
716 =head1 COPYRIGHT
717
718 Copyright (c) 2015 Freeside Internet Services, Inc.
719
720 All rights reserved.
721
722 This program is free software; you can redistribute it and/or modify it under
723 the same terms as Perl itself.
724
725 =head1 ADVERTISEMENT
726
727 Need a complete, open-source back-office and customer self-service solution?
728 The Freeside software includes support for credit card and electronic check
729 processing with vSecureProcessing and over 50 other gateways, invoicing, integrated
730 trouble ticketing, and customer signup and self-service web interfaces.
731
732 http://freeside.biz/freeside/
733
734 =head1 SEE ALSO
735
736 perl(1). L<Business::OnlinePayment>.
737
738 =cut
739
740