$return{$_} = $cust_main->bill_location->get($_)
for qw(address1 address2 city state zip);
- #XXX look for stored cust_payby info
- #
- # $return{payname} = $cust_main->payname
- # || ( $cust_main->first. ' '. $cust_main->get('last') );
- #
- #if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- # $return{card_type} = cardtype($cust_main->payinfo);
- # $return{payinfo} = $cust_main->paymask;
- #
- # @return{'month', 'year'} = $cust_main->paydate_monthyear;
- #
- #}
- #
- #if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
- # my ($payinfo1, $payinfo2) = split '@', $cust_main->paymask;
- # $return{payinfo1} = $payinfo1;
- # $return{payinfo2} = $payinfo2;
- # $return{paytype} = $cust_main->paytype;
- # $return{paystate} = $cust_main->paystate;
- # $return{payname} = $cust_main->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
- #}
+ # look for stored cust_payby info
+ # only if we've been given a clear payment_payby (to avoid payname conflicts)
+ if ($p->{'payment_payby'} =~ /^(CARD|CHEK)$/) {
+ my @search_payby = ($p->{'payment_payby'} eq 'CARD') ? ('CARD','DCRD') : ('CHEK','DCHK');
+ my ($cust_payby) = $cust_main->cust_payby(@search_payby);
+ if ($cust_payby) {
+ $return{payname} = $cust_payby->payname
+ || ( $cust_main->first. ' '. $cust_main->get('last') );
+
+ if ( $cust_payby->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{card_type} = cardtype($cust_payby->payinfo);
+ $return{payinfo} = $cust_payby->paymask;
+
+ @return{'month', 'year'} = $cust_payby->paydate_monthyear;
+
+ }
+
+ if ( $cust_payby->payby =~ /^(CHEK|DCHK)$/ ) {
+ my ($payinfo1, $payinfo2) = split '@', $cust_payby->paymask;
+ $return{payinfo1} = $payinfo1;
+ $return{payinfo2} = $payinfo2;
+ $return{paytype} = $cust_payby->paytype;
+ $return{paystate} = $cust_payby->paystate;
+ $return{payname} = $cust_payby->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
+ }
+ }
+ }
if ( $conf->config('prepayment_discounts-credit_type') ) {
#need to eval?
my $payinfo2 = $1;
$payinfo = $payinfo1. '@'. $payinfo2;
- $payinfo = $cust_main->payinfo
- if $cust_main->paymask eq $payinfo;
+ foreach my $cust_payby ($cust_main->cust_payby('CHEK','DCHK')) {
+ if ( $cust_payby->paymask eq $payinfo ) {
+ $payinfo = $cust_payby->payinfo;
+ last;
+ }
+ }
} elsif ( $payby eq 'CARD' || $payby eq 'DCRD' ) {
#more intelligent matching will be needed here if you change
#card_masking_method and don't remove existing paymasks
- if ( $cust_main->paymask eq $payinfo ) {
- $payinfo = $cust_main->payinfo;
- $onfile = 1;
+ foreach my $cust_payby ($cust_main->cust_payby('CARD','DCRD')) {
+ if ( $cust_payby->paymask eq $payinfo ) {
+ $payinfo = $cust_payby->payinfo;
+ $onfile = 1;
+ last;
+ }
}
$payinfo =~ s/\D//g;
my $payby = delete $validate->{'payby'};
if ( $validate->{'save'} ) {
- my $new = new FS::cust_main { $cust_main->hash };
- if ($payby eq 'CARD' || $payby eq 'DCRD') {
- $new->set( $_ => $validate->{$_} )
- foreach qw( payname paystart_month paystart_year payissue payip );
- $new->set( 'payby' => $validate->{'auto'} ? 'CARD' : 'DCRD' );
+ my %saveopt;
+ foreach my $field ( qw( auto payinfo paymask payname payip ) ) {
+ $saveopt{$field} = $validate->{$field};
+ }
+
+ if ( $payby eq 'CARD' ) {
my $bill_location = FS::cust_location->new({
map { $_ => $validate->{$_} }
qw(address1 address2 city state country zip)
- }); # county?
- $new->set('bill_location' => $bill_location);
- # but don't allow the service address to change this way.
-
- } elsif ($payby eq 'CHEK' || $payby eq 'DCHK') {
- $new->set( $_ => $validate->{$_} )
- foreach qw( payname payip paytype paystate
- stateid stateid_state );
- $new->set( 'payby' => $validate->{'auto'} ? 'CHEK' : 'DCHK' );
+ });
+ $saveopt{'bill_location'} = $bill_location;
+ foreach my $field ( qw( paydate paystart_month paystart_year payissue ) ) {
+ $saveopt{$field} = $validate->{$field};
+ }
+ } else {
+ # stateid/stateid_state won't be saved, might be broken as of 4.x
+ foreach my $field ( qw( paytype paystate ) ) {
+ $saveopt{$field} = $validate->{$field};
+ }
}
- $new->payinfo( $validate->{'payinfo'} ); #to properly set paymask
- $new->set( 'paydate' => $validate->{'paydate'} );
- my $error = $new->replace($cust_main);
+
+ my $error = $cust_main->save_cust_payby(
+ 'payment_payby' => $payby,
+ %saveopt
+ );
+
if ( $error ) {
#no, this causes customers to process their payments again
#return { 'error' => $error };
#address" page but indicate if the payment processed?
delete($validate->{'payinfo'}); #don't want to log this!
warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n".
- "NEW: ". Dumper($new)."\n".
- "OLD: ". Dumper($cust_main)."\n".
+ "PAYBY: $payby\n".
+ "SAVEOPT: ".Dumper(\%saveopt)."\n".
+ "CUST_MAIN: ". Dumper($cust_main)."\n".
"PACKET: ". Dumper($validate)."\n";
- } else {
- $cust_main = $new;
}
}
qsearch('cust_contact', { 'custnum' => $self->custnum } );
}
-=item cust_payby
+=item cust_payby PAYBY
Returns all payment methods (see L<FS::cust_payby>) for this customer.
+If one or more PAYBY are specified, returns only payment methods for specified PAYBY.
+Does not validate PAYBY--do not pass tainted values.
+
=cut
sub cust_payby {
my $self = shift;
- qsearch({
+ my @payby = @_;
+ my $search = {
'table' => 'cust_payby',
'hashref' => { 'custnum' => $self->custnum },
'order_by' => "ORDER BY payby IN ('CARD','CHEK') DESC, weight ASC",
- });
+ };
+ $search->{'extra_sql'} = ' AND payby IN ( ' . join(',', map { "'$_'" } @payby) . ' ) '
+ if @payby;
+
+ qsearch($search);
}
+=item has_cust_payby_auto
+
+Returns true if customer has an automatic payment method ('CARD' or 'CHEK')
+
+=cut
+
sub has_cust_payby_auto {
my $self = shift;
scalar( qsearch({
}
-=item paydate_monthyear
-
-Returns a two-element list consisting of the month and year of this customer's
-paydate (credit card expiration date for CARD customers)
-
-=cut
-
-sub paydate_monthyear {
- my $self = shift;
- if ( $self->paydate =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #Pg date format
- ( $2, $1 );
- } elsif ( $self->paydate =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
- ( $1, $3 );
- } else {
- ('', '');
- }
-}
-
=item paydate_epoch
Returns the exact time in seconds corresponding to the payment method
return @out;
}
+=item save_cust_payby
+
+Saves a new cust_payby for this customer, replacing an existing entry only
+in select circumstances. Does not validate input.
+
+If auto is specified, marks this as the customer's primary method (weight 1)
+and changes existing primary methods for that payby to secondary methods (weight 2.)
+If bill_location is specified with auto, also sets location in cust_main.
+
+Will not insert complete duplicates of existing records, or records in which the
+only difference from an existing record is to turn off automatic payment (will
+return without error.) Will replace existing records in which the only difference
+is to add a value to a previously empty preserved field and/or turn on automatic payment.
+Fields marked as preserved are optional, and existing values will not be overwritten with
+blanks when replacing.
+
+Accepts the following named parameters:
+
+payment_payby - either CARD or CHEK
+
+auto - save as an automatic payment type (CARD/CHEK if true, DCRD/DCHK if false)
+
+payinfo - required
+
+paymask - optional, but should be specified for anything that might be tokenized, will be preserved when replacing
+
+payname - required
+
+payip - optional, will be preserved when replacing
+
+paydate - CARD only, required
+
+bill_location - CARD only, required, FS::cust_location object
+
+paystart_month - CARD only, optional, will be preserved when replacing
+
+paystart_year - CARD only, optional, will be preserved when replacing
+
+payissue - CARD only, optional, will be preserved when replacing
+
+paycvv - CARD only, only used if conf cvv-save is set appropriately
+
+paytype - CHEK only
+
+paystate - CHEK only
+
+=cut
+
+#The code for this option is in place, but it's not currently used
+#
+# replace - existing cust_payby object to be replaced (must match custnum)
+
+# stateid/stateid_state/ss are not currently supported in cust_payby,
+# might not even work properly in 4.x, but will need to work here if ever added
+
+sub save_cust_payby {
+ my $self = shift;
+ my %opt = @_;
+
+ my $old = $opt{'replace'};
+ my $new = new FS::cust_payby { $old ? $old->hash : () };
+ return "Customer number does not match" if $new->custnum and $new->custnum != $self->custnum;
+ $new->set( 'custnum' => $self->custnum );
+
+ my $payby = $opt{'payment_payby'};
+ return "Bad payby" unless grep(/^$payby$/,('CARD','CHEK'));
+
+ # don't allow turning off auto when replacing
+ $opt{'auto'} ||= 1 if $old and $old->payby !~ /^D/;
+
+ my @check_existing; # payby relevant to this payment_payby
+
+ # set payby based on auto
+ if ( $payby eq 'CARD' ) {
+ $new->set( 'payby' => ( $opt{'auto'} ? 'CARD' : 'DCRD' ) );
+ @check_existing = qw( CARD DCRD );
+ } elsif ( $payby eq 'CHEK' ) {
+ $new->set( 'payby' => ( $opt{'auto'} ? 'CHEK' : 'DCHK' ) );
+ @check_existing = qw( CHEK DCHK );
+ }
+
+ # every automatic payment type added here will be marked primary
+ $new->set( 'weight' => $opt{'auto'} ? 1 : '' );
+
+ # basic fields
+ $new->payinfo($opt{'payinfo'}); # sets default paymask, but not if it's already tokenized
+ $new->paymask($opt{'paymask'}) if $opt{'paymask'}; # in case it's been tokenized, override with loaded paymask
+ $new->set( 'payname' => $opt{'payname'} );
+ $new->set( 'payip' => $opt{'payip'} ); # will be preserved below
+
+ my $conf = new FS::Conf;
+
+ # compare to FS::cust_main::realtime_bop - check both to make sure working correctly
+ if ( $payby eq 'CARD' &&
+ grep { $_ eq cardtype($opt{'payinfo'}) } $conf->config('cvv-save') ) {
+ $new->set( 'paycvv' => $opt{'paycvv'} );
+ } else {
+ $new->set( 'paycvv' => '');
+ }
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ # set fields specific to payment_payby
+ if ( $payby eq 'CARD' ) {
+ if ($opt{'bill_location'}) {
+ $opt{'bill_location'}->set('custnum' => $self->custnum);
+ my $error = $opt{'bill_location'}->find_or_insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $new->set( 'locationnum' => $opt{'bill_location'}->locationnum );
+ }
+ foreach my $field ( qw( paydate paystart_month paystart_year payissue ) ) {
+ $new->set( $field => $opt{$field} );
+ }
+ } else {
+ foreach my $field ( qw(paytype paystate) ) {
+ $new->set( $field => $opt{$field} );
+ }
+ }
+
+ # other cust_payby to compare this to
+ my @existing = $self->cust_payby(@check_existing);
+
+ # fields that can overwrite blanks with values, but not values with blanks
+ my @preserve = qw( paymask locationnum paystart_month paystart_year payissue payip );
+
+ my $skip_cust_payby = 0; # true if we don't need to save or reweight cust_payby
+ unless ($old) {
+ # generally, we don't want to overwrite existing cust_payby with this,
+ # but we can replace if we're only marking it auto or adding a preserved field
+ # and we can avoid saving a total duplicate or merely turning off auto
+PAYBYLOOP:
+ foreach my $cust_payby (@existing) {
+ # check fields that absolutely should not change
+ foreach my $field ($new->fields) {
+ next if grep(/^$field$/, qw( custpaybynum payby weight ) );
+ next if grep(/^$field$/, @preserve );
+ next PAYBYLOOP unless $new->get($field) eq $cust_payby->get($field);
+ }
+ # now check fields that can replace if one value is blank
+ my $replace = 0;
+ foreach my $field (@preserve) {
+ if (
+ ( $new->get($field) and !$cust_payby->get($field) ) or
+ ( $cust_payby->get($field) and !$new->get($field) )
+ ) {
+ # prevention of overwriting values with blanks happens farther below
+ $replace = 1;
+ } elsif ( $new->get($field) ne $cust_payby->get($field) ) {
+ next PAYBYLOOP;
+ }
+ }
+ unless ( $replace ) {
+ # nearly identical, now check weight
+ if ($new->get('weight') eq $cust_payby->get('weight') or !$new->get('weight')) {
+ # ignore identical cust_payby, and ignore attempts to turn off auto
+ # no need to save or re-weight cust_payby (but still need to update/commit $self)
+ $skip_cust_payby = 1;
+ last PAYBYLOOP;
+ }
+ # otherwise, only change is to mark this as primary
+ }
+ # if we got this far, we're definitely replacing
+ $old = $cust_payby;
+ last PAYBYLOOP;
+ }
+ }
+
+ if ($old) {
+ $new->set( 'custpaybynum' => $old->custpaybynum );
+ # don't turn off automatic payment (but allow it to be turned on)
+ if ($new->payby =~ /^D/ and $new->payby ne $old->payby) {
+ $opt{'auto'} = 1;
+ $new->set( 'payby' => $old->payby );
+ $new->set( 'weight' => 1 );
+ }
+ # make sure we're not overwriting values with blanks
+ foreach my $field (@preserve) {
+ if ( $old->get($field) and !$new->get($field) ) {
+ $new->set( $field => $old->get($field) );
+ }
+ }
+ }
+
+ # only overwrite cust_main bill_location if auto
+ if ($opt{'auto'} && $opt{'bill_location'}) {
+ $self->set('bill_location' => $opt{'bill_location'});
+ my $error = $self->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ # done with everything except reweighting and saving cust_payby
+ # still need to commit changes to cust_main and cust_location
+ if ($skip_cust_payby) {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+ }
+
+ # re-weight existing primary cust_pay for this payby
+ if ($opt{'auto'}) {
+ foreach my $cust_payby (@existing) {
+ # relies on cust_payby return order
+ last unless $cust_payby->payby !~ /^D/;
+ last if $cust_payby->weight > 1;
+ next if $new->custpaybynum eq $cust_payby->custpaybynum;
+ $cust_payby->set( 'weight' => 2 );
+ my $error = $cust_payby->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error reweighting cust_payby: $error";
+ }
+ }
+ }
+
+ # finally, save cust_payby
+ my $error = $old ? $new->replace($old) : $new->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
=back
=head1 CLASS METHODS
&& !($options{payby} && $options{payinfo} && $options{paydate} && $options{payname});
#false laziness with Billing_Realtime
- my @cust_payby = qsearch({
- 'table' => 'cust_payby',
- 'hashref' => { 'custnum' => $self->custnum, },
- 'extra_sql' => " AND payby IN ( 'CARD', 'CHEK' ) ",
- 'order_by' => 'ORDER BY weight ASC',
- });
+ my @cust_payby = $self->cust_payby('CARD','CHEK');
# batch can't try out every one like realtime, just use first one
my $cust_payby = $cust_payby[0];
$options{amount} = $self->balance unless exists( $options{amount} );
- my @cust_payby = qsearch({
- 'table' => 'cust_payby',
- 'hashref' => { 'custnum' => $self->custnum, },
- 'extra_sql' => " AND payby IN ( 'CARD', 'CHEK' ) ",
- 'order_by' => 'ORDER BY weight ASC',
- });
+ my @cust_payby = $self->cust_payby('CARD','CHEK');
my $error;
foreach my $cust_payby (@cust_payby) {
# remove paycvv after initial transaction
###
- #false laziness w/misc/process/payment.cgi - check both to make sure working
- # correctly
+ # compare to FS::cust_main::save_cust_payby - check both to make sure working correctly
if ( length($self->paycvv)
&& ! grep { $_ eq cardtype($options{payinfo}) } $conf->config('cvv-save')
) {
use base qw( FS::payinfo_Mixin FS::cust_main_Mixin FS::Record );
use strict;
+use Scalar::Util qw( blessed );
use Digest::SHA qw( sha512_base64 );
use Business::CreditCard qw( validate cardtype );
use FS::UID qw( dbh );
)
)
{
-warn $self->payinfo;
-warn $old->payinfo;
+
$self->payinfo($old->payinfo);
} elsif ( $self->payby =~ /^(CHEK|DCHK)$/ && $self->payinfo =~ /xx/ ) {
}
}
+=item paydate_monthyear
+
+Returns a two-element list consisting of the month and year of this customer's
+paydate (credit card expiration date for CARD customers)
+
+=cut
+
+sub paydate_monthyear {
+ my $self = shift;
+ if ( $self->paydate =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #Pg date format
+ ( $2, $1 );
+ } elsif ( $self->paydate =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $1, $3 );
+ } else {
+ ('', '');
+ }
+}
+
=back
=head1 BUGS
sub make_payment {
- my $payment_info = payment_info( 'session_id' => $session_id );
+ my $payment_info = payment_info( 'session_id' => $session_id, 'payment_payby' => 'CARD' );
my $amount =
($payment_info->{'balance'} && ($payment_info->{'balance'} > 0))
}
sub make_ach_payment {
- payment_info( 'session_id' => $session_id );
+ payment_info( 'session_id' => $session_id, 'payment_payby' => 'CHEK' );
}
sub ach_payment_results {
&>
% }
+% my $auto = 0;
% if ( $payby eq 'CARD' ) {
%
% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' );
% my $payname = $cust_main->first. ' '. $cust_main->getfield('last');
-% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
-% $payinfo = $cust_main->paymask;
-% $paycvv = $cust_main->paycvv;
-% ( $month, $year ) = $cust_main->paydate_monthyear;
-% $payname = $cust_main->payname if $cust_main->payname;
+% my $location = $cust_main->bill_location;
+%
+% #auto-fill with the highest weighted match
+% my ($cust_payby) = $cust_main->cust_payby('CARD','DCRD');
+% if ($cust_payby) {
+% $payinfo = $cust_payby->paymask;
+% $paycvv = $cust_payby->paycvv;
+% ( $month, $year ) = $cust_payby->paydate_monthyear;
+% $payname = $cust_payby->payname if $cust_payby->payname;
+% $location = $cust_payby->cust_location || $location;
+% $auto = 1 if $cust_payby->payby eq 'CARD';
% }
<TR>
</TR>
<& /elements/location.html,
- 'object' => $cust_main->bill_location,
+ 'object' => $location,
'no_asterisks' => 1,
'address1_label' => emt('Card billing address'),
&>
% my( $account, $aba, $branch, $payname, $ss, $paytype, $paystate,
% $stateid, $stateid_state )
% = ( '', '', '', '', '', '', '', '', '' );
-% if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
-% $cust_main->paymask =~ /^([\dx]+)\@([\d\.x]*)$/i
-% or die "unparsable payinfo ". $cust_main->payinfo;
+% my ($cust_payby) = $cust_main->cust_payby('CHEK','DCHK');
+% if ($cust_payby) {
+% $cust_payby->paymask =~ /^([\dx]+)\@([\d\.x]*)$/i
+% or die "unparsable paymask ". $cust_payby->paymask;
% ($account, $aba) = ($1, $2);
% ($branch,$aba) = split('\.',$aba)
% if $conf->config('echeck-country') eq 'CA';
-% $payname = $cust_main->payname;
+% $payname = $cust_payby->payname;
+% $paytype = $cust_payby->getfield('paytype');
+% $paystate = $cust_payby->getfield('paystate');
+% $auto = 1 if $cust_payby->payby eq 'CHEK';
+% # these values aren't in cust_payby, but maybe should be...
% $ss = $cust_main->ss;
-% $paytype = $cust_main->getfield('paytype');
-% $paystate = $cust_main->getfield('paystate');
% $stateid = $cust_main->getfield('stateid');
% $stateid_state = $cust_main->getfield('stateid_state');
% }
<TR>
<TD COLSPAN=2>
- <INPUT TYPE="checkbox"<% ( ( $payby eq 'CARD' && $cust_main->payby ne 'DCRD' ) || ( $payby eq 'CHEK' && $cust_main->payby eq 'CHEK' ) ) ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
+ <INPUT TYPE="checkbox"<% $auto ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
<% mt("Charge future payments to this [_1] automatically",$type{$payby}) |h %>
</TD>
</TR>
my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } );
die "unknown custnum $custnum" unless $cust_main;
-my $location = $cust_main->bill_location;
-# no proper error handling on this anyway, but when we have it,
-# remember to repopulate fields in $location
-
my $balance = $cust_main->balance;
my $payinfo = '';
my $payinfo;
my $paymask; # override only used by loaded cust payinfo, only implemented for realtime processing
my $paycvv = '';
+my $loaded_cust_payby;
if ( $payby eq 'CHEK' ) {
if ($cgi->param('payinfo1') =~ /xx/i || $cgi->param('payinfo2') =~ /xx/i ) {
- $payinfo = $cust_main->payinfo;
- $paymask = $cust_main->paymask;
+
+ my $search_paymask = $cgi->param('payinfo1') . '@' . $cgi->param('payinfo2');
+ $search_paymask .= '.' . $cgi->param('payinfo3')
+ if $conf->config('echeck-country') eq 'CA';
+
+ #paymask might not be saved in database, need to run paymask method for any potential match
+ foreach my $search_cust_payby ($cust_main->cust_payby('CHEK','DCHK')) {
+ if ($search_paymask eq $search_cust_payby->paymask) {
+ # if there are multiple matches, assume for now that it's the first one returned,
+ # since that's what auto-fills; it's unlikely a masked number would be entered by hand,
+ # but it's very likely users will just click-through what's been auto-filled
+ $loaded_cust_payby = $search_cust_payby;
+ last;
+ }
+ }
+ errorpage("Masked payinfo not found") unless $loaded_cust_payby;
+ $payinfo = $loaded_cust_payby->payinfo;
+ $paymask = $loaded_cust_payby->paymask;
+
} else {
$cgi->param('payinfo1') =~ /^(\d+)$/
or errorpage("Illegal account number ". $cgi->param('payinfo1'));
} elsif ( $payby eq 'CARD' ) {
$payinfo = $cgi->param('payinfo');
- if ($payinfo eq $cust_main->paymask) {
- $payinfo = $cust_main->payinfo;
- $paymask = $cust_main->paymask;
+ if ($payinfo =~ /xx/i) {
+
+ #paymask might not be saved in database, need to run paymask method for any potential match
+ foreach my $search_cust_payby ($cust_main->cust_payby('CARD','DCRD')) {
+ if ($payinfo eq $search_cust_payby->paymask) {
+ $loaded_cust_payby = $search_cust_payby;
+ last;
+ }
+ }
+
+ errorpage("Masked payinfo not found") unless $loaded_cust_payby;
+ $payinfo = $loaded_cust_payby->payinfo;
+ $paymask = $loaded_cust_payby->paymask;
+
}
+
$payinfo =~ s/\D//g;
$payinfo =~ /^(\d{13,16}|\d{8,9})$/
or errorpage(gettext('invalid_card')); # . ": ". $self->payinfo;
if $payinfo !~ /^99\d{14}$/ #token
&& cardtype($payinfo) eq "Unknown";
- if ( defined $cust_main->dbdef_table->column('paycvv') ) {
+ if ( defined $cust_main->dbdef_table->column('paycvv') ) { #is this test necessary anymore?
if ( length($cgi->param('paycvv') ) ) {
if ( cardtype($payinfo) eq 'American Express card' ) {
$cgi->param('paycvv') =~ /^(\d{4})$/
# save first, for proper tokenization later
if ( $cgi->param('save') ) {
- my $new = new FS::cust_main { $cust_main->hash };
- if ( $payby eq 'CARD' ) {
- $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) );
- } elsif ( $payby eq 'CHEK' ) {
- $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) );
- } else {
- die "unknown payby $payby";
- }
- $new->payinfo($payinfo); # sets default paymask, but not if it's already tokenized
- $new->paymask($paymask) if $paymask; # in case it's been tokenized, override with loaded paymask
- $new->set( 'paydate' => "$year-$month-01" );
- $new->set( 'payname' => $payname );
-
- #false laziness w/FS:;cust_main::realtime_bop - check both to make sure
- # working correctly
- if ( $payby eq 'CARD' &&
- grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) {
- $new->set( 'paycvv' => $paycvv );
- } else {
- $new->set( 'paycvv' => '');
- }
+ my %saveopt;
if ( $payby eq 'CARD' ) {
my $bill_location = FS::cust_location->new;
$bill_location->set( $_ => $cgi->param($_) )
foreach @{$payby2fields{$payby}};
- $new->set('bill_location' => $bill_location);
- # will do nothing if the fields are all unchanged
+ $saveopt{'bill_location'} = $bill_location;
+ $saveopt{'paycvv'} = $paycvv; # save_cust_payby contains conf logic for when to use this
+ $saveopt{'paydate'} = "$year-$month-01";
} else {
- $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+ # ss/stateid/stateid_state won't be saved, but should be harmless to pass
+ %saveopt = map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}};
}
- my $error = $new->replace($cust_main);
+ my $error = $cust_main->save_cust_payby(
+ 'payment_payby' => $payby,
+ 'auto' => scalar($cgi->param('auto')),
+ 'payinfo' => $payinfo,
+ 'paymask' => $paymask,
+ 'payname' => $payname,
+ %saveopt
+ );
+
errorpage("error saving info, payment not processed: $error")
- if $error;
- $cust_main = $new;
+ if $error;
}
my $error = '';