Business-OCV

 view release on metacpan or  search on metacpan

OCV.pm  view on Meta::CPAN

# reset things to a virgin state. A reset() may also be in order in 
# response to a HUP signal.
#
# 
# NOTES/CLARIFICATIONS ON THE OCV SERVER DOCUMENTATION
#
# - Pre-authorisations and Completions
#  These transactions are handled completely by the bank - that is, the 
# OCV server doesn't do anything special with them. Moreover, they're 
# apparently treated as disparate transactions - the OCV server (at least,
# possibly also the bank) does nothing to ensure pre-auths and completions 
# match (card data, amount, etc). For example, it is apparently possible 
# for a completion with a given preauth number to 'succeed' even when the 
# card data does not match that of the pre-auth transaction. It appears 
# that behaviour in these situations is undefined - it is up to the client 
# to make sure the data match.
#
#  Generally, a completion is equivalent to a purchase.
#
# - Accounts
#  Each transaction to the bank must provide a merchant ID (to identify
# the merchant (e.g. bank account details)), and terminal ID (to identify 
# the hardware). OCV "accounts" are used to abstract these details, and 
# more importantly to allow concurrent transactions (requires multiple
# VPPs, which in turn requires both a multiple-VPP license from Ingenico and
# multiple merchant IDs and/or terminal IDs from the bank). The client (us) 
# simply specifies which account to use and the server allocates the first 
# available VPP allocated to that account. It returns the MerchantID and
# TerminalID as part of the RESPONSE message, if the client is interested.
#
#  The account number 0 is the 'Default' account and cannot be removed.
# The Default account is for the OCV Server's internal use and must not be 
# used by clients. Note that the Default account must have a VPP assigned to 
# it (which is why you get 6 accounts when you purchase a 5 account license). 
# Further, when processing concurrent transactions, if an account is busy 
# you'll get a SERVER BUSY response so it pays to allocate as many VPPs to 
# an account as possible (and make sure to retry BUSY responses).
#
# OCV DEVELOPMENT SERVER BUGS
#
#  The OCV 'Development Server' supplied by Ingenico for testing and 
# development purposes has a few bugs which mean it's not an entirely
# reliable means of testing your code. As of v.1.15, it:
#  - often locks up and/or crashes with dud messages
#  - does not respond well to polled requests. It 'locks' the account after 
#    serving some polled requests (i.e. subsequent transactions on the 
#    account return SERVER BUSY or RECORD NOT FOUND). In addition, on 
#    subsequent connections it erroneously sends a response to the polled 
#    request which mis-synchronises the rest of the communications.
#  - does not return full details for status requests (for example, it omits 
#    the settlement date, card info, merchant + terminal IDs)
#
# OCV LIVE SERVER BUGS
#
#  Unfortunately the Ingenico 'live' server (v2.08) has also shown problems,
# with one issue of a complete lockup after a totals requests (the NT registry
# had to be edited to restore service). Additionally, the server is found to
# issue unsolicted 'logon responses' around once per week. Ingenico have 
# advised this is an "undocumented feature". 
#  To work around this, LOGON responses to non-LOGON requests are 
# transparently discarded (the event is logged).
#
#
# MISCELLANEOUS NOTES ON THE CODE
#
#  As is discussed below in "Message Format Specifications", each OCV 
# message is described via a table of field name => data type pairs.
# Internally these are manipulated via hashes (see notes in the code 
# for the details). The use of hashes has required a bit of mucking
# about due to a hash's unpredictable ordering, though at the time
# of writing there was mention of "pseduo-hashes", i.e. arrays which
# support string indices, with perl automatically managing the mapping
# from string to index. Perhaps if/when perl's pseudo-hashes become
# standard the code can be simplified and performance probably improved,
# for what it's worth :-).
#
#
######################################################################
# 
# RCS Identifier:
# $Id:$
# 
# Change Log:
# $Log:$
#
# 
######################################################################
# 

use strict;			# try and pick up silly errors at compile time

use vars qw/@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION $OCV_VERSION 
	$AUTOLOAD $debug/;

$VERSION = 0.1;			# this module
$OCV_VERSION = 1.08;	# the OCV spec to which this module applies

use Exporter ();
@ISA       = qw/Exporter/;
@EXPORT    = qw//;
@EXPORT_OK = qw//;

%EXPORT_TAGS = 
(
	'server'		=> [qw/SERVER_PORT POLL_BLOCK POLL_NONBLOCK/],
	'transaction'	=> [qw/TRANS_CLIENTNET TRANS_CLIENTTEL TRANS_CLIENTMAIL 
						TRANS_APPROVED TRANS_DECLINED TRANS_INPROGRESS
						TRANS_BUSY/],
	'statistics'	=> [qw/STATS_CURRENT STATS_PERMANENT/],
	'log'			=> [qw/LOGSEPARATOR parsetxnlog/],
);
# add %EXPORT_TAGS to @EXPORT,_OK
Exporter::export_tags(qw/server transaction statistics log/);
Exporter::export_ok_tags();

use Carp;

use Socket;			# socket symbolic constants (AF_INET, SOCK_, etc)
use IO::Socket;
use IO::Select;
use IO::File;

OCV.pm  view on Meta::CPAN


	return $m;
}

############################################
# The following are wrappers around _transaction, one for each "transaction"
# type. Note that due to the way the arguments are parsed, arguments listed 
# before the @_ can be overridden by the caller (@_); arguments listed after 
# @_ can't be overridden by the caller.
# - the 'nb' subroutines are 'non busy' (or 'non blocking', take your pick),
#   that is they'll simply return when SERVER BUSY responses are encountered
#   The non-'nb' subs will attempt to retry BUSY responses, up to a point.
# - all transactions need a unique Transaction Reference ID, except for status
# - note that new transaction references should be generated for each attempt, 
#   successful or not, busy or not.
sub nbpurchase
{
	my $self = shift;
	my $n = ref($self->{'txnref'}) eq 'CODE' ? &{$self->{'txnref'}} : 
		$self->{'txnref'}++;

	$self->_transaction(TT_TRANS_PURCHASE, TxnRef => $n, @_, AuthNum => '');
}

sub nbrefund
{
	my $self = shift;
	my $n = ref($self->{'txnref'}) eq 'CODE' ? &{$self->{'txnref'}} : 
		$self->{'txnref'}++;

	$self->_transaction(TT_TRANS_REFUND, TxnRef => $n, @_, AuthNum => '');
}

sub nbpreauth
{
	my $self = shift;
	my $n = ref($self->{'txnref'}) eq 'CODE' ? &{$self->{'txnref'}} : 
		$self->{'txnref'}++;
	$self->_transaction(TT_TRANS_PREAUTH, TxnRef => $n, @_, AuthNum => '');
}

sub nbcompletion
{
	my $self = shift;
	my $n = ref($self->{'txnref'}) eq 'CODE' ? &{$self->{'txnref'}} : 
		$self->{'txnref'}++;
	$self->_transaction(TT_TRANS_COMPLETION, TxnRef => $n, @_);
}

sub nbstatus
# Get the status of a given transaction, specified by its Transaction 
# reference number (string), TxnRef. If TxnRef is not provided, 
# _transaction will default to the last one.
{
	shift->_transaction(TT_TRANS_STATUS, 
		CardData => '', CardExpiry => '', Amount => '', AuthNum => '', 
		@_,
		PolledMode => POLL_BLOCK);
}

# the following set of subroutines will transparently retry the transaction
# in the face of SERVER BUSY responses (up to a limit)
sub _busy
{
	my ($s, $self, @a) = @_;
	my $m;
	my $n = $self->{'busyattempts'};	# maximum no. of attempts
	$m = $s->($self, @a);
	while ($m and $m->ResponseCode eq TRANS_BUSY and $n-- > 0)
	{
		select(undef, undef, undef, $self->{'busywait'} * (0.5 + rand));
		$m = $s->($self, @a);
	}

	return $m;
}

sub purchase   { _busy(\&nbpurchase,   @_) }
sub refund     { _busy(\&nbrefund,     @_) }
sub preauth    { _busy(\&nbpreauth,    @_) }
sub completion { _busy(\&nbcompletion, @_) }
sub status     { _busy(\&nbstatus,     @_) }


############################################
# now the 'miscellaneous' requests

sub statistics
# Server statistics
# - sends a Statistics request, receives one of two response
# Arguments:
#  Reset - set to 1 to reset statistics.
#  SubCode - statistics type, STATS_PERMANENT | STATS_CURRENT 
#            (default STATS_CURRENT)
{
	my ($self, @args) = @_;
	my $args;

	return undef unless $args = _args(@args);

	$args->{'subcode'} = STATS_CURRENT unless defined $args->{'subcode'};
	$args->{'reset'}   = 0 unless defined $args->{'reset'};

	# sanity check - the type of response message must be valid
	$@ = "invalid statistics subcode [$args->{'subcode'}]", return undef 
		unless exists($Responses{TT_STATS().$args->{'subcode'}});
	$self->logdebug("statistics", $args );

	# process the message - send Statistics request, receive 
	#  Statistics.SubCode
	# - note this 'Statistics.SubCode' representation used solely within 
	#   this module to disambiguate the various responses
	$self->_message($args, TT_STATS(), TT_STATS().$args->{'subcode'});
}

sub vppconfig
# Virtual pinpad configuration.
# - used to associate an Account with a VPP, and the VPP to a physical pinpad.
# - note that one Account can have multiple VPPs, which would allow concurrent 
#   transactions for that account.
# - vppstatus should be used to confirm the configuration



( run in 0.618 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )