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