X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_payby.pm;h=36f90dfb565e818131eb9ed181708612be662585;hb=18af4f3316291938fa8f0a74e083209f62eac4fa;hp=fd75567e6bc90c06a12c53524c79e9e7cb897bef;hpb=093747609fc543f09353f32c0ff0a1d47925dafc;p=freeside.git diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm index fd75567e6..36f90dfb5 100644 --- a/FS/FS/cust_payby.pm +++ b/FS/FS/cust_payby.pm @@ -115,6 +115,9 @@ paytype payip +=item paycardtype + +The credit card type (deduced from the card number). =back @@ -217,14 +220,14 @@ sub replace { $self->payinfo($new_account.'@'.$new_aba); } - # don't preserve paycvv if it was passed blank and payinfo changed - unless ( $self->payby =~ /^(CARD|DCRD)$/ - && $old->payinfo ne $self->payinfo - && $old->paymask ne $self->paymask - && $self->paycvv =~ /^\s*$/ ) - { - if ( length($old->paycvv) && $self->paycvv =~ /^\s*[\*x]*\s*$/ ) { + # only unmask paycvv if payinfo stayed the same + if ( $self->payby =~ /^(CARD|DCRD)$/ and $self->paycvv =~ /^\s*[\*x]+\s*$/ ) { + if ( $old->payinfo eq $self->payinfo + && $old->paymask eq $self->paymask + ) { $self->paycvv($old->paycvv); + } else { + $self->paycvv(''); } } @@ -247,8 +250,11 @@ sub replace { if ( $conf->exists('business-onlinepayment-verification') ) { $error = $self->verify; - return $error if $error; + } else { + $error = $self->tokenize; } + return $error if $error; + } local $SIG{HUP} = 'IGNORE'; @@ -270,7 +276,7 @@ sub replace { if ( $self->payby =~ /^(CARD|CHEK)$/ && ( ( $self->get('payinfo') ne $old->get('payinfo') - && $self->get('payinfo') !~ /^99\d{14}$/ + && !$self->tokenized ) || grep { $self->get($_) ne $old->get($_) } qw(paydate payname) ) @@ -331,6 +337,13 @@ sub check { # Need some kind of global flag to accept invalid cards, for testing # on scrubbed data. #XXX if ( !$import && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) { + + # In this block: detect card type; reject credit card / account numbers that + # are impossible or banned; reject other payment features (date, CVV length) + # that are inappropriate for the card type. + # However, if the payinfo is encrypted then just detect card type and assume + # the other checks were already done. + if ( !$ignore_invalid_card && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) { @@ -343,9 +356,11 @@ sub check { validate($payinfo) or return gettext('invalid_card'); # . ": ". $self->payinfo; - return gettext('unknown_card_type') - if $self->payinfo !~ /^99\d{14}$/ #token - && cardtype($self->payinfo) eq "Unknown"; + my $cardtype = $self->paycardtype || cardtype($payinfo); + + return gettext('unknown_card_type') if $cardtype eq "Unknown"; + + $self->set('paycardtype', $cardtype); unless ( $ignore_banned_card ) { my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } ); @@ -367,7 +382,7 @@ sub check { } if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) { - if ( cardtype($self->payinfo) eq 'American Express card' ) { + if ( $cardtype eq 'American Express card' ) { $self->paycvv =~ /^(\d{4})$/ or return "CVV2 (CID) for American Express cards is four digits."; $self->paycvv($1); @@ -380,7 +395,6 @@ sub check { $self->paycvv(''); } - my $cardtype = cardtype($payinfo); if ( $cardtype =~ /^(Switch|Solo)$/i ) { return "Start date or issue number is required for $cardtype cards" @@ -438,6 +452,15 @@ sub check { } } + } elsif ( $self->payby =~ /^CARD|DCRD$/ and $self->paymask ) { + # either ignoring invalid cards, or we can't decrypt the payinfo, but + # try to detect the card type anyway. this never returns failure, so + # the contract of $ignore_invalid_cards is maintained. + $self->set('paycardtype', cardtype($self->paymask)); + } else { + $self->set('paycardtype', ''); + } + # } elsif ( $self->payby eq 'PREPAY' ) { # # my $payinfo = $self->payinfo; @@ -449,8 +472,6 @@ sub check { # unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } ); # $self->paycvv(''); - } - if ( $self->payby =~ /^(CHEK|DCHK)$/ ) { $self->paydate(''); @@ -458,6 +479,7 @@ sub check { } elsif ( $self->payby =~ /^(CARD|DCRD)$/ ) { # shouldn't payinfo_check do this? + # (except we don't ever call payinfo_check from here) return "Expiration date required" if $self->paydate eq '' || $self->paydate eq '-'; @@ -501,9 +523,12 @@ sub check { } - if ( ! $self->custpaybynum - && $conf->exists('business-onlinepayment-verification') ) { - $error = $self->verify; + if ( ! $self->custpaybynum ) { + if ($conf->exists('business-onlinepayment-verification')) { + $error = $self->verify; + } else { + $error = $self->tokenize; + } return $error if $error; } @@ -520,10 +545,13 @@ sub check_payinfo_cardtype { my $payinfo = $self->payinfo; $payinfo =~ s/\D//g; - return '' if $payinfo =~ /^99\d{14}$/; #token + if ( $self->tokenized($payinfo) ) { + return ''; + } my %bop_card_types = map { $_=>1 } values %{ card_types() }; my $cardtype = cardtype($payinfo); + $self->set('paycardtype', $cardtype); return "$cardtype not accepted" unless $bop_card_types{$cardtype}; @@ -599,7 +627,7 @@ sub label { my $self = shift; my $name = $self->payby =~ /^(CARD|DCRD)$/ - && cardtype($self->paymask) || FS::payby->shortname($self->payby); + && $self->paycardtype || FS::payby->shortname($self->payby); ( $self->payby =~ /^(CARD|CHEK)$/ ? $weight{$self->weight}. ' automatic ' : 'Manual ' @@ -614,59 +642,48 @@ sub label { =item realtime_bop +Runs a L transaction on this card + =cut sub realtime_bop { my( $self, %opt ) = @_; - $opt{$_} = $self->$_() for qw( payinfo payname paydate ); - - if ( $self->locationnum ) { - my $cust_location = $self->cust_location; - $opt{$_} = $cust_location->$_() for qw( address1 address2 city state zip ); - } - $self->cust_main->realtime_bop({ - 'method' => FS::payby->payby2bop( $self->payby ), %opt, + 'cust_payby' => $self, }); } -=item verify +=item tokenize + +Runs a L transaction on this card =cut -sub verify { +sub tokenize { my $self = shift; return '' unless $self->payby =~ /^(CARD|DCRD)$/; - my %opt = (); + $self->cust_main->realtime_tokenize({ + 'cust_payby' => $self, + }); - # false laziness with check - my( $m, $y ); - if ( $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ ) { - ( $m, $y ) = ( $1, length($2) == 4 ? $2 : "20$2" ); - } elsif ( $self->paydate =~ /^19(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) { - ( $m, $y ) = ( $2, "19$1" ); - } elsif ( $self->paydate =~ /^(20)?(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) { - ( $m, $y ) = ( $3, "20$2" ); - } else { - return "Illegal expiration date: ". $self->paydate; - } - $m = sprintf('%02d',$m); - $opt{paydate} = "$y-$m-01"; +} - $opt{$_} = $self->$_() for qw( payinfo payname paycvv ); +=item verify - if ( $self->locationnum ) { - my $cust_location = $self->cust_location; - $opt{$_} = $cust_location->$_() for qw( address1 address2 city state zip ); - } +Runs a L transaction on this card + +=cut + +sub verify { + my $self = shift; + return '' unless $self->payby =~ /^(CARD|DCRD)$/; $self->cust_main->realtime_verify_bop({ - 'method' => FS::payby->payby2bop( $self->payby ), - %opt, + 'cust_payby' => $self, }); } @@ -715,6 +732,9 @@ sub cgi_hash_callback { 'CARD' => 'DCRD', 'CHEK' => 'DCHK', ); + # the payby selector gives the choice of CARD or CHEK (or others, but + # those are the ones with auto and on-demand versions). if the user didn't + # choose a weight, then they mean DCRD/DCHK. $hashref->{payby} = $noauto{$hashref->{payby}} if ! $hashref->{weight} && exists $noauto{$hashref->{payby}}; @@ -869,6 +889,18 @@ sub search_sql { =back +=cut + +sub _upgrade_data { + + my $class = shift; + local $ignore_banned_card = 1; + local $ignore_expired_card = 1; + local $ignore_invalid_card = 1; + $class->upgrade_set_cardtype; + +} + =head1 BUGS =head1 SEE ALSO