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
17 use Business::CreditCard;
18 use FS::UID qw( getotaker );
19 use FS::Record qw( qsearchs qsearch );
22 use FS::cust_bill_pkg;
25 use FS::cust_pay_batch;
26 use FS::part_referral;
27 use FS::cust_main_county;
29 use FS::cust_main_invoice;
31 @ISA = qw( FS::Record );
33 #ask FS::UID to run this stuff for us later
34 $FS::UID::callback{'FS::cust_main'} = sub {
36 $lpr = $conf->config('lpr');
37 $invoice_from = $conf->config('invoice_from');
38 $smtpmachine = $conf->config('smtpmachine');
40 if ( $conf->exists('cybercash3.2') ) {
42 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
43 require CCMckDirectLib3_2;
45 require CCMckErrno3_2;
46 #qw(MCKGetErrorMessage $E_NoErr);
47 import CCMckErrno3_2 qw($E_NoErr);
50 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
51 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
52 if ( $status != $E_NoErr ) {
53 warn "CCMckLib3_2::InitConfig error:\n";
54 foreach my $key (keys %CCMckLib3_2::Config) {
55 warn " $key => $CCMckLib3_2::Config{$key}\n"
57 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
58 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
60 $processor='cybercash3.2';
61 } elsif ( $conf->exists('cybercash2') ) {
64 ( $main::paymentserverhost,
65 $main::paymentserverport,
66 $main::paymentserversecret,
68 ) = $conf->config('cybercash2');
69 $processor='cybercash2';
75 FS::cust_main - Object methods for cust_main records
81 $record = new FS::cust_main \%hash;
82 $record = new FS::cust_main { 'column' => 'value' };
84 $error = $record->insert;
86 $error = $new_record->replace($old_record);
88 $error = $record->delete;
90 $error = $record->check;
92 @cust_pkg = $record->all_pkgs;
94 @cust_pkg = $record->ncancelled_pkgs;
96 $error = $record->bill;
97 $error = $record->bill %options;
98 $error = $record->bill 'time' => $time;
100 $error = $record->collect;
101 $error = $record->collect %options;
102 $error = $record->collect 'invoice_time' => $time,
103 'batch_card' => 'yes',
104 'report_badcard' => 'yes',
109 An FS::cust_main object represents a customer. FS::cust_main inherits from
110 FS::Record. The following fields are currently supported:
114 =item custnum - primary key (assigned automatically for new customers)
116 =item agentnum - agent (see L<FS::agent>)
118 =item refnum - referral (see L<FS::part_referral>)
124 =item ss - social security number (optional)
126 =item company - (optional)
130 =item address2 - (optional)
134 =item county - (optional, see L<FS::cust_main_county>)
136 =item state - (see L<FS::cust_main_county>)
140 =item country - (see L<FS::cust_main_county>)
142 =item daytime - phone (optional)
144 =item night - phone (optional)
146 =item payby - `CARD' (credit cards), `BILL' (billing), or `COMP' (free)
148 =item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
150 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
152 =item payname - name on card or billing name
154 =item tax - tax exempt, empty or `Y'
156 =item otaker - order taker (assigned automatically, see L<FS::UID>)
166 Creates a new customer. To add the customer to the database, see L<"insert">.
168 Note that this stores the hash reference, not a distinct copy of the hash it
169 points to. You can ask the object for a copy with the I<hash> method.
173 sub table { 'cust_main'; }
177 Adds this customer to the database. If there is an error, returns the error,
178 otherwise returns false.
182 Currently unimplemented. Maybe cancel all of this customer's
185 I don't remove the customer record in the database because there would then
186 be no record the customer ever existed (which is bad, no?)
191 return "Can't (yet?) delete customers.";
194 =item replace OLD_RECORD
196 Replaces the OLD_RECORD with this one in the database. If there is an error,
197 returns the error, otherwise returns false.
201 Checks all fields to make sure this is a valid customer record. If there is
202 an error, returns the error, otherwise returns false. Called by the insert
211 $self->ut_numbern('custnum')
212 || $self->ut_number('agentnum')
213 || $self->ut_number('refnum')
214 || $self->ut_textn('company')
215 || $self->ut_text('address1')
216 || $self->ut_textn('address2')
217 || $self->ut_text('city')
218 || $self->ut_textn('county')
219 || $self->ut_text('state')
220 || $self->ut_phonen('daytime')
221 || $self->ut_phonen('night')
222 || $self->ut_phonen('fax')
224 return $error if $error;
226 return "Unknown agent"
227 unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
229 return "Unknown referral"
230 unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
232 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
233 $self->setfield('last',$1);
235 $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name";
238 if ( $self->ss eq '' ) {
243 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
244 or return "Illegal social security number";
245 $self->ss("$1-$2-$3");
248 $self->country =~ /^(\w\w)$/ or return "Illegal country";
250 unless ( qsearchs('cust_main_county', {
251 'country' => $self->country,
254 return "Unknown state/county/country"
255 #" state ". $self->state. " county ". $self->county. " country ". $self->country
256 unless qsearchs('cust_main_county',{
257 'state' => $self->state,
258 'county' => $self->county,
259 'country' => $self->country,
263 $self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/ or return "Illegal zip";
266 $self->payby =~ /^(CARD|BILL|COMP)$/ or return "Illegal payby";
269 if ( $self->payby eq 'CARD' ) {
271 my $payinfo = $self->payinfo;
273 $payinfo =~ /^(\d{13,16})$/
274 or return "Illegal credit card number";
276 $self->payinfo($payinfo);
277 validate($payinfo) or return "Illegal credit card number";
278 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
280 } elsif ( $self->payby eq 'BILL' ) {
282 $error = $self->ut_textn('payinfo');
283 return "Illegal P.O. number" if $error;
285 } elsif ( $self->payby eq 'COMP' ) {
287 $error = $self->ut_textn('payinfo');
288 return "Illegal comp account issuer" if $error;
292 if ( $self->paydate eq '' ) {
293 return "Expriation date required" unless $self->payby eq 'BILL';
296 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
297 or return "Illegal expiration date";
298 if ( length($2) == 4 ) {
299 $self->paydate("$2-$1-01");
300 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
301 $self->paydate("19$2-$1-01");
303 $self->paydate("20$2-$1-01");
307 if ( $self->payname eq '' ) {
308 $self->payname( $self->first. " ". $self->getfield('last') );
310 $self->payname =~ /^([\w \,\.\-\']+)$/
311 or return "Illegal billing name";
315 $self->tax =~ /^(Y?)$/ or return "Illegal tax";
318 $self->otaker(getotaker);
325 Returns all packages (see L<FS::cust_pkg>) for this customer.
331 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
334 =item ncancelled_pkgs
336 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
340 sub ncancelled_pkgs {
342 qsearch( 'cust_pkg', {
343 'custnum' => $self->custnum,
350 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
351 conjunction with the collect method.
353 The only currently available option is `time', which bills the customer as if
354 it were that time. It is specified as a UNIX timestamp; see
355 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
358 If there is an error, returns the error, otherwise returns false.
363 my( $self, %options ) = @_;
364 my $time = $options{'time'} || time;
369 local $SIG{HUP} = 'IGNORE';
370 local $SIG{INT} = 'IGNORE';
371 local $SIG{QUIT} = 'IGNORE';
372 local $SIG{TERM} = 'IGNORE';
373 local $SIG{TSTP} = 'IGNORE';
374 local $SIG{PIPE} = 'IGNORE';
376 # find the packages which are due for billing, find out how much they are
377 # & generate invoice database.
379 my( $total_setup, $total_recur ) = ( 0, 0 );
382 foreach my $cust_pkg (
383 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
386 next if $cust_pkg->getfield('cancel');
388 #? to avoid use of uninitialized value errors... ?
389 $cust_pkg->setfield('bill', '')
390 unless defined($cust_pkg->bill);
392 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
394 #so we don't modify cust_pkg record unnecessarily
395 my $cust_pkg_mod_flag = 0;
396 my %hash = $cust_pkg->hash;
397 my $old_cust_pkg = new FS::cust_pkg \%hash;
401 unless ( $cust_pkg->setup ) {
402 my $setup_prog = $part_pkg->getfield('setup');
404 #$cpt->permit(); #what is necessary?
405 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
406 $setup = $cpt->reval($setup_prog);
407 unless ( defined($setup) ) {
408 warn "Error reval-ing part_pkg->setup pkgpart ",
409 $part_pkg->pkgpart, ": $@";
411 $cust_pkg->setfield('setup',$time);
412 $cust_pkg_mod_flag=1;
419 if ( $part_pkg->getfield('freq') > 0 &&
420 ! $cust_pkg->getfield('susp') &&
421 ( $cust_pkg->getfield('bill') || 0 ) < $time
423 my $recur_prog = $part_pkg->getfield('recur');
425 #$cpt->permit(); #what is necessary?
426 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
427 $recur = $cpt->reval($recur_prog);
428 unless ( defined($recur) ) {
429 warn "Error reval-ing part_pkg->recur pkgpart ",
430 $part_pkg->pkgpart, ": $@";
432 #change this bit to use Date::Manip?
433 #$sdate=$cust_pkg->bill || time;
434 #$sdate=$cust_pkg->bill || $time;
435 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
436 my ($sec,$min,$hour,$mday,$mon,$year) =
437 (localtime($sdate) )[0,1,2,3,4,5];
438 $mon += $part_pkg->getfield('freq');
439 until ( $mon < 12 ) { $mon -= 12; $year++; }
440 $cust_pkg->setfield('bill',
441 timelocal($sec,$min,$hour,$mday,$mon,$year));
442 $cust_pkg_mod_flag = 1;
446 warn "setup is undefinded" unless defined($setup);
447 warn "recur is undefinded" unless defined($recur);
448 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
450 if ( $cust_pkg_mod_flag ) {
451 $error=$cust_pkg->replace($old_cust_pkg);
452 if ( $error ) { #just in case
453 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
455 $setup = sprintf( "%.2f", $setup );
456 $recur = sprintf( "%.2f", $recur );
457 my $cust_bill_pkg = new FS::cust_bill_pkg ({
458 'pkgnum' => $cust_pkg->pkgnum,
462 'edate' => $cust_pkg->bill,
464 push @cust_bill_pkg, $cust_bill_pkg;
465 $total_setup += $setup;
466 $total_recur += $recur;
472 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
474 return '' if scalar(@cust_bill_pkg) == 0;
476 unless ( $self->getfield('tax') =~ /Y/i
477 || $self->getfield('payby') eq 'COMP'
479 my $cust_main_county = qsearchs('cust_main_county',{
480 'state' => $self->state,
481 'county' => $self->county,
482 'country' => $self->country,
484 my $tax = sprintf( "%.2f",
485 $charged * ( $cust_main_county->getfield('tax') / 100 )
487 $charged = sprintf( "%.2f", $charged+$tax );
489 my $cust_bill_pkg = new FS::cust_bill_pkg ({
496 push @cust_bill_pkg, $cust_bill_pkg;
499 my $cust_bill = new FS::cust_bill ( {
500 'custnum' => $self->getfield('custnum'),
502 'charged' => $charged,
504 $error = $cust_bill->insert;
505 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
507 die "Error creating cust_bill record: $error!\n",
508 "Check updated but unbilled packages for customer", $self->custnum, "\n"
511 my $invnum = $cust_bill->invnum;
513 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
514 $cust_bill_pkg->setfield( 'invnum', $invnum );
515 $error = $cust_bill_pkg->insert;
516 #shouldn't happen, but how else tohandle this?
517 die "Error creating cust_bill_pkg record: $error!\n",
518 "Check incomplete invoice ", $invnum, "\n"
525 =item collect OPTIONS
527 (Attempt to) collect money for this customer's outstanding invoices (see
528 L<FS::cust_bill>). Usually used after the bill method.
530 Depending on the value of `payby', this may print an invoice (`BILL'), charge
531 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
533 If there is an error, returns the error, otherwise returns false.
535 Currently available options are:
537 invoice_time - Use this time when deciding when to print invoices and
538 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>
539 for conversion functions.
541 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
542 default, cards are processed immediately, which will generate an error if
543 CyberCash is not installed.
545 report_badcard - Set this true if you want bad card transactions to
546 return an error. By default, they don't.
551 my( $self, %options ) = @_;
552 my $invoice_time = $options{'invoice_time'} || time;
554 my $total_owed = $self->balance;
555 return '' unless $total_owed > 0; #redundant?????
558 local $SIG{HUP} = 'IGNORE';
559 local $SIG{INT} = 'IGNORE';
560 local $SIG{QUIT} = 'IGNORE';
561 local $SIG{TERM} = 'IGNORE';
562 local $SIG{TSTP} = 'IGNORE';
563 local $SIG{PIPE} = 'IGNORE';
565 foreach my $cust_bill (
566 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
569 #this has to be before next's
570 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
574 $total_owed = sprintf( "%.2f", $total_owed - $amount );
576 next unless $cust_bill->owed > 0;
578 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
580 #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
582 next unless $amount > 0;
584 if ( $self->payby eq 'BILL' ) {
587 my $since = $invoice_time - ( $cust_bill->_date || 0 );
588 #warn "$invoice_time ", $cust_bill->_date, " $since";
589 if ( $since >= 0 #don't print future invoices
590 && ( $cust_bill->printed * 2592000 ) <= $since
593 #my @print_text = $cust_bill->print_text; #( date )
594 my @invoicing_list = $self->invoicing_list;
595 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
596 $ENV{SMTPHOSTS} = $smtpmachine;
597 $ENV{MAILADDRESS} = $invoice_from;
598 my $header = new Mail::Header ( [
599 "From: $invoice_from",
600 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
601 "Sender: $invoice_from",
602 "Reply-To: $invoice_from",
603 "Date: ". time2str("%a, %d %b %Y %X %z", time),
606 my $message = new Mail::Internet (
608 'Body' => [ $cust_bill->print_text ], #( date)
610 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
612 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
613 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
614 print LPR $cust_bill->print_text; #( date )
616 or die $! ? "Error closing $lpr: $!"
617 : "Exit status $? from $lpr";
620 my %hash = $cust_bill->hash;
622 my $new_cust_bill = new FS::cust_bill(\%hash);
623 my $error = $new_cust_bill->replace($cust_bill);
624 warn "Error updating $cust_bill->printed: $error" if $error;
628 } elsif ( $self->payby eq 'COMP' ) {
629 my $cust_pay = new FS::cust_pay ( {
630 'invnum' => $cust_bill->invnum,
634 'payinfo' => $self->payinfo,
637 my $error = $cust_pay->insert;
638 return 'Error COMPing invnum #' . $cust_bill->invnum .
639 ':' . $error if $error;
641 } elsif ( $self->payby eq 'CARD' ) {
643 if ( $options{'batch_card'} ne 'yes' ) {
645 return "Real time card processing not enabled!" unless $processor;
647 if ( $processor =~ /^cybercash/ ) {
649 #fix exp. date for cybercash
650 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
651 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
654 my $paybatch = $cust_bill->invnum.
655 '-' . time2str("%y%m%d%H%M%S", time);
657 my $payname = $self->payname ||
658 $self->getfield('first'). ' '. $self->getfield('last');
660 my $address = $self->address1;
661 $address .= ", ". $self->address2 if $self->address2;
663 my $country = 'USA' if $self->country eq 'US';
665 my @full_xaction = ( $xaction,
666 'Order-ID' => $paybatch,
667 'Amount' => "usd $amount",
668 'Card-Number' => $self->getfield('payinfo'),
669 'Card-Name' => $payname,
670 'Card-Address' => $address,
671 'Card-City' => $self->getfield('city'),
672 'Card-State' => $self->getfield('state'),
673 'Card-Zip' => $self->getfield('zip'),
674 'Card-Country' => $country,
679 if ( $processor eq 'cybercash2' ) {
680 $^W=0; #CCLib isn't -w safe, ugh!
681 %result = &CCLib::sendmserver(@full_xaction);
683 } elsif ( $processor eq 'cybercash3.2' ) {
684 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
686 return "Unkonwn real-time processor $processor\n";
689 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
690 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
691 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
692 my $cust_pay = new FS::cust_pay ( {
693 'invnum' => $cust_bill->invnum,
697 'payinfo' => $self->payinfo,
698 'paybatch' => "$processor:$paybatch",
700 my $error = $cust_pay->insert;
701 return 'Error applying payment, invnum #' .
702 $cust_bill->invnum. ':'. $error if $error;
703 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
704 || $options{'report_badcard'} ) {
705 return 'Cybercash error, invnum #' .
706 $cust_bill->invnum. ':'. $result{'MErrMsg'};
712 return "Unkonwn real-time processor $processor\n";
717 my $cust_pay_batch = new FS::Record ('cust_pay_batch', {
718 'invnum' => $cust_bill->getfield('invnum'),
719 'custnum' => $self->getfield('custnum'),
720 'last' => $self->getfield('last'),
721 'first' => $self->getfield('first'),
722 'address1' => $self->getfield('address1'),
723 'address2' => $self->getfield('address2'),
724 'city' => $self->getfield('city'),
725 'state' => $self->getfield('state'),
726 'zip' => $self->getfield('zip'),
727 'country' => $self->getfield('country'),
729 'cardnum' => $self->getfield('payinfo'),
730 'exp' => $self->getfield('paydate'),
731 'payname' => $self->getfield('payname'),
734 my $error = $cust_pay_batch->insert;
735 return "Error adding to cust_pay_batch: $error" if $error;
740 return "Unknown payment type ". $self->payby;
754 Returns the total owed for this customer on all invoices
755 (see L<FS::cust_bill>).
762 foreach my $cust_bill ( qsearch('cust_bill', {
763 'custnum' => $self->custnum,
765 $total_bill += $cust_bill->owed;
767 sprintf( "%.2f", $total_bill );
772 Returns the total credits (see L<FS::cust_credit>) for this customer.
778 my $total_credit = 0;
779 foreach my $cust_credit ( qsearch('cust_credit', {
780 'custnum' => $self->custnum,
782 $total_credit += $cust_credit->credited;
784 sprintf( "%.2f", $total_credit );
789 Returns the balance for this customer (total owed minus total credited).
795 sprintf( "%.2f", $self->total_owed - $self->total_credited );
798 =item invoicing_list [ ARRAYREF ]
800 If an arguement is given, sets these email addresses as invoice recipients
801 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
802 (except as warnings), so use check_invoicing_list first.
804 Returns a list of email addresses (with svcnum entries expanded).
806 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
807 check it without disturbing anything by passing nothing.
809 This interface may change in the future.
814 my( $self, $arrayref ) = @_;
816 my @cust_main_invoice =
817 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
818 foreach my $cust_main_invoice ( @cust_main_invoice ) {
819 #warn $cust_main_invoice->destnum;
820 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
821 #warn $cust_main_invoice->destnum;
822 my $error = $cust_main_invoice->delete;
823 warn $error if $error;
827 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
828 foreach my $address ( @{$arrayref} ) {
829 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
830 my $cust_main_invoice = new FS::cust_main_invoice ( {
831 'custnum' => $self->custnum,
834 my $error = $cust_main_invoice->insert;
835 warn $error if $error;
840 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
843 =item check_invoicing_list ARRAYREF
845 Checks these arguements as valid input for the invoicing_list method. If there
846 is an error, returns the error, otherwise returns false.
850 sub check_invoicing_list {
851 my( $self, $arrayref ) = @_;
852 foreach my $address ( @{$arrayref} ) {
853 my $cust_main_invoice = new FS::cust_main_invoice ( {
854 'custnum' => $self->custnum,
857 my $error = $self->custnum
858 ? $cust_main_invoice->check
859 : $cust_main_invoice->checkdest
861 return $error if $error;
870 $Id: cust_main.pm,v 1.14 1999-03-29 12:06:15 ivan Exp $
876 Bill and collect options should probably be passed as references instead of a
879 CyberCash v2 forces us to define some variables in package main.
881 There should probably be a configuration file with a list of allowed credit
884 CyberCash is the only processor.
886 No multiple currency support (probably a larger project than just this module).
890 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
891 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
892 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
893 L<FS::UID>, schema.html from the base documentation.
897 ivan@voicenet.com 97-jul-28
899 Changed to standard Business::CreditCard
901 EXPORT_OK FS::Record's hfields
902 removed unique calls and locking (not needed here now)
903 wrapped the (now) optional fields in if statements in sub check (notyetdone!)
904 ivan@sisd.com 97-nov-12
906 updated paydate with SQL-type date info ivan@sisd.com 98-mar-5
908 Added export of datasrc from UID.pm for Pg6.3
909 changed 'day' to 'daytime' because Pg6.3 reserves the day word
910 bmccane@maxbaud.net 98-apr-3
912 in ->create, s/svc_acct/cust_main/, now it should actually eliminate the
913 warnings it was meant to ivan@sisd.com 98-jul-16
915 don't require a phone number and allow '/' in company names
916 ivan@sisd.com 98-jul-18
918 use ut_ and rewrite &check, &*_pkgs ivan@sisd.com 98-sep-5
920 pod, merge with FS::Bill (about time!), total_owed, total_credited and balance
921 methods, cleaned collect method, source modifications no longer necessary to
922 enable cybercash, cybercash v3 support, don't need to import
923 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
925 $Log: cust_main.pm,v $
926 Revision 1.14 1999-03-29 12:06:15 ivan
927 buglet in email invoices fixed
929 Revision 1.13 1999/02/28 20:09:03 ivan
930 allow spaces in zip codes, for (at least) canada. pointed out by
931 Clayton Gray <clgray@bcgroup.net>
933 Revision 1.12 1999/02/27 21:24:22 ivan
934 parse paydate correctly for cybercash
936 Revision 1.11 1999/02/23 08:09:27 ivan
937 beginnings of one-screen new customer entry and some other miscellania
939 Revision 1.10 1999/01/25 12:26:09 ivan
940 yet more mod_perl stuff
942 Revision 1.9 1999/01/18 09:22:41 ivan
943 changes to track email addresses for email invoicing
945 Revision 1.8 1998/12/29 11:59:39 ivan
946 mostly properly OO, some work still to be done with svc_ stuff
948 Revision 1.7 1998/12/16 09:58:52 ivan
949 library support for editing email invoice destinations (not in sub collect yet)
951 Revision 1.6 1998/11/18 09:01:42 ivan
954 Revision 1.5 1998/11/15 11:23:14 ivan
955 use FS::table_name for all searches to eliminate warnings,
956 emit state/county when they don't match
958 Revision 1.4 1998/11/15 05:30:48 ivan
959 bugfix for new config layout
961 Revision 1.3 1998/11/13 09:56:54 ivan
962 change configuration file layout to support multiple distinct databases (with
963 own set of config files, export, etc.)
965 Revision 1.2 1998/11/07 10:24:25 ivan
966 don't use depriciated FS::Bill and FS::Invoice, other miscellania