Name

charge — perform a transaction with a payment gateway

ATTRIBUTES

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?

DESCRIPTION

Returns transaction identifier.

The transaction identifier returned from the payment gateway will be stored in the session as payment_id.

BEHAVIOR

This tag does not appear to be affected by, or affect, the rest of Interchange.

EXAMPLES

No examples are available at this time. We do consider this a problem and will try to supply some.

NOTES

AVAILABILITY

charge is available in Interchange versions:

4.6.0-5.9.0 (git-head)

SOURCE

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'};
}

AUTHORS

Interchange Development Group

SEE ALSO

MV_PAYMENT_PRECISION(7ic), MV_PAYMENT_CURRENCY(7ic)

DocBook! Interchange!