X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpayinfo_Mixin.pm;h=82632526d2f63b68d0032cae972e520aec5b9f31;hb=a1a0800de7c69fe5ee414b79e408ceacd4a1c2c3;hp=b1790c6256f0b3802c9068a59de9c8d30dd874fe;hpb=dcdf657e77ec7b46dc69e19a849a9c133123db7c;p=freeside.git diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm index b1790c625..82632526d 100644 --- a/FS/FS/payinfo_Mixin.pm +++ b/FS/FS/payinfo_Mixin.pm @@ -2,6 +2,10 @@ package FS::payinfo_Mixin; use strict; use Business::CreditCard; +use FS::payby; +use FS::Record qw(qsearch); + +use vars qw($ignore_masked_payinfo); =head1 NAME @@ -17,15 +21,7 @@ use vars qw(@ISA); This is a mixin class for records that contain payinfo. -This class handles the following functions for payinfo... - -Payment Mask (Generation and Storage) -Data Validation (parent checks need to be sure to call this) -Encryption - In the Future (Pull from Record.pm) -Bad Card Stuff - In the Future (Integrate Banned Pay) -Currency - In the Future - -=head1 fields +=head1 FIELDS =over 4 @@ -37,47 +33,40 @@ For Customers (cust_main): 'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand), 'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand), 'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or -'PREPAY' (special billing type: applies a credit - see L and sets billing type to I) +'PREPAY' (special billing type: applies a credit and sets billing type to I - see L) For Refunds (cust_refund): 'CARD' (credit cards), 'CHEK' (electronic check/ACH), 'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash), -'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' Chargeback, or 'COMP' (free), +'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' Chargeback, or 'COMP' (free) For Payments (cust_pay): 'CARD' (credit cards), 'CHEK' (electronic check/ACH), 'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card), -'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card) +'CASH' (cash), 'WEST' (Western Union), 'MCRD' (Manual credit card), +'PPAL' (PayPal) 'COMP' (free) is depricated as a payment type in cust_pay =cut - -sub payby { - my($self,$payby) = @_; - if ( defined($payby) ) { - $self->setfield('payby', $payby); - } - return $self->getfield('payby') -} - =item payinfo Payment information (payinfo) can be one of the following types: -Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L) +Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) +prepayment identifier (see L), PayPal transaction ID =cut sub payinfo { my($self,$payinfo) = @_; + if ( defined($payinfo) ) { - $self->setfield('payinfo', $payinfo); # This is okay since we are the 'setter' - $self->paymask($self->mask_payinfo()); + $self->setfield('payinfo', $payinfo); + $self->paymask($self->mask_payinfo) unless $payinfo =~ /^99\d{14}$/; #token } else { - $payinfo = $self->getfield('payinfo'); # This is okay since we are the 'getter' - return $payinfo; + $self->getfield('payinfo'); } } @@ -99,6 +88,7 @@ sub paycvv { } } else { # warn "This doesn't work for other tables besides cust_main + ''; } } @@ -107,62 +97,83 @@ sub paycvv { =cut sub paymask { - my($self,$paymask)=@_; + my($self, $paymask) = @_; - - if ($paymask ne '') { - # I hate this little bit of magic... I don't expect it to cause a problem, but who knows... - # If the payinfo is passed in masked then ignore it and set it based on the payinfo - # The only guy that should call this in this way is... $self->payinfo - $self->setfield('paymask', $self->mask_payinfo()); + if ( defined($paymask) ) { + $self->setfield('paymask', $paymask); } else { - $paymask=$self->getfield('paymask'); - if (!defined($paymask) || $paymask eq '') { - # Generate it if it's blank - Note that we're not going to set it - just generate - $paymask = $self->mask_payinfo(); - } + $self->getfield('paymask') || $self->mask_payinfo; } - return $paymask; } -=item mask_payinfo() +=back + +=head1 METHODS + +=over 4 + +=item mask_payinfo [ PAYBY, PAYINFO ] + +This method converts the payment info (credit card, bank account, etc.) into a +masked string. -This method converts the payment info (credit card, bank account, etc.) into a masked string. +Optionally, an arbitrary payby and payinfo can be passed. =cut sub mask_payinfo { my $self = shift; - my $paymask; - my $payinfo = $self->payinfo; - my $payby = $self->payby; + my $payby = scalar(@_) ? shift : $self->payby; + my $payinfo = scalar(@_) ? shift : $self->payinfo; + # Check to see if it's encrypted... - if ($self->is_encrypted($payinfo)) { - $paymask = 'N/A'; - } else { - # if not, mask it... - if ($payby eq 'CARD' || $payby eq 'DCRD' || $payby eq 'MCRD') { # Credit Cards (Show first and last four) - $paymask = substr($payinfo,0,4). 'x'x(length($payinfo)-8). substr($payinfo,(length($payinfo)-4)); - } elsif ($payby eq 'CHEK' || - $payby eq 'DCHK' ) { # Checks (Show last 2 @ bank) + if ( $self->is_encrypted($payinfo) ) { + return 'N/A'; + } elsif ( $payinfo =~ /^99\d{14}$/ || $payinfo eq 'N/A' ) { #token + return 'N/A (tokenized)'; #? + } else { # if not, mask it... + + if ($payby eq 'CARD' || $payby eq 'DCRD' || $payby eq 'MCRD') { + + # Credit Cards + + # special handling for Local Isracards: always show last 4 + if ( $payinfo =~ /^(\d{8,9})$/ ) { + + return 'x'x(length($payinfo)-4). + substr($payinfo,(length($payinfo)-4)); + + } + + my $conf = new FS::Conf; + my $mask_method = $conf->config('card_masking_method') || 'first6last4'; + $mask_method =~ /^first(\d+)last(\d+)$/ + or die "can't parse card_masking_method $mask_method"; + my($first, $last) = ($1, $2); + + return substr($payinfo,0,$first). + 'x'x(length($payinfo)-$first-$last). + substr($payinfo,(length($payinfo)-$last)); + + } elsif ($payby eq 'CHEK' || $payby eq 'DCHK' ) { + + # Checks (Show last 2 @ bank) my( $account, $aba ) = split('@', $payinfo ); - $paymask = 'x'x(length($account)-2). substr($account,(length($account)-2))."@".$aba; + return 'x'x(length($account)-2). + substr($account,(length($account)-2)). + ( length($aba) ? "@".$aba : ''); + } else { # Tie up loose ends - $paymask = $payinfo; + return $payinfo; } } - return $paymask; + #die "shouldn't be reached"; } -=back - - -=head1 METHODS - -=over 4 - =item payinfo_check +Checks payby and payinfo. + For Customers (cust_main): 'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand), 'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand), @@ -182,62 +193,110 @@ For Payments (cust_pay): =cut - - - - sub payinfo_check { my $self = shift; - # Make sure it's a valid payby - $self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD|PREP|CBAK)$/ - or return "Illegal payby (overall payinfo_check)"; - $self->payby($1); + FS::payby->can_payby($self->table, $self->payby) + or return "Illegal payby: ". $self->payby; - - # Okay some aren't valid depending on table - if ($self->table eq 'cust_main') { - if ($self->payby =~ /^(CASH|WEST|MCRD|PREP|CBAK)$/) { - return "Illegal payby (cust_main)"; - } - } elsif ($self->table eq 'cust_refund') { - if ($self->payby =~ /^(DCRD|DCHK|PREPAY|PREP)$/) { - return "Illegal payby (cust_refund)"; + if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) { + my $payinfo = $self->payinfo; + if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) { + # allow it + } else { + $payinfo =~ s/\D//g; + $self->payinfo($payinfo); + if ( $self->payinfo ) { + $self->payinfo =~ /^(\d{13,16}|\d{8,9})$/ + or return "Illegal (mistyped?) credit card number (payinfo)"; + $self->payinfo($1); + validate($self->payinfo) or return "Illegal credit card number"; + return "Unknown card type" if $self->payinfo !~ /^99\d{14}$/ #token + && cardtype($self->payinfo) eq "Unknown"; + } else { + $self->payinfo('N/A'); #??? + } } - } elsif ($self->table eq 'cust_pay') { - if ($self->payby =~ /^(DCRD|DCHK|PREPAY|CBAK)$/) { - return "Illegal payby (cust_pay)"; + } else { + if ( $self->is_encrypted($self->payinfo) ) { + #something better? all it would cause is a decryption error anyway? + my $error = $self->ut_anything('payinfo'); + return $error if $error; + } else { + my $error = $self->ut_textn('payinfo'); + return $error if $error; } } +} + +=item payby_payinfo_pretty + +Returns payment method and information (suitably masked, if applicable) as +a human-readable string, such as: + + Card #54xxxxxxxxxxxx32 + +or + + Check #119006 + +=cut + +sub payby_payinfo_pretty { + my $self = shift; if ( $self->payby eq 'CARD' ) { - my $payinfo = $self->payinfo; - $payinfo =~ s/\D//g; - $self->payinfo($payinfo); - if ( $self->payinfo ) { - $self->payinfo =~ /^(\d{13,16})$/ - or return "Illegal (mistyped?) credit card number (payinfo)"; - $self->payinfo($1); - Business::CreditCard::validate($self->payinfo) or return "Illegal credit card number"; - return "Unknown card type" if Business::CreditCard::cardtype($self->payinfo) eq "Unknown"; - } else { - $self->payinfo('N/A'); - } + 'Card #'. $self->paymask; + } elsif ( $self->payby eq 'CHEK' ) { + 'E-check acct#'. $self->payinfo; + } elsif ( $self->payby eq 'BILL' ) { + 'Check #'. $self->payinfo; + } elsif ( $self->payby eq 'PREP' ) { + 'Prepaid card #'. $self->payinfo; + } elsif ( $self->payby eq 'CASH' ) { + 'Cash '. $self->payinfo; + } elsif ( $self->payby eq 'WEST' ) { + 'Western Union'; #. $self->payinfo; + } elsif ( $self->payby eq 'MCRD' ) { + 'Manual credit card'; #. $self->payinfo; + } elsif ( $self->payby eq 'PPAL' ) { + 'PayPal transaction#' . $self->order_number; } else { - my $error = $self->ut_textn('payinfo'); - return $error if $error; + $self->payby. ' '. $self->payinfo; } } +=item payinfo_used [ PAYINFO ] +Returns 1 if there's an existing payment using this payinfo. This can be +used to set the 'recurring payment' flag required by some processors. -=head1 BUGS +=cut + +sub payinfo_used { + my $self = shift; + my $payinfo = shift || $self->payinfo; + my %hash = ( + 'custnum' => $self->custnum, + 'payby' => 'CARD', + ); + + return 1 + if qsearch('cust_pay', { %hash, 'payinfo' => $payinfo } ) + || qsearch('cust_pay', + { %hash, 'paymask' => $self->mask_payinfo('CARD', $payinfo) } ) + ; + + return 0; +} + +=back -Have to add the future items... +=head1 BUGS =head1 SEE ALSO -L +L, L =cut