Business-OCV
view release on metacpan or search on metacpan
# 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;
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 )