[freeside-commits] branch master updated. 91dbe4c3834f38d428367d9a1e2c6cf9ea9d84a4

Ivan ivan at 420.am
Wed Jul 17 09:04:10 PDT 2013


The branch, master has been updated
       via  91dbe4c3834f38d428367d9a1e2c6cf9ea9d84a4 (commit)
       via  2101a32bdf12abdb2afdb654d6da30975ddd4fc9 (commit)
      from  d0fcbc3d04250ec54cb5dea7abcc58d1f45d78b1 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 91dbe4c3834f38d428367d9a1e2c6cf9ea9d84a4
Merge: 2101a32 d0fcbc3
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Jul 17 09:04:06 2013 -0700

    Merge branch 'master' of git.freeside.biz:/home/git/freeside


commit 2101a32bdf12abdb2afdb654d6da30975ddd4fc9
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Jul 17 09:03:56 2013 -0700

    multiple payment options, RT#23741

diff --git a/FS/FS.pm b/FS/FS.pm
index 076f80b..a318a20 100644
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -338,6 +338,8 @@ L<FS::cust_main::Billing_Realtime> - Customer real-time billing class
 
 L<FS::cust_main::Packages> - Customer packages class
 
+L<FS::cust_payby> - Customer payment information class
+
 L<FS::cust_location> - Customer location class
 
 L<FS::cust_main_Mixin> - Mixin class for records that contain fields from cust_main
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6df45e2..2b7db26 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1084,6 +1084,8 @@ sub tables_hashref {
         'ship_fax',      'varchar', 'NULL', 12, '', '', 
         'ship_mobile',   'varchar', 'NULL', 12, '', '', 
         'currency',         'char', 'NULL',  3, '', '',
+
+        #deprecated, info moved to cust_payby
         'payby',    'char', '',     4, '', '', 
         'payinfo',  'varchar', 'NULL', 512, '', '', 
         'paycvv',   'varchar', 'NULL', 512, '', '', 
@@ -1097,6 +1099,7 @@ sub tables_hashref {
         'paystate', 'varchar', 'NULL', $char_d, '', '', 
         'paytype',  'varchar', 'NULL', $char_d, '', '', 
         'payip',    'varchar', 'NULL', 15, '', '', 
+
         'geocode',  'varchar', 'NULL', 20,  '', '',
         'censustract', 'varchar', 'NULL', 20,  '', '', # 7 to save space?
         'censusyear', 'char', 'NULL', 4, '', '',
@@ -1138,6 +1141,31 @@ sub tables_hashref {
                  ],
     },
 
+    'cust_payby' => {
+      'columns' => [
+        'custpaybynum', 'serial',     '',        '', '', '', 
+        'custnum',         'int',     '',        '', '', '',
+        'weight',          'int',     '',        '', '', '', 
+        'payby',          'char',     '',         4, '', '', 
+        'payinfo',     'varchar', 'NULL',       512, '', '', 
+        'paycvv',      'varchar', 'NULL',       512, '', '', 
+        'paymask',     'varchar', 'NULL',   $char_d, '', '', 
+        #'paydate',   @date_type, '', '', 
+        'paydate',     'varchar', 'NULL',        10, '', '', 
+        'paystart_month',  'int', 'NULL',        '', '', '', 
+        'paystart_year',   'int', 'NULL',        '', '', '', 
+        'payissue',    'varchar', 'NULL',         2, '', '', 
+        'payname',     'varchar', 'NULL', 2*$char_d, '', '', 
+        'paystate',    'varchar', 'NULL',   $char_d, '', '', 
+        'paytype',     'varchar', 'NULL',   $char_d, '', '', 
+        'payip',       'varchar', 'NULL',        15, '', '', 
+        'locationnum',     'int', 'NULL',        '', '', '',
+      ],
+      'primary_key' => 'custpaybynum',
+      'unique'      => [],
+      'index'       => [ [ 'custnum' ] ],
+    },
+
     'cust_recon' => {  # (some sort of not-well understood thing for OnPac)
       'columns' => [
         'reconid',      'serial',  '',          '', '', '', 
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index cda3198..056b80b 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -175,6 +175,9 @@ sub upgrade {
   local($FS::cust_main::ignore_banned_card) = 1;
   local($FS::cust_main::skip_fuzzyfiles) = 1;
 
+  local($FS::cust_payby::ignore_expired_card) = 1;
+  local($FS::cust_payby::ignore_banned_card) = 1;
+
   # decrypt inadvertantly-encrypted payinfo where payby != CARD,DCRD,CHEK,DCHK
   # kind of a weird spot for this, but it's better than duplicating
   # all this code in each class...
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 7c7c9e2..30d6fa0 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -1791,6 +1791,8 @@ sub check {
   
   }
 
+  ### start of stuff moved to cust_payby
+
   #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
   #  or return "Illegal payby: ". $self->payby;
   #$self->payby($1);
@@ -2000,6 +2002,8 @@ sub check {
     $self->payname($1);
   }
 
+  ### end of stuff moved to cust_payby
+
   return "Please select an invoicing locale"
     if ! $self->locale
     && ! $self->custnum
diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
new file mode 100644
index 0000000..7bf8c76
--- /dev/null
+++ b/FS/FS/cust_payby.pm
@@ -0,0 +1,414 @@
+package FS::cust_payby;
+
+use strict;
+use base qw( FS::payinfo_Mixin FS::Record );
+use FS::UID;
+use FS::Record qw( qsearchs ); #qsearch;
+use FS::payby;
+use FS::cust_main;
+
+use vars qw( $conf $ignore_expired_card $ignore_banned_card  );
+
+$ignore_expired_card = 0;
+$ignore_banned_card = 0;
+
+install_callback FS::UID sub { 
+  $conf = new FS::Conf;
+  #yes, need it for stuff below (prolly should be cached)
+};
+
+=head1 NAME
+
+FS::cust_payby - Object methods for cust_payby records
+
+=head1 SYNOPSIS
+
+  use FS::cust_payby;
+
+  $record = new FS::cust_payby \%hash;
+  $record = new FS::cust_payby { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_payby object represents customer stored payment information.
+FS::cust_payby inherits from FS::Record.  The following fields are currently
+supported:
+
+=over 4
+
+=item custpaybynum
+
+primary key
+
+=item custnum
+
+custnum
+
+=item weight
+
+weight
+
+=item payby
+
+payby
+
+=item payinfo
+
+payinfo
+
+=item paycvv
+
+paycvv
+
+=item paymask
+
+paymask
+
+=item paydate
+
+paydate
+
+=item paystart_month
+
+paystart_month
+
+=item paystart_year
+
+paystart_year
+
+=item payissue
+
+payissue
+
+=item payname
+
+payname
+
+=item paystate
+
+paystate
+
+=item paytype
+
+paytype
+
+=item payip
+
+payip
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_payby'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('custpaybynum')
+    || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+    || $self->ut_number('weight')
+    || $self->ut_('payby')
+    || $self->ut_textn('payinfo')
+    || $self->ut_textn('paycvv')
+    || $self->ut_textn('paymask')
+    || $self->ut_textn('paydate')
+    || $self->ut_numbern('paystart_month')
+    || $self->ut_numbern('paystart_year')
+    || $self->ut_textn('payissue')
+    || $self->ut_textn('payname')
+    || $self->ut_textn('paystate')
+    || $self->ut_textn('paytype')
+    || $self->ut_textn('payip')
+  ;
+  return $error if $error;
+
+
+  ### from cust_main
+
+
+  #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
+  #  or return "Illegal payby: ". $self->payby;
+  #$self->payby($1);
+  FS::payby->can_payby($self->table, $self->payby)
+    or return "Illegal payby: ". $self->payby;
+
+  $error =    $self->ut_numbern('paystart_month')
+           || $self->ut_numbern('paystart_year')
+           || $self->ut_numbern('payissue')
+           || $self->ut_textn('paytype')
+  ;
+  return $error if $error;
+
+  if ( $self->payip eq '' ) {
+    $self->payip('');
+  } else {
+    $error = $self->ut_ip('payip');
+    return $error if $error;
+  }
+
+  # If it is encrypted and the private key is not availaible then we can't
+  # check the credit card.
+  my $check_payinfo = ! $self->is_encrypted($self->payinfo);
+
+  # Need some kind of global flag to accept invalid cards, for testing
+  # on scrubbed data.
+  #XXX if ( !$import && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
+  if ( $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
+
+    my $payinfo = $self->payinfo;
+    $payinfo =~ s/\D//g;
+    $payinfo =~ /^(\d{13,16}|\d{8,9})$/
+      or return gettext('invalid_card'); # . ": ". $self->payinfo;
+    $payinfo = $1;
+    $self->payinfo($payinfo);
+    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";
+
+    unless ( $ignore_banned_card ) {
+      my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
+      if ( $ban ) {
+        if ( $ban->bantype eq 'warn' ) {
+          #or others depending on value of $ban->reason ?
+          return '_duplicate_card'.
+                 ': disabled from'. time2str('%a %h %o at %r', $ban->_date).
+                 ' until '.         time2str('%a %h %o at %r', $ban->_end_date).
+                 ' (ban# '. $ban->bannum. ')'
+            unless $self->override_ban_warn;
+        } else {
+          return 'Banned credit card: banned on '.
+                 time2str('%a %h %o at %r', $ban->_date).
+                 ' by '. $ban->otaker.
+                 ' (ban# '. $ban->bannum. ')';
+        }
+      }
+    }
+
+    if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) {
+      if ( cardtype($self->payinfo) eq 'American Express card' ) {
+        $self->paycvv =~ /^(\d{4})$/
+          or return "CVV2 (CID) for American Express cards is four digits.";
+        $self->paycvv($1);
+      } else {
+        $self->paycvv =~ /^(\d{3})$/
+          or return "CVV2 (CVC2/CID) is three digits.";
+        $self->paycvv($1);
+      }
+    } else {
+      $self->paycvv('');
+    }
+
+    my $cardtype = cardtype($payinfo);
+    if ( $cardtype =~ /^(Switch|Solo)$/i ) {
+
+      return "Start date or issue number is required for $cardtype cards"
+        unless $self->paystart_month && $self->paystart_year or $self->payissue;
+
+      return "Start month must be between 1 and 12"
+        if $self->paystart_month
+           and $self->paystart_month < 1 || $self->paystart_month > 12;
+
+      return "Start year must be 1990 or later"
+        if $self->paystart_year
+           and $self->paystart_year < 1990;
+
+      return "Issue number must be beween 1 and 99"
+        if $self->payissue
+          and $self->payissue < 1 || $self->payissue > 99;
+
+    } else {
+      $self->paystart_month('');
+      $self->paystart_year('');
+      $self->payissue('');
+    }
+
+  } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) {
+
+    my $payinfo = $self->payinfo;
+    $payinfo =~ s/[^\d\@\.]//g;
+    if ( $conf->config('echeck-country') eq 'CA' ) {
+      $payinfo =~ /^(\d+)\@(\d{5})\.(\d{3})$/
+        or return 'invalid echeck account at branch.bank';
+      $payinfo = "$1\@$2.$3";
+    } elsif ( $conf->config('echeck-country') eq 'US' ) {
+      $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account at aba';
+      $payinfo = "$1\@$2";
+    } else {
+      $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account at routing';
+      $payinfo = "$1\@$2";
+    }
+    $self->payinfo($payinfo);
+    $self->paycvv('');
+
+    unless ( $ignore_banned_card ) {
+      my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
+      if ( $ban ) {
+        if ( $ban->bantype eq 'warn' ) {
+          #or others depending on value of $ban->reason ?
+          return '_duplicate_ach' unless $self->override_ban_warn;
+        } else {
+          return 'Banned ACH account: banned on '.
+                 time2str('%a %h %o at %r', $ban->_date).
+                 ' by '. $ban->otaker.
+                 ' (ban# '. $ban->bannum. ')';
+        }
+      }
+    }
+
+  } elsif ( $self->payby eq 'LECB' ) {
+
+    my $payinfo = $self->payinfo;
+    $payinfo =~ s/\D//g;
+    $payinfo =~ /^1?(\d{10})$/ or return 'invalid btn billing telephone number';
+    $payinfo = $1;
+    $self->payinfo($payinfo);
+    $self->paycvv('');
+
+  } elsif ( $self->payby eq 'BILL' ) {
+
+    $error = $self->ut_textn('payinfo');
+    return "Illegal P.O. number: ". $self->payinfo if $error;
+    $self->paycvv('');
+
+  } elsif ( $self->payby eq 'COMP' ) {
+
+    my $curuser = $FS::CurrentUser::CurrentUser;
+    if (    ! $self->custnum
+         && ! $curuser->access_right('Complimentary customer')
+       )
+    {
+      return "You are not permitted to create complimentary accounts."
+    }
+
+    $error = $self->ut_textn('payinfo');
+    return "Illegal comp account issuer: ". $self->payinfo if $error;
+    $self->paycvv('');
+
+  } elsif ( $self->payby eq 'PREPAY' ) {
+
+    my $payinfo = $self->payinfo;
+    $payinfo =~ s/\W//g; #anything else would just confuse things
+    $self->payinfo($payinfo);
+    $error = $self->ut_alpha('payinfo');
+    return "Illegal prepayment identifier: ". $self->payinfo if $error;
+    return "Unknown prepayment identifier"
+      unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
+    $self->paycvv('');
+
+  }
+
+  if ( $self->paydate eq '' || $self->paydate eq '-' ) {
+    return "Expiration date required"
+      # shouldn't payinfo_check do this?
+      unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD|PPAL)$/;
+    $self->paydate('');
+  } else {
+    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);
+    $self->paydate("$y-$m-01");
+    my($nowm,$nowy)=(localtime(time))[4,5]; $nowm++; $nowy+=1900;
+    return gettext('expired_card')
+      if #XXX !$import
+      #&&
+         !$ignore_expired_card 
+      && ( $y<$nowy || ( $y==$nowy && $1<$nowm ) );
+  }
+
+  if ( $self->payname eq '' && $self->payby !~ /^(CHEK|DCHK)$/ &&
+       ( ! $conf->exists('require_cardname')
+         || $self->payby !~ /^(CARD|DCRD)$/  ) 
+  ) {
+    $self->payname( $self->first. " ". $self->getfield('last') );
+  } else {
+    $self->payname =~ /^([\w \,\.\-\'\&]+)$/
+      or return gettext('illegal_name'). " payname: ". $self->payname;
+    $self->payname($1);
+  }
+
+  ###
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index a86683d..7e61868 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -705,3 +705,5 @@ FS/currency_exchange.pm
 t/currency_exchange.t
 FS/part_pkg_currency.pm
 t/part_pkg_currency.t
+FS/cust_payby.pm
+t/cust_payby.t
diff --git a/FS/t/cust_payby.t b/FS/t/cust_payby.t
new file mode 100644
index 0000000..b5f7f51
--- /dev/null
+++ b/FS/t/cust_payby.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_payby;
+$loaded=1;
+print "ok 1\n";

-----------------------------------------------------------------------

Summary of changes:
 FS/FS.pm                          |    2 +
 FS/FS/Schema.pm                   |   28 +++
 FS/FS/Upgrade.pm                  |    3 +
 FS/FS/cust_main.pm                |    4 +
 FS/FS/cust_payby.pm               |  414 +++++++++++++++++++++++++++++++++++++
 FS/MANIFEST                       |    2 +
 FS/t/{ConfItem.t => cust_payby.t} |    2 +-
 7 files changed, 454 insertions(+), 1 deletions(-)
 create mode 100644 FS/FS/cust_payby.pm
 copy FS/t/{ConfItem.t => cust_payby.t} (83%)




More information about the freeside-commits mailing list