summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Prykop <jonathan@freeside.biz>2016-12-03 10:36:03 -0600
committerJonathan Prykop <jonathan@freeside.biz>2016-12-03 10:36:03 -0600
commit9a8399783bb9d87ef662b4371bebe983d2781dce (patch)
treefd4e2fb4c1348b1d532f17d44884b5ed35752473
parent0ed150d5dd7027513942b74eb362460bc7c2e884 (diff)
71513: Card tokenization [refund gateway choice]
-rw-r--r--FS/FS/cust_main/Billing_Realtime.pm115
-rw-r--r--FS/FS/payinfo_Mixin.pm2
-rw-r--r--FS/FS/payinfo_transaction_Mixin.pm2
-rw-r--r--FS/FS/payment_gateway.pm25
4 files changed, 98 insertions, 46 deletions
diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index f331a39..3a3947b 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -1480,10 +1480,12 @@ sub realtime_refund_bop {
@bop_options = $payment_gateway->gatewaynum
? $payment_gateway->options
: @{ $payment_gateway->get('options') };
+ my %bop_options = @bop_options;
return "processor of payment $options{'paynum'} $processor does not".
" match default processor $conf_processor"
- unless $processor eq $conf_processor;
+ unless ($processor eq $conf_processor)
+ || (($conf_processor eq 'CardFortress') && ($processor eq $bop_options{'gateway'}));
}
@@ -1492,9 +1494,7 @@ sub realtime_refund_bop {
# like a normal transaction
my $payment_gateway =
- $self->agent->payment_gateway( 'method' => $options{method},
- #'payinfo' => $payinfo,
- );
+ $self->agent->payment_gateway( 'method' => $options{method} );
my( $processor, $login, $password, $namespace ) =
map { my $method = "gateway_$_"; $payment_gateway->$method }
qw( module username password namespace );
@@ -1627,18 +1627,22 @@ sub realtime_refund_bop {
if length($payip);
my $payinfo = '';
+ my $paymask = ''; # for refund record
if ( $options{method} eq 'CC' ) {
if ( $cust_pay ) {
$content{card_number} = $payinfo = $cust_pay->payinfo;
+ $paymask = $cust_pay->paymask;
(exists($options{'paydate'}) ? $options{'paydate'} : $cust_pay->paydate)
=~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/ &&
($content{expiration} = "$2/$1"); # where available
} else {
- $content{card_number} = $payinfo = $self->payinfo;
- (exists($options{'paydate'}) ? $options{'paydate'} : $self->paydate)
- =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
- $content{expiration} = "$2/$1";
+ # this really needs a better cleanup
+ die "Refund without paynum not supported";
+# $content{card_number} = $payinfo = $self->payinfo;
+# (exists($options{'paydate'}) ? $options{'paydate'} : $self->paydate)
+# =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+# $content{expiration} = "$2/$1";
}
} elsif ( $options{method} eq 'ECHECK' ) {
@@ -1702,6 +1706,7 @@ sub realtime_refund_bop {
'_date' => '',
'payby' => $bop_method2payby{$options{method}},
'payinfo' => $payinfo,
+ 'paymask' => $paymask,
'reasonnum' => $options{'reasonnum'},
'gatewaynum' => $gatewaynum, # may be null
'processor' => $processor,
@@ -2443,7 +2448,7 @@ CUSTLOOP:
}
# only load gateway if we need to, and only need to load it once
- my $payment_gateway ||= $cust_main->_payment_gateway({
+ $payment_gateway ||= $cust_main->_payment_gateway({
'method' => 'CC',
'conf' => $conf,
'nofatal' => 1, # handle lack of gateway smoothly below
@@ -2543,15 +2548,72 @@ CUSTLOOP:
$dbh->commit or die $dbh->errstr; # commit log message
}
- # don't use customer agent gateway here, use the gatewaynum specified by the record
- my $gateway = FS::payment_gateway->by_key_or_default(
- 'method' => 'CC',
- 'conf' => $conf,
- 'nofatal' => 1,
- 'gatewaynum' => $record->gatewaynum || '',
- );
+ my $cust_main = $record->cust_main;
+ if (!$cust_main) {
+ # might happen for cust_pay_pending from failed verify records,
+ # in which case we attempt tokenization without cust_main
+ # everything else should absolutely have a cust_main
+ unless ($table eq 'cust_pay_pending' && $record->{'custnum_pending'}) {
+ my $error = "Could not load cust_main for $table ".$record->get($record->primary_key);
+ if ($opt{'queue'}) {
+ $log->critical($error);
+ $dbh->commit or die $dbh->errstr; # commit log message
+ next;
+ }
+ $dbh->rollback if $oldAutoCommit;
+ die $error;
+ }
+ }
+
+ my $gateway;
+
+ # use the gatewaynum specified by the record if possible
+ $gateway = FS::payment_gateway->by_key_with_namespace(
+ 'gatewaynum' => $record->gatewaynum,
+ ) if $record->gateway;
+
+ # otherwise use the cust agent gateway if possible (which realtime_refund_bop would do)
+ # otherwise just use default gateway
+ unless ($gateway) {
+
+ $gateway = $cust_main
+ ? $cust_main->agent->payment_gateway
+ : FS::payment_gateway->default_gateway;
+
+ # check for processor mismatch
+ unless ($table eq 'cust_pay_pending') { # has no processor table
+ if (my $processor = $record->processor) {
+
+ my $conf_processor = $gateway->gateway_module;
+ my %bop_options = $gateway->gatewaynum
+ ? $gateway->options
+ : @{ $gateway->get('options') };
+
+ # this is the same standard used by realtime_refund_bop
+ unless (
+ ($processor eq $conf_processor) ||
+ (($conf_processor eq 'CardFortress') && ($processor eq $bop_options{'gateway'}))
+ ) {
+
+ # processors don't match, so refund already cannot be run on this object,
+ # regardless of what we do now...
+ # but unless we gotta tokenize everything, just leave well enough alone
+ unless ($require_tokenized) {
+ warn "Skipping mismatched processor for $table ".$record->get($record->primary_key) if $debug;
+ next;
+ }
+ ### no error--we'll tokenize using the new gateway, just to remove stored payinfo,
+ ### because refunds are already impossible for this record, anyway
+
+ } # end processor mismatch
+
+ } # end record has processor
+ } # end not cust_pay_pending
+
+ }
+
+ # means no default gateway, no promise to tokenize, can skip
unless ($gateway) {
- # means no default gateway, no promise to tokenize, can skip
warn "Skipping missing gateway for $table ".$record->get($record->primary_key) if $debug;
next;
}
@@ -2570,24 +2632,7 @@ CUSTLOOP:
next;
}
- my $cust_main = $record->cust_main;
- if (!$cust_main) {
- # might happen for cust_pay_pending from failed verify records,
- # in which case we attempt tokenization without cust_main
- # everything else should absolutely have a cust_main
- if ($table eq 'cust_pay_pending' && $record->{'custnum_pending'}) {
- warn "ATTEMPTING GATEWAY-ONLY TOKENIZE" if $debug;
- } else {
- my $error = "Could not load cust_main for $table ".$record->get($record->primary_key);
- if ($opt{'queue'}) {
- $log->critical($error);
- $dbh->commit or die $dbh->errstr; # commit log message
- next;
- }
- $dbh->rollback if $oldAutoCommit;
- die $error;
- }
- }
+ warn "ATTEMPTING GATEWAY-ONLY TOKENIZE" if $debug && !$cust_main;
# if we got this far, time to mutex
$record = $record->select_for_update;
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index be37568..3a51022 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -195,8 +195,6 @@ sub payinfo_check {
FS::payby->can_payby($self->table, $self->payby)
or return "Illegal payby: ". $self->payby;
- my $conf = new FS::Conf;
-
if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
my $payinfo = $self->payinfo;
diff --git a/FS/FS/payinfo_transaction_Mixin.pm b/FS/FS/payinfo_transaction_Mixin.pm
index c27d049..1b5a0cd 100644
--- a/FS/FS/payinfo_transaction_Mixin.pm
+++ b/FS/FS/payinfo_transaction_Mixin.pm
@@ -102,8 +102,6 @@ auth, and order_number) as well as payby and payinfo
sub payinfo_check {
my $self = shift;
- my $conf = new FS::Conf;
-
$self->SUPER::payinfo_check()
|| $self->ut_numbern('gatewaynum')
# not ut_foreign_keyn, it causes upgrades to fail
diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm
index 170d37a..3500bf9 100644
--- a/FS/FS/payment_gateway.pm
+++ b/FS/FS/payment_gateway.pm
@@ -385,6 +385,23 @@ sub default_gateway {
return $payment_gateway;
}
+=item by_key_with_namespace GATEWAYNUM
+
+Like usual by_key, but makes sure namespace is set,
+and dies if not found.
+
+=cut
+
+sub by_key_with_namespace {
+ my $self = shift;
+ my $payment_gateway = $self->by_key(@_);
+ die "payment_gateway not found"
+ unless $payment_gateway;
+ $payment_gateway->gateway_namespace('Business::OnlinePayment')
+ unless $payment_gateway->gateway_namespace;
+ return $payment_gateway;
+}
+
=item by_key_or_default OPTIONS
Either returns the gateway specified by option gatewaynum, or the default gateway.
@@ -399,13 +416,7 @@ sub by_key_or_default {
my ($self,%options) = @_;
if ($options{'gatewaynum'}) {
- my $payment_gateway = $self->by_key($options{'gatewaynum'});
- # regardless of nofatal, which is only meant for handling lack of default gateway
- die "payment_gateway ".$options{'gatewaynum'}." not found"
- unless $payment_gateway;
- $payment_gateway->gateway_namespace('Business::OnlinePayment')
- unless $payment_gateway->gateway_namespace;
- return $payment_gateway;
+ return $self->by_key_with_namespace($options{'gatewaynum'});
} else {
return $self->default_gateway(%options);
}