[freeside-commits] branch master updated. ad1d603d9e9a813e697c267d29fefd00fee56ae7

Ivan ivan at 420.am
Tue Feb 10 01:44:03 PST 2015


The branch, master has been updated
       via  ad1d603d9e9a813e697c267d29fefd00fee56ae7 (commit)
       via  92c9e14aa28f017837fb94e4da1d9862e30b02f8 (commit)
       via  a4d4d3df88b33a6db30b565921f6d62efb252351 (commit)
       via  6615733676adb431ae48c78ce24758fe571614c1 (commit)
       via  137be4be5a414a74f02d8081786d2031f0e7cc63 (commit)
       via  7ba0aabd949895bfd40f3fc8c7c173cec90b8c40 (commit)
      from  ec7e8155fce544f19f2b6734476ed6db8c200aa9 (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 ad1d603d9e9a813e697c267d29fefd00fee56ae7
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Feb 10 01:44:00 2015 -0800

    reliabily kill off old freeside-selfservice-server processes, RT#33174

diff --git a/init.d/freeside-init b/init.d/freeside-init
index 705afc1..099603e 100644
--- a/init.d/freeside-init
+++ b/init.d/freeside-init
@@ -170,10 +170,10 @@ case "$1" in
           if [ -e /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid ]
           then
             echo -n "Stopping freeside-selfservice-server to $MACHINE"
+            kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid`
             howlong=10
             while [ $howlong -gt 0 ] && kill -0 `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid` 2>/dev/null; do
               echo -n '.'
-              kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid`
               sleep 1
               howlong=$(( $howlong - 1 ))
               if [ $howlong -eq 0 ]; then

commit 92c9e14aa28f017837fb94e4da1d9862e30b02f8
Merge: a4d4d3d ec7e815
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Feb 10 01:43:57 2015 -0800

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


commit a4d4d3df88b33a6db30b565921f6d62efb252351
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Feb 10 01:38:56 2015 -0800

    multiple payment options, RT#23741

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 2b959e6..4497916 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -2249,7 +2249,7 @@ and customer address. Include units.',
     'section'     => 'self-service',
     'description' => 'Acceptable payment types for the signup server',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL BILL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL ) ], # BILL COMP) ],
   },
 
   {
@@ -2652,13 +2652,13 @@ and customer address. Include units.',
     'section'     => 'billing',
     'description' => 'Available payment types.',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD MCHK PPAL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK CASH WEST MCRD MCHK PPAL) ],
   },
 
   {
     'key'         => 'payby-default',
-    'section'     => 'UI',
-    'description' => 'Default payment type.  HIDE disables display of billing information and sets customers to BILL.',
+    'section'     => 'deprecated',
+    'description' => 'Deprecated; in 4.x there is no longer the concept of a single "payment type".  Used to indicate the default payment type.  HIDE disables display of billing information and sets customers to BILL.',
     'type'        => 'select',
     'select_enum' => [ '', qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD PPAL COMP HIDE) ],
   },
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 133b6d8..54a4680 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1651,6 +1651,9 @@ sub tables_hashref {
         'bill_locationnum', 'int', 'NULL', '', '', '',
         'ship_locationnum', 'int', 'NULL', '', '', '',
         'taxstatusnum',   'char', 'NULL',      32, '', '',
+        'complimentary', 'char', 'NULL', 1, '', '',
+        'po_number', 'varchar', 'NULL', $char_d, '', '',
+        'invoice_attn', 'varchar', 'NULL', $char_d, '', '',
       ],
       'primary_key'  => 'custnum',
       'unique'       => [ [ 'agentnum', 'agent_custid' ] ],
@@ -1699,7 +1702,7 @@ sub tables_hashref {
       'columns' => [
         'custpaybynum', 'serial',     '',        '', '', '', 
         'custnum',         'int',     '',        '', '', '',
-        'weight',          'int',     '',        '', '', '', 
+        'weight',          'int', 'NULL',        '', '', '', 
         'payby',          'char',     '',         4, '', '', 
         'payinfo',     'varchar', 'NULL',       512, '', '', 
         'cardtype',    'varchar', 'NULL',   $char_d, '', '',
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 95d001e..fe484a4 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -316,9 +316,6 @@ sub print_generic {
     unless $format =~ /^(latex|html|template)$/;
 
   my $cust_main = $self->cust_main || $self->prospect_main;
-  $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
-    unless $cust_main->payname
-        && $cust_main->payby !~ /^(CARD|DCRD|CHEK|DCHK)$/;
 
   my $locale = $params{'locale'} || $cust_main->locale;
 
@@ -565,9 +562,11 @@ sub print_generic {
     'custnum'         => $cust_main->display_custnum,
     'prospectnum'     => $cust_main->prospectnum,
     'agent_custid'    => &$escape_function($cust_main->agent_custid),
-    ( map { $_ => &$escape_function($cust_main->$_()) } qw(
-      payname company address1 address2 city state zip fax
-    )),
+    ( map { $_ => &$escape_function($cust_main->$_()) }
+        qw( company address1 address2 city state zip fax )
+    ),
+    'payname'         => &$escape_function( $cust_main->invoice_attn
+                                             || $cust_main->contact_firstlast ),
 
     #global config
     'ship_enable'     => $conf->exists('invoice-ship_address'),
@@ -655,10 +654,10 @@ sub print_generic {
   my @address = ();
   $invoice_data{'address'} = \@address;
   push @address,
-    $cust_main->payname.
-      ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
-        ? " (P.O. #". $cust_main->payinfo. ")"
-        : ''
+    $invoice_data{'payname'}.
+      ( $cust_main->po_number
+          ? " (P.O. #". $cust_main->po_number. ")"
+          : ''
       )
   ;
   push @address, $cust_main->company
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index d05b309..263be80 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -309,7 +309,10 @@ sub upgrade_data {
 
   tie my %hash, 'Tie::IxHash', 
 
-    #cust_main (remove paycvv from history)
+    #payby conditions to new ones
+    'part_event_condition' => [],
+
+    #cust_main (remove paycvv from history, locations, cust_payby, etc)
     'cust_main' => [],
 
     #contact -> cust_contact / prospect_contact
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index cd675f9..d38f3d0 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -76,6 +76,7 @@ use FS::Locales;
 use FS::upgrade_journal;
 use FS::sales;
 use FS::cust_payby;
+use FS::contact;
 
 # 1 is mostly method/subroutine entry and options
 # 2 traces progress of some operations
@@ -95,8 +96,6 @@ our $ucfirst_nowarn = 0;
 our @encrypted_fields = ('payinfo', 'paycvv');
 sub nohistory_fields { ('payinfo', 'paycvv'); }
 
-our @paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
-
 our $conf;
 #ask FS::UID to run this stuff for us later
 #$FS::UID::callback{'FS::cust_main'} = sub { 
@@ -331,7 +330,7 @@ invoicing_list destination to the newly-created svc_acct.  Here's an example:
   $cust_main->insert( {}, [ $email, 'POST' ] );
 
 Currently available options are: I<depend_jobnum>, I<noexport>,
-I<tax_exemption> and I<prospectnum>.
+I<tax_exemption>, I<prospectnum>, I<contact> and I<contact_params>.
 
 If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
 on the supplied jobnum (they will not run until the specific job completes).
@@ -351,6 +350,14 @@ If I<prospectnum> is set, moves contacts and locations from that prospect.
 If I<contact> is set to an arrayref of FS::contact objects, inserts those
 new contacts with this new customer.
 
+If I<contact_params> is set to a hashref of CGI parameters (and I<contact> is
+unset), inserts those new contacts with this new customer.  Handles CGI
+paramaters for an "m2" multiple entry field as passed by edit/cust_main.cgi
+
+If I<cust_payby_params> is set to a hashref o fCGI parameters, inserts those
+new stored payment records with this new customer.  Handles CGI parameters
+for an "m2" multiple entry field as passed by edit/cust_main.cgi
+
 =cut
 
 sub insert {
@@ -378,7 +385,7 @@ sub insert {
   my $payby = '';
   if ( $self->payby eq 'PREPAY' ) {
 
-    $self->payby('BILL');
+    $self->payby(''); #'BILL');
     $prepay_identifier = $self->payinfo;
     $self->payinfo('');
 
@@ -403,7 +410,7 @@ sub insert {
   } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|MCHK|PPAL)$/ ) {
 
     $payby = $1;
-    $self->payby('BILL');
+    $self->payby(''); #'BILL');
     $amount = $self->paid;
 
   }
@@ -557,8 +564,10 @@ sub insert {
 
   }
 
-  my $contact = delete $options{'contact'};
-  if ( $contact ) {
+  warn "  setting contacts\n"
+    if $DEBUG > 1;
+
+  if ( my $contact = delete $options{'contact'} ) {
 
     foreach my $c ( @$contact ) {
       $c->custnum($self->custnum);
@@ -570,6 +579,34 @@ sub insert {
 
     }
 
+  } elsif ( my $contact_params = delete $options{'contact_params'} ) {
+
+    my $error = $self->process_o2m( 'table'  => 'contact',
+                                    'fields' => FS::contact->cgi_contact_fields,
+                                    'params' => $contact_params,
+                                  );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  warn "  setting cust_payby\n"
+    if $DEBUG > 1;
+
+  if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) {
+
+    my $error = $self->process_o2m(
+      'table'         => 'cust_payby',
+      'fields'        => FS::cust_payby->cgi_cust_payby_fields,
+      'params'        => $cust_payby_params,
+      'hash_callback' => \&FS::cust_payby::cgi_hash_callback,
+    );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+
   }
 
   warn "  setting cust_main_exemption\n"
@@ -1122,10 +1159,9 @@ sub delete {
 
   #cust_tax_adjustment in financials?
   #cust_pay_pending?  ouch
-  #cust_recon?
   foreach my $table (qw(
     cust_main_invoice cust_main_exemption cust_tag cust_attachment contact
-    cust_location cust_main_note cust_tax_adjustment
+    cust_payby cust_location cust_main_note cust_tax_adjustment
     cust_pay_void cust_pay_batch queue cust_tax_exempt
   )) {
     foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) {
@@ -1250,13 +1286,10 @@ sub replace {
     if $DEBUG;
 
   my $curuser = $FS::CurrentUser::CurrentUser;
-  if (    $self->payby eq 'COMP'
-       && $self->payby ne $old->payby
-       && ! $curuser->access_right('Complimentary customer')
-     )
-  {
-    return "You are not permitted to create complimentary accounts.";
-  }
+  return "You are not permitted to create complimentary accounts."
+    if $self->complimentary eq 'Y'
+    && $self->complimentary ne $old->complimentary
+    && ! $curuser->access_right('Complimentary customer');
 
   local($ignore_expired_card) = 1
     if $old->payby  =~ /^(CARD|DCRD)$/
@@ -1285,8 +1318,8 @@ sub replace {
   my $dbh = dbh;
 
   for my $l (qw(bill_location ship_location)) {
-    my $old_loc = $old->$l;
-    my $new_loc = $self->$l;
+    #my $old_loc = $old->$l;
+    my $new_loc = $self->$l or next;
 
     # find the existing location if there is one
     $new_loc->set('custnum' => $self->custnum);
@@ -1403,21 +1436,19 @@ sub replace {
 
   }
 
-  if ( $self->payby =~ /^(CARD|CHEK|LECB)$/
-       && ( ( $self->get('payinfo') ne $old->get('payinfo')
-              && $self->get('payinfo') !~ /^99\d{14}$/ 
-            )
-            || grep { $self->get($_) ne $old->get($_) } qw(paydate payname)
-          )
-     )
-  {
+  if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) {
 
-    # card/check/lec info has changed, want to retry realtime_ invoice events
-    my $error = $self->retry_realtime;
+    my $error = $self->process_o2m(
+      'table'         => 'cust_payby',
+      'fields'        => FS::cust_payby->cgi_cust_payby_fields,
+      'params'        => $cust_payby_params,
+      'hash_callback' => \&FS::cust_payby::cgi_hash_callback,
+    );
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
       return $error;
     }
+
   }
 
   unless ( $import || $skip_fuzzyfiles ) {
@@ -1555,6 +1586,8 @@ sub check {
     || $self->ut_flag('message_noemail')
     || $self->ut_enum('locale', [ '', FS::Locales->locales ])
     || $self->ut_currencyn('currency')
+    || $self->ut_alphan('po_number')
+    || $self->ut_enum('complimentary', [ '', 'Y' ])
   ;
 
   foreach (qw(company ship_company)) {
@@ -1809,6 +1842,11 @@ sub check {
 
   }
 
+  return "You are not permitted to create complimentary accounts."
+    if ! $self->custnum
+    && $self->complimentary eq 'Y'
+    && ! $FS::CurrentUser->CurrentUser->access_right('Complimentary customer');
+
   if ( $self->paydate eq '' || $self->paydate eq '-' ) {
     return "Expiration date required"
       # shouldn't payinfo_check do this?
@@ -1947,7 +1985,7 @@ sub cust_payby {
   qsearch({
     'table'    => 'cust_payby',
     'hashref'  => { 'custnum' => $self->custnum },
-    'order_by' => 'ORDER BY weight ASC',
+    'order_by' => "ORDER BY payby IN ('CARD','CHEK') DESC, weight ASC",
   });
 }
 
@@ -4816,18 +4854,31 @@ sub _upgrade_data { #class method
 
     while (my $cust_main = $search->fetch) {
 
-      my $cust_payby = new FS::cust_payby {
-        'custnum' => $cust_main->custnum,
-        'weight'  => 1,
-        map { $_ => $cust_main->$_(); } @payfields
-      };
+      unless ( $cust_main->payby =~ /^(BILL|COMP)$/ ) {
 
-      my $error = $cust_payby->insert;
-      die $error if $error;
+        my $cust_payby = new FS::cust_payby {
+          'custnum' => $cust_main->custnum,
+          'weight'  => 1,
+          map { $_ => $cust_main->$_(); } @payfields
+        };
+
+        my $error = $cust_payby->insert;
+        die $error if $error;
+
+      }
+
+      $cust_main->complimentary('Y') if $cust_main->payby eq 'COMP';
+
+      $cust_main->invoice_attn( $cust_main->payname )
+        if $cust_main->payby eq 'BILL' && $cust_main->payname;
+      $cust_main->po_number( $cust_main->payinfo )
+        if $cust_main->payby eq 'BILL' && $cust_main->payinfo;
 
       $cust_main->setfield($_, '') foreach @payfields;
-      $error = $cust_main->replace;
-      die $error if $error;
+      my $error = $cust_main->replace;
+      die "Error upgradging payment information for custnum ".
+          $cust_main->custnum. ": $error"
+        if $error;
 
     };
 
diff --git a/FS/FS/cust_main/Location.pm b/FS/FS/cust_main/Location.pm
index be375dd..3cb73ff 100644
--- a/FS/FS/cust_main/Location.pm
+++ b/FS/FS/cust_main/Location.pm
@@ -74,9 +74,11 @@ sub bill_location {
   $self->hashref->{bill_location} 
     ||= FS::cust_location->by_key($self->bill_locationnum)
     # degraded mode--let the system keep running during upgrades
-    ||  FS::cust_location->new({
-        map { $_ => $self->get($_) } @location_fields
-      })
+    ||  ( $self->get('address1')
+            && FS::cust_location->new({
+                 map { $_ => $self->get($_) } @location_fields
+               })
+        );
 }
 
 =item ship_location
@@ -89,9 +91,17 @@ sub ship_location {
   my $self = shift;
   $self->hashref->{ship_location}
     ||= FS::cust_location->by_key($self->ship_locationnum)
-    ||  FS::cust_location->new({
-        map { $_ => $self->get('ship_'.$_) || $self->get($_) } @location_fields
-      })
+    # degraded mode--let the system keep running during upgrades
+    ||  ( $self->get('ship_address1')
+            ? FS::cust_location->new({
+                map { $_ => $self->get('ship_'.$_) } @location_fields
+              })
+            : $self->get('address1')
+                ? FS::cust_location->new({
+                    map { $_ => $self->get($_) } @location_fields
+                  })
+                : ''
+        );
 
 }
 
diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm
index f0a7d41..097f2fb 100644
--- a/FS/FS/cust_main/Search.pm
+++ b/FS/FS/cust_main/Search.pm
@@ -608,14 +608,6 @@ listref of start date, end date
 
 listref of start date, end date
 
-=item payby
-
-listref
-
-=item paydate_year
-
-=item paydate_month
-
 =item current_balance
 
 listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance'))
@@ -646,7 +638,6 @@ sub search {
     'status'        => '',
     'address'       => '',
     'zip'           => '',
-    'paydate_year'  => '',
     'invoice_terms' => '',
     'custbatch'     => '',
     %$params
@@ -911,40 +902,6 @@ sub search {
   }
 
   ###
-  # payby
-  ###
-
-  if ( $params->{'payby'} ) {
-
-    my @payby = ref( $params->{'payby'} )
-                  ? @{ $params->{'payby'} }
-                  :  ( $params->{'payby'} );
-
-    @payby = grep /^([A-Z]{4})$/, @payby;
-    my $in_payby = 'IN(' . join(',', map {"'$_'"} @payby) . ')';
-    push @where, "EXISTS( SELECT 1 FROM cust_payby WHERE payby $in_payby ".
-                 "AND cust_payby.custnum = cust_main.custnum)"
-      if @payby;
-  }
-
-  ###
-  # paydate_year / paydate_month
-  ###
-
-  if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) {
-    my $year = $1;
-    $params->{'paydate_month'} =~ /^(\d\d?)$/
-      or die "paydate_year without paydate_month?";
-    my $month = $1;
-
-    push @where,
-      'paydate IS NOT NULL',
-      "paydate != ''",
-      "CAST(paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )"
-;
-  }
-
-  ###
   # invoice terms
   ###
 
@@ -1134,7 +1091,6 @@ sub search {
     'extra_headers' => \@extra_headers,
     'extra_fields'  => \@extra_fields,
   };
-  #warn Data::Dumper::Dumper($sql_query);
   $sql_query;
 
 }
diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
index ad3d80a..a65a171 100644
--- a/FS/FS/cust_payby.pm
+++ b/FS/FS/cust_payby.pm
@@ -1,26 +1,23 @@
 package FS::cust_payby;
+use base qw( FS::payinfo_Mixin FS::cust_main_Mixin FS::Record );
 
 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 Business::CreditCard qw( validate cardtype );
+use FS::UID qw( dbh );
 use FS::Msgcat qw( gettext );
+use FS::Record; #qw( qsearch qsearchs );
+use FS::payby;
+use FS::cust_main;
+use FS::banned_pay;
 
-use vars qw( $conf @encrypted_fields
-             $ignore_expired_card $ignore_banned_card
-             $ignore_invalid_card
-           );
-
- at encrypted_fields = ('payinfo', 'paycvv');
+our @encrypted_fields = ('payinfo', 'paycvv');
 sub nohistory_fields { ('payinfo', 'paycvv'); }
 
-$ignore_expired_card = 0;
-$ignore_banned_card = 0;
-$ignore_invalid_card = 0;
+our $ignore_expired_card = 0;
+our $ignore_banned_card = 0;
+our $ignore_invalid_card = 0;
 
+our $conf;
 install_callback FS::UID sub { 
   $conf = new FS::Conf;
   #yes, need it for stuff below (prolly should be cached)
@@ -141,15 +138,44 @@ otherwise returns false.
 
 =cut
 
-# the insert method can be inherited from FS::Record
+sub insert {
+  my $self = shift;
 
-=item delete
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
 
-Delete this record from the database.
+  if ( $self->payby =~ /^(CARD|CHEK)$/ ) {
+    # new auto card/check info, want to retry realtime_ invoice events
+    #  (new customer?  that's okay, they won't have any)
+    my $error = $self->cust_main->retry_realtime;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
 
-=cut
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
 
-# the delete method can be inherited from FS::Record
+}
+
+=item delete
+
+Delete this record from the database.
 
 =item replace OLD_RECORD
 
@@ -158,7 +184,87 @@ returns the error, otherwise returns false.
 
 =cut
 
-# the replace method can be inherited from FS::Record
+sub replace {
+  my $self = shift;
+
+  my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+              ? shift
+              : $self->replace_old;
+
+  if ( length($old->paycvv) && $self->paycvv =~ /^\s*[\*x]*\s*$/ ) {
+    $self->paycvv($old->paycvv);
+  }
+
+  if ( $self->payby =~ /^(CARD|DCRD)$/
+       && (    $self->payinfo =~ /xx/
+            || $self->payinfo =~ /^\s*N\/A\s+\(tokenized\)\s*$/
+          )
+     )
+  {
+warn $self->payinfo;
+warn $old->payinfo;
+    $self->payinfo($old->payinfo);
+
+  } elsif ( $self->payby =~ /^(CHEK|DCHK)$/ && $self->payinfo =~ /xx/ ) {
+    #fix for #3085 "edit of customer's routing code only surprisingly causes
+    #nothing to happen...
+    # this probably won't do the right thing when we don't have the
+    # public key (can't actually get the real $old->payinfo)
+    my($new_account, $new_aba) = split('@', $self->payinfo);
+    my($old_account, $old_aba) = split('@', $old->payinfo);
+    $new_account = $old_account if $new_account =~ /xx/;
+    $new_aba     = $old_aba     if $new_aba     =~ /xx/;
+    $self->payinfo($new_account.'@'.$new_aba);
+  }
+
+  local($ignore_expired_card) = 1
+    if $old->payby  =~ /^(CARD|DCRD)$/
+    && $self->payby =~ /^(CARD|DCRD)$/
+    && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+  local($ignore_banned_card) = 1
+    if (    $old->payby  =~ /^(CARD|DCRD)$/ && $self->payby =~ /^(CARD|DCRD)$/
+         || $old->payby  =~ /^(CHEK|DCHK)$/ && $self->payby =~ /^(CHEK|DCHK)$/ )
+    && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::replace($old);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $self->payby =~ /^(CARD|CHEK)$/
+       && ( ( $self->get('payinfo') ne $old->get('payinfo')
+              && $self->get('payinfo') !~ /^99\d{14}$/ 
+            )
+            || grep { $self->get($_) ne $old->get($_) } qw(paydate payname)
+          )
+     )
+  {
+
+    # card/check/lec info has changed, want to retry realtime_ invoice events
+    my $error = $self->cust_main->retry_realtime;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
 
 =item check
 
@@ -174,7 +280,7 @@ sub check {
   my $error = 
     $self->ut_numbern('custpaybynum')
     || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
-    || $self->ut_number('weight')
+    || $self->ut_numbern('weight')
     #encrypted #|| $self->ut_textn('payinfo')
     #encrypted #|| $self->ut_textn('paycvv')
 #    || $self->ut_textn('paymask') #XXX something
@@ -207,7 +313,7 @@ sub check {
     my $payinfo = $self->payinfo;
     $payinfo =~ s/\D//g;
     $payinfo =~ /^(\d{13,16}|\d{8,9})$/
-      or return gettext('invalid_card'); # . ": ". $self->payinfo;
+      or return gettext('invalid_card'); #. ": ". $self->payinfo;
     $payinfo = $1;
     $self->payinfo($payinfo);
     validate($payinfo)
@@ -308,52 +414,23 @@ sub check {
       }
     }
 
-  } 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('');
+#  } 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|MCHK|PPAL)$/;
+      unless $self->payby =~ /^(CHEK|DCHK)$/;
     $self->paydate('');
   } else {
     my( $m, $y );
@@ -400,6 +477,247 @@ sub check {
   $self->SUPER::check;
 }
 
+sub _banned_pay_hashref {
+  my $self = shift;
+
+  my %payby2ban = (
+    'CARD' => 'CARD',
+    'DCRD' => 'CARD',
+    'CHEK' => 'CHEK',
+    'DCHK' => 'CHEK'
+  );
+
+  {
+    'payby'   => $payby2ban{$self->payby},
+    'payinfo' => $self->payinfo,
+    #don't ever *search* on reason! #'reason'  =>
+  };
+}
+
+=item paydate_mon_year
+
+Returns a two element list consisting of the paydate month and year.
+
+=cut
+
+sub paydate_mon_year {
+  my $self = shift;
+
+  my $date = $self->paydate; # || '12-2037';
+
+  #false laziness w/elements/select-month_year.html
+  if ( $date  =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+    ( $2, $1 );
+  } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+    ( $1, $3 );
+  } else {
+    warn "unrecognized expiration date format: $date";
+    ( '', '' );
+  }
+
+}
+
+=item realtime_bop
+
+=cut
+
+sub realtime_bop {
+  my( $self, %opt ) = @_;
+
+  $opt{$_} = $self->$_() for qw( payinfo payname paydate );
+
+  if ( $self->locationnum ) {
+    my $cust_location = $self->cust_location;
+    $opt{$_} = $cust_location->$_() for qw( address1 address2 city state zip );
+  }
+
+  $self->cust_main->realtime_bop({
+    'method' => FS::payby->payby2bop( $self->payby ),
+    %opt,
+  });
+
+}
+
+=item paytypes
+
+Returns a list of valid values for the paytype field (bank account type for
+electronic check payment).
+
+=cut
+
+sub paytypes {
+  #my $class = shift;
+
+  ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
+}
+
+=item cgi_cust_payby_fields
+
+Returns the field names used in the web interface (including some pseudo-fields).
+
+=cut
+
+sub cgi_cust_payby_fields {
+  #my $class = shift;
+  [qw( payby payinfo paydate_month paydate_year paycvv payname weight
+       payinfo1 payinfo2 payinfo3 paytype paystate )];
+}
+
+=item cgi_hash_callback HASHREF
+
+Subroutine (not a class or object method).  Processes a hash reference
+of web interface contet (transfers the data from pseudo-fields to real fields).
+
+=cut
+
+sub cgi_hash_callback {
+  my $hashref = shift;
+
+  my %noauto = (
+    'CARD' => 'DCRD',
+    'CHEK' => 'DCHK',
+  );
+  $hashref->{payby} = $noauto{$hashref->{payby}}
+    if ! $hashref->{weight} && exists $noauto{$hashref->{payby}};
+
+  if ( $hashref->{payby} =~ /^(CHEK|DCHK)$/ ) {
+
+    unless ( grep $hashref->{$_}, qw( payinfo1 payinfo2 payinfo3 payname ) ) {
+      %$hashref = ();
+      return;
+    }
+
+    $hashref->{payinfo} = $hashref->{payinfo1}. '@';
+    $hashref->{payinfo} .= $hashref->{payinfo3}.'.' 
+      if $conf->config('echeck-country') eq 'CA';
+    $hashref->{payinfo} .= $hashref->{'payinfo2'};
+
+    $hashref->{payname} .= $hashref->{'payname_CHEK'};
+
+  } elsif ( $hashref->{payby} =~ /^(CARD|DCRD)$/ ) {
+
+    unless ( grep $hashref->{$_}, qw( payinfo paycvv payname ) ) {
+      %$hashref = ();
+      return;
+    }
+
+  }
+
+  $hashref->{paydate}= $hashref->{paydate_month}. '-'. $hashref->{paydate_year};
+
+}
+
+=item search_sql
+
+Class method.
+
+Returns a qsearch hash expression to search for parameters specified in HASHREF.
+Valid paramters are:
+
+=over 4
+
+=item payby
+
+listref
+
+=item paydate_year
+
+=item paydate_month
+
+
+=back
+
+=cut
+
+sub search_sql {
+  my ($class, $params) = @_;
+
+  my @where = ();
+  my $orderby;
+
+  # initialize these to prevent warnings
+  $params = {
+    'paydate_year'  => '',
+    %$params
+  };
+
+  ###
+  # payby
+  ###
+
+  if ( $params->{'payby'} ) {
+
+    my @payby = ref( $params->{'payby'} )
+                  ? @{ $params->{'payby'} }
+                  :  ( $params->{'payby'} );
+
+    @payby = grep /^([A-Z]{4})$/, @payby;
+    my $in_payby = 'IN(' . join(',', map {"'$_'"} @payby) . ')';
+    push @where, "cust_payby.payby $in_payby"
+      if @payby;
+  }
+
+  ###
+  # paydate_year / paydate_month
+  ###
+
+  if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) {
+    my $year = $1;
+    $params->{'paydate_month'} =~ /^(\d\d?)$/
+      or die "paydate_year without paydate_month?";
+    my $month = $1;
+
+    push @where,
+      'cust_payby.paydate IS NOT NULL',
+      "cust_payby.paydate != ''",
+      "CAST(cust_payby.paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )"
+;
+  }
+  ##
+  # setup queries, subs, etc. for the search
+  ##
+
+  $orderby ||= 'ORDER BY custnum';
+
+  # here is the agent virtualization
+  push @where,
+    $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main');
+
+  my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+  my $addl_from = ' LEFT JOIN cust_main USING ( custnum ) ';
+  # always make address fields available in results
+  for my $pre ('bill_', 'ship_') {
+    $addl_from .= 
+      ' LEFT JOIN cust_location AS '.$pre.'location '.
+      'ON (cust_main.'.$pre.'locationnum = '.$pre.'location.locationnum) ';
+  }
+
+  my $count_query = "SELECT COUNT(*) FROM cust_payby $addl_from $extra_sql";
+
+  my @select = ( 'cust_payby.*',
+                 #'cust_main.custnum',
+                 # there's a good chance that we'll need these
+                 'cust_main.bill_locationnum',
+                 'cust_main.ship_locationnum',
+                 FS::UI::Web::cust_sql_fields($params->{'cust_fields'}),
+               );
+
+  my $select = join(', ', @select);
+
+  my $sql_query = {
+    'table'         => 'cust_payby',
+    'select'        => $select,
+    'addl_from'     => $addl_from,
+    'hashref'       => {},
+    'extra_sql'     => $extra_sql,
+    'order_by'      => $orderby,
+    'count_query'   => $count_query,
+  };
+  $sql_query;
+
+}
+
 =back
 
 =head1 BUGS
diff --git a/FS/FS/o2m_Common.pm b/FS/FS/o2m_Common.pm
index d237bef..4f6d2e7 100644
--- a/FS/FS/o2m_Common.pm
+++ b/FS/FS/o2m_Common.pm
@@ -103,6 +103,7 @@ sub process_o2m {
                  map { $_ => $opt{'params'}->{$add_param."_$_"} }
                      @{ $opt{'fields'} }
                );
+    &{ $opt{'hash_callback'} }( \%hash ) if $opt{'hash_callback'};
     #next unless grep { $_ =~ /\S/ } values %hash;
 
     my $new_obj = "FS::$table"->new( { %$hashref, %hash } );
@@ -117,6 +118,7 @@ sub process_o2m {
 
     my %hash = map { $_ => $opt{'params'}->{$add_param."_$_"} }
                @{ $opt{'fields'} };
+    &{ $opt{'hash_callback'} }( \%hash ) if $opt{'hash_callback'};
     next unless grep { $_ =~ /\S/ } values %hash;
 
     my $add_obj = "FS::$table"->new( { %$hashref, %hash } );
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
deleted file mode 100644
index cd03ddc..0000000
--- a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
+++ /dev/null
@@ -1,28 +0,0 @@
-package FS::part_event::Action::cust_bill_realtime_lec;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
-  #'Run phone bill ("LEC") billing with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
-  'Run phone bill ("LEC") billing with a Business::OnlinePayment realtime gateway';
-}
-
-sub deprecated { 1; }
-
-sub eventtable_hashref {
-  { 'cust_bill' => 1 };
-}
-
-sub default_weight { 30; }
-
-sub do_action {
-  my( $self, $cust_bill ) = @_;
-
-  #my $cust_main = $self->cust_main($cust_bill);
-  my $cust_main = $cust_bill->cust_main;
-
-  $cust_bill->realtime_lec;
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/has_cust_payby_auto.pm b/FS/FS/part_event/Condition/has_cust_payby_auto.pm
new file mode 100644
index 0000000..edfb7ec
--- /dev/null
+++ b/FS/FS/part_event/Condition/has_cust_payby_auto.pm
@@ -0,0 +1,43 @@
+package FS::part_event::Condition::has_cust_payby_auto;
+
+use strict;
+use Tie::IxHash;
+use FS::payby;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+  'Customer has automatic payment information';
+}
+
+tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2shortname;
+delete $payby{'DCRD'};
+delete $payby{'DCHK'};
+
+sub option_fields {
+  (
+    'payby' => { 
+                 label         => 'Has automatic payment info',
+                 type          => 'select',
+                 options       => [ keys %payby ],
+                 option_labels => \%payby,
+               },
+  );
+}
+
+sub condition {
+  my( $self, $object ) = @_;
+
+  my $cust_main = $self->cust_main($object);
+
+  scalar( qsearch({ 
+    'table'     => 'cust_payby',
+    'hashref'   => { 'custnum' => $cust_main->custnum,
+                     'payby'   => $self->option('payby')
+                   },
+    'order_by'  => 'LIMIT 1',
+  }) );
+
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm b/FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm
new file mode 100644
index 0000000..6655a63
--- /dev/null
+++ b/FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm
@@ -0,0 +1,27 @@
+package FS::part_event::Condition::hasnt_cust_payby_auto;
+
+use strict;
+use Tie::IxHash;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+  'Customer does not have automatic payment information';
+}
+
+sub condition {
+  my( $self, $object ) = @_;
+
+  my $cust_main = $self->cust_main($object);
+
+  ! scalar( qsearch({ 
+    'table'     => 'cust_payby',
+    'hashref'   => { 'custnum' => $cust_main->custnum,
+                   },
+    'extra_sql' => "AND payby IN ( 'CARD', 'CHEK' )",
+    'order_by'  => 'LIMIT 1',
+  }) );
+
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/payby.pm b/FS/FS/part_event/Condition/payby.pm
deleted file mode 100644
index 16bf480..0000000
--- a/FS/FS/part_event/Condition/payby.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-package FS::part_event::Condition::payby;
-
-use strict;
-use Tie::IxHash;
-use FS::payby;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
-  #'customer payment types: ';
-  'Customer payment type';
-}
-
-#something like this
-tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
-sub option_fields {
-  (
-    'payby' => { 
-                 label         => 'Customer payment type',
-                 #type          => 'select-multiple',
-                 type          => 'checkbox-multiple',
-                 options       => [ keys %payby ],
-                 option_labels => \%payby,
-               },
-  );
-}
-
-sub condition {
-  my( $self, $object ) = @_;
-
-  my $cust_main = $self->cust_main($object);
-
-  my $hashref = $self->option('payby') || {};
-  $hashref->{ $cust_main->payby };
-
-}
-
-sub condition_sql {
-  my( $self, $table ) = @_;
-
-  'cust_main.payby IN '. $self->condition_sql_option_option('payby');
-}
-
-1;
diff --git a/FS/FS/part_event_condition.pm b/FS/FS/part_event_condition.pm
index ac2ee82..200049d 100644
--- a/FS/FS/part_event_condition.pm
+++ b/FS/FS/part_event_condition.pm
@@ -354,6 +354,45 @@ sub order_conditions_sql {
 
 }
 
+sub _upgrade_data { #class method
+  my ($class, %opts) = @_;
+
+  foreach my $part_event_condition (
+    qsearch('part_event_condition', { 'conditionname' => 'payby' } )
+  ) {
+
+    my $payby = $part_event_condition->option('payby');
+
+    if ( scalar( keys %$payby ) == 1 ) {
+
+      if ( $payby->{'CARD'} ) {
+
+        $part_event_condition->conditionname('has_cust_payby_auto');
+
+      } elsif ( $payby->{'CHEK'} ) {
+
+        $part_event_condition->conditionname('has_cust_payby_auto');
+
+      }
+
+    } elsif ( $payby->{'BILL'} && ! $payby->{'CARD'} && ! $payby->{'CHEK'} ) {
+
+      $part_event_condition->conditionname('hasnt_cust_payby_auto');
+
+    } else {
+
+      die 'Unable to automatically convert payby condition for event #'.
+          $part_event_condition->eventpart. "\n";
+
+    }
+
+    my $error = $part_event_condition->replace;
+    die $error if $error;
+
+  }
+
+}
+
 =back
 
 =head1 BUGS
diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm
index c4aa1b1..13423c4 100644
--- a/FS/FS/payby.pm
+++ b/FS/FS/payby.pm
@@ -5,7 +5,6 @@ use vars qw(%hash %payby2bop);
 use Tie::IxHash;
 use Business::CreditCard;
 
-
 =head1 NAME
 
 FS::payby - Object methods for payment type records
@@ -39,9 +38,8 @@ Payment types.
 =cut
 
 # paybys can be any/all of:
-# - a customer payment type (cust_main.payby)
+# - a customer saved payment type (cust_payby.payby)
 # - a payment or refund type (cust_pay.payby, cust_pay_batch.payby, cust_refund.payby)
-# - an event type (part_bill_event.payby)
 
 tie %hash, 'Tie::IxHash',
   'CARD' => {
@@ -70,18 +68,6 @@ tie %hash, 'Tie::IxHash',
     cust_pay  => 'CHEK', #this is a customer type only, payments are CHEK...
     realtime  => 1,
   },
-  #'LECB' => {
-  #  tinyname  => 'phone bill',
-  #  shortname => 'Phone bill billing',
-  #  longname  => 'Phone bill billing',
-  #  realtime  => 1,
-  #},
-  'BILL' => {
-    tinyname  => 'billing',
-    shortname => 'Billing',
-    payname   => 'Check',
-    longname  => 'Billing',
-  },
   'PPAL' => {
     tinyname  => 'PayPal',
     shortname => 'PayPal',
@@ -143,12 +129,6 @@ tie %hash, 'Tie::IxHash',
     longname  => 'Wire transfer',
     cust_main => '', #not a customer type
   },
-  'COMP' => {
-    tinyname  => 'comp',
-    shortname => 'Complimentary',
-    longname  => 'Complimentary',
-    cust_pay  => '', # (free) is depricated as a payment type in cust_pay
-  },
   'CBAK' => {
     tinyname  => 'chargeback',
     shortname => 'Chargeback',
@@ -234,6 +214,11 @@ sub cust_payby {
   grep { ! exists $hash{$_}->{cust_main} } $self->payby;
 }
 
+sub cust_payby2shortname {
+  my $self = shift;
+  map { $_ => $hash{$_}->{shortname} } $self->cust_payby;
+}
+
 sub cust_payby2longname {
   my $self = shift;
   map { $_ => $hash{$_}->{longname} } $self->cust_payby;
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index da87bfc..f99cce2 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -1,8 +1,4 @@
-<& /elements/header.html,
-      $title,
-      '',
-      ' onUnload="myclose()"' #hmm, in billing.html
-&>
+<& /elements/header.html, $title, &>
 
 <& /elements/error.html &>
 
@@ -60,7 +56,7 @@
     </TABLE>
   </TD>
 </TR>
-<TR><TD STYLE="height:40px"></TD></TR>
+<TR><TD STYLE="height:14px"></TD></TR>
 <TR>
   <TD STYLE="width:650px">
     <FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT>
@@ -105,16 +101,13 @@ function samechanged(what) {
 
 <BR>
 
-<& cust_main/contacts_new.html,
-             'cust_main' => $cust_main,
-&>
+<& cust_main/contacts_new.html, 'cust_main'=>$cust_main, &>
 
 %# billing info
 <& cust_main/billing.html, $cust_main,
                'payinfo'        => $payinfo,
                'invoicing_list' => \@invoicing_list,
 &>
-<BR>
 
 % my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
 % if (!$ro_comments || $cust_main->comments) {
@@ -156,18 +149,6 @@ function samechanged(what) {
 
 <INPUT TYPE="hidden" NAME="usernum" VALUE="<% $cust_main->usernum %>">
 
-%# cust_main/bottomfixup.js
-% foreach my $hidden (
-%    'payauto', 'billday',
-%    'payinfo', 'payinfo1', 'payinfo2', 'payinfo3', 'paytype',
-%    'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
-%    'paystart_month', 'paystart_year', 'payissue',
-%    'payip',
-%    'paid',
-% ) {
-    <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE="">
-% } 
-
 <& cust_main/bottomfixup.html, 'custnum' => $custnum &>
 
 <BR>
@@ -375,9 +356,6 @@ if ( $cgi->param('error') ) {
 
 }
 
-my %keep = map { $_=>1 } qw( error tagnum lock_agentnum lock_pkgpart );
-$cgi->delete( grep { !$keep{$_} && $_ !~ /^tax_/ } $cgi->param );
-
 my $title = $custnum ? 'Edit Customer' : 'Add Customer';
 $title = mt($title);
 $title .= ": ". $cust_main->name if $custnum;
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index fcd8f01..fa392bb 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -1,123 +1,7 @@
-%if ( $payby_default eq 'HIDE' ) {
-%  $cust_main->payby('BILL') unless $cust_main->payby;
-%  my $payby = $cust_main->payby;
-
-  <INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
-
-  <INPUT TYPE="hidden" NAME="<%$payby%>_payinfo" VALUE="<% $cust_main->paymask %>">
-
-% foreach my $field (qw( payname paycvv paystart_month paystart_year payissue payip paytype paystate billday )) { 
-
-    <INPUT TYPE="hidden" NAME="<% $payby.'_'.$field %>" VALUE="<% $cust_main->get($field) %>">
-
-% } 
-
-%  #false laziness w/elements/select-month_year.html & view/cust_main/billing.html
-%  my( $mon, $year );
-%  my $date = $cust_main->paydate || '12-2037';
-%  if ( $date  =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
-%    ( $mon, $year ) = ( $2, $1 );
-%  } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
-%    ( $mon, $year ) = ( $1, $3 );
-%  } else {
-%    die "unrecognized expiration date format: $date";
-%  }
-
-  <INPUT TYPE="hidden" NAME="<%$payby%>_exp_month" VALUE="<% $mon %>">
-  <INPUT TYPE="hidden" NAME="<%$payby%>_exp_year"  VALUE="<% $year %>">
-
-  <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>">
-
-  <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>">
-
-% } else {
-%
 %  my $r = qq!<font color="#ff0000">*</font> !;
 
-  <BR><FONT CLASS="fsinnerbox-title"><% mt('Billing information') |h %></FONT>
-  <% &ntable("#cccccc") %>
-
-    <TR>
-      <TD ALIGN="right" WIDTH="200"><%$r%><% mt('Billing type') |h %></TD>
-
   <SCRIPT>
 
-    var mywindow = -1;
-    function myopen(filename,windowname,properties) {
-      myclose();
-      mywindow = window.open(filename,windowname,properties);
-    }
-    function myclose() {
-      if ( mywindow != -1 )
-        mywindow.close();
-      mywindow = -1;
-    }
-
-    var achwindow = -1;
-    function achopen(filename,windowname,properties) {
-      achclose();
-      achwindow = window.open(filename,windowname,properties);
-    }
-    function achclose() {
-      if ( achwindow != -1 )
-        achwindow.close();
-      achwindow = -1;
-    }
-
-    function card_changed(what) {
-      if (
-             what.form.payinfo.value.substring(0, 4) == '4093' 
-          || what.form.payinfo.value.substring(0, 4) == '4911' 
-          || what.form.payinfo.value.substring(0, 4) == '4936' 
-          || what.form.payinfo.value.substring(0, 6) == '564132' 
-          || what.form.payinfo.value.substring(0, 2) == '63' 
-          || what.form.payinfo.value.substring(0, 2) == '67' 
-         )
-      {
-        what.form.paystart_month.disabled = false;
-        what.form.paystart_year.disabled = false;
-        what.form.payissue.disabled = false;
-        what.form.paystart_month.style.backgroundColor = '#ffffff';
-        what.form.paystart_year.style.backgroundColor = '#ffffff';
-        what.form.payissue.style.backgroundColor = '#ffffff';
-        document.getElementById('paystart_label').style.color = '#000000';
-        document.getElementById('payissue_label').style.color = '#000000';
-      } else {
-        what.form.paystart_month.disabled = true;
-        what.form.paystart_year.disabled = true;
-        what.form.payissue.disabled = true;
-        what.form.paystart_month.style.backgroundColor = '#dddddd';
-        what.form.paystart_year.style.backgroundColor = '#dddddd';
-        what.form.payissue.style.backgroundColor = '#dddddd';
-        document.getElementById('paystart_label').style.color = '#999999';
-        document.getElementById('payissue_label').style.color = '#999999';
-      }
-      return true;
-    }
-
-    function init_payauto_changed(){
-        var f = document.getElementById('CARD_payauto');
-        if(f != null) payauto_changed(f);
-        f = document.getElementById('CHEK_payauto');
-        if(f != null) payauto_changed(f);
-    }
-
-    function payauto_changed(payauto_field){
-        var select = (payauto_field.name == 'CARD_payauto') ? 'CARD_billday' : 'CHEK_billday';
-        var span = document.getElementById('td_'+select);
-        select = document.getElementById(select);
-        if (span == null || select == null) return;
-        if(payauto_field.checked) {
-            span.style.color = '#000000';
-            select.disabled = false;
-        }
-        else {
-            span.style.color = '#999999';
-            select.disabled = true;
-            //why? select.selectedIndex = 0;
-        }
-    }
-
     function tax_changed(what) {
       var num = document.getElementById(what.id + '_num'); 
       if ( what.checked ) {
@@ -129,321 +13,10 @@
     
   </SCRIPT>
 
-  <& /elements/init_overlib.html &>
-
-%  my $payby = $cust_main->payby;
-%  my $paytype = $cust_main->paytype;
-%  my( $account, $aba ) = split('@', $payinfo);
-%  my $branch = '';
-%  ($branch,$aba) = split('\.',$aba)
-%    if $conf->config('echeck-country') eq 'CA';
-%
-%  my $disabled = 'DISABLED style="background-color: #dddddd"';
-%  my $text_disabled = 'style="color: #999999"';
-%
-%  if ( $payby =~ /^(CARD|DCRD)$/ && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) {
-%    $disabled = 'style="background-color: #ffffff"';
-%    $text_disabled = 'style="color: #000000";'
-%  }
-%
-%  my $disable_payauto = $conf->exists('disable_payauto_default');
-%  my $CARD_payauto_checked =   $payby eq 'DCRD' ? ''
-%                             : $payby eq 'CARD' ? 'CHECKED'
-%                             : $disable_payauto ? '' : 'CHECKED';
-%  my $CHEK_payauto_checked =   $payby eq 'DCHK' ? ''
-%                             : $payby eq 'CHEK' ? 'CHECKED'
-%                             : $disable_payauto ? '' : 'CHECKED';
-%
-%  sub billday_options {
-%   my $curr_value = shift;
-%   my $ret = '';
-%   for my $billday ( 1 .. 28 ) {
-%       my $sel = '';
-%       $sel = "SELECTED='SELECTED'" if $curr_value == $billday;
-%       $ret .= "<OPTION VALUE='$billday' $sel>$billday</OPTION>";
-%   }
-%   $ret;
-%  }
-%
-%  my $card_billday_style = $payby eq 'CARD' ? '' : 'style="color: #999999"';
-%  my $chek_billday_style = $payby eq 'CHEK' ? '' : 'style="color: #999999"';
-%  my $card_billday_select_disabled = $payby eq 'CARD' ? '' : 'DISABLED';
-%  my $chek_billday_select_disabled = $payby eq 'CHEK' ? '' : 'DISABLED';
-%
-%  #false laziness w/view/cust_main/billing.html and misc/payment.cgi
-%  my $routing_label = $conf->config('echeck-country') eq 'US'
-%                        ? 'ABA/Routing number'
-%                        : 'Routing number';
-%  my $routing_size      = $conf->config('echeck-country') eq 'CA' ? 4 : 10;
-%  my $routing_maxlength = $conf->config('echeck-country') eq 'CA' ? 3 : 9;
-%
-%
-%  my %payby = (
-%
-%    'CARD' =>
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Card number').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Expiration').qq! </TD>!.
-%          '<TD WIDTH="408">'.
-%
-%          include('/elements/select-month_year.html',
-%                    'prefix' => 'CARD_exp',
-%                    'selected_date' =>
-%                      ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ),
-%                 ).
-%
-%          '</TD></TR>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">!.emt('CVV2').qq! !.
-%
-%          qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">!.emt('help').qq!</A>)!.
-%          qq!</TD>!.
-%          '<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ && !$cust_main->is_encrypted($cust_main->paycvv) ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>!.emt('Start date').qq! </SPAN></TD>!.
-%          '<TD WIDTH="408">'.
-%
-%          include('/elements/select-month_year.html',
-%                    'prefix' => 'CARD_paystart',
-%                    'disabled' => $disabled,
-%                    'empty_option' => 1,
-%                    'start_year' => 2000,
-%                    'end_year'   => (localtime())[5] + 1900,
-%                    'selected_date' => (
-%                      ( $payby =~ /^(CARD|DCRD)$/
-%                        && cardtype($payinfo) =~ /^(Switch|Solo)$/ )
-%                          ? $cust_main->paystart_month. '-'.
-%                            $cust_main->paystart_year 
-%                          : ''
-%                    )
-%                 ).
-%
-%        qq!<SPAN ID="payissue_label" $text_disabled>!.emt('or Issue number').qq! </SPAN>!.
-%          '<INPUT TYPE="text" NAME="CARD_payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Exact name on card').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
-%
-%        qq!<TR><TD COLSPAN=2 WIDTH="608">!.
-%           qq!<INPUT TYPE="checkbox" onchange="payauto_changed(this);" ID="CARD_payauto" NAME="CARD_payauto" $CARD_payauto_checked> !.
-%           emt('Charge future payments to this [_1] automatically','credit card').'</TD></TR>'.
-%
-%      ( $conf->exists('cust_main-select-billday') ?
-%        qq!<TR><TD ALIGN="RIGHT" id="td_CARD_billday" $card_billday_style>
-%                    Charge on this day of each month</TD><TD>   
-%                   <SELECT id="CARD_billday" $card_billday_select_disabled NAME="CARD_billday">!
-%                . billday_options($cust_main->billday) . qq!</SELECT> </TD></TR>!
-%        : ''
-%      ).
-%
-%      '</TABLE>',
-%
-%    'CHEK' => 
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Account number').qq! </TD>!.
-%          qq!<TD><INPUT TYPE="text" SIZE=12 NAME="CHEK_payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD>'.
-%          qq!<TD ALIGN="right">!.emt('Type').qq!</TD><TD><SELECT NAME="CHEK_paytype">!.
-%            join('', map { qq!<OPTION VALUE="$_" !.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>" } @FS::cust_main::paytypes).
-%          qq!</SELECT></TD></TR>!.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt($routing_label).qq! </TD>!.
-%          qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" SIZE=$routing_size MAXLENGTH=$routing_maxlength NAME="CHEK_payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!"> !.
-%          qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">!.emt('help').qq!</A>)!.
-%          qq!</TD></TR>!.
-%
-%        qq!<INPUT TYPE="hidden" NAME="CHEK_exp_month" VALUE="12">!.
-%        qq!<INPUT TYPE="hidden" NAME="CHEK_exp_year" VALUE="2037">!.
-%
-%        ( $conf->config('echeck-country') eq 'CA' ? 
-%               qq!<TR><TD ALIGN="right">$r !.emt('Branch number').qq!</TD><TD COLSPAN="3">
-%                   <INPUT TYPE="text" name="CHEK_payinfo3" VALUE="$branch" SIZE=6 MAXLENGTH=5></TD></TR>! : '' ).
-%   
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Bank name').qq! </TD>!.
-%          qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" NAME="CHEK_payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
-%      ( $conf->exists('show_bankstate') ?
-%          qq!<TR><TD ALIGN="right" WIDTH="200">$paystate_label</TD>!.
-%          qq!<TD COLSPAN="3" WIDTH="408">!.
-%          include('/elements/select-state.html',
-%                    'empty'   => emt('(choose)'),
-%                    'state'   => $cust_main->paystate,
-%                    'country' => $cust_main->country,
-%                    'prefix'  => 'CHEK_pay',
-%                 ). "</TD></TR>"
-%         : '<INPUT TYPE="hidden" NAME="CHEK_paystate" VALUE="'.
-%            $cust_main->paystate. '">'
-%       ).
-%
-%
-%        qq!<TR><TD COLSPAN=4 WIDTH="608">!.
-%           qq!<INPUT TYPE="checkbox" onchange="payauto_changed(this);" ID="CHEK_payauto" NAME="CHEK_payauto" $CHEK_payauto_checked> !.
-%           emt('Charge future payments to this [_1] automatically','electronic check').'</TD></TR>'.
-%
-%      ( $conf->exists('cust_main-select-billday') ?
-%        qq!<TR><TD ALIGN="RIGHT" id="td_CHEK_billday" $chek_billday_style>
-%                    Charge on this day of each month</TD><TD>  
-%                   <SELECT id="CHEK_billday" $chek_billday_select_disabled NAME="CHEK_billday">!
-%                . billday_options($cust_main->billday) . qq!</SELECT> </TD></TR>!
-%        : ''
-%      ).
-%
-%      '</TABLE>',
-%
-%    'LECB' =>  
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Phone number').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="LECB_payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!.
-%
-%        qq!<INPUT TYPE="hidden" NAME="LECB_exp_month" VALUE="12">!.
-%        qq!<INPUT TYPE="hidden" NAME="LECB_exp_year" VALUE="2037">!.
-%        qq!<INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!.
-%
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%
-%      '</TABLE>',
-%
-%    'BILL' =>  
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">!.emt('P.O.').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="BILL_payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!.
-%
-%        qq!<INPUT TYPE="hidden" NAME="BILL_exp_month" VALUE="12">!.
-%        qq!<INPUT TYPE="hidden" NAME="BILL_exp_year" VALUE="2037">!.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">!.emt('Attention').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="BILL_payname" VALUE="!. encode_entities( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!.
-%
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%
-%      '</TABLE>',
-%
-%    'COMP' =>   
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Approved by').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""></TD></TR>!.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Expiration').qq! </TD>!.
-%          '<TD WIDTH="408">'.
-%
-%          include('/elements/select-month_year.html',
-%                    'prefix' => 'COMP_exp',
-%                    'selected_date' =>
-%                      ( $payby eq 'COMP' ? $cust_main->paydate : '' ),
-%                 ).
-%
-%          '</TD></TR>'.
-%
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%
-%      '</TABLE>',
-%
-%    'CASH' =>
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Amount').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CASH_paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
-%
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%
-%      '</TABLE>',
-%
-%    'WEST' =>
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Amount').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="WEST_paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
-%
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%
-%      '</TABLE>',
-%
-%    'MCRD' =>
-%
-%      '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
-%
-%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}!.emt('Amount').qq! </TD>!.
-%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="MCRD_paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
-%
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%        '<TR><TD> </TD></TR>'.
-%
-%      '</TABLE>',
-%
-%  );
-%
-%  #this should use FS::payby
-%  my @allopt = qw( CARD CHEK LECB BILL CASH WEST MCRD COMP );
-%
-%  my %allopt = map { $_ => FS::payby->shortname($_) } @allopt;
-%
-%  if ( $cust_main->custnum ) {
-%    #don't offer CASH/WEST/MCRD initial payment types when editing customer
-%    delete $allopt{$_} for qw(CASH WEST MCRD);
-%  }
-%  
-%  my @options = grep exists( $allopt{$_} ), @payby;
-%
-%  my %payby2option = (
-%    ( map { $_ => $_ } @options ),
-%    'DCRD' => 'CARD',
-%    'DCHK' => 'CHEK',
-%  );
-
-  <TD WIDTH="408">
-    <& /elements/selectlayers.html,
-                  'field'      => 'payby',
-                  'curr_value' => $payby2option{$payby || $payby_default || $payby[0] },
-                  'options'    => \@options,
-                  'labels'     => \%allopt,
-                  'html_between' => '</TD></TR></TABLE>',
-                  'layer_callback' => sub { my $layer = shift; $payby{$layer}; },
-                  'onchange'    => 'init_payauto_changed();',
-    &>
+  <BR><FONT CLASS="fsinnerbox-title"><% mt('Billing information') |h %></FONT>
 
   <% &ntable("#cccccc") %>
 
-    <TR><TD> </TD></TR>
-
 %   my $curuser = $FS::CurrentUser::CurrentUser;
 %   my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
 %   if (    $conf->exists('cust_class-tax_exempt')
@@ -490,18 +63,6 @@
       </TD>
     </TR>
 
-    <TR>
-      <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_FAX" VALUE="FAX" <%
-
-        ( grep { $_ eq 'FAX' } @invoicing_list )
-          ? 'CHECKED'
-          : ''
-
-        %>> <% mt('Fax invoices') |h %> 
-
-      </TD>
-    </TR>
-
 % }
 
     <TR>
@@ -544,6 +105,26 @@
 % }
 
     <TR>
+      <TD ALIGN="right" WIDTH="200"><% mt('Charge card/e-check on this day of the month') |h %> </TD>
+      <TD>
+        <SELECT NAME="billday">
+          <% billday_options($cust_main->billday) %>
+        </SELECT>
+      </TD>
+    </TR>
+
+%  sub billday_options {
+%   my $curr_value = shift;
+%   my $ret = '';
+%   for my $billday ( 1 .. 28 ) {
+%       my $sel = '';
+%       $sel = "SELECTED='SELECTED'" if $curr_value == $billday;
+%       $ret .= "<OPTION VALUE='$billday' $sel>$billday</OPTION>";
+%   }
+%   $ret;
+%  }
+
+    <TR>
       <TD ALIGN="right" WIDTH="200"><% mt('Invoice terms') |h %> </TD>
       <TD WIDTH="408">
         <& /elements/select-terms.html,
@@ -673,27 +254,17 @@ function toggle(obj) {
 % }
 
   </TABLE>
+  <BR>
 
-  <% $r %><% mt('required fields') |h %> 
-% } 
-
-<script type="text/javascript">
-    init_payauto_changed();
-</script>
-
-<%once>
-
-my $paystate_label = FS::Msgcat::_gettext('paystate');
-$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
+  <FONT CLASS="fsinnerbox-title"><% mt('Payment information') |h %></FONT>
+  <& cust_payby.html, 'cust_main'=>$cust_main, &>  
 
-</%once>
 <%init>
 
 my( $cust_main, %options ) = @_;
 my @invoicing_list = @{ $options{'invoicing_list'} };
 my $payinfo = $options{'payinfo'};
 my $conf = new FS::Conf;
-my $payby_default = $conf->config('payby-default');
 
 my $money_char = $conf->config('money_char') || '$';
 
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
index 6a9deb9..97816aa 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -5,7 +5,7 @@ my $conf = new FS::Conf;
 my $company_latitude  = $conf->config('company_latitude');
 my $company_longitude = $conf->config('company_longitude');
 
-my @fixups = ('copy_payby_fields', 'standardize_locations');
+my @fixups = ('standardize_locations');
 
 push @fixups, 'confirm_censustract_bill', 'confirm_censustract_ship'
     if $conf->exists('cust_main-require_censustract');
@@ -51,55 +51,12 @@ function do_submit() {
   document.CustomerForm.submit();
 }
 
-function copy_payby_fields() {
-  var layervars = new Array(
-    'payauto', 'billday',
-    'payinfo', 'payinfo1', 'payinfo2', 'payinfo3', 'paytype',
-    'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
-    'paystart_month', 'paystart_year', 'payissue',
-    'payip',
-    'paid'
-  );
-
-  var cf = document.CustomerForm;
-  var payby = cf.payby.options[cf.payby.selectedIndex].value;
-  for ( f=0; f < layervars.length; f++ ) {
-    var field = layervars[f];
-    copyelement( cf.elements[payby + '_' + field],
-                 cf.elements[field]
-               );
-  }
-  submit_continue();
-}
-
 <& /elements/standardize_locations.js,
   'callback' => 'submit_continue();',
   'billship' => 1,
   'with_census' => 1, # no with_firm, apparently
 &>
 
-function copyelement(from, to) {
-  if ( from == undefined ) {
-    to.value = '';
-  } else if ( from.type == 'select-one' ) {
-    to.value = from.options[from.selectedIndex].value;
-    //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value);
-  } else if ( from.type == 'checkbox' ) {
-    if ( from.checked ) {
-      to.value = from.value;
-    } else {
-      to.value = '';
-    }
-  } else {
-    if ( from.value == undefined ) {
-      to.value = '';
-    } else {
-      to.value = from.value;
-    }
-  }
-  //alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
-}
-
 % # the value in pre+'censustract' is the confirmed censustract (either from
 % # the previous saved record, or from address standardization (if the backend
 % # supports it), or from an aborted previous submit. only need to reconfirm
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
deleted file mode 100644
index c2ebb09..0000000
--- a/httemplate/edit/cust_main/contact.html
+++ /dev/null
@@ -1,171 +0,0 @@
-<TABLE CLASS="fsinnerbox">
-
-<TR>
-  <TH ALIGN="right"><%$r%><% mt('Contact name (last, first)') |h %></TH>
-  <TD COLSPAN=7>
-    <INPUT TYPE="text" NAME="<%$pre%>last" VALUE="<% $cust_main->get($pre.'last') |h %>" onChange="<% $onchange %>" <%$disabled%> <%$style%>> , 
-    <INPUT TYPE="text" NAME="<%$pre%>first" VALUE="<% $cust_main->get($pre.'first') |h %>" onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-% if ( $conf->exists('show_ss') && !$pre ) { 
-
-  <TD ALIGN="right"><% mt('SS#') |h %></TD>
-  <TD><INPUT TYPE="text" NAME="ss" VALUE="<% $opt{ss} %>" SIZE=11></TD>
-% } elsif ( !$pre ) { 
-
-  <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<% $opt{ss} %>"></TD>
-% } 
-</TR>
-
-% if ( $conf->exists('cust-email-high-visibility') && !$pre ) {
-    <TR>
-      <TD ALIGN="right" WIDTH="200">
-        <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum) 
-          ? $r : '' %>Email address(es)
-      </TD>
-      <TD bgcolor="#FFFF00">
-        <INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>">
-      </TD>
-    </TR>
-% }
-
-% unless ( $conf->exists('cust-edit-alt-field-order') ) { #standard order
-
-  <& company &>
-  <& location &>
-  <& phones &>
-  <& fax &>
-
-% } else { #alternate field order
-
-  <& phones &>
-  <& location &>
-  <& fax &>
-  <& company &>
-
-% }
-
-% if ( $conf->exists('show_stateid') && !$pre ) { 
-
-<TR>
-  <TD ALIGN="right"><% $stateid_label %></TD>
-  <TD><INPUT TYPE="text" NAME="stateid" VALUE="<% $opt{stateid} %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>></TD>
-  <TD ALIGN="right"><% $stateid_state_label %></TD>
-  <TD><& /elements/select-state.html,
-                   'state'    => $cust_main->stateid_state,
-                   'country'  => $cust_main->country,
-                   'prefix'   => 'stateid_',
-                   'onchange' => $onchange,
-                   'disabled' => $disabled,
-                   'style'    => \@style,
-      &>
-  </TD>
-</TR>
-% } elsif ( !$pre ) { 
-
-  <TD><INPUT TYPE="hidden" NAME="stateid" VALUE="<% $opt{stateid} %>"></TD>
-  <TD><INPUT TYPE="hidden" NAME="stateid_state" VALUE="<% $cust_main->stateid_state %>"></TD>
-% } 
-
-</TABLE>
-<%$r%><% mt('required fields') |h %><BR>
-
-<%def company>
-% my $display = ($cust_main->residential_commercial eq 'Commercial')
-%                 ? '' : 'none';
-  <TR ID="<%$pre%>company_row" STYLE="display:<%$display%>">
-    <TD ALIGN="right"><% mt('Company') |h %></TD>
-    <TD COLSPAN=7>
-      <INPUT TYPE="text" NAME="<%$pre%>company" ID="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') |h %>" SIZE=60 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-    </TD>
-  </TR>
-</%def>
-
-<%def location>
-  <& /elements/location.html,
-               'prefix'       => $pre,
-               'object'       => $cust_main,
-               'onchange'     => $onchange,
-               'disabled'     => $disabled,
-               'style'        => \@style,
-               'same_checked' => $opt{'same_checked'},
-               'geocode'      => $opt{'geocode'},
-               'censustract'  => $opt{'censustract'},
-  &>
-</%def>
-
-<%def phones>
-  <& /elements/tr-cust_main-phones.html,
-       'prefix'       => $pre,
-       'cust_main'    => $cust_main,
-       'onchange'     => $onchange,
-       'disabled'     => $disabled,
-       'style'        => $style,
-  &>
-</%def>
-
-<%def fax>
-  <TR>
-    <TD ALIGN="right"><% mt('Fax') |h %></TD>
-    <TD COLSPAN=5>
-      <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-    </TD>
-  </TR>
-</%def>
-
-<%once>
-
-my $r = qq!<font color="#ff0000">*</font> !;
-
-</%once>
-<%shared>
-
-my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style );
-
-</%shared>
-<%init>
-
-%opt = @_;
-$cust_main = $opt{'cust_main'};
-$pre       = $opt{'pre'};
-$onchange  = $opt{'onchange'};
-$disabled  = $opt{'disabled'};
- at style     = ( $opt{'style'} ? @{ $opt{'style'} } : () );
-
-$style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
-
-my $conf = new FS::Conf;
-
-foreach (qw(ss stateid)) {
-  $opt{$_} = $cust_main->masked($_) unless exists $opt{$_};
-}
-
-#false laziness with ship state
-my $countrydefault = $conf->config('countrydefault') || 'US';
-$cust_main->set($pre.'country', $countrydefault )
-  unless $cust_main->get($pre.'country');
-
-my $statedefault = $conf->config('statedefault')
-                   || ($countrydefault eq 'US' ? 'CA' : '');
-$cust_main->set($pre.'state', $statedefault )
-  unless $cust_main->get($pre.'state')
-         || $cust_main->get($pre.'country') ne $countrydefault;
-
-$cust_main->set('stateid_state', $cust_main->state )
-  unless $pre || $cust_main->get('stateid_state');
-
-$opt{geocode} ||= $cust_main->get('geocode');
-
-$opt{censustract} ||= $cust_main->censustract;
-
-my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
-                  ? 'Driver’s License'
-                  : FS::Msgcat::_gettext('stateid') || 'Driver’s License';
-my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/
-                        ? 'Driver’s License State'
-                        : FS::Msgcat::_gettext('stateid_state') || 'Driver’s License State';
-
-my @invoicing_list = $cust_main->invoicing_list;
-
-my $agentnum = $cust_main->agentnum if $cust_main->custnum;
-
-</%init>
diff --git a/httemplate/edit/cust_main/contacts_new.html b/httemplate/edit/cust_main/contacts_new.html
index 0ab02b4..9ccb45f 100644
--- a/httemplate/edit/cust_main/contacts_new.html
+++ b/httemplate/edit/cust_main/contacts_new.html
@@ -1,10 +1,9 @@
 <DIV ID="contacts_div" STYLE="display:<% $display %>">
-<BR>
 <FONT CLASS="fsinnerbox-title">Contacts</FONT>
 <% include('/edit/elements/edit.html',
      'embed'           => $opt{cust_main},
      'table'           => 'cust_main',
-     'labels'          => { 'contactnum'  => 'Contact',
+     'labels'          => { 'contactnum'  => '', #'Contact',
                             #'locationnum' => ' ',
                           },
      'fields'          => [
@@ -14,7 +13,7 @@
          'custnum'           => $opt{cust_main}->custnum,
          'm2m_method'        => 'cust_contact',
          'm2m_dstcol'        => 'contactnum',   
-         'm2_label'          => 'Contact',
+         'm2_label'          => ' ', #'Contact',
          'm2_error_callback' => $m2_error_callback,
        },
      ],
diff --git a/httemplate/edit/cust_main/cust_payby.html b/httemplate/edit/cust_main/cust_payby.html
new file mode 100644
index 0000000..cf0ada9
--- /dev/null
+++ b/httemplate/edit/cust_main/cust_payby.html
@@ -0,0 +1,56 @@
+<% include('/edit/elements/edit.html',
+     'embed'           => $opt{cust_main},
+     'tablenum'        => 1,
+     'table'           => 'cust_main',
+     'labels'          => { 'custpaybynum'  => '',
+                            #'locationnum' => ' ',
+                          },
+     'fields'          => [
+       { 'field'             => 'custpaybynum',
+         'type'              => 'cust_payby',
+         'colspan'           => 6,
+         #'custnum'           => $opt{cust_main}->custnum,
+         'm2m_method'        => 'cust_payby',
+         'm2m_dstcol'        => 'custpaybynum',   
+         'm2_label'          => ' ',
+         'm2_error_callback' => $m2_error_callback,
+       },
+     ],
+     'agent_virt'      => 1,
+    )
+%>
+</DIV>
+<%init>
+
+my %opt = @_;
+
+my $m2_error_callback = sub {
+  my($cgi, $object) = @_;
+
+  #process_o2m fields in process/cust_main-cust_payby.html
+  my $fields = FS::cust_payby->cgi_cust_payby_fields;
+  my @gfields = ( '', map "_$_", grep $_ !~ /^(payby|paydate_)/, @$fields );
+
+  map {
+        if ( /^custpaybynum(\d+)$/ ) {
+          my $num = $1;
+          if ( grep $cgi->param("custpaybynum$num$_"), @gfields ) {
+            my %hash = (
+              'custpaybynum' => scalar($cgi->param("custpaybynum$num")),
+              map { $_ => scalar($cgi->param("custpaybynum${num}_$_")) }
+                @$fields,
+            );
+            FS::cust_payby::cgi_hash_callback( \%hash );
+            FS::cust_payby->new( \%hash );
+          } else {
+            ();
+          }
+        } else {
+          ();
+        }
+      }
+      $cgi->param;
+};
+
+</%init>
+
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index 4d5beee..5a7920b 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -118,6 +118,8 @@ Example:
     # display, no <FORM>, no hidden fields for table name or primary key, no
     # display of primary key, no submit button, no html_foot, no footer)
     'embed' => $object, #need to pass the object
+    'tablenum' => 4, #need to specify a table number when using multiple
+                     #embedded edits on a page (and m2 stuff)
 
     #don't show the primary key label and value
     'no_pkey_display' => 1,
@@ -256,7 +258,7 @@ Example:
 
 % }
 
-% my $tablenum = 0;
+% my $tablenum = $opt{'tablenum'} || 0;
 <TABLE ID="TableNumber<% $tablenum++ %>" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
 
 % my $g_row = 0;
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 82ec50c..52a2608 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -29,29 +29,6 @@ $cgi->param('tax','') unless defined $cgi->param('tax');
 
 $cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] );
 
-my $payby = $cgi->param('payby');
-
-my %noauto = (
-  'CARD' => 'DCRD',
-  'CHEK' => 'DCHK',
-);
-$payby = $noauto{$payby}
-  if ! $cgi->param('payauto') && exists $noauto{$payby};
-
-$cgi->param('payby', $payby);
-
-if ( $payby ) {
-  if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
-      my $payinfo = $cgi->param('payinfo1'). '@';
-      $payinfo .= $cgi->param('payinfo3').'.' 
-            if $conf->config('echeck-country') eq 'CA';
-      $payinfo .= $cgi->param('payinfo2');
-      $cgi->param('payinfo',$payinfo);
-  }
-  $cgi->param('paydate',
-    $cgi->param( 'exp_month' ). '-'. $cgi->param( 'exp_year' ) );
-}
-
 my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
 push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
 push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
@@ -177,6 +154,9 @@ if ( $curuser->access_right('Edit customer tax exemptions') ) {
   };
 }
 
+$options{'contact_params'} = scalar($cgi->Vars);
+$options{'cust_payby_params'} = scalar($cgi->Vars);
+
 #perhaps this stuff should go to cust_main.pm
 if ( $new->custnum eq '' or $duplicate_of ) {
 
@@ -304,34 +284,12 @@ if ( $new->custnum eq '' or $duplicate_of ) {
   my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } ); 
   $error ||= "Old record not found!" unless $old;
 
-  if ( length($old->paycvv) && $new->paycvv =~ /^\s*\*+\s*$/ ) {
-    $new->paycvv($old->paycvv);
-  }
   if ($new->ss =~ /xx/) {
     $new->ss($old->ss);
   }
   if ($new->stateid =~ /^xxx/) {
     $new->stateid($old->stateid);
   }
-  if ( $new->payby =~ /^(CARD|DCRD)$/
-       && (    $new->payinfo =~ /xx/
-            || $new->payinfo =~ /^\s*N\/A\s+\(tokenized\)\s*$/
-          )
-     )
-  {
-    $new->payinfo($old->payinfo);
-
-  } elsif ( $new->payby =~ /^(CHEK|DCHK)$/ && $new->payinfo =~ /xx/ ) {
-    #fix for #3085 "edit of customer's routing code only surprisingly causes
-    #nothing to happen...
-    # this probably won't do the right thing when we don't have the
-    # public key (can't actually get the real $old->payinfo)
-    my($new_account, $new_aba) = split('@', $new->payinfo);
-    my($old_account, $old_aba) = split('@', $old->payinfo);
-    $new_account = $old_account if $new_account =~ /xx/;
-    $new_aba     = $old_aba     if $new_aba     =~ /xx/;
-    $new->payinfo($new_account.'@'.$new_aba);
-  }
 
   if ( ! $conf->exists('cust_main-edit_signupdate') or
        ! $new->signupdate ) {
@@ -353,13 +311,4 @@ if ( $new->custnum eq '' or $duplicate_of ) {
   
 }
 
-unless ( $error ) { #XXX i should be transactional... all in the insert
-                    # or replace call
-
-  $error = $new->process_o2m( 'table'  => 'contact',
-                              'fields' => FS::contact->cgi_contact_fields,
-                              'params' => scalar($cgi->Vars),
-                            );
-}
-
 </%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 5f6921c..7d5d4f3 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -127,6 +127,7 @@ $report_customers{'Advanced customer reports'} = [ $fsurl. 'search/report_cust_m
 if ( $curuser->access_right('List contacts') ) {
   $report_customers{'separator'} = '';
   $report_customers{'Customer contacts'} = [ $fsurl. 'search/report_contact.html?link=cust_main' ];
+  $report_customers{'Customer stored payment information'} = [ $fsurl. 'search/report_cust_payby.html' ];
 }
 
 tie my %report_invoices_open, 'Tie::IxHash',
diff --git a/httemplate/elements/tr-coords.html b/httemplate/elements/tr-coords.html
index 3248dc2..5ac5ed4 100644
--- a/httemplate/elements/tr-coords.html
+++ b/httemplate/elements/tr-coords.html
@@ -1,6 +1,6 @@
 <TR>
   <TD ALIGN="right"><% mt('Latitude') |h %></TD>
-  <TD COLSPAN=5>
+  <TD COLSPAN=7>
     <FONT STYLE="background-color: #ffffff; border: 1px solid #ffffff"><% $latitude %></FONT>
      <% mt('Longitude') |h %>
     <FONT STYLE="background-color: #ffffff; border: 1px solid #ffffff"><% $longitude %></FONT>
diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html
index 503e782..672c201 100755
--- a/httemplate/search/cust_main.html
+++ b/httemplate/search/cust_main.html
@@ -42,7 +42,7 @@ my %search_hash = ();
 #scalars
 my @scalars = qw (
   agentnum salesnum status address city county state zip country
-  paydate_year paydate_month invoice_terms
+  invoice_terms
   no_censustract with_geocode with_email tax no_tax POST no_POST
   custbatch usernum
   cancelled_pkgs
@@ -58,7 +58,7 @@ for my $param ( @scalars ) {
 }
 
 #lists
-for my $param (qw( classnum refnum payby tagnum pkg_classnum )) {
+for my $param (qw( classnum refnum tagnum pkg_classnum )) {
   $search_hash{$param} = [ $cgi->param($param) ];
 }
 
diff --git a/httemplate/search/cust_payby.html b/httemplate/search/cust_payby.html
new file mode 100644
index 0000000..140391b
--- /dev/null
+++ b/httemplate/search/cust_payby.html
@@ -0,0 +1,94 @@
+<& elements/search.html,
+                  'title'       => emt('Customer stored payment information results'),
+                  'menubar'     => $menubar,
+                  'name'        => emt('cards or bank accounts'), #??
+                  'query'       => $sql_query,
+                  'count_query' => $count_query,
+                  'header'      => [ @headers,
+                                     FS::UI::Web::cust_header(
+                                       $cgi->param('cust_fields')
+                                     ),
+                                   ],
+                  'fields'      => [
+                    @fields,
+                    \&FS::UI::Web::cust_fields,
+                  ],
+                  'color'       => [ 
+                                     ( map '', @fields ),
+                                     FS::UI::Web::cust_colors(),
+                                   ],
+                  'style'       => [
+                                     ( map '', @fields ),
+                                     FS::UI::Web::cust_styles(),
+                                   ],
+                  'align'       => [ 
+                                     ( map '', @fields ),
+                                     FS::UI::Web::cust_aligns(),
+                                   ],
+                  'links'       => [
+                                     ( map '', @fields ),
+                                     ( map { $_ ne 'Cust. Status' ? $link : '' }
+                                           FS::UI::Web::cust_header(
+                                                      $cgi->param('cust_fields')
+                                                                   )
+                                     ),
+                                   ],
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Advanced customer search');
+
+my %search_hash = ();
+
+my @scalars = qw (
+  paydate_year paydate_month
+);
+
+for my $param ( @scalars ) {
+  $search_hash{$param} = scalar( $cgi->param($param) )
+    if length($cgi->param($param));
+}
+
+#lists
+for my $param (qw( payby )) {
+  $search_hash{$param} = [ $cgi->param($param) ];
+}
+
+###
+# etc
+###
+
+my $sql_query = FS::cust_payby->search_sql(\%search_hash);
+my $count_query   = delete($sql_query->{'count_query'});
+
+my @headers = ( 'Payment information',
+              );
+
+my @fields = ( sub { my $cust_payby = shift;
+                     FS::payby->shortname( $cust_payby->payby ). ' '.
+                       $cust_payby->paymask;
+                   }
+             );
+
+my $link = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+###
+# email links
+###
+
+my $menubar = [];
+
+#XXX TODO
+#if ( $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices') ) {
+#
+#  my $uri = new URI;
+#  $uri->query_form( \%search_hash );
+#  my $query = $uri->query;
+#
+#  push @$menubar, emt('Email a notice to these customers') =>
+#                    "${p}misc/email-customers.html?table=cust_main&$query",
+#
+#}
+
+</%init>
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
index cacb7de..ba7c99a 100755
--- a/httemplate/search/report_cust_main.html
+++ b/httemplate/search/report_cust_main.html
@@ -194,43 +194,6 @@
       <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1"><% mt('Billing search options') |h %></FONT></TH>
     </TR>
 
-    <& /elements/tr-select-payby.html,
-                  'payby_type'   => 'cust',
-                  'multiple'     => 1,
-                  'all_selected' => 1,
-    &>
-
-    <TR>
-      <TD ALIGN="right"><% mt('Payment expiration before') |h %></TD>
-      <TD>
-        <SELECT NAME="paydate_month" DISABLED>
-%         foreach my $month ( 1 .. 12 ) {
-            <OPTION VALUE="<% $month %>"><% $month %></OPTION>
-%         }
-        </SELECT>
-        /
-        <SELECT NAME="paydate_year" onChange="paydate_year_changed(this);">
-          <OPTION VALUE=""></OPTION>
-%         my $lastyear = (localtime(time))[5] + 1899;
-%         foreach my $year ( $lastyear .. $lastyear+12 ) {
-            <OPTION VALUE="<% $year %>"><% $year %></OPTION>
-%         }
-        </SELECT>
-      </TD>
-    </TR>
-
-    <SCRIPT TYPE="text/javascript">
-      function paydate_year_changed(what) {
-        var value = what.options[what.selectedIndex].value;
-        var month_select = what.form.paydate_month;
-        if ( value == '' ) {
-          month_select.disabled = true;
-        } else {
-          month_select.disabled = false;
-        }
-      }
-    </SCRIPT>
-
 % my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
 % unless ( @exempt_groups ) { 
 
diff --git a/httemplate/search/report_cust_payby.html b/httemplate/search/report_cust_payby.html
new file mode 100644
index 0000000..81a8270
--- /dev/null
+++ b/httemplate/search/report_cust_payby.html
@@ -0,0 +1,57 @@
+<& /elements/header.html, mt('Customer stored payment infomation report') &>
+
+<FORM ACTION="cust_payby.html" METHOD="GET">
+
+  <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+    <& /elements/tr-select-payby.html,
+                  'payby_type'   => 'cust',
+                  'multiple'     => 1,
+                  'all_selected' => 1,
+    &>
+
+    <TR>
+      <TD ALIGN="right"><% mt('Payment expiration before') |h %></TD>
+      <TD>
+        <SELECT NAME="paydate_month" DISABLED>
+%         foreach my $month ( 1 .. 12 ) {
+            <OPTION VALUE="<% $month %>"><% $month %></OPTION>
+%         }
+        </SELECT>
+        /
+        <SELECT NAME="paydate_year" onChange="paydate_year_changed(this);">
+          <OPTION VALUE=""></OPTION>
+%         my $lastyear = (localtime(time))[5] + 1899;
+%         foreach my $year ( $lastyear .. $lastyear+12 ) {
+            <OPTION VALUE="<% $year %>"><% $year %></OPTION>
+%         }
+        </SELECT>
+      </TD>
+    </TR>
+
+    <SCRIPT TYPE="text/javascript">
+      function paydate_year_changed(what) {
+        var value = what.options[what.selectedIndex].value;
+        var month_select = what.form.paydate_month;
+        if ( value == '' ) {
+          month_select.disabled = true;
+        } else {
+          month_select.disabled = false;
+        }
+      }
+    </SCRIPT>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Advanced customer search');
+
+</%init>
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index d18c7f7..081b96b 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -169,14 +169,12 @@ function areyousure(href, message) {
 <TABLE BORDER=0>
 <TR>
   <TD VALIGN="top">
-    <& cust_main/contacts.html, $cust_main &>
+    <& cust_main/misc.html, $cust_main &>
+    <BR><& cust_main/contacts.html, $cust_main &>
   </TD>
   <TD VALIGN="top" STYLE="padding-left: 54px">
-    <& cust_main/misc.html, $cust_main &>
-% if ( $conf->config('payby-default') ne 'HIDE' ) { 
-      <BR><& cust_main/billing.html, $cust_main &>
-% } 
-
+    <& cust_main/billing.html, $cust_main &>
+    <BR><& cust_main/cust_payby.html, $cust_main &>
   </TD>
 </TR>
 <TR>
@@ -281,9 +279,7 @@ function areyousure(href, message) {
 
 % if ( $view eq 'payment_history' || $view eq 'jumbo' ) {
 
-% if ( $conf->config('payby-default') ne 'HIDE' ) { 
-  <& cust_main/payment_history.html, $cust_main &>
-% } 
+<& cust_main/payment_history.html, $cust_main &>
 
 % }
 
@@ -352,8 +348,7 @@ if ( $conf->config('ticket_system') ) {
 }
 $views{emt('Quotations')}      =  'quotations';
 $views{emt('Packages')}        =  'packages';
-$views{emt('Payment History')} =  'payment_history'
-                               unless $conf->config('payby-default' eq 'HIDE');
+$views{emt('Payment History')} =  'payment_history';
 $views{emt('Change History')}  =  'change_history'
   if $curuser->access_right('View customer history');
 $views{$conf->config('cust_main-custom_title') || emt('Custom')} =  'custom'
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index 64ec591..f1125c0 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -32,165 +32,25 @@
 % }
 
 % if ( $conf->exists('cust_main-select-billday') 
-%    && ($cust_main->payby eq 'CARD' || $cust_main->payby eq 'CHEK') ) {
-<TR>
-  <TD ALIGN="right"><% mt('Billing day of month') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->billday %>
-  </TD>
-</TR>
-% }
-
-<TR>
-  <TD ALIGN="right"><% mt('Billing type') |h %></TD>
-  <TD BGCOLOR="#ffffff">
-% if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) { 
-
-%   my $autodemand = $cust_main->payby eq 'CARD' ? 'automatic' : 'on-demand';
-    <% mt("Credit card ([_1])",$autodemand) |h %>
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Card number') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->paymask %></TD>
-</TR>
-%
-%#false laziness w/elements/select-month_year.html & edit/cust_main/billing.html
-%my( $mon, $year );
-%my $date = $cust_main->paydate || '12-2037';
-%if ( $date  =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
-%  ( $mon, $year ) = ( $2, $1 );
-%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
-%  ( $mon, $year ) = ( $1, $3 );
-%} else {
-%  warn "unrecognized expiration date format: $date";
-%  ( $mon, $year ) = ( '', '' );
-%}
-%
-
-<TR>
-  <TD ALIGN="right"><% mt('Expiration') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
-</TR>
-% if ( $cust_main->paystart_month ) { 
-
-  <TR>
-    <TD ALIGN="right"><% mt('Start date') |h %></TD>
-    <TD BGCOLOR="#ffffff"><% $cust_main->paystart_month. '/'. $cust_main->paystart_year %>
-  </TR>
-% } elsif ( $cust_main->payissue ) { 
-
-  <TR>
-    <TD ALIGN="right"><% mt('Issue #') |h %></TD>
-    <TD BGCOLOR="#ffffff"><% $cust_main->payissue %>
-  </TR>
-% } 
-
-
-<TR>
-  <TD ALIGN="right"><% mt('Name on card') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
-</TR>
-% } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') {
-%     my( $account, $aba ) = split('@', $cust_main->paymask );
-%  my $branch = '';
-%  ($branch,$aba) = split('\.',$aba) if $conf->config('echeck-country') eq 'CA';
-
-
-% my $autodemand = $cust_main->payby eq 'CHEK' ? 'automatic' : 'on-demand';
-    <% mt("Electronic check ([_1])",$autodemand) |h %>
-  </TD>
-</TR>
-
-%  #false laziness w/edit/cust_main/billing.html and misc/payment.cgi
-%  my $routing_label = $conf->config('echeck-country') eq 'US'
-%                        ? 'ABA/Routing number'
-%                        : 'Routing number';
-
-<TR>
-  <TD ALIGN="right"><% mt($routing_label) |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $aba %></TD>
-</TR>
-
-% if ( $conf->config('echeck-country') eq 'CA' ) {
-<TR>
-  <TD ALIGN="right"><% mt('Branch number') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $branch %></TD>
-<TR>
-% }
-
-  <TD ALIGN="right"><% mt('Account number') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $account %></TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Account type') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->paytype %></TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Bank name') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
-</TR>
-% if ( $conf->exists('show_bankstate') ) {
-<TR>
-  <TD ALIGN="right"><% $paystate_label %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->paystate || '   ' %></TD>
-</TR>
+%        && qsearch({ 'table'     => 'cust_payby',
+%                     'hashref'   => { 'custnum' => $cust_main->custnum, },
+%                     'extra_sql' => "AND payby IN ( 'CARD', 'CHEK' )",
+%                     'order_by'  => 'LIMIT 1',
+%                  })
+%    )
+% {
+    <TR>
+      <TD ALIGN="right"><% mt('Payment day of month') |h %></TD>
+      <TD BGCOLOR="#ffffff"><% $cust_main->billday %>
+      </TD>
+    </TR>
 % }
-% } elsif ( $cust_main->payby eq 'LECB' ) {
-%     $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/;
-%     my $payinfo = "$1-$2-$3";
-
-    <% mt('Phone bill billing') |h %> 
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Phone number') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $payinfo %></TD>
-</TR>
-% } elsif ( $cust_main->payby eq 'BILL' ) { 
-
-    <% mt('Billing') |h %> 
-  </TD>
-</TR>
-% if ( $cust_main->payinfo ) { 
-
-<TR>
-  <TD ALIGN="right"><% mt('P.O.') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
-</TR>
-% } 
 
-
-<TR>
-  <TD ALIGN="right"><% mt('Attention') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->payname |h %></TD>
-</TR>
-% } elsif ( $cust_main->payby eq 'COMP' ) { 
-
-    <% mt('Complimentary') |h %> 
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Authorized by') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
-</TR>
-%
-%#false laziness w/above etc.
-%my( $mon, $year );
-%my $date = $cust_main->paydate || '12-2037';
-%if ( $date  =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
-%  ( $mon, $year ) = ( $2, $1 );
-%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
-%  ( $mon, $year ) = ( $1, $3 );
-%} else {
-%  warn "unrecognized expiration date format: $date";
-%  ( $mon, $year ) = ( '', '' );
-%}
-%
-
-<TR>
-  <TD ALIGN="right"><% mt('Expiration') |h %></TD>
-  <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
-</TR>
+% if ( $cust_main->po_number ) { 
+    <TR>
+      <TD ALIGN="right"><% mt('Purchase Order #') |h %></TD>
+      <TD BGCOLOR="#ffffff"><% $cust_main->po_number %></TD>
+    </TR>
 % } 
 
 % my $yes = emt('yes');
@@ -230,13 +90,13 @@
 <TR>
   <TD ALIGN="right"><% mt('Postal mail invoices') |h %></TD>
   <TD BGCOLOR="#ffffff">
-    <% ( grep { $_ eq 'POST' } @invoicing_list ) ? $yes : $no %>
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Fax invoices') |h %></TD>
-  <TD BGCOLOR="#ffffff">
-    <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? $yes : $no %>
+    <% ( grep { $_ eq 'POST' } @invoicing_list )
+         ? $yes. ( $cust_main->invoice_attn
+                     ? ', attn: '. $cust_main->invoice_attn
+                     : ''
+                 )
+         : $no
+    %>
   </TD>
 </TR>
 <TR>
@@ -336,12 +196,6 @@
 
 
 </TABLE>
-<%once>
-
-my $paystate_label = FS::Msgcat::_gettext('paystate');
-$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
-
-</%once>
 <%init>
 
 my( $cust_main ) = @_;
diff --git a/httemplate/view/cust_main/cust_payby.html b/httemplate/view/cust_main/cust_payby.html
new file mode 100644
index 0000000..3ebb055
--- /dev/null
+++ b/httemplate/view/cust_main/cust_payby.html
@@ -0,0 +1,133 @@
+% if ( @cust_payby ) {
+
+    <FONT CLASS="fsinnerbox-title"><% mt('Payment information') |h %></FONT>
+    <TABLE CLASS="fsinnerbox">
+
+%   my $num = 0;
+%   foreach my $cust_payby ( @cust_payby ) {
+
+%     #one line per piece of info?  maybe, but just getting something working
+%     # for now
+
+%     if ( $cust_payby->payby eq 'CARD' || $cust_payby->payby eq 'DCRD' ) { 
+
+%       my $auto = $cust_payby->payby eq 'CARD' ? 'automatic' : 'on-demand';
+        <TR>
+          <TD COLSPAN=2 ALIGN="center" BGCOLOR="#ffffff">
+            <% mt("Credit card ([_1])",$auto) |h %>
+          </TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right"><% mt('Card number') |h %></TD>
+          <TD BGCOLOR="#ffffff"><% $cust_payby->paymask %></TD>
+        </TR>
+
+%       my( $mon, $year ) = $cust_payby->paydate_mon_year;
+        <TR>
+          <TD ALIGN="right"><% mt('Expiration') |h %></TD>
+          <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
+        </TR>
+
+%       if ( $cust_payby->paystart_month ) { 
+          <TR>
+            <TD ALIGN="right"><% mt('Start date') |h %></TD>
+            <TD BGCOLOR="#ffffff"><% $cust_payby->paystart_month. '/'. $cust_payby->paystart_year %>
+          </TR>
+%       } elsif ( $cust_payby->payissue ) { 
+          <TR>
+            <TD ALIGN="right"><% mt('Issue #') |h %></TD>
+            <TD BGCOLOR="#ffffff"><% $cust_payby->payissue %>
+          </TR>
+%       } 
+
+        <TR>
+          <TD ALIGN="right"><% mt('Name on card') |h %></TD>
+          <TD BGCOLOR="#ffffff"><% $cust_payby->payname %></TD>
+        </TR>
+
+%     } elsif ( $cust_payby->payby eq 'CHEK' || $cust_payby->payby eq 'DCHK') {
+
+%       my $auto = $cust_payby->payby eq 'CHEK' ? 'automatic' : 'on-demand';
+%
+%       my( $account, $aba ) = split('@', $cust_payby->paymask );
+%       my $branch = '';
+%       ($branch,$aba) = split('\.',$aba)
+%         if $conf->config('echeck-country') eq 'CA';
+
+        <TR>
+          <TD COLSPAN=2 ALIGN="center" BGCOLOR="#ffffff">
+            <% mt("Electronic check ([_1])",$auto) |h %>
+          </TD>
+        </TR>
+
+%       #false laziness w/edit/cust_main/billing.html and misc/payment.cgi
+%       my $routing_label = $conf->config('echeck-country') eq 'US'
+%                             ? 'ABA/Routing number'
+%                             : 'Routing number';
+        <TR>
+          <TD ALIGN="right"><% mt($routing_label) |h %></TD>
+          <TD BGCOLOR="#ffffff"><% $aba %></TD>
+        </TR>
+
+%       if ( $conf->config('echeck-country') eq 'CA' ) {
+          <TR>
+            <TD ALIGN="right"><% mt('Branch number') |h %></TD>
+            <TD BGCOLOR="#ffffff"><% $branch %></TD>
+          </TR>
+%       }
+
+        <TR>
+          <TD ALIGN="right"><% mt('Account number') |h %></TD>
+          <TD BGCOLOR="#ffffff"><% $account %></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right"><% mt('Account type') |h %></TD>
+          <TD BGCOLOR="#ffffff"><% $cust_payby->paytype %></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right"><% mt('Bank name') |h %></TD>
+          <TD BGCOLOR="#ffffff"><% $cust_payby->payname %></TD>
+        </TR>
+
+%       if ( $conf->exists('show_bankstate') ) {
+          <TR>
+            <TD ALIGN="right"><% $paystate_label %></TD>
+            <TD BGCOLOR="#ffffff"><% $cust_payby->paystate || '   ' %></TD>
+          </TR>
+%       }
+
+%     } else {
+        <TR>
+          <TD COLSPAN="2"><FONT COLOR="#FF0000">
+            Unknown cust_pay.payby <% $cust_payby->payby %>
+          </FONT></TD>
+        </TR>
+%     }
+
+%     unless ( $num++ == $#cust_payby ) {
+        <TR>
+          <TD COLSPAN="2"></TD>
+        </TR>
+        <TR>
+          <TD COLSPAN="2" STYLE="border-top: 1px solid black; padding:2px"></TD>
+        </TR>
+%     }
+
+%   }
+
+    </TABLE>
+
+% }
+<%once>
+
+my $paystate_label = FS::Msgcat::_gettext('paystate');
+$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+my @cust_payby = $cust_main->cust_payby;
+
+</%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 9a2332b..9098511 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -92,9 +92,7 @@ if ( el ) el.scrollIntoView(true);
   <& /elements/order_pkg_link.html, 'cust_main'=>$cust_main &>
 % } 
 
-% if ( $curuser->access_right('One-time charge')
-%        && $conf->config('payby-default') ne 'HIDE'
-%      ) {
+% if ( $curuser->access_right('One-time charge') ) {
   <% $s++ ? ' | ' : '' %>
   <& /elements/one_time_charge_link.html, 'custnum'=>$cust_main->custnum &>
 % } 

commit 6615733676adb431ae48c78ce24758fe571614c1
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Feb 9 22:58:47 2015 -0800

    removing 17 year old obsolete file

diff --git a/etc/domain-template.txt b/etc/domain-template.txt
deleted file mode 100644
index 8e4983c..0000000
--- a/etc/domain-template.txt
+++ /dev/null
@@ -1,231 +0,0 @@
-[ URL ftp://rs.internic.net/templates/domain-template.txt ] [ 03/98 ] 
-
-******* Please DO NOT REMOVE Version Number or Sections A-Q ********
-
-Domain Version Number: 4.0
-
-******* Email completed agreement to hostmaster at internic.net *******
-
-	NETWORK SOLUTIONS, INC.
-
-	DOMAIN NAME REGISTRATION AGREEMENT
-
-
-A.	Introduction. This domain name registration agreement
-("Registration Agreement") is submitted to NETWORK SOLUTIONS, INC.
-("NSI") for the purpose of applying for and registering a domain name
-on the Internet. If this Registration Agreement is accepted by NSI,
-and a domain name is registered in NSI's domain name database and
-assigned to the Registrant, Registrant ("Registrant") agrees to be
-bound by the terms of this Registration Agreement and the terms of
-NSI's Domain Name Dispute Policy ("Dispute Policy") which is
-incorporated herein by reference and made a part of this Registration
-Agreement. This Registration Agreement shall be accepted at the
-offices of NSI. 
-
-B. Fees and Payments.
-
-1) Registration or renewal (re-registration) date through March 31, 1998:
-Registrant agrees to pay a registration fee of One Hundred United States
-Dollars (US$100) as consideration for the registration of each new domain
-name or Fifty United States Dollars (US$50) to renew (re-register) an
-existing registration.
-2) Registration or renewal date on and after April 1, 1998:  Registrant
-agrees to pay a registration fee of Seventy United States Dollars (US$70) 
-as consideration for the registration of each new domain name or the 
-applicable renewal (re-registration) fee (currently Thirty-Five United 
-States Dollars (US$35)) at the time of renewal (re-registration).
-3) Period of Service:  The non-refundable fee covers a period of two (2)
-years for each new registration, and one (1) year for each renewal, 
-and includes any permitted modification(s) to the domain name record
-during the covered period.
-4) Payment:  Payment is due to Network Solutions within thirty (30) 
-days from the date of the invoice.
-
-C.	Dispute Policy. Registrant agrees, as a condition to
-submitting this Registration Agreement, and if the Registration
-Agreement is accepted by NSI, that the Registrant shall be bound by
-NSI's current Dispute Policy. The current version of the Dispute
-Policy may be found at the InterNIC Registration Services web site:
-"http://www.netsol.com/rs/dispute-policy.html". 
-
-D.	Dispute Policy Changes or Modifications. Registrant agrees
-that NSI, in its sole discretion, may change or modify the Dispute
-Policy, incorporated by reference herein, at any time. Registrant
-agrees that Registrant's maintaining the registration of a domain name
-after changes or modifications to the Dispute Policy become effective
-constitutes Registrant's continued acceptance of these changes or
-modifications. Registrant agrees that if Registrant considers any such
-changes or modifications to be unacceptable, Registrant may request
-that the domain name be deleted from the domain name database. 
-
-E.	Disputes. Registrant agrees that, if the registration of its
-domain name is challenged by any third party, the Registrant will be
-subject to the provisions specified in the Dispute Policy. 
-
-F.	Agents. Registrant agrees that if this Registration Agreement
-is completed by an agent for the Registrant, such as an ISP or
-Administrative Contact/Agent, the Registrant is nonetheless bound as a
-principal by all terms and conditions herein, including the Dispute
-Policy. 
-
-G.	Limitation of Liability. Registrant agrees that NSI shall have
-no liability to the Registrant for any loss Registrant may incur in
-connection with NSI's processing of this Registration Agreement, in
-connection with NSI's processing of any authorized modification to the
-domain name's record during the covered period, as a result of the
-Registrant's ISP's failure to pay either the initial registration fee
-or renewal fee, or as a result of the application of the provisions of
-the Dispute Policy. Registrant agrees that in no event shall the
-maximum liability of NSI under this Agreement for any matter exceed
-Five Hundred United States Dollars (US$500). 
-
-H.	Indemnity. Registrant agrees, in the event the Registration
-Agreement is accepted by NSI and a subsequent dispute arises with any
-third party, to indemnify and hold NSI harmless pursuant to the terms
-and conditions contained in the Dispute Policy. 
-
-I.	Breach. Registrant agrees that failure to abide by any
-provision of this Registration Agreement or the Dispute Policy may be
-considered by NSI to be a material breach and that NSI may provide a
-written notice, describing the breach, to the Registrant. If, within
-thirty (30) days of the date of mailing such notice, the Registrant
-fails to provide evidence, which is reasonably satisfactory to NSI,
-that it has not breached its obligations, then NSI may delete
-Registrant's registration of the domain name. Any such breach by a
-Registrant shall not be deemed to be excused simply because NSI did
-not act earlier in response to that, or any other, breach by the
-Registrant. 
-
-J.	No Guaranty. Registrant agrees that, by registration of a
-domain name, such registration does not confer immunity from objection
-to either the registration or use of the domain name. 
-
-K.	Warranty. Registrant warrants by submitting this Registration
-Agreement that, to the best of Registrant's knowledge and belief, the
-information submitted herein is true and correct, and that any future
-changes to this information will be provided to NSI in a timely manner
-according to the domain name modification procedures in place at that
-time. Breach of this warranty will constitute a material breach. 
-
-L.	Revocation. Registrant agrees that NSI may delete a
-Registrant's domain name if this Registration Agreement, or subsequent
-modification(s) thereto, contains false or misleading information, or
-conceals or omits any information NSI would likely consider material
-to its decision to approve this Registration Agreement. 
-
-M.	Right of Refusal. NSI, in its sole discretion, reserves the
-right to refuse to approve the Registration Agreement for any
-Registrant. Registrant agrees that the submission of this Registration
-Agreement does not obligate NSI to accept this Registration Agreement.
-Registrant agrees that NSI shall not be liable for loss or damages
-that may result from NSI's refusal to accept this Registration
-Agreement. 
-
-N.	Severability. Registrant agrees that the terms of this
-Registration Agreement are severable. If any term or provision is
-declared invalid, it shall not affect the remaining terms or
-provisions which shall continue to be binding. 
-
-O.	Entirety. Registrant agrees that this Registration Agreement
-and the Dispute Policy is the complete and exclusive agreement between
-Registrant and NSI regarding the registration of Registrant's domain
-name. This Registration Agreement and the Dispute Policy supersede all
-prior agreements and understandings, whether established by custom,
-practice, policy, or precedent. 
-
-P.	Governing Law. Registrant agrees that this Registration
-Agreement shall be governed in all respects by and construed in
-accordance with the laws of the Commonwealth of Virginia, United
-States of America. By submitting this Registration Agreement,
-Registrant consents to the exclusive jurisdiction and venue of the
-United States District Court for the Eastern District of Virginia,
-Alexandria Division. If there is no jurisdiction in the United States
-District Court for the Eastern District of Virginia, Alexandria
-Division, then jurisdiction shall be in the Circuit Court of Fairfax
-County, Fairfax, Virginia. 
-
-Q.	This is Domain Name Registration Agreement Version
-Number 4.0. This Registration Agreement is only for registrations
-under top-level domains: COM, ORG, NET, and EDU. By completing
-and submitting this Registration Agreement for consideration and
-acceptance by NSI, the Registrant agrees that he/she has read and
-agrees to be bound by A through P above. 
-
-
-Authorization
-0a.  (N)ew (M)odify (D)elete....:###action###
-0b.  Auth Scheme................: 
-0c.  Auth Info..................: 
-
-1.   Comments...................:###purpose###
-
-2.   Complete Domain Name.......:###domain###
-
-Organization Using Domain Name
-
-3a.  Organization Name..........:###company###
-###LOOP###
-3b.  Street Address.............:###address###
-###ENDLOOP###
-3c.  City.......................:###city###
-3d.  State......................:###state###
-3e.  Postal Code................:###zip###
-3f.  Country....................:###country###
-
-Administrative Contact
-4a.  NIC Handle (if known)......: 
-4b.  (I)ndividual (R)ole........:I
-4c.  Name (Last, First).........:###last###, ###first###
-4d.  Organization Name..........:###company###
-###LOOP###
-4e.  Street Address.............:###address###
-###ENDLOOP###
-4f.  City.......................:###city###
-4g.  State......................:###state###
-4h.  Postal Code................:###zip### 
-4i.  Country....................:###country###
-4j.  Phone Number...............:###daytime###
-4k.  Fax Number.................:###fax###
-4l.  E-Mailbox..................:###email###
-
-Technical Contact
-5a.  NIC Handle (if known)......:###tech_contact###
-5b.  (I)ndividual (R)ole........: 
-5c.  Name (Last, First).........: 
-5d.  Organization Name..........: 
-5e.  Street Address.............: 
-5f.  City.......................: 
-5g.  State......................: 
-5h.  Postal Code................: 
-5i.  Country....................: 
-5j.  Phone Number...............: 
-5k.  Fax Number.................: 
-5l.  E-Mailbox..................: 
-
-Billing Contact
-6a.  NIC Handle (if known)......: 
-6b.  (I)ndividual (R)ole........: 
-6c.  Name (Last, First).........: 
-6d.  Organization Name..........: 
-6e.  Street Address.............: 
-6f.  City.......................: 
-6g.  State......................:
-6h.  Postal Code................:
-6i.  Country....................:
-6j.  Phone Number...............:
-6k.  Fax Number.................: 
-6l.  E-Mailbox..................: 
-
-Prime Name Server
-7a.  Primary Server Hostname....:###primary###
-7b.  Primary Server Netaddress..:###primary_ip###
-
-Secondary Name Server(s)
-###LOOP###
-8a.  Secondary Server Hostname..:###secondary###
-8b.  Secondary Server Netaddress:###secondary_ip###
-###ENDLOOP###
-
-END OF AGREEMENT
-

commit 137be4be5a414a74f02d8081786d2031f0e7cc63
Merge: 7ba0aab 16542dc
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Feb 9 12:11:21 2015 -0800

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


commit 7ba0aabd949895bfd40f3fc8c7c173cec90b8c40
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sun Feb 8 13:47:14 2015 -0800

    credit

diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html
index b5ed451..232adcf 100644
--- a/httemplate/docs/credits.html
+++ b/httemplate/docs/credits.html
@@ -71,6 +71,7 @@ Dave Denney<BR>
 Serge Dolgov<BR>
 Scott Edwards<BR>
 Kenny Elliott<BR>
+Joshua Goodman<BR>
 Donald Greer<BR>
 Joel Griffiths<BR>
 Brian Grinstead<BR>
@@ -99,7 +100,9 @@ Stanislav Sinyagin<BR>
 Jason Spence<BR>
 James Switzer<BR>
 Audrey Tang<BR>
+Jason Terry<BR>
 Jason Thomas<BR>
+Rob Van Dam<BR>
 Jesse Vincent<BR>
 Johan Vromans<BR>
 Peter Wemm<BR>

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

Summary of changes:
 FS/FS/Conf.pm                                      |    8 +-
 FS/FS/Schema.pm                                    |    5 +-
 FS/FS/Template_Mixin.pm                            |   19 +-
 FS/FS/Upgrade.pm                                   |    5 +-
 FS/FS/cust_main.pm                                 |  127 ++++--
 FS/FS/cust_main/Location.pm                        |   22 +-
 FS/FS/cust_main/Search.pm                          |   44 --
 FS/FS/cust_payby.pm                                |  442 +++++++++++++++---
 FS/FS/o2m_Common.pm                                |    2 +
 FS/FS/part_event/Action/cust_bill_realtime_lec.pm  |   28 --
 FS/FS/part_event/Condition/has_cust_payby_auto.pm  |   43 ++
 .../part_event/Condition/hasnt_cust_payby_auto.pm  |   27 ++
 FS/FS/part_event/Condition/payby.pm                |   44 --
 FS/FS/part_event_condition.pm                      |   39 ++
 FS/FS/payby.pm                                     |   27 +-
 etc/domain-template.txt                            |  231 ----------
 httemplate/docs/credits.html                       |    3 +
 httemplate/edit/cust_main.cgi                      |   28 +-
 httemplate/edit/cust_main/billing.html             |  477 +-------------------
 httemplate/edit/cust_main/bottomfixup.js           |   45 +-
 httemplate/edit/cust_main/contact.html             |  171 -------
 httemplate/edit/cust_main/contacts_new.html        |    5 +-
 httemplate/edit/cust_main/cust_payby.html          |   56 +++
 httemplate/edit/elements/edit.html                 |    4 +-
 httemplate/edit/process/cust_main.cgi              |   57 +--
 httemplate/elements/menu.html                      |    1 +
 httemplate/elements/tr-coords.html                 |    2 +-
 httemplate/search/cust_main.html                   |    4 +-
 httemplate/search/cust_payby.html                  |   94 ++++
 httemplate/search/report_cust_main.html            |   37 --
 httemplate/search/report_cust_payby.html           |   57 +++
 httemplate/view/cust_main.cgi                      |   17 +-
 httemplate/view/cust_main/billing.html             |  194 +-------
 httemplate/view/cust_main/cust_payby.html          |  133 ++++++
 httemplate/view/cust_main/packages.html            |    4 +-
 init.d/freeside-init                               |    2 +-
 36 files changed, 1038 insertions(+), 1466 deletions(-)
 delete mode 100644 FS/FS/part_event/Action/cust_bill_realtime_lec.pm
 create mode 100644 FS/FS/part_event/Condition/has_cust_payby_auto.pm
 create mode 100644 FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm
 delete mode 100644 FS/FS/part_event/Condition/payby.pm
 delete mode 100644 etc/domain-template.txt
 delete mode 100644 httemplate/edit/cust_main/contact.html
 create mode 100644 httemplate/edit/cust_main/cust_payby.html
 create mode 100644 httemplate/search/cust_payby.html
 create mode 100644 httemplate/search/report_cust_payby.html
 create mode 100644 httemplate/view/cust_main/cust_payby.html




More information about the freeside-commits mailing list