# This is how you can use CCVS with Interchange.
# 
Variable CCVS_HELP <<EOV

 1. Modify interchange.cfg to use this file.
 
#include globalsub/ccvs

	(you might have to create that directory and copy this file there)

 2. Modify catalog.cfg to set the server and your CCVS config info

# CCVS configuration name
Variable MV_PAYMENT_ID   ccvs_configname

 3. Set the payment mode in catalog.cfg:

Variable MV_PAYMENT_MODE ccvs

 4. Make sure CreditCardAuto is off (default)

 5. Restart Interchange.

EOV

GlobalSub <<EOS
sub ccvs {
    my ($opt, $configname, $amount) = @_;

#::logDebug("ccvs called, args=" . ::uneval(\@_));

	# Requires CCVS perl libs, get from
	#
	#	http://www.redhat.com/products/software/ecommerce/ccvs/
	#
	use CCVS;
	my $sess;
	my %result;

	my $ccvs_die = sub {
		my ($msg, @args) = @_;
		$msg = "CCVS: $msg" unless $msg =~ /^CCVS/;
		$msg = ::errmsg($msg, @args);
		CCVS::done($sess) if $sess;
#::logDebug("ccvs erred, result=$msg");
		die("$msg\n");
	};

	my $actual = $opt->{actual};
	if(! $actual) {
		my %actual = Vend::Order::map_actual();
		$actual = \%actual;
	}

    if(! $configname  ) {
        $configname =  $opt->{id}
					|| $::Variable->{MV_PAYMENT_ID}
                    || $::Variable->{CYBER_ID}
                    or return undef;
    }
    if(! defined $opt->{precision} ) {
        $opt->{precision} = $::Variable->{MV_PAYMENT_PRECISION}
					|| $::Variable->{CYBER_PRECISION}
					|| 2;
    }

#::logDebug("ccvs configuration name '$configname'");

    my $exp;
	if($actual->{mv_credit_card_exp_month}) {
		$actual->{mv_credit_card_exp_month} =~ s/\D//g;
		$actual->{mv_credit_card_exp_month} =~ s/^0+//;
		$actual->{mv_credit_card_exp_year} =~ s/\D//g;
		$actual->{mv_credit_card_exp_year} =~ s/\d\d(\d\d)/$1/;
		$exp = sprintf '%02d/%02d',
                        $actual->{mv_credit_card_exp_month},
                        $actual->{mv_credit_card_exp_year};
	}

	$actual->{mv_credit_card_number} =~ s/\D//g
		if $actual->{mv_credit_card_number};


    my %type_map = (
        qw/
                        mauthcapture  sale
                        mauthonly     auth
                        mauthreturn   return
                        S             sale
                        C             auth
                        V             void
                        sale          sale
                        auth          auth
                        void          void
                        delete        delete
        /
    );

	my $op;

    if (defined $type_map{$actual->{cyber_mode}}) {
        $op = $actual->{cyber_mode} = $type_map{$actual->{cyber_mode}};
    }
    else {
        $op = $actual->{cyber_mode} = 'sale';
    }

    if(! $amount) {
        $amount = $opt->{total_cost} || 
				  Vend::Util::round_to_frac_digits(
				  		Vend::Interpolate::total_cost(),
						$opt->{precision},
					);
    }

    my $invoice;

    unless ($invoice = $opt->{order_id}) {
		if($op ne 'auth' and $op ne 'sale') {
			return $ccvs_die->("must supply order ID for transaction type %s", $op);
		}
		my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time());

		# We'll make an order ID based on date, time, and Interchange session

		# $mon is the month index where Jan=0 and Dec=11, so we use
		# $mon+1 to get the more familiar Jan=1 and Dec=12
		$invoice = sprintf("%02d%02d%02d%02d%02d%02d%05d%s",
				$year + 1900,$mon + 1,$mday,$hour,$min, $sec, $$);
	}

	$sess = CCVS::init($configname) 
		or
		return $ccvs_die->("failed to init configuration %s", $configname);

	if($op eq 'auth' or $op eq 'sale') {

		CCVS::new($sess, $invoice) == CV_OK
			or return $ccvs_die->("can't create invoice %s", $invoice);
		CCVS::add($sess, $invoice, CV_ARG_AMOUNT, $amount) == CV_OK
			or return $ccvs_die->("can't add amount %s", $amount);
		CCVS::add(
			$sess,
			$invoice,
			CV_ARG_CARDNUM,
			$actual->{mv_credit_card_number},
			)
		  == CV_OK  or do {
						my $num = $actual->{mv_credit_card_number};
						$num =~ s/(\d\d).*(\d\d\d\d)/$1**$2/;
						return $ccvs_die->("CCVS can't add card number %s", $num);
					};

		CCVS::add($sess, $invoice, CV_ARG_EXPDATE, $exp) == CV_OK
			or return $ccvs_die->("can't add expdate %s", $exp);
	
		if($opt->{extended_process}) {
			my %extended = qw/
               address       address
               shipzipcode   zip
               zipcode       b_zip
               accountname   0
               acode         auth_code
               ccv2          ccv2
               purchaseorder po_number
               comment       gift_note
			   /;
			$extended{type}    = sub { 'ecommerce' };
			$extended{tax}     = \&Vend::Interpolate::salestax,
			$extended{product} = sub {
				return join ",", map { $_->{code} } @$Vend::Items;
			};
			$extended{entrysource} = sub { $Vend::admin ? 'merchant' : 'customer' };

			$extended{setmerchant} = sub {
										$opt->{set_merchant} ||
										$::Variable->{SET_MERCHANT}
										};
			$extended{setcardholder} = sub { $opt->{set_cardholder} };
			for(keys %extended) {
				my $val;
				my $ok;
				unless ($val = $opt->{"x_$_"}) {
					my $thing = $opt->{"x_$_"} || $extended{$_};
					if(ref $thing) {
						$val = $thing->($_);
					}
					else {
						$val = $actual->{$thing};
					}
				}
				next unless $val;
				eval {
					$ok = CCVS::add($sess, $invoice, &{"CV_ARG_\U$_"}, $val);
				};
				if($@) {
					::logDebug($@);
					next;
				}
				if($ok != CV_OK) {
					::logDebug(::errmsg("can't add %s", "CV_ARG_\U$_"));
				}
			}
		}


		# Nothing has happened yet.  Let's do the authorization.
		CCVS::auth($sess, $invoice) == CV_OK
			or return $ccvs_die->("couldn't issue auth request");

		# In theory, the background processor is working on this right now.
		# Let's loop until it's done.
		my $status;

		do {
			sleep 1;
			$status = CCVS::status($sess, $invoice);
		} until ($status == CV_AUTH
			 || $status == CV_DENIED
			 || $status == CV_REVIEW);


		# At this point, the status is either "CV_AUTH", "CV_DENIED" or "CV_REVIEW".
		# Let's get the full extended status and unpack it into an associative array.
		%result = split / *{|} */,CCVS::textvalue($sess);

		# If everything was succesful, push through the sale.
		if ($status == CV_AUTH) {
			$result{MStatus} = 'success';
			$result{invoice} = $invoice;
		}
		elsif($status == CV_REVIEW) {
			$result{MStatus} = 'success';
			$result{AVS_message} = $result{result_text};
			$result{invoice} = $invoice;
		}
		elsif($status == CV_DENIED) {
			$result{MStatus} = 'failed';
			my $msg = errmsg(
				"CCVS error: %s %s. Please call in your order or try again.",
				$result{MStatus},
				$result{result_text},
			);
			$Vend::Session->{errors}{mv_credit_card_valid} = $msg;
		}
	}

	if($result{MStatus} =~ /^success/ and $op eq 'sale') {
		CCVS::sale($sess, $invoice) == CV_OK
			or return $ccvs_die->("couldn't issue sale for order ID %s", $invoice);
	}

	# When you're finished, you need to clean up, like this:
	CCVS::done($sess);

    my %result_map = ( qw/

            pop.status            MStatus
            MErrMsg               result_text
            pop.error-message     result_text
            order-id              invoice
            pop.order-id          invoice
            pop.auth-code         AUTHCODE
            pop.avs_code          AVSZIP
            pop.avs_zip           AVSZIP
            pop.avs_addr          AVSADDR
		/
    );

    for (keys %result_map) {
        $result{$_} = $result{$result_map{$_}}
            if defined $result{$result_map{$_}};
    }

#::logDebug("ccvs returns, result=" . ::uneval(\%result));
    return %result;
}
EOS
