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_textn('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 \,\.\-\']+)$/
233 or return "Illegal last name: ". $self->getfield('last');
234 $self->setfield('last',$1);
236 $self->first =~ /^([\w \,\.\-\']+)$/
237 or return "Illegal first name: ". $self->first;
240 if ( $self->ss eq '' ) {
245 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
246 or return "Illegal social security number: ". $self->ss;
247 $self->ss("$1-$2-$3");
250 $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country;
252 unless ( qsearchs('cust_main_county', {
253 'country' => $self->country,
256 return "Unknown state/county/country: ".
257 $self->state. "/". $self->county. "/". $self->country
258 unless qsearchs('cust_main_county',{
259 'state' => $self->state,
260 'county' => $self->county,
261 'country' => $self->country,
265 $self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/
266 or return "Illegal zip: ". $self->zip;
269 $self->payby =~ /^(CARD|BILL|COMP)$/
270 or return "Illegal payby: ". $self->payby;
273 if ( $self->payby eq 'CARD' ) {
275 my $payinfo = $self->payinfo;
277 $payinfo =~ /^(\d{13,16})$/
278 or return "Illegal credit card number: ". $self->payinfo;
280 $self->payinfo($payinfo);
282 or return "Illegal credit card number: ". $self->payinfo;
283 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
285 } elsif ( $self->payby eq 'BILL' ) {
287 $error = $self->ut_textn('payinfo');
288 return "Illegal P.O. number: ". $self->payinfo if $error;
290 } elsif ( $self->payby eq 'COMP' ) {
292 $error = $self->ut_textn('payinfo');
293 return "Illegal comp account issuer: ". $self->payinfo if $error;
297 if ( $self->paydate eq '' ) {
298 return "Expriation date required" unless $self->payby eq 'BILL';
301 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
302 or return "Illegal expiration date: ". $self->paydate;
303 if ( length($2) == 4 ) {
304 $self->paydate("$2-$1-01");
305 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
306 $self->paydate("19$2-$1-01");
308 $self->paydate("20$2-$1-01");
312 if ( $self->payname eq '' ) {
313 $self->payname( $self->first. " ". $self->getfield('last') );
315 $self->payname =~ /^([\w \,\.\-\']+)$/
316 or return "Illegal billing name: ". $self->payname;
320 $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax;
323 $self->otaker(getotaker);
330 Returns all packages (see L<FS::cust_pkg>) for this customer.
336 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
339 =item ncancelled_pkgs
341 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
345 sub ncancelled_pkgs {
347 qsearch( 'cust_pkg', {
348 'custnum' => $self->custnum,
355 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
356 conjunction with the collect method.
358 The only currently available option is `time', which bills the customer as if
359 it were that time. It is specified as a UNIX timestamp; see
360 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
363 If there is an error, returns the error, otherwise returns false.
368 my( $self, %options ) = @_;
369 my $time = $options{'time'} || time;
374 local $SIG{HUP} = 'IGNORE';
375 local $SIG{INT} = 'IGNORE';
376 local $SIG{QUIT} = 'IGNORE';
377 local $SIG{TERM} = 'IGNORE';
378 local $SIG{TSTP} = 'IGNORE';
379 local $SIG{PIPE} = 'IGNORE';
381 # find the packages which are due for billing, find out how much they are
382 # & generate invoice database.
384 my( $total_setup, $total_recur ) = ( 0, 0 );
387 foreach my $cust_pkg (
388 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
391 next if $cust_pkg->getfield('cancel');
393 #? to avoid use of uninitialized value errors... ?
394 $cust_pkg->setfield('bill', '')
395 unless defined($cust_pkg->bill);
397 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
399 #so we don't modify cust_pkg record unnecessarily
400 my $cust_pkg_mod_flag = 0;
401 my %hash = $cust_pkg->hash;
402 my $old_cust_pkg = new FS::cust_pkg \%hash;
406 unless ( $cust_pkg->setup ) {
407 my $setup_prog = $part_pkg->getfield('setup');
409 #$cpt->permit(); #what is necessary?
410 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
411 $setup = $cpt->reval($setup_prog);
412 unless ( defined($setup) ) {
413 warn "Error reval-ing part_pkg->setup pkgpart ",
414 $part_pkg->pkgpart, ": $@";
416 $cust_pkg->setfield('setup',$time);
417 $cust_pkg_mod_flag=1;
424 if ( $part_pkg->getfield('freq') > 0 &&
425 ! $cust_pkg->getfield('susp') &&
426 ( $cust_pkg->getfield('bill') || 0 ) < $time
428 my $recur_prog = $part_pkg->getfield('recur');
430 #$cpt->permit(); #what is necessary?
431 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
432 $recur = $cpt->reval($recur_prog);
433 unless ( defined($recur) ) {
434 warn "Error reval-ing part_pkg->recur pkgpart ",
435 $part_pkg->pkgpart, ": $@";
437 #change this bit to use Date::Manip?
438 #$sdate=$cust_pkg->bill || time;
439 #$sdate=$cust_pkg->bill || $time;
440 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
441 my ($sec,$min,$hour,$mday,$mon,$year) =
442 (localtime($sdate) )[0,1,2,3,4,5];
443 $mon += $part_pkg->getfield('freq');
444 until ( $mon < 12 ) { $mon -= 12; $year++; }
445 $cust_pkg->setfield('bill',
446 timelocal($sec,$min,$hour,$mday,$mon,$year));
447 $cust_pkg_mod_flag = 1;
451 warn "setup is undefinded" unless defined($setup);
452 warn "recur is undefinded" unless defined($recur);
453 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
455 if ( $cust_pkg_mod_flag ) {
456 $error=$cust_pkg->replace($old_cust_pkg);
457 if ( $error ) { #just in case
458 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
460 $setup = sprintf( "%.2f", $setup );
461 $recur = sprintf( "%.2f", $recur );
462 my $cust_bill_pkg = new FS::cust_bill_pkg ({
463 'pkgnum' => $cust_pkg->pkgnum,
467 'edate' => $cust_pkg->bill,
469 push @cust_bill_pkg, $cust_bill_pkg;
470 $total_setup += $setup;
471 $total_recur += $recur;
477 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
479 return '' if scalar(@cust_bill_pkg) == 0;
481 unless ( $self->getfield('tax') =~ /Y/i
482 || $self->getfield('payby') eq 'COMP'
484 my $cust_main_county = qsearchs('cust_main_county',{
485 'state' => $self->state,
486 'county' => $self->county,
487 'country' => $self->country,
489 my $tax = sprintf( "%.2f",
490 $charged * ( $cust_main_county->getfield('tax') / 100 )
492 $charged = sprintf( "%.2f", $charged+$tax );
494 my $cust_bill_pkg = new FS::cust_bill_pkg ({
501 push @cust_bill_pkg, $cust_bill_pkg;
504 my $cust_bill = new FS::cust_bill ( {
505 'custnum' => $self->getfield('custnum'),
507 'charged' => $charged,
509 $error = $cust_bill->insert;
510 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
512 die "Error creating cust_bill record: $error!\n",
513 "Check updated but unbilled packages for customer", $self->custnum, "\n"
516 my $invnum = $cust_bill->invnum;
518 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
519 $cust_bill_pkg->setfield( 'invnum', $invnum );
520 $error = $cust_bill_pkg->insert;
521 #shouldn't happen, but how else tohandle this?
522 die "Error creating cust_bill_pkg record: $error!\n",
523 "Check incomplete invoice ", $invnum, "\n"
530 =item collect OPTIONS
532 (Attempt to) collect money for this customer's outstanding invoices (see
533 L<FS::cust_bill>). Usually used after the bill method.
535 Depending on the value of `payby', this may print an invoice (`BILL'), charge
536 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
538 If there is an error, returns the error, otherwise returns false.
540 Currently available options are:
542 invoice_time - Use this time when deciding when to print invoices and
543 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>
544 for conversion functions.
546 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
547 default, cards are processed immediately, which will generate an error if
548 CyberCash is not installed.
550 report_badcard - Set this true if you want bad card transactions to
551 return an error. By default, they don't.
556 my( $self, %options ) = @_;
557 my $invoice_time = $options{'invoice_time'} || time;
559 my $total_owed = $self->balance;
560 return '' unless $total_owed > 0; #redundant?????
563 local $SIG{HUP} = 'IGNORE';
564 local $SIG{INT} = 'IGNORE';
565 local $SIG{QUIT} = 'IGNORE';
566 local $SIG{TERM} = 'IGNORE';
567 local $SIG{TSTP} = 'IGNORE';
568 local $SIG{PIPE} = 'IGNORE';
570 foreach my $cust_bill (
571 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
574 #this has to be before next's
575 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
579 $total_owed = sprintf( "%.2f", $total_owed - $amount );
581 next unless $cust_bill->owed > 0;
583 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
585 #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
587 next unless $amount > 0;
589 if ( $self->payby eq 'BILL' ) {
592 my $since = $invoice_time - ( $cust_bill->_date || 0 );
593 #warn "$invoice_time ", $cust_bill->_date, " $since";
594 if ( $since >= 0 #don't print future invoices
595 && ( $cust_bill->printed * 2592000 ) <= $since
598 #my @print_text = $cust_bill->print_text; #( date )
599 my @invoicing_list = $self->invoicing_list;
600 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
601 $ENV{SMTPHOSTS} = $smtpmachine;
602 $ENV{MAILADDRESS} = $invoice_from;
603 my $header = new Mail::Header ( [
604 "From: $invoice_from",
605 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
606 "Sender: $invoice_from",
607 "Reply-To: $invoice_from",
608 "Date: ". time2str("%a, %d %b %Y %X %z", time),
611 my $message = new Mail::Internet (
613 'Body' => [ $cust_bill->print_text ], #( date)
615 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
617 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
618 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
619 print LPR $cust_bill->print_text; #( date )
621 or die $! ? "Error closing $lpr: $!"
622 : "Exit status $? from $lpr";
625 my %hash = $cust_bill->hash;
627 my $new_cust_bill = new FS::cust_bill(\%hash);
628 my $error = $new_cust_bill->replace($cust_bill);
629 warn "Error updating $cust_bill->printed: $error" if $error;
633 } elsif ( $self->payby eq 'COMP' ) {
634 my $cust_pay = new FS::cust_pay ( {
635 'invnum' => $cust_bill->invnum,
639 'payinfo' => $self->payinfo,
642 my $error = $cust_pay->insert;
643 return 'Error COMPing invnum #' . $cust_bill->invnum .
644 ':' . $error if $error;
646 } elsif ( $self->payby eq 'CARD' ) {
648 if ( $options{'batch_card'} ne 'yes' ) {
650 return "Real time card processing not enabled!" unless $processor;
652 if ( $processor =~ /^cybercash/ ) {
654 #fix exp. date for cybercash
655 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
656 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
659 my $paybatch = $cust_bill->invnum.
660 '-' . time2str("%y%m%d%H%M%S", time);
662 my $payname = $self->payname ||
663 $self->getfield('first'). ' '. $self->getfield('last');
665 my $address = $self->address1;
666 $address .= ", ". $self->address2 if $self->address2;
668 my $country = 'USA' if $self->country eq 'US';
670 my @full_xaction = ( $xaction,
671 'Order-ID' => $paybatch,
672 'Amount' => "usd $amount",
673 'Card-Number' => $self->getfield('payinfo'),
674 'Card-Name' => $payname,
675 'Card-Address' => $address,
676 'Card-City' => $self->getfield('city'),
677 'Card-State' => $self->getfield('state'),
678 'Card-Zip' => $self->getfield('zip'),
679 'Card-Country' => $country,
684 if ( $processor eq 'cybercash2' ) {
685 $^W=0; #CCLib isn't -w safe, ugh!
686 %result = &CCLib::sendmserver(@full_xaction);
688 } elsif ( $processor eq 'cybercash3.2' ) {
689 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
691 return "Unkonwn real-time processor $processor\n";
694 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
695 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
696 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
697 my $cust_pay = new FS::cust_pay ( {
698 'invnum' => $cust_bill->invnum,
702 'payinfo' => $self->payinfo,
703 'paybatch' => "$processor:$paybatch",
705 my $error = $cust_pay->insert;
706 return 'Error applying payment, invnum #' .
707 $cust_bill->invnum. ':'. $error if $error;
708 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
709 || $options{'report_badcard'} ) {
710 return 'Cybercash error, invnum #' .
711 $cust_bill->invnum. ':'. $result{'MErrMsg'};
717 return "Unkonwn real-time processor $processor\n";
722 my $cust_pay_batch = new FS::Record ('cust_pay_batch', {
723 'invnum' => $cust_bill->getfield('invnum'),
724 'custnum' => $self->getfield('custnum'),
725 'last' => $self->getfield('last'),
726 'first' => $self->getfield('first'),
727 'address1' => $self->getfield('address1'),
728 'address2' => $self->getfield('address2'),
729 'city' => $self->getfield('city'),
730 'state' => $self->getfield('state'),
731 'zip' => $self->getfield('zip'),
732 'country' => $self->getfield('country'),
734 'cardnum' => $self->getfield('payinfo'),
735 'exp' => $self->getfield('paydate'),
736 'payname' => $self->getfield('payname'),
739 my $error = $cust_pay_batch->insert;
740 return "Error adding to cust_pay_batch: $error" if $error;
745 return "Unknown payment type ". $self->payby;
759 Returns the total owed for this customer on all invoices
760 (see L<FS::cust_bill>).
767 foreach my $cust_bill ( qsearch('cust_bill', {
768 'custnum' => $self->custnum,
770 $total_bill += $cust_bill->owed;
772 sprintf( "%.2f", $total_bill );
777 Returns the total credits (see L<FS::cust_credit>) for this customer.
783 my $total_credit = 0;
784 foreach my $cust_credit ( qsearch('cust_credit', {
785 'custnum' => $self->custnum,
787 $total_credit += $cust_credit->credited;
789 sprintf( "%.2f", $total_credit );
794 Returns the balance for this customer (total owed minus total credited).
800 sprintf( "%.2f", $self->total_owed - $self->total_credited );
803 =item invoicing_list [ ARRAYREF ]
805 If an arguement is given, sets these email addresses as invoice recipients
806 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
807 (except as warnings), so use check_invoicing_list first.
809 Returns a list of email addresses (with svcnum entries expanded).
811 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
812 check it without disturbing anything by passing nothing.
814 This interface may change in the future.
819 my( $self, $arrayref ) = @_;
821 my @cust_main_invoice;
822 if ( $self->custnum ) {
824 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
826 @cust_main_invoice = ();
828 foreach my $cust_main_invoice ( @cust_main_invoice ) {
829 #warn $cust_main_invoice->destnum;
830 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
831 #warn $cust_main_invoice->destnum;
832 my $error = $cust_main_invoice->delete;
833 warn $error if $error;
836 if ( $self->custnum ) {
838 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
840 @cust_main_invoice = ();
842 foreach my $address ( @{$arrayref} ) {
843 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
844 my $cust_main_invoice = new FS::cust_main_invoice ( {
845 'custnum' => $self->custnum,
848 my $error = $cust_main_invoice->insert;
849 warn $error if $error;
853 if ( $self->custnum ) {
855 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
861 =item check_invoicing_list ARRAYREF
863 Checks these arguements as valid input for the invoicing_list method. If there
864 is an error, returns the error, otherwise returns false.
868 sub check_invoicing_list {
869 my( $self, $arrayref ) = @_;
870 foreach my $address ( @{$arrayref} ) {
871 my $cust_main_invoice = new FS::cust_main_invoice ( {
872 'custnum' => $self->custnum,
875 my $error = $self->custnum
876 ? $cust_main_invoice->check
877 : $cust_main_invoice->checkdest
879 return $error if $error;
888 $Id: cust_main.pm,v 1.21 1999-04-14 07:47:53 ivan Exp $
894 Bill and collect options should probably be passed as references instead of a
897 CyberCash v2 forces us to define some variables in package main.
899 There should probably be a configuration file with a list of allowed credit
902 CyberCash is the only processor.
904 No multiple currency support (probably a larger project than just this module).
908 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
909 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
910 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
911 L<FS::UID>, schema.html from the base documentation.
915 ivan@voicenet.com 97-jul-28
917 Changed to standard Business::CreditCard
919 EXPORT_OK FS::Record's hfields
920 removed unique calls and locking (not needed here now)
921 wrapped the (now) optional fields in if statements in sub check (notyetdone!)
922 ivan@sisd.com 97-nov-12
924 updated paydate with SQL-type date info ivan@sisd.com 98-mar-5
926 Added export of datasrc from UID.pm for Pg6.3
927 changed 'day' to 'daytime' because Pg6.3 reserves the day word
928 bmccane@maxbaud.net 98-apr-3
930 in ->create, s/svc_acct/cust_main/, now it should actually eliminate the
931 warnings it was meant to ivan@sisd.com 98-jul-16
933 don't require a phone number and allow '/' in company names
934 ivan@sisd.com 98-jul-18
936 use ut_ and rewrite &check, &*_pkgs ivan@sisd.com 98-sep-5
938 pod, merge with FS::Bill (about time!), total_owed, total_credited and balance
939 methods, cleaned collect method, source modifications no longer necessary to
940 enable cybercash, cybercash v3 support, don't need to import
941 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
943 $Log: cust_main.pm,v $
944 Revision 1.21 1999-04-14 07:47:53 ivan
947 Revision 1.20 1999/04/10 08:35:14 ivan
948 say what the unknown state/county/country are!
950 Revision 1.19 1999/04/10 07:38:06 ivan
951 _all_ check stuff with illegal data return the bad data too, to help debugging
953 Revision 1.18 1999/04/10 06:54:11 ivan
956 Revision 1.17 1999/04/10 05:27:38 ivan
957 display an illegal payby, to assist importing
959 Revision 1.16 1999/04/07 14:32:19 ivan
960 more &invoicing_list logic to skip searches when there is no custnum
962 Revision 1.15 1999/04/07 13:41:54 ivan
963 in &invoicing_list, don't search if there's no custnum yet
965 Revision 1.14 1999/03/29 12:06:15 ivan
966 buglet in email invoices fixed
968 Revision 1.13 1999/02/28 20:09:03 ivan
969 allow spaces in zip codes, for (at least) canada. pointed out by
970 Clayton Gray <clgray@bcgroup.net>
972 Revision 1.12 1999/02/27 21:24:22 ivan
973 parse paydate correctly for cybercash
975 Revision 1.11 1999/02/23 08:09:27 ivan
976 beginnings of one-screen new customer entry and some other miscellania
978 Revision 1.10 1999/01/25 12:26:09 ivan
979 yet more mod_perl stuff
981 Revision 1.9 1999/01/18 09:22:41 ivan
982 changes to track email addresses for email invoicing
984 Revision 1.8 1998/12/29 11:59:39 ivan
985 mostly properly OO, some work still to be done with svc_ stuff
987 Revision 1.7 1998/12/16 09:58:52 ivan
988 library support for editing email invoice destinations (not in sub collect yet)
990 Revision 1.6 1998/11/18 09:01:42 ivan
993 Revision 1.5 1998/11/15 11:23:14 ivan
994 use FS::table_name for all searches to eliminate warnings,
995 emit state/county when they don't match
997 Revision 1.4 1998/11/15 05:30:48 ivan
998 bugfix for new config layout
1000 Revision 1.3 1998/11/13 09:56:54 ivan
1001 change configuration file layout to support multiple distinct databases (with
1002 own set of config files, export, etc.)
1004 Revision 1.2 1998/11/07 10:24:25 ivan
1005 don't use depriciated FS::Bill and FS::Invoice, other miscellania