use strict;
use Business::CreditCard;
+use FS::payby;
=head1 NAME
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
'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<FS::prepay_credit> and sets billing type to I<BILL>)
+'PREPAY' (special billing type: applies a credit and sets billing type to I<BILL> - see L<FS::prepay_credit>)
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):
'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')
-}
+# was this supposed to do something?
+
+#sub payby {
+# my($self,$payby) = @_;
+# if ( defined($payby) ) {
+# $self->setfield('payby', $payby);
+# }
+# return $self->getfield('payby')
+#}
=item payinfo
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');
}
}
}
} else {
# warn "This doesn't work for other tables besides cust_main
+ '';
}
}
=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),
=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)";
- }
- } elsif ($self->table eq 'cust_pay') {
- if ($self->payby =~ /^(DCRD|DCHK|PREPAY|CBAK)$/) {
- return "Illegal payby (cust_pay)";
- }
- }
-
- if ( $self->payby eq 'CARD' ) {
+ if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
my $payinfo = $self->payinfo;
$payinfo =~ s/\D//g;
$self->payinfo($payinfo);
if ( $self->payinfo ) {
- $self->payinfo =~ /^(\d{13,16})$/
+ $self->payinfo =~ /^(\d{13,16}|\d{8,9})$/
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";
+ 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');
+ $self->payinfo('N/A'); #???
}
} else {
- my $error = $self->ut_textn('payinfo');
- return $error if $error;
+ 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:
-=head1 BUGS
+ Card #54xxxxxxxxxxxx32
+
+or
+
+ Check #119006
+
+=cut
+
+sub payby_payinfo_pretty {
+ my $self = shift;
+ if ( $self->payby eq 'CARD' ) {
+ '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;
+ } else {
+ $self->payby. ' '. $self->payinfo;
+ }
+}
+
+=back
-Have to add the future items...
+=head1 BUGS
=head1 SEE ALSO
-L<FS::Record>
+L<FS::payby>, L<FS::Record>
=cut