one-time charge enhancements
[freeside.git] / FS / FS / cust_main.pm
index 2a9c4c7..0534686 100644 (file)
@@ -1093,7 +1093,7 @@ sub replace {
   local($ignore_expired_card) = 1
     if $old->payby  =~ /^(CARD|DCRD)$/
     && $self->payby =~ /^(CARD|DCRD)$/
-    && $old->payinfo eq $self->payinfo;
+    && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
 
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
@@ -1321,7 +1321,10 @@ sub check {
     }
   }
 
-  $self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
+  #$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')
@@ -1346,8 +1349,6 @@ sub check {
     $check_payinfo = 0;
   }
 
-  $self->payby($1);
-
   if ( $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
 
     my $payinfo = $self->payinfo;
@@ -1370,20 +1371,18 @@ sub check {
              ' (ban# '. $ban->bannum. ')';
     }
 
-    if ( defined $self->dbdef_table->column('paycvv') ) {
-      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);
-        }
+    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('');
+        $self->paycvv =~ /^(\d{3})$/
+          or return "CVV2 (CVC2/CID) is three digits.";
+        $self->paycvv($1);
       }
+    } else {
+      $self->paycvv('');
     }
 
     my $cardtype = cardtype($payinfo);
@@ -1416,12 +1415,13 @@ sub check {
     $payinfo =~ s/[^\d\@]//g;
     if ( $conf->exists('echeck-nonus') ) {
       $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account@aba';
+      $payinfo = "$1\@$2";
     } else {
       $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba';
+      $payinfo = "$1\@$2";
     }
-    $payinfo = "$1\@$2";
     $self->payinfo($payinfo);
-    $self->paycvv('') if $self->dbdef_table->column('paycvv');
+    $self->paycvv('');
 
     my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
     if ( $ban ) {
@@ -1438,13 +1438,13 @@ sub check {
     $payinfo =~ /^1?(\d{10})$/ or return 'invalid btn billing telephone number';
     $payinfo = $1;
     $self->payinfo($payinfo);
-    $self->paycvv('') if $self->dbdef_table->column('paycvv');
+    $self->paycvv('');
 
   } elsif ( $self->payby eq 'BILL' ) {
 
     $error = $self->ut_textn('payinfo');
     return "Illegal P.O. number: ". $self->payinfo if $error;
-    $self->paycvv('') if $self->dbdef_table->column('paycvv');
+    $self->paycvv('');
 
   } elsif ( $self->payby eq 'COMP' ) {
 
@@ -1458,7 +1458,7 @@ sub check {
 
     $error = $self->ut_textn('payinfo');
     return "Illegal comp account issuer: ". $self->payinfo if $error;
-    $self->paycvv('') if $self->dbdef_table->column('paycvv');
+    $self->paycvv('');
 
   } elsif ( $self->payby eq 'PREPAY' ) {
 
@@ -1469,7 +1469,7 @@ sub check {
     return "Illegal prepayment identifier: ". $self->payinfo if $error;
     return "Unknown prepayment identifier"
       unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
-    $self->paycvv('') if $self->dbdef_table->column('paycvv');
+    $self->paycvv('');
 
   }
 
@@ -1526,11 +1526,17 @@ Returns all packages (see L<FS::cust_pkg>) for this customer.
 
 sub all_pkgs {
   my $self = shift;
+
+  return $self->num_pkgs unless wantarray;
+
+  my @cust_pkg = ();
   if ( $self->{'_pkgnum'} ) {
-    values %{ $self->{'_pkgnum'}->cache };
+    @cust_pkg = values %{ $self->{'_pkgnum'}->cache };
   } else {
-    qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
+    @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
   }
+
+  sort sort_packages @cust_pkg;
 }
 
 =item ncancelled_pkgs
@@ -1541,19 +1547,43 @@ Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
 
 sub ncancelled_pkgs {
   my $self = shift;
+
+  return $self->num_ncancelled_pkgs unless wantarray;
+
+  my @cust_pkg = ();
   if ( $self->{'_pkgnum'} ) {
-    grep { ! $_->getfield('cancel') } values %{ $self->{'_pkgnum'}->cache };
+
+    @cust_pkg = grep { ! $_->getfield('cancel') }
+                values %{ $self->{'_pkgnum'}->cache };
+
   } else {
-    @{ [ # force list context
+
+    @cust_pkg =
       qsearch( 'cust_pkg', {
-        'custnum' => $self->custnum,
-        'cancel'  => '',
-      }),
+                             'custnum' => $self->custnum,
+                             'cancel'  => '',
+                           });
+    push @cust_pkg,
       qsearch( 'cust_pkg', {
-        'custnum' => $self->custnum,
-        'cancel'  => 0,
-      }),
-    ] };
+                             'custnum' => $self->custnum,
+                             'cancel'  => 0,
+                           });
+  }
+
+  sort sort_packages @cust_pkg;
+
+}
+
+# This should be generalized to use config options to determine order.
+sub sort_packages {
+  if ( $a->get('cancel') and $b->get('cancel') ) {
+    $a->pkgnum <=> $b->pkgnum;
+  } elsif ( $a->get('cancel') or $b->get('cancel') ) {
+    return -1 if $b->get('cancel');
+    return  1 if $a->get('cancel');
+    return 0;
+  } else {
+    $a->pkgnum <=> $b->pkgnum;
   }
 }
 
@@ -1602,14 +1632,18 @@ customer.
 =cut
 
 sub num_cancelled_pkgs {
-  my $self = shift;
-  $self->num_pkgs("cancel IS NOT NULL AND cust_pkg.cancel != 0");
+  shift->num_pkgs("cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0");
+}
+
+sub num_ncancelled_pkgs {
+  shift->num_pkgs("( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )");
 }
 
 sub num_pkgs {
   my( $self, $sql ) = @_;
+  $sql = "AND $sql" if $sql && $sql !~ /^\s*$/ && $sql !~ /^\s*AND/i;
   my $sth = dbh->prepare(
-    "SELECT COUNT(*) FROM cust_pkg WHERE custnum = ? AND $sql"
+    "SELECT COUNT(*) FROM cust_pkg WHERE custnum = ? $sql"
   ) or die dbh->errstr;
   $sth->execute($self->custnum) or die $sth->errstr;
   $sth->fetchrow_arrayref->[0];
@@ -1737,7 +1771,7 @@ sub _banned_pay_hashref {
   {
     'payby'   => $payby2ban{$self->payby},
     'payinfo' => md5_base64($self->payinfo),
-    #'reason'  =>
+    #don't ever *search* on reason! #'reason'  =>
   };
 }
 
@@ -1870,7 +1904,7 @@ sub bill {
     
       warn "    bill setup\n" if $DEBUG > 1;
 
-      $setup = eval { $cust_pkg->calc_setup( $time ) };
+      $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
       if ( $@ ) {
         $dbh->rollback if $oldAutoCommit;
         return "$@ running calc_setup for $cust_pkg\n";
@@ -3337,23 +3371,6 @@ sub paydate_monthyear {
   }
 }
 
-=item payinfo_masked
-
-< DEPRICATED > Use $self->paymask
-
-Returns a "masked" payinfo field appropriate to the payment type.  Masked characters are replaced by 'x'es.  Use this to display publicly accessable account Information.
-
-Credit Cards - Mask all but the last four characters.
-Checks - Mask all but last 2 of account number and bank routing number.
-Others - Do nothing, return the unmasked string.
-
-=cut
-
-sub payinfo_masked {
-  my $self = shift;
-  return $self->paymask;
-}
-
 =item invoicing_list [ ARRAYREF ]
 
 If an arguement is given, sets these email addresses as invoice recipients
@@ -3598,10 +3615,22 @@ the error, otherwise returns false.
 =cut
 
 sub charge {
-  my ( $self, $amount ) = ( shift, shift );
-  my $pkg      = @_ ? shift : 'One-time charge';
-  my $comment  = @_ ? shift : '$'. sprintf("%.2f",$amount);
-  my $taxclass = @_ ? shift : '';
+  my $self = shift;
+  my ( $amount, $pkg, $comment, $taxclass, $additional );
+  if ( ref( $_[0] ) ) {
+    $amount     = $_[0]->{amount};
+    $pkg        = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge';
+    $comment    = exists($_[0]->{comment}) ? $_[0]->{comment}
+                                           : '$'. sprintf("%.2f",$amount);
+    $taxclass   = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : '';
+    $additional = $_[0]->{additional};
+  }else{
+    $amount     = shift;
+    $pkg        = @_ ? shift : 'One-time charge';
+    $comment    = @_ ? shift : '$'. sprintf("%.2f",$amount);
+    $taxclass   = @_ ? shift : '';
+    $additional = [];
+  }
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -3617,16 +3646,20 @@ sub charge {
   my $part_pkg = new FS::part_pkg ( {
     'pkg'      => $pkg,
     'comment'  => $comment,
-    #'setup'    => $amount,
-    #'recur'    => '0',
     'plan'     => 'flat',
-    'plandata' => "setup_fee=$amount",
     'freq'     => 0,
     'disabled' => 'Y',
     'taxclass' => $taxclass,
   } );
 
-  my $error = $part_pkg->insert;
+  my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) }
+                        ( 0 .. @$additional - 1 )
+                  ),
+                  'additional_count' => scalar(@$additional),
+                  'setup_fee' => $amount,
+                );
+
+  my $error = $part_pkg->insert( options => \%options );
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;