#!/usr/bin/perl =head1 NAME CCLib.pm -- Compatibility module for PAYMENTNET.COM and CyberCash version 2 =cut package CCLib; use Carp; use Exporter; @EXPORT = qw/ SetServer sendmserver /; use POSIX qw/tmpnam/; $VERSION = '0.01'; use vars qw/$VERSION/; my $Port = 1605; my $Server = 'connect.seguesystems.com'; my $Password; my $VendorID; my $Executable; =head1 VERSION 0.01 =head1 SYNOPSIS use CCLib; SetServer( host => 'connect.sequesystems.com', port => '1605', secret => 'analyze', vendor => 'sltest', ); my $mode = 'mauthonly' || 'mauthcapture'; %result = sendmserver ( $mode, # may be anything 'Order-ID' => $orderID, # can be the Cybercash 'usd 33.44' 'Amount' => $amount, # spaces OK 'Card-Number' => $ccnum, 'Card-Name' => $name, 'Card-Address' => $address, 'Card-City' => $city, 'Card-State' => $state, 'Card-Zip' => $zip, 'Card-Country' => $country, # Can be cybercash style, must be # one of MM/YY, M/YY, MYY, or MMYY 'Card-Exp' => $exp, ); if($result{MStatus} !~ /^success/) { $error = $result{MErrMsg}; return undef; } else { return 1; } =head1 DESCRIPTION This module interfaces PaymentNet.com servers to software designed for CyberCash 2. It is also quite suitable for standalone use with Perl. This module was designed to work with MiniVend, so if you see references to $Vend::Cfg that is the reason. You may ignore those references; they should not cause problems. If you want to set your Vendor ID in the environment, use $ENV{PAYMENTNET_VENDORID}. The C executable can either reside in the Perl @INC libary include path, or can be in your $ENV{PATH}. They are used in that order. =head2 Example of use with MiniVend Set the following variables in catalog.cfg: # the paymentnet.com host Variable CYBER_HOST connect.sequesystems.com # the paymentnet.com port, defaults to 1605 #Variable CYBER_PORT 1605 # your password Variable CYBER_SECRET analyze # your password Variable CYBER_VENDOR sltest You can set your mv_cyber_mode variable to any valid PaymentNet transaction type, or to the CyberCash: CyberCash PaymentNet ------------------------------ mauthonly C1 mauthcapture C6 =head1 AUTHOR Mike Heins, Internet Robotics, . =head1 BUGS Doesn't capture and log approval, AVS, and verify. That would be easy to add. =cut # # socklink # # host name 'www2.onanalysis.com' # port number '443' # local path on host to cgi '/socketlink/cc.cgi' # user name for SocketLink account 'XYZcorp' # password for SocketLink account 'foobar' # credit card transaction type 'C6' # credit card number '1234234534564567' # ZIP code for card (AVS) '94612' # credit card expiration date '0698' # amount of transaction '49.95' # Comment #1 'SuperWidget (green)' # Comment #2 'Sales Agent: 4432' # sub SetServer { #::logGlobal("Setserver called", @_); my (%hash) = @_; $Server = $hash{'host'} if $hash{'host'} and $hash{'host'} ne 'localhost'; $Port = $hash{'port'} if $hash{'port'} and $hash{'port'} ne '8000'; $Password = $hash{'secret'} if $hash{'secret'}; if ($hash{'vendor'}) { $VendorID = $hash{'vendor'}; #::logGlobal("vendor id assigned from hash"); } elsif($ENV{PAYMENTNET_VENDORID}) { #::logGlobal("vendor id assigned from PAYMENTNET env"); $VendorID = $ENV{PAYMENTNET_VENDORID}; } elsif( defined $Vend::Cfg and ref $Vend::Cfg->{Variable} and $Vend::Cfg->{Variable}{CYBER_VENDOR} ) { ::logGlobal("vendor id assigned from CYBER_VENDOR"); $VendorID = $Vend::Cfg->{Variable}{CYBER_VENDOR}; } else { carp "No vendor id assigned.\n"; $VendorID = 'sltest'; } my $ext = ''; my $joiner = "/"; if($^O =~ /win32/) { $joiner = "\\"; $ext = '.exe'; } if ( defined $Vend::Cfg and ref $Vend::Cfg =~ m'HASH' and ref $Vend::Cfg->{Variable} =~ m'HASH' and $Vend::Cfg->{Variable}{CYBER_EXECUTABLE} ) { $Executable = $Vend::Cfg->{Variable}{CYBER_EXECUTABLE}; } else { for(@INC) { my $file = 'socklink' . $ext; $Executable = "$_$joiner" . 'socklink' if -x "$_$joiner$file"; } } $Executable = 'socklink' unless $Executable; #::logGlobal("Vendor=$VendorID exec=$Executable pass=$Password\n"); return 1; } my %status_translate = ( -99 => 'Internal (unknown) error', -8 => 'X509 cerification verification error', -7 => 'SSL verify location error', -6 => 'SSL verification policy error', -5 => 'SSL context initialization failed', -4 => 'Socket initialization error', -2 => 'Hostname lookup failed', -1 => 'Server socket unavailable', 0 => 'success', 1 => 'Invalid Vendor ID', 2 => 'Invalid password', 3 => 'Invalid transaction type', 4 => 'Invalid amount', 5 => 'Processor host connection error', 11 => 'Transaction timeout error', 12 => 'Transaction declined', 19 => 'Invalid authorization transaction ID', 21 => 'Recapture not allowed', 23 => 'Credit card number is invalid', 24 => 'Credit card expiration date is invalid', ); my %mode_translate = ( qw/ C0 C0 C1 C1 C3 C3 C6 C6 C7 C7 mauthonly C1 mauthcapture C6 authonly C1 authcapture C6 / ); sub sendmserver { my $mode = shift; my %opt = @_; #my $debug = "mode=$mode\n"; #for(keys %opt) { # $debug .= "$_=$opt{$_}\n"; #} #::logGlobal("sendmserver called:\n$debug"); my (@err_parms); unless( $Server && $Password && $VendorID && $Port ) { @err_parms = ( 'MStatus' , 'error_before_connect', 'MErrMsg' ); my $err = ''; $err .= "No server set.\n" unless $Server; $err .= "No password given.\n" unless $Password; $err .= "No port set.\n" unless $Port; $err .= "No Vendor ID given.\n" unless $VendorID; push @err_parms, $err; #::logGlobal("error", @err_parms); return(@err_parms); } $opt{'Card-Number'} =~ tr/0-9//cd; $opt{'Card-Exp'} =~ tr/0-9//cd; $opt{'Card-Exp'} = "0" . $opt{'Card-Exp'} if length $opt{'Card-Exp'} < 4; $opt{'Amount'} =~ tr/.0-9//cd; unless ( $mode_translate{$mode}) { @err_parms = ('MStatus', 'error_before_connect', 'MErrMsg', 'No mode set.'); #::logGlobal("error", @err_parms); return(@err_parms); } # The order of these params is documented in PaymentNet # literature (and above from the socklink usage message). # We use Order ID and customer name as the last two # user-definable ones. my (@param); @param = ( $Server, $Port, $opt{'Card-Address'}, $VendorID, $Password, $mode_translate{$mode}, $opt{'Card-Number'}, $opt{'Card-Zip'}, $opt{'Card-Exp'}, $opt{'Amount'}, $opt{'Order-ID'}, $opt{'Card-Name'}, ); for(@param) { next if /^[-\w.]+$/; s/\s/ /g; s/"/\\"/g; $_ = qq{"$_"}; } $debug = "parms:\n" . join "\n", @param; #::logGlobal($debug); @err_parms = ( 'MStatus' , 'error_on_execution', 'MErrMsg' ); my $rfile = POSIX::tmpnam(); open(TRANS, "| $Executable -i > $rfile") or die "Can't fork: $!\n"; print TRANS join " ", @param; print TRANS "\n"; close TRANS; if($?) { my $status = $? >> 8; @err_parms = ( 'MStatus', 'error_on_execution', 'MErrMsg' ); push @err_parms, "Status $status: $status_translate{$status}"; #::logGlobal("error", @err_parms); return @err_parms if $status != 1; } my $status_msg; open(RESULT, $rfile) or croak "read $rfile: $!\n"; my $status = ; chomp $status; my $result = ; chomp $result; # Not using this at the moment # my $process = ; # chomp $process; close RESULT; if($?) { my $code = $? >> 8; @err_parms = ( 'MStatus', 'error_during_execution', 'MErrMsg' ); carp("Error running $Executable, error $code: $!\n"); push @err_parms, $status_translate{$status}; #::logGlobal("error", @err_parms); return @err_parms; } $result = $status_translate{$status} unless $result; my $success = ($status eq '0'); #if($status eq '0') { # $status_msg = 'success'; #} #elsif ($status < 0) { # $status_msg = 'connection error'; #} #else { # $status_msg = 'transaction error'; #} my %result; if ($success) { $result{MStatus} = 'success'; $result{'order-id'} = $result; } else { $result{MStatus} = $status_translate{$status}; $result{MErrMsg} = $result; } return (%result); } 1;