'usernum', 'int', 'NULL', '', '', '',
'payby', 'char', '', 4, '', '',
'payinfo', 'varchar', 'NULL', 512, '', '',
+ 'cardtype', 'varchar', 'NULL', $char_d, '', '',
'paymask', 'varchar', 'NULL', $char_d, '', '',
'paydate', 'varchar', 'NULL', 10, '', '',
'paybatch', 'varchar', 'NULL', $char_d, '', '',#for auditing purposes
'usernum', 'int', 'NULL', '', '', '',
'payby', 'char', '', 4, '', '',
'payinfo', 'varchar', 'NULL', 512, '', '',
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'cardtype', 'varchar', 'NULL', $char_d, '', '',
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
#'paydate' ?
'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
'closed', 'char', 'NULL', 1, '', '',
# be index into payby
# table eventually
'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'cardtype', 'varchar', 'NULL', $char_d, '', '',
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
'paybatch', 'varchar', 'NULL', $char_d, '', '',
'closed', 'char', 'NULL', 1, '', '',
'source_paynum', 'int', 'NULL', '', '', '', # link to cust_payby, to prevent unapply of gateway-generated refunds
'cust_refund' => [],
'banned_pay' => [],
+ #cardtype
+ 'cust_payby' => [],
+
#default namespace
'payment_gateway' => [],
Payment Information (See L<FS::payinfo_Mixin> for data format)
+=item cardtype
+
+Credit card type, if appropriate; autodetected.
+
=item paymask
Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
process_upgrade_paybatch();
}
}
+
+ ###
+ # set cardtype
+ ###
+ $class->upgrade_set_cardtype;
+
}
sub process_upgrade_paybatch {
card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively
+=item cardtype
+
+Credit card type, if appropriate.
+
=item paybatch
text field for tracking card processing
payip
+=item cardtype
+
+The credit card type (deduced from the card number).
=back
# 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)$/ ) {
validate($payinfo)
or return gettext('invalid_card'); # . ": ". $self->payinfo;
+ my $cardtype = cardtype($payinfo);
+ $self->set('cardtype', $cardtype);
return gettext('unknown_card_type')
if $self->payinfo !~ /^99\d{14}$/ #token
- && cardtype($self->payinfo) eq "Unknown";
+ && $cardtype eq "Unknown";
unless ( $ignore_banned_card ) {
my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
}
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);
$self->paycvv('');
}
- my $cardtype = cardtype($payinfo);
if ( $cardtype =~ /^(Switch|Solo)$/i ) {
return "Start date or issue number is required for $cardtype cards"
}
}
+ } 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('cardtype', cardtype($self->paymask));
+ } else {
+ $self->set('cardtype', '');
+ }
+
# } elsif ( $self->payby eq 'PREPAY' ) {
#
# my $payinfo = $self->payinfo;
# unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
# $self->paycvv('');
- }
-
if ( $self->payby =~ /^(CHEK|DCHK)$/ ) {
$self->paydate('');
} 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 '-';
my %bop_card_types = map { $_=>1 } values %{ card_types() };
my $cardtype = cardtype($payinfo);
+ $self->set('cardtype', $cardtype);
return "$cardtype not accepted" unless $bop_card_types{$cardtype};
my $self = shift;
my $name = $self->payby =~ /^(CARD|DCRD)$/
- && cardtype($self->paymask) || FS::payby->shortname($self->payby);
+ && $self->cardtype || FS::payby->shortname($self->payby);
( $self->payby =~ /^(CARD|CHEK)$/ ? $weight{$self->weight}. ' automatic '
: 'Manual '
=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
Payment Information (See L<FS::payinfo_Mixin> for data format)
+=item cardtype
+
+Detected credit card type, if appropriate; autodetected.
+
=item paymask
Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
my ($class, %opts) = @_;
$class->_upgrade_reasonnum(%opts);
$class->_upgrade_otaker(%opts);
+
+ local $ignore_empty_reasonnum = 1;
+ $class->upgrade_set_cardtype;
}
=back
use FS::payby;
use FS::Record qw(qsearch);
use FS::UID qw(driver_name);
+use FS::Cursor;
use Time::Local qw(timelocal);
use vars qw($ignore_masked_payinfo);
if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
my $payinfo = $self->payinfo;
+ my $cardtype = cardtype($payinfo);
+ $self->set('cardtype', $cardtype);
if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) {
# allow it
} else {
$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";
+ && $cardtype eq "Unknown";
} else {
$self->payinfo('N/A'); #???
}
}
} else {
+ if ( $self->payby eq 'CARD' and $self->paymask ) {
+ # if we can't decrypt the card, at least detect the cardtype
+ $self->set('cardtype', cardtype($self->paymask));
+ } else {
+ $self->set('cardtype', '');
+ }
if ( $self->is_encrypted($self->payinfo) ) {
#something better? all it would cause is a decryption error anyway?
my $error = $self->ut_anything('payinfo');
END"
}
+=item upgrade_set_cardtype
+
+Find all records with a credit card payment type and no cardtype, and
+replace them in order to set their cardtype.
+
+=cut
+
+sub upgrade_set_cardtype {
+ my $class = shift;
+ # assign cardtypes to CARD/DCRDs that need them; check_payinfo_cardtype
+ # will do this. ignore any problems with the cards.
+ local $ignore_masked_payinfo = 1;
+ my $search = FS::Cursor->new({
+ table => $class->table,
+ extra_sql => q[ WHERE payby IN('CARD','DCRD') AND cardtype IS NULL ],
+ });
+ while (my $record = $search->fetch) {
+ my $error = $record->replace;
+ die $error if $error;
+ }
+}
+
=back
=head1 BUGS