summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Prykop <jonathan@freeside.biz>2016-11-05 01:20:35 -0500
committerJonathan Prykop <jonathan@freeside.biz>2016-11-29 05:04:09 -0600
commit1185daff43389fe53ad43e84962329a63d31523e (patch)
treefc50a3d1ded5cce2974b7fd573e5a956e303b59b
parent16498ac263bf5f3e90e23b866706ada768486f40 (diff)
71513: Card tokenization [bug fixes to previous checkpoint]
-rw-r--r--FS/FS/cust_main/Billing_Realtime.pm115
-rw-r--r--FS/FS/payinfo_Mixin.pm13
-rw-r--r--httemplate/misc/process/payment.cgi5
3 files changed, 76 insertions, 57 deletions
diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index 09e2dfa..407b9ca 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -376,44 +376,16 @@ sub _bop_content {
}
sub _tokenize_card {
- my ($self,$transaction,$options,$log,%opt) = @_;
- # options is for entire process, so we can update payinfo
- # opt is just for this call, only key is replace
-
- my $cust_payby = $options->{'cust_payby'};
- if ( $cust_payby
- and $transaction->can('card_token')
+ my ($self,$transaction,$options) = @_;
+ if ( $transaction->can('card_token')
and $transaction->card_token
- and !$cust_payby->tokenized #not already tokenized
+ and !$self->tokenized($options->{'payinfo'})
) {
-
- $options->{'payinfo'} = $transaction->card_token;
- $cust_payby->payinfo($transaction->card_token);
-
- my $error;
- $error = $cust_payby->replace if $opt{'replace'};
- if ( $error ) {
- $log->error('Error storing token for cust '.$self->custnum.', cust_payby '.$cust_payby->custpaybynum.': '.$error);
- return $error;
- } else {
- $log->debug('Tokenized card for cust '.$self->custnum.', cust_payby '.$cust_payby->custpaybynum);
- return '';
- }
-
+ $options->{'payinfo'} = $transaction->card_token; #for creating cust_pay
+ $options->{'cust_payby'}->payinfo($transaction->card_token) if $options->{'cust_payby'};
+ return $transaction->card_token;
}
-
-}
-
-# only store payinfo in cust_pay/cust_pay_pending
-# if it's a tokenized card or if processor requires card for void
-sub _cust_pay_opts {
- my ($self,$payby,$payinfo,$transaction) = @_;
- ( (($payby eq 'CARD') && $self->tokenized($payinfo))
- || (($payby eq 'CARD') && $transaction->info('CC_void_requires_card'))
- || (($payby eq 'CHEK') && $transaction->info('ECHECK_void_requires_account'))
- )
- ? ('payinfo' => $payinfo)
- : ();
+ return '';
}
my %bop_method2payby = (
@@ -681,15 +653,12 @@ sub realtime_bop {
#okay, good to go, if we're a duplicate, cust_pay_pending will kick us out
- my $transaction = new $namespace( $payment_gateway->gateway_module,
- $self->_bop_options(\%options),
- );
-
my $cust_pay_pending = new FS::cust_pay_pending {
'custnum' => $self->custnum,
'paid' => $options{amount},
'_date' => '',
'payby' => $bop_method2payby{$options{method}},
+ 'payinfo' => $options{payinfo},
'paymask' => $options{paymask},
'paydate' => $paydate,
'recurring_billing' => $content{recurring_billing},
@@ -698,7 +667,6 @@ sub realtime_bop {
'gatewaynum' => $payment_gateway->gatewaynum || '',
'session_id' => $options{session_id} || '',
'jobnum' => $options{depend_jobnum} || '',
- $self->_cust_pay_opts($options{payinfo},$transaction),
};
$cust_pay_pending->payunique( $options{payunique} )
if defined($options{payunique}) && length($options{payunique});
@@ -715,6 +683,10 @@ sub realtime_bop {
my( $action1, $action2 ) =
split( /\s*\,\s*/, $payment_gateway->gateway_action );
+ my $transaction = new $namespace( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
+
$transaction->content(
'type' => $options{method},
$self->_bop_auth(\%options),
@@ -819,6 +791,8 @@ sub realtime_bop {
) {
my $error = $self->remove_cvv_from_cust_payby($options{payinfo});
if ( $error ) {
+ $log->critical('Error removing cvv for cust '.$self->custnum.': '.$error);
+ #not returning error, should at least attempt to handle results of an otherwise valid transaction
warn "WARNING: error removing cvv: $error\n";
}
}
@@ -827,8 +801,15 @@ sub realtime_bop {
# Tokenize
###
- my $error = $self->_tokenize_card($transaction,\%options,$log,'replace' => 1);
- return $error if $error;
+ if (my $card_token = $self->_tokenize_card($transaction,\%options)) {
+ # cpp will be replaced in _realtime_bop_result
+ $cust_pay_pending->payinfo($card_token);
+ if ($options{'cust_payby'} and my $error = $options{'cust_payby'}->replace) {
+ $log->critical('Error storing token for cust '.$self->custnum.', cust_payby '.$options{'cust_payby'}->custpaybynum.': '.$error);
+ #not returning error, should at least attempt to handle results of an otherwise valid transaction
+ #this leaves real card number in cust_payby, but can't do much else if cust_payby won't replace
+ }
+ }
###
# result handling
@@ -925,7 +906,7 @@ sub _realtime_bop_result {
or return "no payment gateway in arguments to _realtime_bop_result";
$cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined');
- my $cpp_captured_err = $cust_pay_pending->replace;
+ my $cpp_captured_err = $cust_pay_pending->replace; #also saves tokenization
return $cpp_captured_err if $cpp_captured_err;
if ( $transaction->is_success() ) {
@@ -939,6 +920,7 @@ sub _realtime_bop_result {
'paid' => $cust_pay_pending->paid,
'_date' => '',
'payby' => $cust_pay_pending->payby,
+ 'payinfo' => $options{'payinfo'},
'paymask' => $options{'paymask'} || $cust_pay_pending->paymask,
'paydate' => $cust_pay_pending->paydate,
'pkgnum' => $cust_pay_pending->pkgnum,
@@ -948,7 +930,6 @@ sub _realtime_bop_result {
'auth' => $transaction->authorization,
'order_number' => $order_number || '',
'no_auto_apply' => $options{'no_auto_apply'} ? 'Y' : '',
- $self->_cust_pay_opts($options{payinfo},$transaction),
} );
#doesn't hurt to know, even though the dup check is in cust_pay_pending now
$cust_pay->payunique( $options{payunique} )
@@ -1854,9 +1835,7 @@ sub realtime_verify_bop {
###
my $error;
- my $transaction = new $namespace( $payment_gateway->gateway_module,
- $self->_bop_options(\%options),
- ); #need this back so we can do _tokenize_card
+ my $transaction; #need this back so we can do _tokenize_card
# don't mutex the customer here, because they might be uncommitted. and
# this is only verification. it doesn't matter if they have other
@@ -1867,13 +1846,13 @@ sub realtime_verify_bop {
'paid' => '1.00',
'_date' => '',
'payby' => $bop_method2payby{'CC'},
+ 'payinfo' => $options{payinfo},
'paymask' => $options{paymask},
'paydate' => $paydate,
'pkgnum' => $options{'pkgnum'},
'status' => 'new',
'gatewaynum' => $payment_gateway->gatewaynum || '',
'session_id' => $options{session_id} || '',
- $self->_cust_pay_opts($options{payinfo},$transaction),
};
$cust_pay_pending->payunique( $options{payunique} )
if defined($options{payunique}) && length($options{payunique});
@@ -1904,6 +1883,10 @@ sub realtime_verify_bop {
if $DEBUG > 1;
warn Dumper($cust_pay_pending) if $DEBUG > 2;
+ $transaction = new $namespace( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
+
$transaction->content(
'type' => 'CC',
$self->_bop_auth(\%options),
@@ -2122,12 +2105,22 @@ sub realtime_verify_bop {
}
###
+ # remove paycvv here? need to find out if a reversed auth
+ # counts as an initial transaction for paycvv retention requirements
+ ###
+
+ ###
# Tokenize
###
- #important that we not pass replace option here,
+ #important that we not replace cust_payby here,
#because cust_payby->replace uses realtime_verify_bop!
- $self->_tokenize_card($transaction,\%options,$log);
+ if (my $card_token = $self->_tokenize_card($transaction,\%options)) {
+ $cust_pay_pending->payinfo($card_token);
+ my $cpp_token_err = $cust_pay_pending->replace;
+ #this leaves real card number in cust_payby, but can't do much else if cust_payby won't replace
+ return $cpp_token_err if $cpp_token_err;
+ }
###
# result handling
@@ -2263,9 +2256,16 @@ sub realtime_tokenize {
if ( $transaction->card_token() ) { # no is_success flag
- #important that we not pass replace option here,
+ # realtime_tokenize should not clear paycvv at this time. it might be
+ # needed for the first transaction, and a tokenize isn't actually a
+ # transaction that hits the gateway. at some point in the future, card
+ # fortress should take on the "store paycvv until first transaction"
+ # functionality and we should fix this in freeside, but i that's a bigger
+ # project for another time.
+
+ #important that we not replace cust_payby here,
#because cust_payby->replace uses realtime_tokenize!
- $self->_tokenize_card($transaction,\%options,$log);
+ $self->_tokenize_card($transaction,\%options);
} else {
@@ -2277,10 +2277,19 @@ sub realtime_tokenize {
}
+
+=item tokenized PAYINFO
+
+Convenience wrapper for L<FS::payinfo_Mixin/tokenized>
+
+PAYINFO is required
+
+=cut
+
sub tokenized {
my $this = shift;
my $payinfo = shift;
- $payinfo =~ /^99\d{14}$/;
+ FS::cust_pay->tokenized($payinfo);
}
=back
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index a0a2cbc..6982834 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -67,7 +67,7 @@ sub payinfo {
my($self,$payinfo) = @_;
if ( defined($payinfo) ) {
- $self->paymask($self->mask_payinfo) unless $self->tokenized; #make sure old mask is set
+ $self->paymask($self->mask_payinfo) unless $self->paymask || $self->tokenized; #make sure old mask is set
$self->setfield('payinfo', $payinfo);
$self->paymask($self->mask_payinfo) unless $self->tokenized($payinfo); #remask unless tokenizing
} else {
@@ -454,12 +454,17 @@ sub process_set_cardtype {
}
}
+=item tokenized [ PAYINFO ]
+
+Returns true if object payinfo is tokenized
+
+Optionally, an arbitrary payby and payinfo can be passed.
+
+=cut
+
sub tokenized {
my $self = shift;
my $payinfo = scalar(@_) ? shift : $self->payinfo;
- ## or just $self->cust_main->tokenized($payinfo) ??
- ## everything that currently uses this mixin is linked to cust_main,
- ## but just in case, false laziness w/ FS::cust_main::Billing_Realtime
$payinfo =~ /^99\d{14}$/;
}
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
index 84687f0..1532605 100644
--- a/httemplate/misc/process/payment.cgi
+++ b/httemplate/misc/process/payment.cgi
@@ -193,6 +193,11 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) {
errorpage("error saving info, payment not processed: $error")
if $error;
+
+ } elsif ( $payby eq 'CARD' ) { # not saving
+
+ $paymask = FS::payinfo_Mixin->mask_payinfo('CARD',$payinfo); # for untokenized but tokenizable payinfo
+
}
}