X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=lib%2FBusiness%2FOnlinePayment%2FvSecureProcessing.pm;h=75675f3c4a46696e8de7fa98691e9a98d7860e9b;hb=51c7503002afd10e1070a60494bb1daca9daba16;hp=e7514a370f86b746c6269916bdb6345069378d05;hpb=1e90419f5b23ab4b4fe17b5861958d75ac285c4d;p=Business-OnlinePayment-vSecureProcessing.git diff --git a/lib/Business/OnlinePayment/vSecureProcessing.pm b/lib/Business/OnlinePayment/vSecureProcessing.pm index e7514a3..75675f3 100644 --- a/lib/Business/OnlinePayment/vSecureProcessing.pm +++ b/lib/Business/OnlinePayment/vSecureProcessing.pm @@ -1,35 +1,45 @@ package Business::OnlinePayment::vSecureProcessing; use strict; +use vars qw($VERSION $DEBUG @ISA); use Carp; -use Template; +use XML::Writer; 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 +$VERSION = '0.03'; # mapping out all possible endpoints # but this version will only be building out "charge", "void", & "credit" my %payment_actions = ( 'charge' => { path => '/vsg2/processpayment', + process => 'ProcessPayment', + fields => [qw/ + Amount Trk1 Trk2 TypeOfSale Cf1 Cf2 Cf AccountNumber + ExpirationMonth ExpirationYear Cvv + CardHolderFirstName CardHolderLastName AvsZip AvsStreet + IndustryType ApplicationId Recurring + /], }, 'void' => { path => '/vsg2/processvoid', + process => 'ProcessVoid', + fields => [qw( + Amount AccountNumber ExpirationMonth ExpirationYear ReferenceNumber + TransactionDate IndustryType ApplicationId + )], }, 'refund' => { path => '/vsg2/processrefund', + process => 'ProcessRefund', + fields => [qw( + Amount AccountNumber ExpirationMonth ExpirationYear ApplicationId + )], }, 'authorize' => { path => '/vsg2/processauth', @@ -77,27 +87,23 @@ sub set_defaults { result_code error_message server - port path server_response/; # B::OP creates the following accessors: - # server, port, path, test_transaction, transaction_type, + # server, 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 + platform tid appid + action reference_number cvv_response avs_response response_code + risk_score txn_amount txn_date /); $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG; - - - $self->server($options{'server'}); - - $self->gid($options{'gid'}); + $self->server('svr1.vsecureprocessing.com'); $self->tid($options{'tid'}); @@ -105,13 +111,10 @@ sub set_defaults { $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(); @@ -125,15 +128,25 @@ sub clean_content { $content{card_number} =~ s/\D//g; } + if ($content{'description'} && length($content{'description'}) >20) { + $content{'description'} = substr($content{'description'},0,20); + } + # separate month and year values for expiry_date if ( $content{expiration} ) { - ($content{exp_month}, $content{exp_year}) = split /\//, $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); + $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{'first_name'} + || !$content{'last_name'} && $content{'name'} + ) + { + ($content{'first_name'}, $content{'last_name'}) = + split(' ', $content{'name'}, 2); } if ($content{'address'} =~ m/[\D ]*(\d+)\D/) { @@ -148,7 +161,10 @@ 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->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}); @@ -168,6 +184,10 @@ sub submit { $self->process_content(); my %content = $self->content; my $action = $self->action(); + + if ( $self->test_transaction ) { + $self->server('dvrotsos2.kattare.com'); + } my @acceptable_actions = ('charge', 'refund', 'void'); @@ -175,98 +195,99 @@ sub submit { croak "'$action' is not supported at this time."; } - # fill out the template vars - my $template_vars = { - + # fill in the xml vars + my $xml_vars = { auth => { - platform => $self->platform, - userid => $self->userid, - gid => $self->gid, - tid => $self->tid + Platform => $self->platform, + UserId => $content{'login'}, + GID => $content{'password'}, + Tid => $self->tid || '01', }, 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() + Amount => $content{'amount'}, + Trk1 => ($content{'track1'}) ? $content{'track1'} : '', + Trk2 => ($content{'track2'}) ? $content{'track2'} : '', + TypeOfSale => ($content{'description'}) ? $content{'description'} : '', + Cf1 => ($content{'UDField1'}) ? $content{'UDField1'} : '', + Cf2 => ($content{'UDField2'}) ? $content{'UDField2'} : '', + Cf3 => '', + AccountNumber => ($content{'card_number'}) ? $content{'card_number'} : '', + ExpirationMonth => $content{'exp_month'}, + ExpirationYear => $content{'exp_year'}, + Cvv => ($content{'cvv'}) ? $content{'cvv'} : ($content{'cvv2'}) ? $content{'cvv2'} : '', + CardHolderFirstName => ($content{'first_name'}) ? $content{'first_name'} : '', + CardHolderLastName => ($content{'last_name'}) ? $content{'last_name'} : '', + AvsZip => ($content{'zip'}) ? $content{'zip'} : '', + AvsStreet => ($content{'street_number'}) ? $content{'street_number'} : '', +# IndustryType => { +# IndType => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '', +# IndInvoice => ($content{'invoice_number'}) ? $content{'invoice_number'} : '' +# }, + ApplicationId => $self->appid(), + Recurring => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0, + ReferenceNumber => ($content{'ref_num'}) ? $content{'ref_num'} : '', + Token => ($content{'token'}) ? $content{'token'} : '', + Receipt => ($content{'receipt'}) ? $content{'receipt'} : '', + TransactionDate => ($content{'txn_date'}) ? $content{'txn_date'} : '' } + # we won't be using level2 nor level3. So I'm leaving them out for now. }; - # create the list of required fields based on the action - my @required_fields = qw/ amount /; + my @required_fields = qw/ Amount /; if ($action eq 'charge') { - push(@required_fields, $_) foreach (qw/ account_number cvv exp_month exp_year /); + push @required_fields, $_ + foreach (qw/ AccountNumber ExpirationMonth ExpirationYear /); }elsif ($action eq 'void') { - push(@required_fields, $_) foreach (qw/ reference_number transaction_date /); + push @required_fields, $_ + foreach (qw/ ReferenceNumber /); }elsif ($action eq 'refund') { - push(@required_fields, $_) foreach (qw/ amount account_number exp_month exp_year /); + push @required_fields, $_ + foreach (qw/ Amount AccountNumber ExpirationMonth ExpirationYear /); } # check the requirements are met. my @missing_fields; foreach my $field (@required_fields) { - push(@missing_fields, $field) if (!$template_vars->{payment}{$field}); + push(@missing_fields, $field) if (!$xml_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 $process_action = $action; + $process_action =~ s/\b([a-z])/\u$1/g; + $process_action = 'Process'.$process_action; my $xml_data; - $tt->process( \$xml_template, $template_vars, \$xml_data ) || croak $tt->error(); + my $writer = new XML::Writer( OUTPUT => \$xml_data, + DATA_MODE => 0, + DATA_INDENT => 0, + ENCODING => 'utf-8', + ); + $writer->xmlDecl(); + $writer->startTag('Request'); + $writer->startTag('MerchantData'); + foreach my $key ( keys ( %{$xml_vars->{auth}} ) ) { + $writer->dataElement( $key, $xml_vars->{auth}{$key} ); + } + $writer->endTag('MerchantData'); + $writer->startTag($payment_actions{ $self->action }{process}); + foreach my $key ( @{$payment_actions{ $self->action }{fields}} ) { + next if (!$xml_vars->{payment}{$key}); + if (ref $xml_vars->{payment}{$key} eq '') { + $writer->dataElement( $key, $xml_vars->{payment}{$key}); + }else { + $writer->startTag($key); + foreach my $key2 (keys %{$xml_vars->{payment}{$key}}) { + $writer->dataElement( $key2, $xml_vars->{payment}{$key}{$key2} ); + } + $writer->endTag($key); + } + } + $writer->endTag($payment_actions{ $self->action }{process}); + $writer->endTag('Request'); + $writer->end(); warn "XML:\n$xml_data\n" if $DEBUG > 2; @@ -286,7 +307,8 @@ sub submit { # conform to RFC standards $content =~ s/\n/\r\n/gs; - my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content ); + my ( $page, $server_response, %headers ) = + $self->https_post( $opts, $content ); # store the server response. $self->server_response($server_response); @@ -295,9 +317,7 @@ sub submit { 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) + #additional logging information, possibly too sensitive for an error $self->error_message( "(HTTPS response: ".$server_response.") ". "(HTTPS headers: ". @@ -323,6 +343,7 @@ sub parse_response { if ($self->server_response =~ /^200/) { my $response = XMLin($page); + warn "Response:\n".Dumper($response)."\n" if $DEBUG > 2; $self->result_code($response->{Status}); # 0 /1 $self->response_code($response->{ResponseCode}); # see documentation for translation $self->avs_response($response->{AvsResponse}); # Y / N @@ -333,7 +354,7 @@ sub parse_response { $self->is_success($self->result_code() eq '0' ? 1 : 0); if ($self->is_success()) { - $self->authorization($response->{AuthIdentificationResponse}); + $self->authorization($response->{ReferenceNumber}); } # fill in error_message if there is is an error if ( !$self->is_success && exists($response->{AdditionalResponseData})) { @@ -342,218 +363,13 @@ sub parse_response { $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : ''); } - }else { - $self->is_success(0); - $self->error_message('Error communicating with vSecureProcessing server'); + } else { + die 'Error communicating with vSecureProcessing server'; return; } - -} - -sub _get_xml_template { - my $action = shift; - - my $xml_template = q| - - [% auth.platform %] - [% auth.userid %] - [% auth.gid %] - [% auth.tid %] - - |; - - 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 .= ""; - $xml_template =~ s/[\n\t\s]*//g; - - return $xml_template; -} - -sub _get_xml_template_charge { - my $xml_template = q| - [% payment.amount %] - [% payment.track1 %] - [% payment.track2 %] - [% payment.type %] - [% payment.cf1 %] - [% payment.cf2 %] - [% payment.cf3 %] - [% payment.account_number %] - [% payment.exp_month %] - [% payment.exp_year %] - [% payment.cvv %] - [% payment.first_name %] - [% payment.last_name %] - [% payment.postal_code %] - [% payment.street_address %] - - [% payment.industry_type %] - [% payment.invoice_num %] - - [% payment.appid %] - [% payment.recurring %] - |; - - # other options (that we are not using right now): -# -# [% level2.card_type %] -# [% level2.purchase_code %] -# [% level2.country_code %] -# [% level2.ship_tp_postal_code %] -# [% level2.ship_from_postal_code %] -# [% level2.sales_tax %] -# [% level2.product_description1 %] -# [% level2.product_description2 %] -# [% level2.product_description3 %] -# [% level2.product_description4 %] -# -# -# [% level3.purchase_order_num %] -# [% level3.order_date %] -# [% level3.duty_amount %] -# [% level3.alt_tax_amount %] -# [% level3.discount_amount %] -# [% level3.freight_amount %] -# [% level3.tax_exempt %] -# [% level3.line_item_count %] -# -# [% level3.purchase_items %] -# -# - - return $xml_template; -} - -sub _parse_line_items { - my $self = shift; - my %content = $self->content(); - - return '' if (!$content{'items'}); - - my @line_items; - my $template = q| - [% seq_num %] - [% code %] - [% desc %] - [% qty %] - [% unit %] - [% unit_cost %] - [% amount %] - [% discount_amount %] - [% tax_amount %] - [% tax_rate %] - |; - - - 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| - [% payment.amount %] - [% payment.account_number %] - [% payment.exp_month %] - [% payment.exp_year %] - [% payment.reference_number %] - - [% payment.industry_type %] - [% payment.appid %] - |; - - return $xml_template; -} - -sub _get_xml_template_refund { - my $xml_template = q| - [% payment.amount %] - [% payment.account_number %] - [% payment.exp_month %] - [% payment.exp_year %] - [% payment.appid %] - |; - - 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__ @@ -566,17 +382,17 @@ Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Busin use Business::OnlinePayment; my %processor_info = ( - platform => '####', - gid => 12345678901234567890, - tid => 01, - user_id => '####', - url => 'www.####.com' + platform => 'vsecure_platform', + appid => 'vsecure_appid', + tid => '54', #optional, defaults to 01 ); my $tx = new Business::OnlinePayment( "vSecureProcessing", %processor_info); $tx->content( - appid => '######', - type => 'VISA', + login => 'vsecure@user.id', + password => '12345678901234567890', #vsecure gid + + type => 'CC', action => 'Normal Authorization', description => 'Business::OnlinePayment test', amount => '49.95', @@ -653,7 +469,8 @@ from content(%content): =head1 COMPATIBILITY -Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014). +Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document +Version: 140901 (September 1, 2014). See http://www.vsecureprocessing.com/ for more information. @@ -661,7 +478,7 @@ See http://www.vsecureprocessing.com/ for more information. Original author: Alex Brelsfoard -Current maintainer: Alex Brelsfoard +Current maintainer: Ivan Kohler =head1 COPYRIGHT @@ -687,4 +504,3 @@ perl(1). L. =cut -