charge — perform a transaction with a payment gateway
| Attribute | Pos. | Req. | Default | Description |
|---|---|---|---|---|
| route | Yes | |||
| gateway | payment gateways | |||
| transaction | transaction type | |||
| amount | amount of money to charge | |||
| cyber_mode | ||||
| log_to_error | ||||
| hash | No | Return complete result hash as a reference? | ||
| interpolate | 0 | interpolate output? | ||
| hide | 0 | Hide the tag return value? |
Returns transaction identifier.
The transaction identifier returned from the payment gateway will be stored
in the session as payment_id.
Interchange 5.9.0:
Source: code/SystemTag/charge.coretag
Lines: 14
# Copyright 2002-2007 Interchange Development Group and others # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. See the LICENSE file for details. # # $Id: charge.coretag,v 1.5 2007-03-30 23:40:49 pajamian Exp $ UserTag charge Order route UserTag charge addAttr UserTag charge PosNumber 1 UserTag charge Version $Revision: 1.5 $ UserTag charge MapRoutine Vend::Payment::charge
Source: lib/Vend/Payment.pm
Lines: 559
sub charge {
my ($charge_type, $opt) = @_;
my $pay_route;
### We get the payment base information from a route with the
### same name as $charge_type if it is there
if($Vend::Cfg->{Route}) {
$pay_route = $Vend::Cfg->{Route_repository}{$charge_type} || {};
}
else {
$pay_route = {};
}
### Then we take any payment options set in &charge, [charge ...],
### or $Tag->charge
# $pay_opt is package-scoped but lexical
$pay_opt = { %$pay_route };
for(keys %$opt) {
$pay_opt->{$_} = $opt->{$_};
}
# We relocate these to subroutines to standardize
### Maps the form variable names to the names needed by the routine
### Standard names are defined ala Interchange or MV4.0x, b_name, lname,
### etc. with b_varname taking precedence for these. Falls back to lname
### if the b_lname is not set
my (%actual) = map_actual();
$pay_opt->{actual} = \%actual;
# We relocate this to a subroutine to standardize. Uses the payment
# counter if there
my $orderID = gen_order_id($pay_opt);
### Set up the amounts. The {amount} key will have the currency prepended,
### e.g. "usd 19.95". {total_cost} has just the cost.
# Uses the {currency} -> MV_PAYMENT_CURRENCY options if set
my $currency = charge_param('currency')
|| ($Vend::Cfg->{Locale} && $Vend::Cfg->{Locale}{currency_code})
|| 'usd';
# Uses the {precision} -> MV_PAYMENT_PRECISION options if set
my $precision = charge_param('precision') || 2;
my $penny = charge_param('penny_pricing') || 0;
my $amount = $pay_opt->{amount} || Vend::Interpolate::total_cost();
$amount = round_to_frac_digits($amount, $precision);
$amount = sprintf "%.${precision}f", $amount;
$amount *= 100 if $penny;
$pay_opt->{total_cost} = $amount;
$pay_opt->{amount} = "$currency $amount";
###
### Finish setting amounts and currency
# If we have a previous payment amount, delete it but push it on a stack
#
my $stack = $Vend::Session->{payment_stack} || [];
delete $Vend::Session->{payment_result};
delete $Vend::Session->{cybercash_result}; ### Deprecated
#::logDebug("Called charge at " . scalar(localtime));
#::logDebug("Charge caller is " . join(':', caller));
#::logDebug("mode=$pay_opt->{gateway}");
#::logDebug("pay_opt=" . ::uneval($pay_opt));
# Default to the gateway same as charge type if no gateway specified,
# and set the gateway in the session for logging on completion
if(! $opt->{gateway}) {
$pay_opt->{gateway} = charge_param('gateway') || $charge_type;
}
#$charge_type ||= $pay_opt->{gateway};
$Vend::Session->{payment_mode} = $pay_opt->{gateway};
# See if we are in test mode
$pay_opt->{test} = charge_param('test');
# just convenience
my $gw = $pay_opt->{gateway};
# See if we are calling a defined GlobalSub payment mode
my $sub = $Global::GlobalSub->{$gw};
# Try our predefined modes
if (! $sub and defined &{"Vend::Payment::$gw"} ) {
$sub = \&{"Vend::Payment::$gw"};
}
# This is the return from all routines
my %result;
if($sub) {
#::logDebug("Charge sub");
# Calling a defined GlobalSub payment mode
# Arguments are the passed option hash (if any) and the route hash
my $pid;
my $timeout = $pay_opt->{global_timeout} || charge_param('global_timeout');
%result = eval {
if ($timeout > 0) {
my $pipe = IO::Pipe->new;
unless ($pid = fork) {
Vend::Server::child_process_dbi_prep();
$pipe->writer;
my %rv = $sub->($pay_opt);
$pipe->print( ::uneval(\%rv) );
exit;
}
$pipe->reader;
my $to_msg = $pay_opt->{global_timeout_msg}
|| charge_param('global_timeout_msg')
|| 'Due to technical difficulties, your order could not be processed.';
local $SIG{ALRM} = sub { die "$to_msg\n" };
alarm $timeout;
wait;
alarm 0;
$pid = undef;
my $rv = eval join ('', $pipe->getlines);
return %$rv;
}
return $sub->($pay_opt);
};
if($@) {
my $msg = errmsg(
"payment routine '%s' returned error: %s",
$charge_type,
$@,
);
kill (KILL => $pid)
if $pid && kill (0 => $pid);
::logError($msg);
$result{MStatus} = 'died';
$result{MErrMsg} = $msg;
}
}
elsif($charge_type =~ /^\s*custom\s+(\w+)(?:\s+(.*))?/si) {
#::logDebug("Charge custom");
# MV4 and IC4.6.x methods
my (@args);
@args = Text::ParseWords::shellwords($2) if $2;
if(! defined ($sub = $Global::GlobalSub->{$1}) ) {
::logError("bad custom payment GlobalSub: %s", $1);
return undef;
}
eval {
%result = $sub->(@args);
};
if($@) {
my $msg = errmsg(
"payment routine '%s' returned error: %s",
$charge_type,
$@,
);
::logError($msg);
$result{MStatus} = $msg;
}
}
elsif (
$actual{cyber_mode} =~ /^minivend_test(?:_(.*))?/
or
$charge_type =~ /^internal_test(?:[ _]+(.*))?/
)
{
#::logDebug("Internal test");
# Test mode....
my $status = $1 || charge_param('result') || undef;
# Interchange test mode
my %payment = ( %$pay_opt );
&testSetServer ( %payment );
%result = testsendmserver(
$actual{cyber_mode},
'Order-ID' => $orderID,
'Amount' => $amount,
'Card-Number' => $actual{mv_credit_card_number},
'Card-Name' => $actual{b_name},
'Card-Address' => $actual{b_address},
'Card-City' => $actual{b_city},
'Card-State' => $actual{b_state},
'Card-Zip' => $actual{b_zip},
'Card-Country' => $actual{b_country},
'Card-Exp' => $actual{mv_credit_card_exp_all},
);
$result{MStatus} = $status if defined $status;
}
else {
#::logDebug("Unknown charge type");
my $msg = errmsg("Unknown charge type: %s", $charge_type);
::logError($msg);
$result{MStatus} = $msg;
}
push @$stack, \%result;
$Vend::Session->{payment_result} = \%result;
$Vend::Session->{payment_stack} = $stack;
my $svar = charge_param('success_variable') || 'MStatus';
my $evar = charge_param('error_variable') || 'MErrMsg';
if($result{$svar} !~ /^success/) {
$Vend::Session->{payment_error} = $result{$evar};
if ($result{$evar} =~ /\S/) {
$Vend::Session->{errors}{mv_credit_card_valid} = $result{$evar};
}
$result{'invalid-order-id'} = delete $result{'order-id'}
if $result{'order-id'};
}
elsif($result{$svar} =~ /success-duplicate/) {
$Vend::Session->{payment_error} = $result{$evar};
$result{'invalid-order-id'} = delete $result{'order-id'}
if $result{'order-id'};
}
else {
delete $Vend::Session->{payment_error};
}
$Vend::Session->{payment_id} = $result{'order-id'};
my $encrypt = charge_param('encrypt');
if($encrypt and $CGI::values{mv_credit_card_number} and $Vend::Cfg->{EncryptKey}) {
my $prog = charge_param('encrypt_program') || $Vend::Cfg->{EncryptProgram};
if($prog =~ /pgp|gpg/) {
$CGI::values{mv_credit_card_force} = 1;
(
undef,
$::Values->{mv_credit_card_info},
$::Values->{mv_credit_card_exp_month},
$::Values->{mv_credit_card_exp_year},
$::Values->{mv_credit_card_exp_all},
$::Values->{mv_credit_card_type},
$::Values->{mv_credit_card_error}
) = encrypt_standard_cc(\%CGI::values);
}
}
::logError(
"Order id for charge type %s: %s",
$charge_type,
$Vend::Session->{cybercash_id},
)
if $pay_opt->{log_to_error};
# deprecated
for(qw/ id error result /) {
$Vend::Session->{"cybercash_$_"} = $Vend::Session->{"payment_$_"};
}
return \%result if $pay_opt->{hash};
return $result{'order-id'};
}