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;
30 use FS::prepay_credit;
32 @ISA = qw( FS::Record );
37 #ask FS::UID to run this stuff for us later
38 $FS::UID::callback{'FS::cust_main'} = sub {
40 $lpr = $conf->config('lpr');
41 $invoice_from = $conf->config('invoice_from');
42 $smtpmachine = $conf->config('smtpmachine');
44 if ( $conf->exists('cybercash3.2') ) {
46 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
47 require CCMckDirectLib3_2;
49 require CCMckErrno3_2;
50 #qw(MCKGetErrorMessage $E_NoErr);
51 import CCMckErrno3_2 qw($E_NoErr);
54 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
55 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
56 if ( $status != $E_NoErr ) {
57 warn "CCMckLib3_2::InitConfig error:\n";
58 foreach my $key (keys %CCMckLib3_2::Config) {
59 warn " $key => $CCMckLib3_2::Config{$key}\n"
61 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
62 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
64 $processor='cybercash3.2';
65 } elsif ( $conf->exists('cybercash2') ) {
68 ( $main::paymentserverhost,
69 $main::paymentserverport,
70 $main::paymentserversecret,
72 ) = $conf->config('cybercash2');
73 $processor='cybercash2';
79 FS::cust_main - Object methods for cust_main records
85 $record = new FS::cust_main \%hash;
86 $record = new FS::cust_main { 'column' => 'value' };
88 $error = $record->insert;
90 $error = $new_record->replace($old_record);
92 $error = $record->delete;
94 $error = $record->check;
96 @cust_pkg = $record->all_pkgs;
98 @cust_pkg = $record->ncancelled_pkgs;
100 $error = $record->bill;
101 $error = $record->bill %options;
102 $error = $record->bill 'time' => $time;
104 $error = $record->collect;
105 $error = $record->collect %options;
106 $error = $record->collect 'invoice_time' => $time,
107 'batch_card' => 'yes',
108 'report_badcard' => 'yes',
113 An FS::cust_main object represents a customer. FS::cust_main inherits from
114 FS::Record. The following fields are currently supported:
118 =item custnum - primary key (assigned automatically for new customers)
120 =item agentnum - agent (see L<FS::agent>)
122 =item refnum - referral (see L<FS::part_referral>)
128 =item ss - social security number (optional)
130 =item company - (optional)
134 =item address2 - (optional)
138 =item county - (optional, see L<FS::cust_main_county>)
140 =item state - (see L<FS::cust_main_county>)
144 =item country - (see L<FS::cust_main_county>)
146 =item daytime - phone (optional)
148 =item night - phone (optional)
150 =item fax - phone (optional)
152 =item payby - `CARD' (credit cards), `BILL' (billing), `COMP' (free), or `PREPAY' (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to BILL)
154 =item payinfo - card number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
156 =item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
158 =item payname - name on card or billing name
160 =item tax - tax exempt, empty or `Y'
162 =item otaker - order taker (assigned automatically, see L<FS::UID>)
172 Creates a new customer. To add the customer to the database, see L<"insert">.
174 Note that this stores the hash reference, not a distinct copy of the hash it
175 points to. You can ask the object for a copy with the I<hash> method.
179 sub table { 'cust_main'; }
183 Adds this customer to the database. If there is an error, returns the error,
184 otherwise returns false.
192 if ( $self->payby eq 'PREPAY' ) {
193 $self->payby('BILL');
197 local $SIG{HUP} = 'IGNORE';
198 local $SIG{INT} = 'IGNORE';
199 local $SIG{QUIT} = 'IGNORE';
200 local $SIG{TERM} = 'IGNORE';
201 local $SIG{TSTP} = 'IGNORE';
202 local $SIG{PIPE} = 'IGNORE';
204 my $error = $self->SUPER::insert;
205 return $error if $error;
209 qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
210 warn "WARNING: can't find pre-found prepay_credit: ". $self->payinfo
211 unless $prepay_credit;
212 my $amount = $prepay_credit->amount;
213 my $error = $prepay_credit->delete;
215 warn "WARNING: can't delete prepay_credit: ". $self->payinfo;
217 my $cust_credit = new FS::cust_credit {
218 'custnum' => $self->custnum,
221 my $error = $cust_credit->insert;
222 warn "WARNING: error inserting cust_credit for prepay_credit: $error"
232 =item delete NEW_CUSTNUM
234 This deletes the customer. If there is an error, returns the error, otherwise
237 This will completely remove all traces of the customer record. This is not
238 what you want when a customer cancels service; for that, cancel all of the
239 customer's packages (see L<FS::cust_pkg/cancel>).
241 If the customer has any packages, you need to pass a new (valid) customer
242 number for those packages to be transferred to.
244 You can't delete a customer with invoices (see L<FS::cust_bill>),
245 or credits (see L<FS::cust_credit>).
252 if ( qsearch( 'cust_bill', { 'custnum' => $self->custnum } ) ) {
253 return "Can't delete a customer with invoices";
255 if ( qsearch( 'cust_credit', { 'custnum' => $self->custnum } ) ) {
256 return "Can't delete a customer with credits";
259 local $SIG{HUP} = 'IGNORE';
260 local $SIG{INT} = 'IGNORE';
261 local $SIG{QUIT} = 'IGNORE';
262 local $SIG{TERM} = 'IGNORE';
263 local $SIG{TSTP} = 'IGNORE';
264 local $SIG{PIPE} = 'IGNORE';
266 my @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum } );
268 my $new_custnum = shift;
269 return "Invalid new customer number: $new_custnum"
270 unless qsearchs( 'cust_main', { 'custnum' => $new_custnum } );
271 foreach my $cust_pkg ( @cust_pkg ) {
272 my %hash = $cust_pkg->hash;
273 $hash{'custnum'} = $new_custnum;
274 my $new_cust_pkg = new FS::cust_pkg ( \%hash );
275 my $error = $new_cust_pkg->replace($cust_pkg);
276 return $error if $error;
279 foreach my $cust_main_invoice (
280 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } )
282 my $error = $cust_main_invoice->delete;
283 return $error if $error;
286 $self->SUPER::delete;
289 =item replace OLD_RECORD
291 Replaces the OLD_RECORD with this one in the database. If there is an error,
292 returns the error, otherwise returns false.
296 Checks all fields to make sure this is a valid customer record. If there is
297 an error, returns the error, otherwise returns false. Called by the insert
306 $self->ut_numbern('custnum')
307 || $self->ut_number('agentnum')
308 || $self->ut_number('refnum')
309 || $self->ut_textn('company')
310 || $self->ut_text('address1')
311 || $self->ut_textn('address2')
312 || $self->ut_text('city')
313 || $self->ut_textn('county')
314 || $self->ut_textn('state')
315 || $self->ut_phonen('daytime')
316 || $self->ut_phonen('night')
317 || $self->ut_phonen('fax')
319 return $error if $error;
321 return "Unknown agent"
322 unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
324 return "Unknown referral"
325 unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
327 $self->getfield('last') =~ /^([\w \,\.\-\']+)$/
328 or return "Illegal last name: ". $self->getfield('last');
329 $self->setfield('last',$1);
331 $self->first =~ /^([\w \,\.\-\']+)$/
332 or return "Illegal first name: ". $self->first;
335 if ( $self->ss eq '' ) {
340 $ss =~ /^(\d{3})(\d{2})(\d{4})$/
341 or return "Illegal social security number: ". $self->ss;
342 $self->ss("$1-$2-$3");
345 $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country;
347 unless ( qsearchs('cust_main_county', {
348 'country' => $self->country,
351 return "Unknown state/county/country: ".
352 $self->state. "/". $self->county. "/". $self->country
353 unless qsearchs('cust_main_county',{
354 'state' => $self->state,
355 'county' => $self->county,
356 'country' => $self->country,
360 $self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/
361 or return "Illegal zip: ". $self->zip;
364 $self->payby =~ /^(CARD|BILL|COMP|PREPAY)$/
365 or return "Illegal payby: ". $self->payby;
368 if ( $self->payby eq 'CARD' ) {
370 my $payinfo = $self->payinfo;
372 $payinfo =~ /^(\d{13,16})$/
373 or return "Illegal credit card number: ". $self->payinfo;
375 $self->payinfo($payinfo);
377 or return "Illegal credit card number: ". $self->payinfo;
378 return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
380 } elsif ( $self->payby eq 'BILL' ) {
382 $error = $self->ut_textn('payinfo');
383 return "Illegal P.O. number: ". $self->payinfo if $error;
385 } elsif ( $self->payby eq 'COMP' ) {
387 $error = $self->ut_textn('payinfo');
388 return "Illegal comp account issuer: ". $self->payinfo if $error;
390 } elsif ( $self->payby eq 'PREPAY' ) {
392 my $payinfo = $self->payinfo;
393 $payinfo =~ s/\W//g; #anything else would just confuse things
394 $self->payinfo($payinfo);
395 $error = $self->ut_alpha('payinfo');
396 return "Illegal prepayment identifier: ". $self->payinfo if $error;
397 return "Unknown prepayment identifier"
398 unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
402 if ( $self->paydate eq '' || $self->paydate eq '-' ) {
403 return "Expriation date required"
404 unless $self->payby eq 'BILL' || $self->payby eq 'PREPAY';
407 $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/
408 or return "Illegal expiration date: ". $self->paydate;
409 if ( length($2) == 4 ) {
410 $self->paydate("$2-$1-01");
411 } elsif ( $2 > 97 ) { #should pry change to check for "this year"
412 $self->paydate("19$2-$1-01");
414 $self->paydate("20$2-$1-01");
418 if ( $self->payname eq '' ) {
419 $self->payname( $self->first. " ". $self->getfield('last') );
421 $self->payname =~ /^([\w \,\.\-\']+)$/
422 or return "Illegal billing name: ". $self->payname;
426 $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax;
429 $self->otaker(getotaker);
436 Returns all packages (see L<FS::cust_pkg>) for this customer.
442 qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
445 =item ncancelled_pkgs
447 Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
451 sub ncancelled_pkgs {
453 @{ [ # force list context
454 qsearch( 'cust_pkg', {
455 'custnum' => $self->custnum,
458 qsearch( 'cust_pkg', {
459 'custnum' => $self->custnum,
467 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
468 conjunction with the collect method.
470 The only currently available option is `time', which bills the customer as if
471 it were that time. It is specified as a UNIX timestamp; see
472 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
475 If there is an error, returns the error, otherwise returns false.
480 my( $self, %options ) = @_;
481 my $time = $options{'time'} || time;
486 local $SIG{HUP} = 'IGNORE';
487 local $SIG{INT} = 'IGNORE';
488 local $SIG{QUIT} = 'IGNORE';
489 local $SIG{TERM} = 'IGNORE';
490 local $SIG{TSTP} = 'IGNORE';
491 local $SIG{PIPE} = 'IGNORE';
493 # find the packages which are due for billing, find out how much they are
494 # & generate invoice database.
496 my( $total_setup, $total_recur ) = ( 0, 0 );
499 foreach my $cust_pkg (
500 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
503 next if $cust_pkg->getfield('cancel');
505 #? to avoid use of uninitialized value errors... ?
506 $cust_pkg->setfield('bill', '')
507 unless defined($cust_pkg->bill);
509 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
511 #so we don't modify cust_pkg record unnecessarily
512 my $cust_pkg_mod_flag = 0;
513 my %hash = $cust_pkg->hash;
514 my $old_cust_pkg = new FS::cust_pkg \%hash;
518 unless ( $cust_pkg->setup ) {
519 my $setup_prog = $part_pkg->getfield('setup');
521 #$cpt->permit(); #what is necessary?
522 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
523 $setup = $cpt->reval($setup_prog);
524 unless ( defined($setup) ) {
525 warn "Error reval-ing part_pkg->setup pkgpart ",
526 $part_pkg->pkgpart, ": $@";
528 $cust_pkg->setfield('setup',$time);
529 $cust_pkg_mod_flag=1;
536 if ( $part_pkg->getfield('freq') > 0 &&
537 ! $cust_pkg->getfield('susp') &&
538 ( $cust_pkg->getfield('bill') || 0 ) < $time
540 my $recur_prog = $part_pkg->getfield('recur');
542 #$cpt->permit(); #what is necessary?
543 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
544 $recur = $cpt->reval($recur_prog);
545 unless ( defined($recur) ) {
546 warn "Error reval-ing part_pkg->recur pkgpart ",
547 $part_pkg->pkgpart, ": $@";
549 #change this bit to use Date::Manip?
550 #$sdate=$cust_pkg->bill || time;
551 #$sdate=$cust_pkg->bill || $time;
552 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
553 my ($sec,$min,$hour,$mday,$mon,$year) =
554 (localtime($sdate) )[0,1,2,3,4,5];
555 $mon += $part_pkg->getfield('freq');
556 until ( $mon < 12 ) { $mon -= 12; $year++; }
557 $cust_pkg->setfield('bill',
558 timelocal($sec,$min,$hour,$mday,$mon,$year));
559 $cust_pkg_mod_flag = 1;
563 warn "setup is undefinded" unless defined($setup);
564 warn "recur is undefinded" unless defined($recur);
565 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
567 if ( $cust_pkg_mod_flag ) {
568 $error=$cust_pkg->replace($old_cust_pkg);
569 if ( $error ) { #just in case
570 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
572 $setup = sprintf( "%.2f", $setup );
573 $recur = sprintf( "%.2f", $recur );
574 my $cust_bill_pkg = new FS::cust_bill_pkg ({
575 'pkgnum' => $cust_pkg->pkgnum,
579 'edate' => $cust_pkg->bill,
581 push @cust_bill_pkg, $cust_bill_pkg;
582 $total_setup += $setup;
583 $total_recur += $recur;
589 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
591 return '' if scalar(@cust_bill_pkg) == 0;
593 unless ( $self->getfield('tax') =~ /Y/i
594 || $self->getfield('payby') eq 'COMP'
596 my $cust_main_county = qsearchs('cust_main_county',{
597 'state' => $self->state,
598 'county' => $self->county,
599 'country' => $self->country,
601 my $tax = sprintf( "%.2f",
602 $charged * ( $cust_main_county->getfield('tax') / 100 )
604 $charged = sprintf( "%.2f", $charged+$tax );
606 my $cust_bill_pkg = new FS::cust_bill_pkg ({
613 push @cust_bill_pkg, $cust_bill_pkg;
616 my $cust_bill = new FS::cust_bill ( {
617 'custnum' => $self->getfield('custnum'),
619 'charged' => $charged,
621 $error = $cust_bill->insert;
622 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
624 die "Error creating cust_bill record: $error!\n",
625 "Check updated but unbilled packages for customer", $self->custnum, "\n"
628 my $invnum = $cust_bill->invnum;
630 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
631 $cust_bill_pkg->setfield( 'invnum', $invnum );
632 $error = $cust_bill_pkg->insert;
633 #shouldn't happen, but how else tohandle this?
634 die "Error creating cust_bill_pkg record: $error!\n",
635 "Check incomplete invoice ", $invnum, "\n"
642 =item collect OPTIONS
644 (Attempt to) collect money for this customer's outstanding invoices (see
645 L<FS::cust_bill>). Usually used after the bill method.
647 Depending on the value of `payby', this may print an invoice (`BILL'), charge
648 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
650 If there is an error, returns the error, otherwise returns false.
652 Currently available options are:
654 invoice_time - Use this time when deciding when to print invoices and
655 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>
656 for conversion functions.
658 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
659 default, cards are processed immediately, which will generate an error if
660 CyberCash is not installed.
662 report_badcard - Set this true if you want bad card transactions to
663 return an error. By default, they don't.
668 my( $self, %options ) = @_;
669 my $invoice_time = $options{'invoice_time'} || time;
671 my $total_owed = $self->balance;
672 warn "collect: total owed $total_owed " if $Debug;
673 return '' unless $total_owed > 0; #redundant?????
676 local $SIG{HUP} = 'IGNORE';
677 local $SIG{INT} = 'IGNORE';
678 local $SIG{QUIT} = 'IGNORE';
679 local $SIG{TERM} = 'IGNORE';
680 local $SIG{TSTP} = 'IGNORE';
681 local $SIG{PIPE} = 'IGNORE';
683 foreach my $cust_bill (
684 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
687 #this has to be before next's
688 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
692 $total_owed = sprintf( "%.2f", $total_owed - $amount );
694 next unless $cust_bill->owed > 0;
696 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
698 warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)" if $Debug;
700 next unless $amount > 0;
702 if ( $self->payby eq 'BILL' ) {
705 my $since = $invoice_time - ( $cust_bill->_date || 0 );
706 #warn "$invoice_time ", $cust_bill->_date, " $since";
707 if ( $since >= 0 #don't print future invoices
708 && ( $cust_bill->printed * 2592000 ) <= $since
711 #my @print_text = $cust_bill->print_text; #( date )
712 my @invoicing_list = $self->invoicing_list;
713 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
714 $ENV{SMTPHOSTS} = $smtpmachine;
715 $ENV{MAILADDRESS} = $invoice_from;
716 my $header = new Mail::Header ( [
717 "From: $invoice_from",
718 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
719 "Sender: $invoice_from",
720 "Reply-To: $invoice_from",
721 "Date: ". time2str("%a, %d %b %Y %X %z", time),
724 my $message = new Mail::Internet (
726 'Body' => [ $cust_bill->print_text ], #( date)
728 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
730 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
731 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
732 print LPR $cust_bill->print_text; #( date )
734 or die $! ? "Error closing $lpr: $!"
735 : "Exit status $? from $lpr";
738 my %hash = $cust_bill->hash;
740 my $new_cust_bill = new FS::cust_bill(\%hash);
741 my $error = $new_cust_bill->replace($cust_bill);
742 warn "Error updating $cust_bill->printed: $error" if $error;
746 } elsif ( $self->payby eq 'COMP' ) {
747 my $cust_pay = new FS::cust_pay ( {
748 'invnum' => $cust_bill->invnum,
752 'payinfo' => $self->payinfo,
755 my $error = $cust_pay->insert;
756 return 'Error COMPing invnum #' . $cust_bill->invnum .
757 ':' . $error if $error;
759 } elsif ( $self->payby eq 'CARD' ) {
761 if ( $options{'batch_card'} ne 'yes' ) {
763 return "Real time card processing not enabled!" unless $processor;
765 if ( $processor =~ /^cybercash/ ) {
767 #fix exp. date for cybercash
768 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
769 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
772 my $paybatch = $cust_bill->invnum.
773 '-' . time2str("%y%m%d%H%M%S", time);
775 my $payname = $self->payname ||
776 $self->getfield('first'). ' '. $self->getfield('last');
778 my $address = $self->address1;
779 $address .= ", ". $self->address2 if $self->address2;
781 my $country = 'USA' if $self->country eq 'US';
783 my @full_xaction = ( $xaction,
784 'Order-ID' => $paybatch,
785 'Amount' => "usd $amount",
786 'Card-Number' => $self->getfield('payinfo'),
787 'Card-Name' => $payname,
788 'Card-Address' => $address,
789 'Card-City' => $self->getfield('city'),
790 'Card-State' => $self->getfield('state'),
791 'Card-Zip' => $self->getfield('zip'),
792 'Card-Country' => $country,
797 if ( $processor eq 'cybercash2' ) {
798 $^W=0; #CCLib isn't -w safe, ugh!
799 %result = &CCLib::sendmserver(@full_xaction);
801 } elsif ( $processor eq 'cybercash3.2' ) {
802 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
804 return "Unkonwn real-time processor $processor\n";
807 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
808 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
809 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
810 my $cust_pay = new FS::cust_pay ( {
811 'invnum' => $cust_bill->invnum,
815 'payinfo' => $self->payinfo,
816 'paybatch' => "$processor:$paybatch",
818 my $error = $cust_pay->insert;
819 return 'Error applying payment, invnum #' .
820 $cust_bill->invnum. ':'. $error if $error;
821 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
822 || $options{'report_badcard'} ) {
823 return 'Cybercash error, invnum #' .
824 $cust_bill->invnum. ':'. $result{'MErrMsg'};
830 return "Unkonwn real-time processor $processor\n";
835 my $cust_pay_batch = new FS::cust_pay_batch ( {
836 'invnum' => $cust_bill->getfield('invnum'),
837 'custnum' => $self->getfield('custnum'),
838 'last' => $self->getfield('last'),
839 'first' => $self->getfield('first'),
840 'address1' => $self->getfield('address1'),
841 'address2' => $self->getfield('address2'),
842 'city' => $self->getfield('city'),
843 'state' => $self->getfield('state'),
844 'zip' => $self->getfield('zip'),
845 'country' => $self->getfield('country'),
847 'cardnum' => $self->getfield('payinfo'),
848 'exp' => $self->getfield('paydate'),
849 'payname' => $self->getfield('payname'),
852 my $error = $cust_pay_batch->insert;
853 return "Error adding to cust_pay_batch: $error" if $error;
858 return "Unknown payment type ". $self->payby;
868 Returns the total owed for this customer on all invoices
869 (see L<FS::cust_bill>).
876 foreach my $cust_bill ( qsearch('cust_bill', {
877 'custnum' => $self->custnum,
879 $total_bill += $cust_bill->owed;
881 sprintf( "%.2f", $total_bill );
886 Returns the total credits (see L<FS::cust_credit>) for this customer.
892 my $total_credit = 0;
893 foreach my $cust_credit ( qsearch('cust_credit', {
894 'custnum' => $self->custnum,
896 $total_credit += $cust_credit->credited;
898 sprintf( "%.2f", $total_credit );
903 Returns the balance for this customer (total owed minus total credited).
909 sprintf( "%.2f", $self->total_owed - $self->total_credited );
912 =item invoicing_list [ ARRAYREF ]
914 If an arguement is given, sets these email addresses as invoice recipients
915 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
916 (except as warnings), so use check_invoicing_list first.
918 Returns a list of email addresses (with svcnum entries expanded).
920 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
921 check it without disturbing anything by passing nothing.
923 This interface may change in the future.
928 my( $self, $arrayref ) = @_;
930 my @cust_main_invoice;
931 if ( $self->custnum ) {
933 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
935 @cust_main_invoice = ();
937 foreach my $cust_main_invoice ( @cust_main_invoice ) {
938 #warn $cust_main_invoice->destnum;
939 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
940 #warn $cust_main_invoice->destnum;
941 my $error = $cust_main_invoice->delete;
942 warn $error if $error;
945 if ( $self->custnum ) {
947 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
949 @cust_main_invoice = ();
951 foreach my $address ( @{$arrayref} ) {
952 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
953 my $cust_main_invoice = new FS::cust_main_invoice ( {
954 'custnum' => $self->custnum,
957 my $error = $cust_main_invoice->insert;
958 warn $error if $error;
962 if ( $self->custnum ) {
964 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
970 =item check_invoicing_list ARRAYREF
972 Checks these arguements as valid input for the invoicing_list method. If there
973 is an error, returns the error, otherwise returns false.
977 sub check_invoicing_list {
978 my( $self, $arrayref ) = @_;
979 foreach my $address ( @{$arrayref} ) {
980 my $cust_main_invoice = new FS::cust_main_invoice ( {
981 'custnum' => $self->custnum,
984 my $error = $self->custnum
985 ? $cust_main_invoice->check
986 : $cust_main_invoice->checkdest
988 return $error if $error;
997 $Id: cust_main.pm,v 1.5 2000-06-15 12:38:09 ivan Exp $
1003 The delete method should possibly take an FS::cust_main object reference
1004 instead of a scalar customer number.
1006 Bill and collect options should probably be passed as references instead of a
1009 CyberCash v2 forces us to define some variables in package main.
1011 There should probably be a configuration file with a list of allowed credit
1014 CyberCash is the only processor.
1016 No multiple currency support (probably a larger project than just this module).
1020 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
1021 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
1022 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
1023 L<FS::UID>, schema.html from the base documentation.