Getting code ready for CPAN and debian relase.
authorAlex Brelsfoard <alex@Alexs-MacBook-Pro.local>
Mon, 9 Feb 2015 16:58:10 +0000 (11:58 -0500)
committerAlex Brelsfoard <alex@Alexs-MacBook-Pro.local>
Mon, 9 Feb 2015 16:58:10 +0000 (11:58 -0500)
16 files changed:
.gitignore [new file with mode: 0644]
Changes [new file with mode: 0644]
MANIFEST
extra/test.pl [new file with mode: 0755]
ignore.txt [new file with mode: 0644]
lib/Business/OnlinePayment/vSecureProcessing.pm [new file with mode: 0644]
t/lib/test_account.pl [new file with mode: 0644]
t/t_00-load.t [new file with mode: 0644]
t/t_boilerplate.t [new file with mode: 0644]
t/t_manifest.t [new file with mode: 0644]
t/t_pod-coverage.t [new file with mode: 0644]
t/t_pod.t [new file with mode: 0644]
t/t_transaction.t [new file with mode: 0644]
t/t_transaction_decline.t [new file with mode: 0644]
test.pl [deleted file]
vSecureProcessing.pm [deleted file]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9788afa
--- /dev/null
@@ -0,0 +1,6 @@
+blib/
+*.sw?
+Makefile
+Makefile.old
+MYMETA.yml
+pm_to_blib
diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..716346c
--- /dev/null
+++ b/Changes
@@ -0,0 +1,5 @@
+Revision history for Perl module Business::OnlinePayment::vSecureProcessing
+
+0.01    Feb 06 2015
+        Initial release.
+
index 2c03f96..df6a903 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -4,10 +4,10 @@ Makefile.PL
 README
 ignore.txt
 lib/Business/OnlinePayment/vSecureProcessing.pm
-t/00-load.t
-t/boilerplate.t
-t/manifest.t
-t/pod-coverage.t
-t/pod.t
-t/transaction.t
-t/transaction_decline.t
+t/t_00-load.t
+t/t_boilerplate.t
+t/t_manifest.t
+t/t_pod-coverage.t
+t/t_pod.t
+t/t_transaction.t
+t/t_transaction_decline.t
diff --git a/extra/test.pl b/extra/test.pl
new file mode 100755 (executable)
index 0000000..bca3628
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/bin/perl -w
+
+#
+# Make sure to copy Business::OnlinePayment::vSecureProcessing into its
+# proper system directory (aka /usr/share/perl5/Business/Onlinepayment)
+#
+
+use strict;
+use Data::Dumper;
+use Business::OnlinePayment;
+
+my %opt = (
+    server =>'dvrotsos2.kattare.com',
+    platform => 'Buypass',
+    gid => '1432479912596791',
+    tid => '01',
+    userid=> 'tom@yiptv.com',
+    port => 443,
+    env => 'test'
+);
+
+my $action = shift || 'Normal Authorization';
+my $auth = shift || '';
+
+
+my %content = (
+    appid          => 'yiptv',
+    action         => $action,
+    auth           => $auth,
+    description    => 'Business::OnlinePayment visa test',
+#    card_number    => '4007000000027',
+    card_number    => '4111111111111111',
+    cvv2           => '111',
+    expiration     => expiration_date(),
+    amount         => '42.24',
+    name           => 'Murphy Law',
+    email          => 'fake@acme.com',
+    address        => '123 Anystreet',
+    zip            => '84058',
+);
+
+main();
+
+sub main {
+    my $transaction = Business::OnlinePayment->new("vSecureProcessing", %opt);
+    
+    print "MAKING PAYMENT\n";
+    ProcessTransaction($transaction);
+    $content{'action'} = 'void';
+    $content{'ref_num'} = $transaction->authorization();
+    $content{'txn_date'} = $transaction->txn_date();
+    $content{'amount'} = $transaction->txn_amount;
+    print "VOIDING PAYMENT\n";
+    ProcessTransaction($transaction);
+    $content{'action'} = 'Normal Authorization';
+    $content{'amount'} = '30.00';
+    print "MAKING PAYMENT\n";
+    ProcessTransaction($transaction);
+    $content{'action'} = 'credit';
+    $content{'ref_num'} = $transaction->authorization;
+    $content{'txn_date'} = $transaction->txn_date;
+    $content{'amount'} = $transaction->txn_amount;
+    print "REFUNDING PAYMENT\n";
+    ProcessTransaction($transaction);
+}
+
+sub ProcessTransaction {
+    my $transaction = shift;
+    print "Processing transaction with content:\n".Dumper(\%content)."\n";
+    $transaction->content(%content);
+
+    eval { $transaction->submit(); };
+
+    if ( $@ ) {
+    
+    die "Error: $@\n";
+    
+    } else {
+    
+        if ( $transaction->is_success() ) {
+            print "Card processed successfully: ". $transaction->authorization()."\n";
+        } else {
+            print "Card was rejected: ". $transaction->error_message(). "\n";
+        }
+    }
+}
+
+
+sub expiration_date {
+    my($month, $year) = (localtime)[4,5];
+    $month += 1;
+    $year++;       # So we expire next year.
+    $year %= 100;  # y2k?  What's that?
+
+    return sprintf("%02d/%02d", $month, $year);
+}
diff --git a/ignore.txt b/ignore.txt
new file mode 100644 (file)
index 0000000..997590b
--- /dev/null
@@ -0,0 +1,12 @@
+blib*
+Makefile
+Makefile.old
+Build
+Build.bat
+_build*
+pm_to_blib*
+*.tar.gz
+.lwpcookies
+cover_db
+pod2htm*.tmp
+Business-OnlinePayment-vSecureProcessing-*
\ No newline at end of file
diff --git a/lib/Business/OnlinePayment/vSecureProcessing.pm b/lib/Business/OnlinePayment/vSecureProcessing.pm
new file mode 100644 (file)
index 0000000..e7514a3
--- /dev/null
@@ -0,0 +1,690 @@
+package Business::OnlinePayment::vSecureProcessing;
+
+use strict;
+use Carp;
+use Template;
+use XML::Simple;
+use Data::Dumper;
+
+use Business::OnlinePayment;
+use Business::OnlinePayment::HTTPS;
+#use Net::SSLeay qw(post_http post_https make_headers make_form);
+use vars qw($VERSION $DEBUG @ISA $me);
+
+@ISA = qw(Business::OnlinePayment::HTTPS);
+$DEBUG = 0;
+$VERSION = '0.01';
+$me = 'Business::OnlinePayment::vSecureProcessing';
+
+
+# $server: http://dvrotsos2.kattare.com
+
+# mapping out all possible endpoints
+# but this version will only be building out "charge", "void", & "credit"
+my %payment_actions = (
+    'charge' => {
+        path      => '/vsg2/processpayment',
+    },
+    'void' => {
+        path      => '/vsg2/processvoid',
+    },
+    'refund' => {
+        path      => '/vsg2/processrefund',
+    },
+    'authorize' => {
+        path      => '/vsg2/processauth',
+    },
+    'authorize_cancel' => {
+        path      => '/vsg2/processauthcancel',
+    },
+    'capture' => {
+        path      => '/vsg2/processcaptureonly',
+    },
+    'create_token' => {
+        path      => '/vsg2/createtoken',
+    },
+    'delete_token' => {
+        path      => '/vsg2/deletetoken',
+    },
+    'query_token' => {
+        path      => '/vsg2/querytoken',
+    },
+    'update_exp_date' => {
+        path      => '/vsg2/updateexpiration',
+    },
+    'update_token' => {
+        path      => '/vsg2/updatetoken',
+    },
+
+);
+
+my %action_mapping = (
+    'normal authorization'  => 'charge',
+    'credit'                => 'refund',
+    'authorization only'    => 'authorize',
+    'post authorization'    => 'capture',
+    'reverse authorization' => 'authorize_cancel'
+    # void => void
+);
+
+sub set_defaults {
+    my $self = shift;
+    my %options = @_;
+    
+    # inistialize standard B::OP attributes
+    $self->is_success(0);
+    $self->$_( '' ) for qw/authorization
+                           result_code
+                           error_message
+                           server
+                           port
+                           path
+                           server_response/;
+                           
+    # B::OP creates the following accessors:
+    #     server, port, path, test_transaction, transaction_type,
+    #     server_response, is_success, authorization,
+    #     result_code, error_message,
+    
+    $self->build_subs(qw/
+            env platform userid gid tid appid action reference_number cvv_response
+            avs_response risk_score txn_amount txn_date response_code
+    /);
+    
+    $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG;
+    
+    
+    
+    $self->server($options{'server'});
+    
+    $self->gid($options{'gid'});
+    
+    $self->tid($options{'tid'});
+    
+    $self->platform($options{'platform'});
+    
+    $self->appid($options{'appid'});
+    
+    $self->env((defined($options{'env'})) ? $options{'env'} : 'live'); # 'live'/'test'
+    
+    $self->port(443);
+}
+
+
+
+sub clean_content {
+    my ($self,$content) = @_;
+    my %content = $self->content();
+    
+    {
+        no warnings 'uninitialized';
+        
+        # strip non-digits from card number
+        my $card_number = '';
+        if ( $content{card_number} ) {
+            $content{card_number} =~ s/\D//g;
+        }
+        
+        # separate month and year values for expiry_date
+        if ( $content{expiration} ) {
+            ($content{exp_month}, $content{exp_year}) = split /\//, $content{expiration};
+            $content{exp_month} = sprintf "%02d", $content{exp_month};
+            $content{exp_year}  = substr($content{exp_year},0,2) if ($content{exp_year} > 99);
+        }
+        
+        if (!$content{'first_name'} || !$content{'last_name'} && $content{'name'}) {
+            ($content{'first_name'}, $content{'last_name'}) = split(' ', $content{'name'}, 2);
+        }
+        
+        if ($content{'address'} =~ m/[\D ]*(\d+)\D/) {
+            $content{'street_number'} = $1;
+        }
+    }
+    warn "Content after cleaning:\n".Dumper(\%content)."\n" if ($DEBUG >2);
+    $self->content(%content);
+}
+
+sub process_content {
+    my $self = shift;
+    $self->clean_content();
+    my %content = $self->content();
+    $self->action(($action_mapping{lc $content{'action'}}) ? $action_mapping{lc $content{'action'}} : lc $content{'action'});
+    $self->path($payment_actions{ $self->action }{path})
+      unless length($self->path);
+    $self->appid($content{appid}) if (!$self->appid && $content{appid});
+}
+
+sub submit {
+    my $self = shift;
+    
+    # inistialize standard B::OP attributes
+    $self->is_success(0);
+    $self->$_( '' ) for qw/authorization
+                           result_code
+                           error_message
+                           server_response/;
+                           
+    # clean and process the $self->content info
+    $self->process_content();
+    my %content = $self->content;
+    my $action = $self->action();
+    
+    my @acceptable_actions = ('charge', 'refund', 'void');
+    
+    unless ( grep { $action eq $_ } @acceptable_actions ) {
+        croak "'$action' is not supported at this time.";
+    }
+    
+    # fill out the template vars
+    my $template_vars = {
+        
+        auth => {
+            platform    => $self->platform,
+            userid      => $self->userid,
+            gid         => $self->gid,
+            tid         => $self->tid
+        },
+        
+        payment => {
+            amount          => $content{'amount'},
+            track1          => ($content{'track1'}) ? $content{'track1'} : '',
+            track2          => ($content{'track2'}) ? $content{'track2'} : '',
+            type            => ($content{'description'}) ? $content{'description'} : '',
+            cf1             => ($content{'UDField1'}) ? $content{'UDField1'} : '',
+            cf2             => ($content{'UDField2'}) ? $content{'UDField2'} : '',
+            cf3             => '',
+            account_number  => ($content{'card_number'}) ? $content{'card_number'} : '',
+            exp_month       => $content{'exp_month'},
+            exp_year        => $content{'exp_year'},
+            cvv             => ($content{'cvv'}) ? $content{'cvv'} : ($content{'cvv2'}) ? $content{'cvv2'} : '',
+            first_name      => ($content{'first_name'}) ? $content{'first_name'} : '',
+            last_name       => ($content{'last_name'}) ? $content{'last_name'} : '',
+            postal_code     => ($content{'zip'}) ? $content{'zip'} : '',
+            street_address  => ($content{'street_number'}) ? $content{'street_number'} : '',
+            industry_type   => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '',
+            invoice_num     => ($content{'invoice_number'}) ? $content{'invoice_number'} : '',
+            appid           => $self->appid(),
+            recurring       => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0,
+            response_code   => ($content{'response_code'}) ? $content{'response_code'} : '',
+            reference_number=> ($content{'ref_num'}) ? $content{'ref_num'} : '',
+            token           => ($content{'token'}) ? $content{'token'} : '',
+            receipt         => ($content{'receipt'}) ? $content{'receipt'} : '',
+            transaction_date=> ($content{'txn_date'}) ? $content{'txn_date'} : '',
+            merchant_data   => ($content{'merchant_data'}) ? $content{'merchant_data'} : '',
+        },
+        
+        # we won't be using level2 nor level3.  So I'm leaving them blank for now.
+        level2 => {
+            card_type             => '',
+            purchase_code         => '',
+            country_code          => '',
+            ship_tp_postal_code   => '',
+            ship_from_postal_code => '',
+            sales_tax             => '',
+            product_description1  => '',
+            product_description2  => '',
+            product_description3  => '',
+            product_description4  => ''
+        },
+        
+        level3 => {
+            purchase_order_num    => '',
+            order_date            => '',
+            duty_amount           => '',
+            alt_tax_amount        => '',
+            discount_amount       => '',
+            freight_amount        => '',
+            tax_exempt            => '',
+            line_item_count       => '',
+            purchase_items        => $self->_parse_line_items()
+        }
+    };
+    
+  
+    # create the list of required fields based on the action
+    my @required_fields = qw/ amount /;
+    if ($action eq 'charge') {
+        push(@required_fields, $_) foreach (qw/ account_number cvv exp_month exp_year /);
+    }elsif ($action eq 'void') {
+        push(@required_fields, $_) foreach (qw/ reference_number transaction_date /);
+    }elsif ($action eq 'refund') {
+        push(@required_fields, $_) foreach (qw/ amount account_number exp_month exp_year /);
+    }
+    
+    # check the requirements are met.
+    my @missing_fields;
+    foreach my $field (@required_fields) {
+        push(@missing_fields, $field) if (!$template_vars->{payment}{$field});
+    }
+    if (scalar(@missing_fields)) {
+        croak "Missing required fields: ".join(', ', @missing_fields);
+    }
+    
+    # read in the appropriate xml template
+    my $xml_template = _get_xml_template( $action );
+    # create a template object.
+    my $tt = Template->new();
+    # populate the XML template.
+    my $xml_data;
+    $tt->process( \$xml_template, $template_vars, \$xml_data ) || croak $tt->error();
+    
+    warn "XML:\n$xml_data\n" if $DEBUG > 2;
+    
+    my $boundary = sprintf('FormBoundary%06d', int(rand(1000000)));
+    # opts for B:OP:HTTPS::https_post
+    my $opts = { headers => {}};
+    $opts->{'Content-Type'} =
+    $opts->{headers}->{'Content-Type'} =
+        "multipart/form-data, boundary=$boundary";
+
+    my $content =
+      "--$boundary\n".
+     "Content-Disposition: form-data; name=\"param\"\n\n".
+     $xml_data."\n".
+     "--$boundary--\n";
+
+    # conform to RFC standards
+    $content =~ s/\n/\r\n/gs;
+
+    my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content );
+  
+    # store the server response.
+    $self->server_response($server_response);
+    # parse the result page.
+    $self->parse_response($page);
+    
+    if (!$self->is_success() && !$self->error_message() ) {
+        if ( $DEBUG ) {
+            #additional logging information, possibly too sensitive for an error msg
+            # (vSecureProcessing seems to have a failure mode where they return the full
+            #  original request including card number)
+            $self->error_message(
+              "(HTTPS response: ".$server_response.") ".
+              "(HTTPS headers: ".
+            join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
+              "(Raw HTTPS content: ".$page.")"
+            );
+        } else {
+            my $response_code = $self->response_code() || '';
+            if ($response_code) {
+                $self->error_message(qq|Error code ${response_code} was returned by vSecureProcessing. (enable debugging for raw HTTPS response)|);
+            }else {
+                $self->error_message('No error information was returned by vSecureProcessing (enable debugging for raw HTTPS response)');
+            }
+        }
+    }
+    
+}
+
+# read $self->server_response and decipher any errors
+sub parse_response {
+    my $self = shift;
+    my $page = shift;
+
+    if ($self->server_response =~ /^200/) {
+        my $response = XMLin($page);
+        $self->result_code($response->{Status}); # 0 /1
+        $self->response_code($response->{ResponseCode}); # see documentation for translation
+        $self->avs_response($response->{AvsResponse}); # Y / N
+        $self->cvv_response($response->{CvvResponse}); # P / F
+        $self->txn_date($response->{TransactionDate}); # MMDDhhmmss
+        $self->txn_amount($response->{TransactionAmount} / 100); # 00000003500 / 100
+        $self->reference_number($response->{ReferenceNumber});
+        
+        $self->is_success($self->result_code() eq '0' ? 1 : 0);
+        if ($self->is_success()) {
+            $self->authorization($response->{AuthIdentificationResponse});
+        }
+        # fill in error_message if there is is an error
+        if ( !$self->is_success && exists($response->{AdditionalResponseData})) {
+            $self->error_message('Error '.$response->{ResponseCode}.': '.$response->{AdditionalResponseData});
+        }elsif ( !$self->is_success && exists($response->{Receipt}) ) {
+            $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : '');
+        }
+        
+    }else {
+        $self->is_success(0);
+        $self->error_message('Error communicating with vSecureProcessing server');
+        return;
+    }
+    
+    
+}
+
+sub _get_xml_template {
+    my $action = shift;
+    
+    my $xml_template = q|<Request >
+    <MerchantData> 
+        <Platform>[% auth.platform %]</Platform>
+        <UserId>[% auth.userid %]</UserId> 
+        <GID>[% auth.gid %]</GID>
+        <Tid>[% auth.tid %]</Tid>
+    </MerchantData>
+    |;
+    
+    if ($action eq 'charge') {
+        $xml_template .= _get_xml_template_charge();
+    }elsif($action eq 'void') {
+        $xml_template .= _get_xml_template_void();
+    }elsif($action eq 'authorize') {
+        $xml_template .= _get_xml_template_auth();
+    }elsif($action eq 'authorize_cancel') {
+        $xml_template .= _get_xml_template_auth_cancel();
+    }elsif($action eq 'refund') {
+        $xml_template .= _get_xml_template_refund();
+    }elsif($action eq 'capture') {
+        $xml_template .= _get_xml_template_capture();
+    }elsif($action eq 'create_token') {
+        $xml_template .= _get_xml_template_create_token();
+    }elsif($action eq 'delete_token') {
+        $xml_template .= _get_xml_template_delete_token();
+    }elsif($action eq 'query_token') {
+        $xml_template .= _get_xml_template_query_token();
+    }elsif($action eq 'update_exp_date') {
+        $xml_template .= _get_xml_template_update_exp_date();
+    }elsif($action eq 'update_token') {
+        $xml_template .= _get_xml_template_update_token();
+    }
+    
+    $xml_template .= "</Request>";
+    $xml_template =~ s/[\n\t\s]*//g;
+    
+    return $xml_template;
+}
+
+sub _get_xml_template_charge {
+    my $xml_template = q|<ProcessPayment>
+        <Amount>[% payment.amount %]</Amount>
+        <Trk1>[% payment.track1 %]</Trk1>
+        <Trk2>[% payment.track2 %]</Trk2>
+        <TypeOfSale>[% payment.type %]</TypeOfSale>
+        <Cf1>[% payment.cf1 %]</Cf1>
+        <Cf2>[% payment.cf2 %]</Cf2>
+        <Cf3>[% payment.cf3 %]</Cf3>
+        <AccountNumber>[% payment.account_number %]</AccountNumber>
+        <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
+        <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
+        <Cvv>[% payment.cvv %]</Cvv>
+        <CardHolderFirstName>[% payment.first_name %]</CardHolderFirstName>
+        <CardHolderLastName>[% payment.last_name %]</CardHolderLastName>
+        <AvsZip>[% payment.postal_code %]</AvsZip>
+        <AvsStreet>[% payment.street_address %]</AvsStreet>
+        <IndustryType>
+            <IndType >[% payment.industry_type %]</IndType >
+            <IndInvoice>[% payment.invoice_num %]</IndInvoice>
+        </IndustryType>
+        <ApplicationId>[% payment.appid %]</ApplicationId>
+        <Recurring>[% payment.recurring %]</Recurring>
+    </ProcessPayment>|;
+    
+    # other options (that we are not using right now):    
+#     <Level2PurchaseInfo>
+#         <Level2CardType>[% level2.card_type %]</Level2CardType >
+#         <PurchaseCode>[% level2.purchase_code %]</PurchaseCode>
+#         <ShipToCountryCode>[% level2.country_code %]</ShipToCountryCode>
+#         <ShipToPostalCode>[% level2.ship_tp_postal_code %]</ShipToPostalCode>
+#         <ShipFromPostalCode>[% level2.ship_from_postal_code %]</ShipFromPostalCode>
+#         <SalesTax>[% level2.sales_tax %]</SalesTax>
+#         <ProductDescription1>[% level2.product_description1 %]</ProductDescription1>
+#         <ProductDescription2>[% level2.product_description2 %]</ProductDescription2>
+#         <ProductDescription3>[% level2.product_description3 %]</ProductDescription3>
+#         <ProductDescription4>[% level2.product_description4 %]</ProductDescription4>
+#     </Level2PurchaseInfo>
+#     <Level3PurchaseInfo>
+#         <PurchaseOrderNumber>[% level3.purchase_order_num %]</PurchaseOrderNumber>
+#         <OrderDate>[% level3.order_date %]</OrderDate>
+#         <DutyAmount>[% level3.duty_amount %]</DutyAmount>
+#         <AlternateTaxAmount>[% level3.alt_tax_amount %]</AlternateTaxAmount>
+#         <DiscountAmount>[% level3.discount_amount %]</DiscountAmount>
+#         <FreightAmount>[% level3.freight_amount %]</FreightAmount>
+#         <TaxExemptFlag>[% level3.tax_exempt %]</TaxExemptFlag>
+#         <LineItemCount>[% level3.line_item_count %]</LineItemCount>
+#         <PurchaseItems>
+#             [% level3.purchase_items %]
+#         </PurchaseItems>
+#     </Level3PurchaseInfo>
+
+    return $xml_template;
+}
+
+sub _parse_line_items {
+    my $self = shift;
+    my %content = $self->content();
+    
+    return '' if (!$content{'items'});
+    
+    my @line_items;
+    my $template = q|            <LineItem>
+                <ItemSequenceNumber>[% seq_num %]</ItemSequenceNumber>
+                <ItemCode>[% code %]</ItemCode>
+                <ItemDescription>[% desc %]</ItemDescription>
+                <ItemQuantity>[% qty %]</ItemQuantity>
+                <ItemUnitOfMeasure>[% unit %]</ItemUnitOfMeasure>
+                <ItemUnitCost>[% unit_cost %]</ItemUnitCost>
+                <ItemAmount>[% amount %]</ItemAmount>
+                <ItemDiscountAmount>[% discount_amount %]</ItemDiscountAmount>
+                <ItemTaxAmount>[% tax_amount %]</ItemTaxAmount>
+                <ItemTaxRate>[% tax_rate %]</ItemTaxRate>
+            </LineItem>|;
+    
+    
+    my @items = $content{'items'};
+    foreach my $item (@items) {
+        # fille in the slots from $template with details in $item
+        # push to @line_items
+    }
+    
+    return join("\n", @line_items);
+}
+
+sub _get_xml_template_void {
+    my $xml_template = q|<ProcessVoid>
+        <Amount>[% payment.amount %]</Amount>
+        <AccountNumber>[% payment.account_number %]</AccountNumber>
+        <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
+        <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
+        <ReferenceNumber>[% payment.reference_number %]</ReferenceNumber>
+        <TransactionDate/>
+        <IndustryType1>[% payment.industry_type %]</IndustryType1>
+        <ApplicationId>[% payment.appid %]</ApplicationId>
+    </ProcessVoid>|;
+
+    return $xml_template;
+}
+
+sub _get_xml_template_refund {
+    my $xml_template = q|<ProcessRefund>
+        <Amount>[% payment.amount %]</Amount>
+        <AccountNumber>[% payment.account_number %]</AccountNumber>
+        <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
+        <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
+        <ApplicationId>[% payment.appid %]</ApplicationId>
+    </ProcessRefund>|;
+
+    return $xml_template;
+}
+
+sub _get_xml_template_auth {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_auth_cancel {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_capture {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_create_token {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_delete_token {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_query_token {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_update_exp_date {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+sub _get_xml_template_update_token {
+    my $xml_template = '';
+
+    return $xml_template;
+}
+
+
+1;
+__END__
+
+
+=head1 NAME
+
+Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Business::OnlinePayment
+
+=head1 SYNOPSIS
+
+  use Business::OnlinePayment;
+  my %processor_info = (
+    platform    => '####',
+    gid         => 12345678901234567890,
+    tid         => 01,
+    user_id     => '####',
+    url         => 'www.####.com'
+  );
+  my $tx =
+    new Business::OnlinePayment( "vSecureProcessing", %processor_info);
+  $tx->content(
+      appid          => '######',
+      type           => 'VISA',
+      action         => 'Normal Authorization',
+      description    => 'Business::OnlinePayment test',
+      amount         => '49.95',
+      customer_id    => 'tfb',
+      name           => 'Tofu Beast',
+      address        => '123 Anystreet',
+      city           => 'Anywhere',
+      state          => 'UT',
+      zip            => '84058',
+      card_number    => '4007000000027',
+      expiration     => '09/02',
+      cvv2           => '1234', #optional
+  );
+  $tx->submit();
+
+  if($tx->is_success()) {
+      print "Card processed successfully: ".$tx->authorization."\n";
+  } else {
+      print "Card was rejected: ".$tx->error_message."\n";
+  }
+
+=head1 DESCRIPTION
+
+For detailed information see L<Business::OnlinePayment>.
+
+=head1 METHODS AND FUNCTIONS
+
+See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
+
+=head2 result_code
+
+Returns the response error code.
+
+=head2 error_message
+
+Returns the response error description text.
+
+=head2 server_response
+
+Returns the complete response from the server.
+
+=head1 Handling of content(%content) data:
+
+=head2 action
+
+The following actions are valid
+
+  normal authorization
+  credit
+  void
+
+=head1 Setting vSecureProcessing parameters from content(%content)
+
+The following rules are applied to map data to vSecureProcessing parameters
+from content(%content):
+
+      # param => $content{<key>}
+      AccountNumber       => 'card_number',
+      Cvv                 => 'cvv2',
+      ExpirationMonth     => \( $month ), # MM from MM/YY of 'expiration'
+      ExpirationYear      => \( $year ), # YY from MM/YY of 'expiration'
+      Trk1                => 'track1',
+      Trk2                => 'track2',
+      CardHolderFirstName => 'first_name',
+      CardHolderLastName  => 'last_name',
+      Amount              => 'amount'
+      AvsStreet           => 'address',
+      AvsZip              => 'zip',
+      Cf1                 => 'UDField1',
+      Cf2                 => 'UDField2',
+      IndustryType        => 'IndustryInfo',
+
+=head1 NOTE
+
+=head1 COMPATIBILITY
+
+Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014).
+
+See http://www.vsecureprocessing.com/ for more information.
+
+=head1 AUTHORS
+
+Original author: Alex Brelsfoard
+
+Current maintainer: Alex Brelsfoard
+
+=head1 COPYRIGHT
+
+Copyright (c) 2015 Freeside Internet Services, Inc.
+
+All rights reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 ADVERTISEMENT
+
+Need a complete, open-source back-office and customer self-service solution?
+The Freeside software includes support for credit card and electronic check
+processing with vSecureProcessing and over 50 other gateways, invoicing, integrated
+trouble ticketing, and customer signup and self-service web interfaces.
+
+http://freeside.biz/freeside/
+
+=head1 SEE ALSO
+
+perl(1). L<Business::OnlinePayment>.
+
+=cut
+
+
diff --git a/t/lib/test_account.pl b/t/lib/test_account.pl
new file mode 100644 (file)
index 0000000..69f8c89
--- /dev/null
@@ -0,0 +1,29 @@
+
+sub test_account {
+    # fill all these fields in to test out transactions
+    my %opts = (
+        server =>'', # be sure to leave out the 'https://'
+        platform => '',
+        gid => '',
+        tid => '',
+        userid=> 'name@server.com',
+        port => 443,
+        env => 'test',
+        appid => ''
+    );
+
+    return %opts;
+}
+
+sub expiration_date {
+    my($month, $year) = (localtime)[4,5];
+    $month += 1;
+    $year++;       # So we expire next year.
+    $year %= 100;
+
+    return sprintf("%02d/%02d", $month, $year);
+}
+
+
+1;
+
diff --git a/t/t_00-load.t b/t/t_00-load.t
new file mode 100644 (file)
index 0000000..a79fa22
--- /dev/null
@@ -0,0 +1,10 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+    use_ok( 'Business::OnlinePayment::vSecureProcessing' ) || print "Bail out!
+";
+}
+
+diag( "Testing Business::OnlinePayment::vSecureProcessing $Business::OnlinePayment::vSecureProcessing::VERSION, Perl $], $^X" );
diff --git a/t/t_boilerplate.t b/t/t_boilerplate.t
new file mode 100644 (file)
index 0000000..50696af
--- /dev/null
@@ -0,0 +1,49 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+sub not_in_file_ok {
+    my ($filename, %regex) = @_;
+    open( my $fh, '<', $filename )
+        or die "couldn't open $filename for reading: $!";
+
+    my %violated;
+
+    while (my $line = <$fh>) {
+        while (my ($desc, $regex) = each %regex) {
+            if ($line =~ $regex) {
+                push @{$violated{$desc}||=[]}, $.;
+            }
+        }
+    }
+
+    if (%violated) {
+        fail("$filename contains boilerplate text");
+        diag "$_ appears on lines @{$violated{$_}}" for keys %violated;
+    } else {
+        pass("$filename contains no boilerplate text");
+    }
+}
+
+sub module_boilerplate_ok {
+    my ($module) = @_;
+    not_in_file_ok($module =>
+        'the great new $MODULENAME'   => qr/ - The great new /,
+        'boilerplate description'     => qr/Quick summary of what the module/,
+        'stub function definition'    => qr/function[12]/,
+    );
+}
+
+  not_in_file_ok(README =>
+    "The README is used..."       => qr/The README is used/,
+    "'version information here'"  => qr/to provide version information/,
+  );
+
+  not_in_file_ok(Changes =>
+    "placeholder date/time"       => qr(Date/time)
+  );
+
+  module_boilerplate_ok('lib/Business/OnlinePayment/vSecureProcessing.pm');
+
diff --git a/t/t_manifest.t b/t/t_manifest.t
new file mode 100644 (file)
index 0000000..45eb83f
--- /dev/null
@@ -0,0 +1,13 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+    plan( skip_all => "Author tests not required for installation" );
+}
+
+eval "use Test::CheckManifest 0.9";
+plan skip_all => "Test::CheckManifest 0.9 required" if $@;
+ok_manifest();
diff --git a/t/t_pod-coverage.t b/t/t_pod-coverage.t
new file mode 100644 (file)
index 0000000..c021dd4
--- /dev/null
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use Test::More skip_all => "don't care about POD coverage right now";
+
+# Ensure a recent version of Test::Pod::Coverage
+my $min_tpc = 1.08;
+eval "use Test::Pod::Coverage $min_tpc";
+plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
+    if $@;
+
+# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
+# but older versions don't recognize some common documentation styles
+my $min_pc = 0.18;
+eval "use Pod::Coverage $min_pc";
+plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
+    if $@;
+
+all_pod_coverage_ok();
diff --git a/t/t_pod.t b/t/t_pod.t
new file mode 100644 (file)
index 0000000..ee8b18a
--- /dev/null
+++ b/t/t_pod.t
@@ -0,0 +1,12 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod
+my $min_tp = 1.22;
+eval "use Test::Pod $min_tp";
+plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
+
+all_pod_files_ok();
diff --git a/t/t_transaction.t b/t/t_transaction.t
new file mode 100644 (file)
index 0000000..401797e
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Test::More;
+use Business::OnlinePayment;
+require "t/lib/test_account.pl";
+
+my %opts = test_account_or_skip('card');
+
+if (!$opt{'gid'} || !$opt{'appid'}) {
+  plan skip_all => "no test credentials provided; fill out t/lib/test_account.pl to test communication with the gateway.",
+  1;
+  exit(0);
+}
+
+plan tests => 2;
+
+###
+# Purchase
+###
+my %content = (
+    appid          => $opts{'appid'},
+    action         => 'Normal Authorization',
+    description    => 'Business::OnlinePayment visa test',
+    card_number    => '4111111111111111',
+    cvv2           => '111',
+    expiration     => expiration_date(),
+    amount         => '24.42',
+    name           => 'Murphy Law',
+    email          => 'fake@acme.com',
+    address        => '123 Anystreet',
+    zip            => '84058',
+);
+
+my $tx = new Business::OnlinePayment( 'vSecureProcessing', \%opts );
+
+$tx->content( %content,
+              action => 'Normal Authorization' );
+
+$tx->test_transaction(1);
+
+$tx->submit;
+
+is( $tx->is_success, 1, 'purchase' )
+  or diag('Gateway error: '. $tx->error_message);
+
+###
+# Refund
+###
+my $auth = $tx->authorization;
+$tx = new Business::OnlinePayment( 'vSecureProcessing' );
+$tx->content( %content,
+              action => 'Credit',
+              authorization => $auth );
+$tx->test_transaction(1);
+
+$tx->submit;
+
+is( $tx->is_success, 1, 'refund' )
+  or diag('Gateway error: '. $tx->error_message);
+
+1;
diff --git a/t/t_transaction_decline.t b/t/t_transaction_decline.t
new file mode 100644 (file)
index 0000000..1309c78
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Test::More;
+use Business::OnlinePayment;
+require "t/lib/test_account.pl";
+
+my %opts = test_account_or_skip('card');
+
+if (!$opt{'gid'} || !$opt{'appid'}) {
+  plan skip_all => "no test credentials provided; fill out t/lib/test_account.pl to test communication with the gateway.",
+  1;
+  exit(0);
+}
+
+plan tests => 2;
+my %content = (
+    appid          => $opts{'appid'},
+    action         => 'Normal Authorization',
+    description    => 'Business::OnlinePayment visa test',
+    card_number    => '4111111111111112', # trigger failure
+    cvv2           => '111',
+    expiration     => expiration_date(),
+    amount         => '24.42',
+    name           => 'Murphy Law',
+    email          => 'fake@acme.com',
+    address        => '123 Anystreet',
+    zip            => '84058',
+);
+
+my $tx = new Business::OnlinePayment( 'vSecureProcessing', \%opts );
+
+$tx->content( %content );
+
+$tx->test_transaction(1);
+
+$tx->submit;
+
+is( $tx->is_success, 0, 'declined purchase')
+  or diag('Test transaction should have failed, but succeeded');
+is( $tx->failure_status, 'nsf', 'failure status' )
+  or diag('Failure status reported as '.$tx->failure_status);
+
+1;
diff --git a/test.pl b/test.pl
deleted file mode 100755 (executable)
index 40f528b..0000000
--- a/test.pl
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/perl -w
-
-#
-# Make sure to copy Business::OnlinePayment::vSecureProcessing into its
-# proper system directory (aka /usr/share/perl5/Business/Onlinepayment)
-#
-
-use strict;
-use Business::OnlinePayment;
-
-my %opt = (
-    server =>'dvrotsos2.kattare.com',
-    platform => 'Buypass',
-    gid => '1432479912596791',
-    tid => '01',
-    userid=> 'tom@yiptv.com',
-    port => 443,
-    env => 'test'
-);
-
-my $action = shift || 'Normal Authorization';
-my $auth = shift || '';
-
-
-my %content = (
-    appid          => 'yiptv',
-    action         => $action,
-    auth           => $auth,
-    description    => 'Business::OnlinePayment visa test',
-#    card_number    => '4007000000027',
-    card_number    => '4111111111111111',
-    cvv2           => '111',
-    expiration     => expiration_date(),
-    amount         => '42.24',
-    name           => 'Murphy Law',
-    email          => 'fake@acme.com',
-    address        => '123 Anystreet',
-    zip            => '84058',
-);
-
-main();
-
-sub main {
-    my $transaction = Business::OnlinePayment->new("vSecureProcessing", %opt);
-    
-    print "MAKING PAYMENT\n";
-    MakPayment($transaction);
-    $content{'action'} = 'void';
-    $content{'reference_number'} = $transaction->authorization;
-    $content{'transaction_date'} = $transaction->txn_date;
-    $content{'amount'} = $transaction->txn_amount;
-    print "VOIDING PAYMENT\n";
-    VoidPayment($transaction);
-    $content{'action'} = 'Normal Authorization';
-    print "MAKING PAYMENT\n";
-    MakePayment($transaction);
-    print "REFUNDING PAYMENT\n";
-    $content{'action'} = 'credit';
-    $content{'reference_number'} = $transaction->authorization;
-    $content{'transaction_date'} = $transaction->txn_date;
-    $content{'amount'} = $transaction->txn_amount;
-    refundPayment($transaction);
-}
-
-sub MakePayment {
-    $transaction->content(%content);
-
-    eval { $transaction->submit(); };
-
-    if ( $@ ) {
-    
-    print "Error: $@\n";
-    
-    } else {
-    
-        if ( $transaction->is_success() ) {
-            print "Card processed successfully: ". $transaction->authorization()."\n";
-        } else {
-            print "Card was rejected: ". $transaction->error_message(). "\n";
-        }
-    }
-}
-
-sub VoidPayment {
-    
-}
-
-sub RefundPayment {
-    
-}
-
-
-sub expiration_date {
-    my($month, $year) = (localtime)[4,5];
-    $month += 1;
-    $year++;       # So we expire next year.
-    $year %= 100;  # y2k?  What's that?
-
-    return sprintf("%02d/%02d", $month, $year);
-}
diff --git a/vSecureProcessing.pm b/vSecureProcessing.pm
deleted file mode 100644 (file)
index 3c73067..0000000
+++ /dev/null
@@ -1,689 +0,0 @@
-package Business::OnlinePayment::vSecureProcessing;
-
-use strict;
-use Carp;
-use Template;
-use XML::Simple;
-use Data::Dumper;
-
-use Business::OnlinePayment;
-use Business::OnlinePayment::HTTPS;
-#use Net::SSLeay qw(post_http post_https make_headers make_form);
-use vars qw($VERSION $DEBUG @ISA $me);
-
-@ISA = qw(Business::OnlinePayment::HTTPS);
-$DEBUG = 0;
-$VERSION = '0.01';
-$me = 'Business::OnlinePayment::vSecureProcessing';
-
-
-# $server: http://dvrotsos2.kattare.com
-
-# mapping out all possible endpoints
-# but this version will only be building out "charge", "void", & "credit"
-my %payment_actions = (
-    'charge' => {
-        path      => '/vsg2/processpayment',
-    },
-    'void' => {
-        path      => '/vsg2/processvoid',
-    },
-    'refund' => {
-        path      => '/vsg2/processrefund',
-    },
-    'authorize' => {
-        path      => '/vsg2/processauth',
-    },
-    'authorize_cancel' => {
-        path      => '/vsg2/processauthcancel',
-    },
-    'capture' => {
-        path      => '/vsg2/processcaptureonly',
-    },
-    'create_token' => {
-        path      => '/vsg2/createtoken',
-    },
-    'delete_token' => {
-        path      => '/vsg2/deletetoken',
-    },
-    'query_token' => {
-        path      => '/vsg2/querytoken',
-    },
-    'update_exp_date' => {
-        path      => '/vsg2/updateexpiration',
-    },
-    'update_token' => {
-        path      => '/vsg2/updatetoken',
-    },
-
-);
-
-my %action_mapping = (
-    'normal authorization'  => 'charge',
-    'credit'                => 'refund',
-    'authorization only'    => 'authorize',
-    'post authorization'    => 'capture',
-    'reverse authorization' => 'authorize_cancel'
-    # void => void
-);
-
-sub set_defaults {
-    my $self = shift;
-    my %options = @_;
-    
-    # inistialize standard B::OP attributes
-    $self->is_success(0);
-    $self->$_( '' ) for qw/authorization
-                           result_code
-                           error_message
-                           server
-                           port
-                           path
-                           server_response/;
-                           
-    # B::OP creates the following accessors:
-    #     server, port, path, test_transaction, transaction_type,
-    #     server_response, is_success, authorization,
-    #     result_code, error_message,
-    
-    $self->build_subs(qw/
-            env platform userid gid tid appid action cvv_response
-            avs_response risk_score txn_amount txn_date
-    /);
-    
-    $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG;
-    
-    
-    
-    $self->server($options{'server'});
-    
-    $self->gid($options{'gid'});
-    
-    $self->tid($options{'tid'});
-    
-    $self->platform($options{'platform'});
-    
-    $self->appid($options{'appid'});
-    
-    $self->env((defined($options{'env'})) ? $options{'env'} : 'live'); # 'live'/'test'
-    
-    $self->port(443);
-}
-
-
-
-sub clean_content {
-    my ($self,$content) = @_;
-    my %content = $self->content();
-    
-    {
-        no warnings 'uninitialized';
-        
-        # strip non-digits from card number
-        my $card_number = '';
-        if ( $content{card_number} ) {
-            $content{card_number} =~ s/\D//g;
-        }
-        
-        # separate month and year values for expiry_date
-        if ( $content{expiration} ) {
-            ($content{exp_month}, $content{exp_year}) = split /\//, $content{expiration};
-            $content{exp_month} = sprintf "%02d", $content{exp_month};
-            $content{exp_year}  = substr($content{exp_year},0,2) if ($content{exp_year} > 99);
-        }
-        
-        if (!$content{'first_name'} || !$content{'last_name'} && $content{'name'}) {
-            ($content{'first_name'}, $content{'last_name'}) = split(' ', $content{'name'}, 2);
-        }
-        
-        if ($content{'address'} =~ m/[\D ]*(\d+)\D/) {
-            $content{'street_number'} = $1;
-        }
-    }
-    warn "Content after cleaning:\n".Dumper(\%content)."\n" if ($DEBUG >2);
-    $self->content(%content);
-}
-
-sub process_content {
-    my $self = shift;
-    $self->clean_content();
-    my %content = $self->content();
-    $self->action(($action_mapping{lc $content{'action'}}) ? $action_mapping{lc $content{'action'}} : lc $content{'action'});
-    $self->path($payment_actions{ $self->action }{path})
-      unless length($self->path);
-    $self->appid($content{appid}) if (!$self->appid && $content{appid});
-}
-
-sub submit {
-    my $self = shift;
-    
-    # inistialize standard B::OP attributes
-    $self->is_success(0);
-    $self->$_( '' ) for qw/authorization
-                           result_code
-                           error_message
-                           server_response/;
-                           
-    # clean and process the $self->content info
-    $self->process_content();
-    my %content = $self->content;
-    my $action = $self->action();
-    
-    my @acceptable_actions = ('charge', 'refund', 'void');
-    
-    unless ( grep { $action eq $_ } @acceptable_actions ) {
-        croak "'$action' is not supported at this time.";
-    }
-    
-    # fill out the template vars
-    my $template_vars = {
-        
-        auth => {
-            platform    => $self->platform,
-            userid      => $self->userid,
-            gid         => $self->gid,
-            tid         => $self->tid
-        },
-        
-        payment => {
-            amount          => $content{'amount'},
-            track1          => ($content{'track1'}) ? $content{'track1'} : '',
-            track2          => ($content{'track2'}) ? $content{'track2'} : '',
-            type            => ($content{'description'}) ? $content{'description'} : '',
-            cf1             => ($content{'UDField1'}) ? $content{'UDField1'} : '',
-            cf2             => ($content{'UDField2'}) ? $content{'UDField2'} : '',
-            cf3             => '',
-            account_number  => ($content{'card_number'}) ? $content{'card_number'} : '',
-            exp_month       => $content{'exp_month'},
-            exp_year        => $content{'exp_year'},
-            cvv             => ($content{'cvv'}) ? $content{'cvv'} : ($content{'cvv2'}) ? $content{'cvv2'} : '',
-            first_name      => ($content{'first_name'}) ? $content{'first_name'} : '',
-            last_name       => ($content{'last_name'}) ? $content{'last_name'} : '',
-            postal_code     => ($content{'zip'}) ? $content{'zip'} : '',
-            street_address  => ($content{'street_number'}) ? $content{'street_number'} : '',
-            industry_type   => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '',
-            invoice_num     => ($content{'invoice_number'}) ? $content{'invoice_number'} : '',
-            appid           => $self->appid(),
-            recurring       => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0,
-            response_code   => ($content{'response_code'}) ? $content{'response_code'} : '',
-            reference_number=> ($content{'ref_num'}) ? $content{'ref_num'} : '',
-            token           => ($content{'token'}) ? $content{'token'} : '',
-            receipt         => ($content{'receipt'}) ? $content{'receipt'} : '',
-            transaction_date=> ($content{'txn_date'}) ? $content{'txn_date'} : '',
-            merchant_data   => ($content{'merchant_data'}) ? $content{'merchant_data'} : '',
-        },
-        
-        # we won't be using level2 nor level3.  So I'm leaving them blank for now.
-        level2 => {
-            card_type             => '',
-            purchase_code         => '',
-            country_code          => '',
-            ship_tp_postal_code   => '',
-            ship_from_postal_code => '',
-            sales_tax             => '',
-            product_description1  => '',
-            product_description2  => '',
-            product_description3  => '',
-            product_description4  => ''
-        },
-        
-        level3 => {
-            purchase_order_num    => '',
-            order_date            => '',
-            duty_amount           => '',
-            alt_tax_amount        => '',
-            discount_amount       => '',
-            freight_amount        => '',
-            tax_exempt            => '',
-            line_item_count       => '',
-            purchase_items        => $self->_parse_line_items()
-        }
-    };
-    
-  
-    # create the list of required fields based on the action
-    my @required_fields = qw/ amount /;
-    if ($action eq 'charge') {
-        push(@required_fields, $_) foreach (qw/ account_number cvv exp_month exp_year /);
-    }elsif ($action eq 'void') {
-        push(@required_fields, $_) foreach (qw/ reference_number transaction_date /);
-    }elsif ($action eq 'refund') {
-        push(@required_fields, $_) foreach (qw/ amount account_number exp_month exp_year /);
-    }
-    
-    # check the requirements are met.
-    my @missing_fields;
-    foreach my $field (@required_fields) {
-        push(@missing_fields, $field) if (!$template_vars->{payment}{$field});
-    }
-    if (scalar(@missing_fields)) {
-        croak "Missing required fields: ".join(', ', @missing_fields);
-    }
-    
-    # read in the appropriate xml template
-    my $xml_template = _get_xml_template( $action );
-    # create a template object.
-    my $tt = Template->new();
-    # populate the XML template.
-    my $xml_data;
-    $tt->process( \$xml_template, $template_vars, \$xml_data ) || croak $tt->error();
-    
-    warn "XML:\n$xml_data\n" if $DEBUG > 2;
-    
-    my $boundary = sprintf('FormBoundary%06d', int(rand(1000000)));
-    # opts for B:OP:HTTPS::https_post
-    my $opts = { headers => {}};
-    $opts->{'Content-Type'} =
-    $opts->{headers}->{'Content-Type'} =
-        "multipart/form-data, boundary=$boundary";
-
-    my $content =
-      "--$boundary\n".
-     "Content-Disposition: form-data; name=\"param\"\n\n".
-     $xml_data."\n".
-     "--$boundary--\n";
-
-    # conform to RFC standards
-    $content =~ s/\n/\r\n/gs;
-
-    my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content );
-  
-    # store the server response.
-    $self->server_response($server_response);
-    # parse the result page.
-    $self->parse_response($page);
-    
-    if (!$self->is_success() && !$self->error_message() ) {
-        if ( $DEBUG ) {
-            #additional logging information, possibly too sensitive for an error msg
-            # (vSecureProcessing seems to have a failure mode where they return the full
-            #  original request including card number)
-            $self->error_message(
-              "(HTTPS response: ".$server_response.") ".
-              "(HTTPS headers: ".
-            join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
-              "(Raw HTTPS content: ".$page.")"
-            );
-        } else {
-            my $response_code = $self->response_code() || '';
-            if ($response_code) {
-                $self->error_message(qq|Error code ${response_code} was returned by vSecureProcessing. (enable debugging for raw HTTPS response)|);
-            }else {
-                $self->error_message('No error information was returned by vSecureProcessing (enable debugging for raw HTTPS response)');
-            }
-        }
-    }
-    
-}
-
-# read $self->server_response and decipher any errors
-sub parse_response {
-    my $self = shift;
-    my $page = shift;
-
-    if ($self->server_response =~ /^200/) {
-        my $response = XMLin($page);
-        warn "RESPONSE: \n".Dumper($response)."\n";
-        $self->result_code($response->{Status});
-        $self->avs_response($response->{AvsResponse});
-        $self->cvv_response($response->{CvvResponse});
-        $self->txn_date($response->{TransactionDate});
-        $self->txn_amount($response->{TransactionAmount} / 100);
-        $self->cvv_response($response->{CvvResponse});
-        $self->is_success($self->result_code() eq '0' ? 1 : 0);
-        if ($self->is_success()) {
-            $self->authorization($response->{AuthIdentificationResponse});
-        }
-        # fill in error_message if there is is an error
-        if ( !$self->is_success && exists($response->{ResultCode})) {
-            $self->error_message('Error '.$response->{ResponseCode}.': '.$response->{ResultCode});
-        }elsif ( !$self->is_success && exists($response->{Receipt}) ) {
-            $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : '');
-        }
-        
-    }else {
-        $self->is_success(0);
-        $self->error_message('Error communicating with vSecureProcessing server');
-        return;
-    }
-    
-    
-}
-
-sub _get_xml_template {
-    my $action = shift;
-    
-    my $xml_template = q|<Request >
-    <MerchantData> 
-        <Platform>[% auth.platform %]</Platform>
-        <UserId>[% auth.userid %]</UserId> 
-        <GID>[% auth.gid %]</GID>
-        <Tid>[% auth.tid %]</Tid>
-    </MerchantData>
-    |;
-    
-    if ($action eq 'charge') {
-        $xml_template .= _get_xml_template_charge();
-    }elsif($action eq 'void') {
-        $xml_template .= _get_xml_template_void();
-    }elsif($action eq 'authorize') {
-        $xml_template .= _get_xml_template_auth();
-    }elsif($action eq 'authorize_cancel') {
-        $xml_template .= _get_xml_template_auth_cancel();
-    }elsif($action eq 'refund') {
-        $xml_template .= _get_xml_template_refund();
-    }elsif($action eq 'capture') {
-        $xml_template .= _get_xml_template_capture();
-    }elsif($action eq 'create_token') {
-        $xml_template .= _get_xml_template_create_token();
-    }elsif($action eq 'delete_token') {
-        $xml_template .= _get_xml_template_delete_token();
-    }elsif($action eq 'query_token') {
-        $xml_template .= _get_xml_template_query_token();
-    }elsif($action eq 'update_exp_date') {
-        $xml_template .= _get_xml_template_update_exp_date();
-    }elsif($action eq 'update_token') {
-        $xml_template .= _get_xml_template_update_token();
-    }
-    
-    $xml_template .= "</Request>";
-    $xml_template =~ s/[\n\t\s]*//g;
-    
-    return $xml_template;
-}
-
-sub _get_xml_template_charge {
-    my $xml_template = q|<ProcessPayment>
-        <Amount>[% payment.amount %]</Amount>
-        <Trk1>[% payment.track1 %]</Trk1>
-        <Trk2>[% payment.track2 %]</Trk2>
-        <TypeOfSale>[% payment.type %]</TypeOfSale>
-        <Cf1>[% payment.cf1 %]</Cf1>
-        <Cf2>[% payment.cf2 %]</Cf2>
-        <Cf3>[% payment.cf3 %]</Cf3>
-        <AccountNumber>[% payment.account_number %]</AccountNumber>
-        <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
-        <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
-        <Cvv>[% payment.cvv %]</Cvv>
-        <CardHolderFirstName>[% payment.first_name %]</CardHolderFirstName>
-        <CardHolderLastName>[% payment.last_name %]</CardHolderLastName>
-        <AvsZip>[% payment.postal_code %]</AvsZip>
-        <AvsStreet>[% payment.street_address %]</AvsStreet>
-        <IndustryType>
-            <IndType >[% payment.industry_type %]</IndType >
-            <IndInvoice>[% payment.invoice_num %]</IndInvoice>
-        </IndustryType>
-        <ApplicationId>[% payment.appid %]</ApplicationId>
-        <Recurring>[% payment.recurring %]</Recurring>
-    </ProcessPayment>|;
-    
-    # other options (that we are not using right now):    
-#     <Level2PurchaseInfo>
-#         <Level2CardType>[% level2.card_type %]</Level2CardType >
-#         <PurchaseCode>[% level2.purchase_code %]</PurchaseCode>
-#         <ShipToCountryCode>[% level2.country_code %]</ShipToCountryCode>
-#         <ShipToPostalCode>[% level2.ship_tp_postal_code %]</ShipToPostalCode>
-#         <ShipFromPostalCode>[% level2.ship_from_postal_code %]</ShipFromPostalCode>
-#         <SalesTax>[% level2.sales_tax %]</SalesTax>
-#         <ProductDescription1>[% level2.product_description1 %]</ProductDescription1>
-#         <ProductDescription2>[% level2.product_description2 %]</ProductDescription2>
-#         <ProductDescription3>[% level2.product_description3 %]</ProductDescription3>
-#         <ProductDescription4>[% level2.product_description4 %]</ProductDescription4>
-#     </Level2PurchaseInfo>
-#     <Level3PurchaseInfo>
-#         <PurchaseOrderNumber>[% level3.purchase_order_num %]</PurchaseOrderNumber>
-#         <OrderDate>[% level3.order_date %]</OrderDate>
-#         <DutyAmount>[% level3.duty_amount %]</DutyAmount>
-#         <AlternateTaxAmount>[% level3.alt_tax_amount %]</AlternateTaxAmount>
-#         <DiscountAmount>[% level3.discount_amount %]</DiscountAmount>
-#         <FreightAmount>[% level3.freight_amount %]</FreightAmount>
-#         <TaxExemptFlag>[% level3.tax_exempt %]</TaxExemptFlag>
-#         <LineItemCount>[% level3.line_item_count %]</LineItemCount>
-#         <PurchaseItems>
-#             [% level3.purchase_items %]
-#         </PurchaseItems>
-#     </Level3PurchaseInfo>
-
-    return $xml_template;
-}
-
-sub _parse_line_items {
-    my $self = shift;
-    my %content = $self->content();
-    
-    return '' if (!$content{'items'});
-    
-    my @line_items;
-    my $template = q|            <LineItem>
-                <ItemSequenceNumber>[% seq_num %]</ItemSequenceNumber>
-                <ItemCode>[% code %]</ItemCode>
-                <ItemDescription>[% desc %]</ItemDescription>
-                <ItemQuantity>[% qty %]</ItemQuantity>
-                <ItemUnitOfMeasure>[% unit %]</ItemUnitOfMeasure>
-                <ItemUnitCost>[% unit_cost %]</ItemUnitCost>
-                <ItemAmount>[% amount %]</ItemAmount>
-                <ItemDiscountAmount>[% discount_amount %]</ItemDiscountAmount>
-                <ItemTaxAmount>[% tax_amount %]</ItemTaxAmount>
-                <ItemTaxRate>[% tax_rate %]</ItemTaxRate>
-            </LineItem>|;
-    
-    
-    my @items = $content{'items'};
-    foreach my $item (@items) {
-        # fille in the slots from $template with details in $item
-        # push to @line_items
-    }
-    
-    return join("\n", @line_items);
-}
-
-sub _get_xml_template_void {
-    my $xml_template = q|<ProcessVoid>
-        <Amount>[% payment.amount %]</Amount>
-        <AccountNumber>[% payment.account_number %]</AccountNumber>
-        <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
-        <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
-        <ReferenceNumber>[% payment.reference_number %]</ReferenceNumber>
-        <TransactionDate/>
-        <IndustryType1>[% payment.industry_type %]</IndustryType1>
-        <ApplicationId>[% payment.appid %]</ApplicationId>
-    </ProcessVoid>|;
-
-    return $xml_template;
-}
-
-sub _get_xml_template_refund {
-    my $xml_template = q|<ProcessRefund>
-        <Amount>[% payment.amount %]</Amount>
-        <AccountNumber>[% payment.account_number %]</AccountNumber>
-        <ExpirationMonth>[% payment.exp_month %]</ExpirationMonth>
-        <ExpirationYear>[% payment.exp_year %]</ExpirationYear>
-        <ApplicationId>[% payment.appid %]</ApplicationId>
-    </ProcessRefund>|;
-
-    return $xml_template;
-}
-
-sub _get_xml_template_auth {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_auth_cancel {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_capture {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_create_token {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_delete_token {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_query_token {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_update_exp_date {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-sub _get_xml_template_update_token {
-    my $xml_template = '';
-
-    return $xml_template;
-}
-
-
-1;
-__END__
-
-
-=head1 NAME
-
-Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Business::OnlinePayment
-
-=head1 SYNOPSIS
-
-  use Business::OnlinePayment;
-  my %processor_info = (
-    platform    => '####',
-    gid         => 12345678901234567890,
-    tid         => 01,
-    user_id     => '####',
-    url         => 'www.####.com'
-  );
-  my $tx =
-    new Business::OnlinePayment( "vSecureProcessing", %processor_info);
-  $tx->content(
-      appid          => '######',
-      type           => 'VISA',
-      action         => 'Normal Authorization',
-      description    => 'Business::OnlinePayment test',
-      amount         => '49.95',
-      customer_id    => 'tfb',
-      name           => 'Tofu Beast',
-      address        => '123 Anystreet',
-      city           => 'Anywhere',
-      state          => 'UT',
-      zip            => '84058',
-      card_number    => '4007000000027',
-      expiration     => '09/02',
-      cvv2           => '1234', #optional
-  );
-  $tx->submit();
-
-  if($tx->is_success()) {
-      print "Card processed successfully: ".$tx->authorization."\n";
-  } else {
-      print "Card was rejected: ".$tx->error_message."\n";
-  }
-
-=head1 DESCRIPTION
-
-For detailed information see L<Business::OnlinePayment>.
-
-=head1 METHODS AND FUNCTIONS
-
-See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
-
-=head2 result_code
-
-Returns the response error code.
-
-=head2 error_message
-
-Returns the response error description text.
-
-=head2 server_response
-
-Returns the complete response from the server.
-
-=head1 Handling of content(%content) data:
-
-=head2 action
-
-The following actions are valid
-
-  normal authorization
-  credit
-  void
-
-=head1 Setting vSecureProcessing parameters from content(%content)
-
-The following rules are applied to map data to vSecureProcessing parameters
-from content(%content):
-
-      # param => $content{<key>}
-      AccountNumber       => 'card_number',
-      Cvv                 => 'cvv2',
-      ExpirationMonth     => \( $month ), # MM from MM/YY of 'expiration'
-      ExpirationYear      => \( $year ), # YY from MM/YY of 'expiration'
-      Trk1                => 'track1',
-      Trk2                => 'track2',
-      CardHolderFirstName => 'first_name',
-      CardHolderLastName  => 'last_name',
-      Amount              => 'amount'
-      AvsStreet           => 'address',
-      AvsZip              => 'zip',
-      Cf1                 => 'UDField1',
-      Cf2                 => 'UDField2',
-      IndustryType        => 'IndustryInfo',
-
-=head1 NOTE
-
-=head1 COMPATIBILITY
-
-Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014).
-
-See http://www.vsecureprocessing.com/ for more information.
-
-=head1 AUTHORS
-
-Original author: Alex Brelsfoard
-
-Current maintainer: Alex Brelsfoard
-
-=head1 COPYRIGHT
-
-Copyright (c) 2015 Freeside Internet Services, Inc.
-
-All rights reserved.
-
-This program is free software; you can redistribute it and/or modify it under
-the same terms as Perl itself.
-
-=head1 ADVERTISEMENT
-
-Need a complete, open-source back-office and customer self-service solution?
-The Freeside software includes support for credit card and electronic check
-processing with vSecureProcessing and over 50 other gateways, invoicing, integrated
-trouble ticketing, and customer signup and self-service web interfaces.
-
-http://freeside.biz/freeside/
-
-=head1 SEE ALSO
-
-perl(1). L<Business::OnlinePayment>.
-
-=cut
-
-