1 #this is so kludgy i'd be embarassed if it wasn't cybercash's fault
3 use vars qw($paymentserversecret $paymentserverport $paymentserverhost);
8 use vars qw(@ISA $conf $lpr $processor $xaction $E_NoErr $invoice_from);
16 use Business::CreditCard;
17 use FS::UID qw( getotaker );
18 use FS::Record qw( qsearchs qsearch );
21 use FS::cust_bill_pkg;
24 use FS::cust_pay_batch;
25 use FS::part_referral;
26 use FS::cust_main_county;
28 use FS::cust_main_invoice;
30 @ISA = qw( FS::Record );
32 #ask FS::UID to run this stuff for us later
33 $FS::UID::callback{'FS::cust_main'} = sub {
35 $lpr = $conf->config('lpr');
36 $invoice_from = $conf->config('invoice_from');
38 if ( $conf->exists('cybercash3.2') ) {
40 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
41 require CCMckDirectLib3_2;
43 require CCMckErrno3_2;
44 #qw(MCKGetErrorMessage $E_NoErr);
45 import CCMckErrno3_2 qw($E_NoErr);
48 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
49 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
50 if ( $status != $E_NoErr ) {
51 warn "CCMckLib3_2::InitConfig error:\n";
52 foreach my $key (keys %CCMckLib3_2::Config) {
53 warn " $key => $CCMckLib3_2::Config{$key}\n"
55 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
56 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
58 $processor='cybercash3.2';
59 } elsif ( $conf->exists('cybercash2') ) {
62 ( $main::paymentserverhost,
63 $main::paymentserverport,
64 $main::paymentserversecret,
66 ) = $conf->config('cybercash2');
67 $processor='cybercash2';
73 FS::cust_main - Object methods for cust_main records
79 $record = new FS::cust_main \%hash;
80 $record = new FS::cust_main { 'column' => 'value' };
82 $error = $record->insert;
84 $error = $new_record->replace($old_record);
86 $error = $record->delete;
88 $error = $record->check;
90 @cust_pkg = $record->all_pkgs;
92 @cust_pkg = $record->ncancelled_pkgs;
94 $error = $record->bill;
95 $error = $record->bill %options;
96 $error = $record->bill 'time' => $time;
98 $error = $record->collect;
99 $error = $record->collect %options;
100 $error = $record->collect 'invoice_time' => $time,
101 'batch_card' => 'yes',
102 'report_badcard' => 'yes',
107 An FS::cust_main object represents a customer. FS::cust_main inherits from
108 FS::Record. The following fields are currently supported:
112 =item custnum - primary key (assigned automatically for new customers)
114 =item agentnum - agent (see L<FS::agent>)
116 =item refnum - referral (see L<FS::part_referral>)
122 =item ss - social security number (optional)
124 =item company - (optional)
128 =item address2 - (optional)
132 =item county - (optional, see L<FS::cust_main_county>)
134 =item state - (see L<FS::cust_main_county>)
138 =item country - (see L<FS::cust_main_county>)
140 =item daytime - phone (optional)
142 =item night - phone (optional)
144 =item payby - `CARD' (credit cards), `BILL' (billing), or `COMP' (free)
146 =item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
148 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
150 =item payname - name on card or billing name
152 =item tax - tax exempt, empty or `Y'
154 =item otaker - order taker (assigned automatically, see L<FS::UID>)
164 Creates a new customer. To add the customer to the database, see L<"insert">.
166 Note that this stores the hash reference, not a distinct copy of the hash it
167 points to. You can ask the object for a copy with the I<hash> method.
171 sub table { 'cust_main'; }
175 Adds this customer to the database. If there is an error, returns the error,
176 otherwise returns false.
180 Currently unimplemented. Maybe cancel all of this customer's
183 I don't remove the customer record in the database because there would then
184 be no record the customer ever existed (which is bad, no?)
189 return "Can't (yet?) delete customers.";
192 =item replace OLD_RECORD
194 Replaces the OLD_RECORD with this one in the database. If there is an error,
195 returns the error, otherwise returns false.
199 Checks all fields to make sure this is a valid customer record. If there is
200 an error, returns the error, otherwise returns false. Called by the insert
209 $self->ut_numbern('custnum')
210 || $self->ut_number('agentnum')
211 || $self->ut_number('refnum')
212 || $self->ut_textn('company')
213 || $self->ut_text('address1')
214 || $self->ut_textn('address2')
215 || $self->ut_text('city')
216 || $self->ut_textn('county')
217 || $self->ut_text('state')
218 || $self->ut_phonen('daytime')
219 || $self->ut_phonen('night')
220 || $self->ut_phonen('fax')
222 return $error if $error;
224 return "Unknown agent"
225 unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
227 return "Unknown referral"
228 unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
230 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
231 $self->setfield('last',$1);
233 $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name";
236 if ( $self->ss eq '' ) {
241 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
242 or return "Illegal social security number";
243 $self->ss("$1-$2-$3");
246 $self->country =~ /^(\w\w)$/ or return "Illegal country";
248 unless ( qsearchs('cust_main_county', {
249 'country' => $self->country,
252 return "Unknown state/county/country"
253 #" state ". $self->state. " county ". $self->county. " country ". $self->country
254 unless qsearchs('cust_main_county',{
255 'state' => $self->state,
256 'county' => $self->county,
257 'country' => $self->country,
261 $self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/ or return "Illegal zip";
264 $self->payby =~ /^(CARD|BILL|COMP)$/ or return "Illegal payby";
267 if ( $self->payby eq 'CARD' ) {
269 my $payinfo = $self->payinfo;
271 $payinfo =~ /^(\d{13,16})$/
272 or return "Illegal credit card number";
274 $self->payinfo($payinfo);
275 validate($payinfo) or return "Illegal credit card number";
276 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
278 } elsif ( $self->payby eq 'BILL' ) {
280 $error = $self->ut_textn('payinfo');
281 return "Illegal P.O. number" if $error;
283 } elsif ( $self->payby eq 'COMP' ) {
285 $error = $self->ut_textn('payinfo');
286 return "Illegal comp account issuer" if $error;
290 if ( $self->paydate eq '' ) {
291 return "Expriation date required" unless $self->payby eq 'BILL';
294 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
295 or return "Illegal expiration date";
296 if ( length($2) == 4 ) {
297 $self->paydate("$2-$1-01");
298 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
299 $self->paydate("19$2-$1-01");
301 $self->paydate("20$2-$1-01");
305 if ( $self->payname eq '' ) {
306 $self->payname( $self->first. " ". $self->getfield('last') );
308 $self->payname =~ /^([\w \,\.\-\']+)$/
309 or return "Illegal billing name";
313 $self->tax =~ /^(Y?)$/ or return "Illegal tax";
316 $self->otaker(getotaker);
323 Returns all packages (see L<FS::cust_pkg>) for this customer.
329 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
332 =item ncancelled_pkgs
334 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
338 sub ncancelled_pkgs {
340 qsearch( 'cust_pkg', {
341 'custnum' => $self->custnum,
348 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
349 conjunction with the collect method.
351 The only currently available option is `time', which bills the customer as if
352 it were that time. It is specified as a UNIX timestamp; see
353 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
356 If there is an error, returns the error, otherwise returns false.
361 my( $self, %options ) = @_;
362 my $time = $options{'time'} || time;
367 local $SIG{HUP} = 'IGNORE';
368 local $SIG{INT} = 'IGNORE';
369 local $SIG{QUIT} = 'IGNORE';
370 local $SIG{TERM} = 'IGNORE';
371 local $SIG{TSTP} = 'IGNORE';
372 local $SIG{PIPE} = 'IGNORE';
374 # find the packages which are due for billing, find out how much they are
375 # & generate invoice database.
377 my( $total_setup, $total_recur ) = ( 0, 0 );
380 foreach my $cust_pkg (
381 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
384 next if $cust_pkg->getfield('cancel');
386 #? to avoid use of uninitialized value errors... ?
387 $cust_pkg->setfield('bill', '')
388 unless defined($cust_pkg->bill);
390 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
392 #so we don't modify cust_pkg record unnecessarily
393 my $cust_pkg_mod_flag = 0;
394 my %hash = $cust_pkg->hash;
395 my $old_cust_pkg = new FS::cust_pkg \%hash;
399 unless ( $cust_pkg->setup ) {
400 my $setup_prog = $part_pkg->getfield('setup');
402 #$cpt->permit(); #what is necessary?
403 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
404 $setup = $cpt->reval($setup_prog);
405 unless ( defined($setup) ) {
406 warn "Error reval-ing part_pkg->setup pkgpart ",
407 $part_pkg->pkgpart, ": $@";
409 $cust_pkg->setfield('setup',$time);
410 $cust_pkg_mod_flag=1;
417 if ( $part_pkg->getfield('freq') > 0 &&
418 ! $cust_pkg->getfield('susp') &&
419 ( $cust_pkg->getfield('bill') || 0 ) < $time
421 my $recur_prog = $part_pkg->getfield('recur');
423 #$cpt->permit(); #what is necessary?
424 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
425 $recur = $cpt->reval($recur_prog);
426 unless ( defined($recur) ) {
427 warn "Error reval-ing part_pkg->recur pkgpart ",
428 $part_pkg->pkgpart, ": $@";
430 #change this bit to use Date::Manip?
431 #$sdate=$cust_pkg->bill || time;
432 #$sdate=$cust_pkg->bill || $time;
433 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
434 my ($sec,$min,$hour,$mday,$mon,$year) =
435 (localtime($sdate) )[0,1,2,3,4,5];
436 $mon += $part_pkg->getfield('freq');
437 until ( $mon < 12 ) { $mon -= 12; $year++; }
438 $cust_pkg->setfield('bill',
439 timelocal($sec,$min,$hour,$mday,$mon,$year));
440 $cust_pkg_mod_flag = 1;
444 warn "setup is undefinded" unless defined($setup);
445 warn "recur is undefinded" unless defined($recur);
446 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
448 if ( $cust_pkg_mod_flag ) {
449 $error=$cust_pkg->replace($old_cust_pkg);
450 if ( $error ) { #just in case
451 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
453 $setup = sprintf( "%.2f", $setup );
454 $recur = sprintf( "%.2f", $recur );
455 my $cust_bill_pkg = new FS::cust_bill_pkg ({
456 'pkgnum' => $cust_pkg->pkgnum,
460 'edate' => $cust_pkg->bill,
462 push @cust_bill_pkg, $cust_bill_pkg;
463 $total_setup += $setup;
464 $total_recur += $recur;
470 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
472 return '' if scalar(@cust_bill_pkg) == 0;
474 unless ( $self->getfield('tax') =~ /Y/i
475 || $self->getfield('payby') eq 'COMP'
477 my $cust_main_county = qsearchs('cust_main_county',{
478 'state' => $self->state,
479 'county' => $self->county,
480 'country' => $self->country,
482 my $tax = sprintf( "%.2f",
483 $charged * ( $cust_main_county->getfield('tax') / 100 )
485 $charged = sprintf( "%.2f", $charged+$tax );
487 my $cust_bill_pkg = new FS::cust_bill_pkg ({
494 push @cust_bill_pkg, $cust_bill_pkg;
497 my $cust_bill = new FS::cust_bill ( {
498 'custnum' => $self->getfield('custnum'),
500 'charged' => $charged,
502 $error = $cust_bill->insert;
503 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
505 die "Error creating cust_bill record: $error!\n",
506 "Check updated but unbilled packages for customer", $self->custnum, "\n"
509 my $invnum = $cust_bill->invnum;
511 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
512 $cust_bill_pkg->setfield( 'invnum', $invnum );
513 $error = $cust_bill_pkg->insert;
514 #shouldn't happen, but how else tohandle this?
515 die "Error creating cust_bill_pkg record: $error!\n",
516 "Check incomplete invoice ", $invnum, "\n"
523 =item collect OPTIONS
525 (Attempt to) collect money for this customer's outstanding invoices (see
526 L<FS::cust_bill>). Usually used after the bill method.
528 Depending on the value of `payby', this may print an invoice (`BILL'), charge
529 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
531 If there is an error, returns the error, otherwise returns false.
533 Currently available options are:
535 invoice_time - Use this time when deciding when to print invoices and
536 late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse>
537 for conversion functions.
539 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
540 default, cards are processed immediately, which will generate an error if
541 CyberCash is not installed.
543 report_badcard - Set this true if you want bad card transactions to
544 return an error. By default, they don't.
549 my( $self, %options ) = @_;
550 my $invoice_time = $options{'invoice_time'} || time;
552 my $total_owed = $self->balance;
553 return '' unless $total_owed > 0; #redundant?????
556 local $SIG{HUP} = 'IGNORE';
557 local $SIG{INT} = 'IGNORE';
558 local $SIG{QUIT} = 'IGNORE';
559 local $SIG{TERM} = 'IGNORE';
560 local $SIG{TSTP} = 'IGNORE';
561 local $SIG{PIPE} = 'IGNORE';
563 foreach my $cust_bill (
564 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
567 #this has to be before next's
568 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
572 $total_owed = sprintf( "%.2f", $total_owed - $amount );
574 next unless $cust_bill->owed > 0;
576 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
578 #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
580 next unless $amount > 0;
582 if ( $self->payby eq 'BILL' ) {
585 my $since = $invoice_time - ( $cust_bill->_date || 0 );
586 #warn "$invoice_time ", $cust_bill->_date, " $since";
587 if ( $since >= 0 #don't print future invoices
588 && ( $cust_bill->printed * 2592000 ) <= $since
591 #my @print_text = $cust_bill->print_text; #( date )
592 my @invoicing_list = $self->invoicing_list;
593 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
594 my $header = new Mail::Header ( [
595 "From: $invoice_from",
596 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
597 "Sender: $invoice_from",
598 "Reply-To: $invoice_from",
599 "Date: ". time2str("%a, %d %b %Y %X %z", time),
602 my $message = new Mail::Internet (
604 'Body' => [ $cust_bill->print_text ], #( date)
606 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
608 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
609 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
610 print LPR $cust_bill->print_text; #( date )
612 or die $! ? "Error closing $lpr: $!"
613 : "Exit status $? from $lpr";
616 my %hash = $cust_bill->hash;
618 my $new_cust_bill = new FS::cust_bill(\%hash);
619 my $error = $new_cust_bill->replace($cust_bill);
620 warn "Error updating $cust_bill->printed: $error" if $error;
624 } elsif ( $self->payby eq 'COMP' ) {
625 my $cust_pay = new FS::cust_pay ( {
626 'invnum' => $cust_bill->invnum,
630 'payinfo' => $self->payinfo,
633 my $error = $cust_pay->insert;
634 return 'Error COMPing invnum #' . $cust_bill->invnum .
635 ':' . $error if $error;
637 } elsif ( $self->payby eq 'CARD' ) {
639 if ( $options{'batch_card'} ne 'yes' ) {
641 return "Real time card processing not enabled!" unless $processor;
643 if ( $processor =~ /^cybercash/ ) {
645 #fix exp. date for cybercash
646 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
647 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
650 my $paybatch = $cust_bill->invnum.
651 '-' . time2str("%y%m%d%H%M%S", time);
653 my $payname = $self->payname ||
654 $self->getfield('first'). ' '. $self->getfield('last');
656 my $address = $self->address1;
657 $address .= ", ". $self->address2 if $self->address2;
659 my $country = 'USA' if $self->country eq 'US';
661 my @full_xaction = ( $xaction,
662 'Order-ID' => $paybatch,
663 'Amount' => "usd $amount",
664 'Card-Number' => $self->getfield('payinfo'),
665 'Card-Name' => $payname,
666 'Card-Address' => $address,
667 'Card-City' => $self->getfield('city'),
668 'Card-State' => $self->getfield('state'),
669 'Card-Zip' => $self->getfield('zip'),
670 'Card-Country' => $country,
675 if ( $processor eq 'cybercash2' ) {
676 $^W=0; #CCLib isn't -w safe, ugh!
677 %result = &CCLib::sendmserver(@full_xaction);
679 } elsif ( $processor eq 'cybercash3.2' ) {
680 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
682 return "Unkonwn real-time processor $processor\n";
685 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
686 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
687 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
688 my $cust_pay = new FS::cust_pay ( {
689 'invnum' => $cust_bill->invnum,
693 'payinfo' => $self->payinfo,
694 'paybatch' => "$processor:$paybatch",
696 my $error = $cust_pay->insert;
697 return 'Error applying payment, invnum #' .
698 $cust_bill->invnum. ':'. $error if $error;
699 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
700 || $options{'report_badcard'} ) {
701 return 'Cybercash error, invnum #' .
702 $cust_bill->invnum. ':'. $result{'MErrMsg'};
708 return "Unkonwn real-time processor $processor\n";
713 my $cust_pay_batch = new FS::Record ('cust_pay_batch', {
714 'invnum' => $cust_bill->getfield('invnum'),
715 'custnum' => $self->getfield('custnum'),
716 'last' => $self->getfield('last'),
717 'first' => $self->getfield('first'),
718 'address1' => $self->getfield('address1'),
719 'address2' => $self->getfield('address2'),
720 'city' => $self->getfield('city'),
721 'state' => $self->getfield('state'),
722 'zip' => $self->getfield('zip'),
723 'country' => $self->getfield('country'),
725 'cardnum' => $self->getfield('payinfo'),
726 'exp' => $self->getfield('paydate'),
727 'payname' => $self->getfield('payname'),
730 my $error = $cust_pay_batch->insert;
731 return "Error adding to cust_pay_batch: $error" if $error;
736 return "Unknown payment type ". $self->payby;
750 Returns the total owed for this customer on all invoices
751 (see L<FS::cust_bill>).
758 foreach my $cust_bill ( qsearch('cust_bill', {
759 'custnum' => $self->custnum,
761 $total_bill += $cust_bill->owed;
763 sprintf( "%.2f", $total_bill );
768 Returns the total credits (see L<FS::cust_credit>) for this customer.
774 my $total_credit = 0;
775 foreach my $cust_credit ( qsearch('cust_credit', {
776 'custnum' => $self->custnum,
778 $total_credit += $cust_credit->credited;
780 sprintf( "%.2f", $total_credit );
785 Returns the balance for this customer (total owed minus total credited).
791 sprintf( "%.2f", $self->total_owed - $self->total_credited );
794 =item invoicing_list [ ARRAYREF ]
796 If an arguement is given, sets these email addresses as invoice recipients
797 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
798 (except as warnings), so use check_invoicing_list first.
800 Returns a list of email addresses (with svcnum entries expanded).
802 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
803 check it without disturbing anything by passing nothing.
805 This interface may change in the future.
810 my( $self, $arrayref ) = @_;
812 my @cust_main_invoice =
813 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
814 foreach my $cust_main_invoice ( @cust_main_invoice ) {
815 #warn $cust_main_invoice->destnum;
816 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
817 #warn $cust_main_invoice->destnum;
818 my $error = $cust_main_invoice->delete;
819 warn $error if $error;
823 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
824 foreach my $address ( @{$arrayref} ) {
825 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
826 my $cust_main_invoice = new FS::cust_main_invoice ( {
827 'custnum' => $self->custnum,
830 my $error = $cust_main_invoice->insert;
831 warn $error if $error;
836 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
839 =item check_invoicing_list ARRAYREF
841 Checks these arguements as valid input for the invoicing_list method. If there
842 is an error, returns the error, otherwise returns false.
846 sub check_invoicing_list {
847 my( $self, $arrayref ) = @_;
848 foreach my $address ( @{$arrayref} ) {
849 my $cust_main_invoice = new FS::cust_main_invoice ( {
850 'custnum' => $self->custnum,
853 my $error = $self->custnum
854 ? $cust_main_invoice->check
855 : $cust_main_invoice->checkdest
857 return $error if $error;
866 $Id: cust_main.pm,v 1.13 1999-02-28 20:09:03 ivan Exp $
872 Bill and collect options should probably be passed as references instead of a
875 CyberCash v2 forces us to define some variables in package main.
877 There should probably be a configuration file with a list of allowed credit
880 CyberCash is the only processor.
882 No multiple currency support (probably a larger project than just this module).
886 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
887 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
888 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
889 L<FS::UID>, schema.html from the base documentation.
893 ivan@voicenet.com 97-jul-28
895 Changed to standard Business::CreditCard
897 EXPORT_OK FS::Record's hfields
898 removed unique calls and locking (not needed here now)
899 wrapped the (now) optional fields in if statements in sub check (notyetdone!)
900 ivan@sisd.com 97-nov-12
902 updated paydate with SQL-type date info ivan@sisd.com 98-mar-5
904 Added export of datasrc from UID.pm for Pg6.3
905 changed 'day' to 'daytime' because Pg6.3 reserves the day word
906 bmccane@maxbaud.net 98-apr-3
908 in ->create, s/svc_acct/cust_main/, now it should actually eliminate the
909 warnings it was meant to ivan@sisd.com 98-jul-16
911 don't require a phone number and allow '/' in company names
912 ivan@sisd.com 98-jul-18
914 use ut_ and rewrite &check, &*_pkgs ivan@sisd.com 98-sep-5
916 pod, merge with FS::Bill (about time!), total_owed, total_credited and balance
917 methods, cleaned collect method, source modifications no longer necessary to
918 enable cybercash, cybercash v3 support, don't need to import
919 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
921 $Log: cust_main.pm,v $
922 Revision 1.13 1999-02-28 20:09:03 ivan
923 allow spaces in zip codes, for (at least) canada. pointed out by
924 Clayton Gray <clgray@bcgroup.net>
926 Revision 1.12 1999/02/27 21:24:22 ivan
927 parse paydate correctly for cybercash
929 Revision 1.11 1999/02/23 08:09:27 ivan
930 beginnings of one-screen new customer entry and some other miscellania
932 Revision 1.10 1999/01/25 12:26:09 ivan
933 yet more mod_perl stuff
935 Revision 1.9 1999/01/18 09:22:41 ivan
936 changes to track email addresses for email invoicing
938 Revision 1.8 1998/12/29 11:59:39 ivan
939 mostly properly OO, some work still to be done with svc_ stuff
941 Revision 1.7 1998/12/16 09:58:52 ivan
942 library support for editing email invoice destinations (not in sub collect yet)
944 Revision 1.6 1998/11/18 09:01:42 ivan
947 Revision 1.5 1998/11/15 11:23:14 ivan
948 use FS::table_name for all searches to eliminate warnings,
949 emit state/county when they don't match
951 Revision 1.4 1998/11/15 05:30:48 ivan
952 bugfix for new config layout
954 Revision 1.3 1998/11/13 09:56:54 ivan
955 change configuration file layout to support multiple distinct databases (with
956 own set of config files, export, etc.)
958 Revision 1.2 1998/11/07 10:24:25 ivan
959 don't use depriciated FS::Bill and FS::Invoice, other miscellania