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 qsearch( 'cust_pkg', {
454 'custnum' => $self->custnum,
457 qsearch( 'cust_pkg', {
458 'custnum' => $self->custnum,
466 Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
467 conjunction with the collect method.
469 The only currently available option is `time', which bills the customer as if
470 it were that time. It is specified as a UNIX timestamp; see
471 L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
474 If there is an error, returns the error, otherwise returns false.
479 my( $self, %options ) = @_;
480 my $time = $options{'time'} || time;
485 local $SIG{HUP} = 'IGNORE';
486 local $SIG{INT} = 'IGNORE';
487 local $SIG{QUIT} = 'IGNORE';
488 local $SIG{TERM} = 'IGNORE';
489 local $SIG{TSTP} = 'IGNORE';
490 local $SIG{PIPE} = 'IGNORE';
492 # find the packages which are due for billing, find out how much they are
493 # & generate invoice database.
495 my( $total_setup, $total_recur ) = ( 0, 0 );
498 foreach my $cust_pkg (
499 qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
502 next if $cust_pkg->getfield('cancel');
504 #? to avoid use of uninitialized value errors... ?
505 $cust_pkg->setfield('bill', '')
506 unless defined($cust_pkg->bill);
508 my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
510 #so we don't modify cust_pkg record unnecessarily
511 my $cust_pkg_mod_flag = 0;
512 my %hash = $cust_pkg->hash;
513 my $old_cust_pkg = new FS::cust_pkg \%hash;
517 unless ( $cust_pkg->setup ) {
518 my $setup_prog = $part_pkg->getfield('setup');
520 #$cpt->permit(); #what is necessary?
521 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
522 $setup = $cpt->reval($setup_prog);
523 unless ( defined($setup) ) {
524 warn "Error reval-ing part_pkg->setup pkgpart ",
525 $part_pkg->pkgpart, ": $@";
527 $cust_pkg->setfield('setup',$time);
528 $cust_pkg_mod_flag=1;
535 if ( $part_pkg->getfield('freq') > 0 &&
536 ! $cust_pkg->getfield('susp') &&
537 ( $cust_pkg->getfield('bill') || 0 ) < $time
539 my $recur_prog = $part_pkg->getfield('recur');
541 #$cpt->permit(); #what is necessary?
542 $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
543 $recur = $cpt->reval($recur_prog);
544 unless ( defined($recur) ) {
545 warn "Error reval-ing part_pkg->recur pkgpart ",
546 $part_pkg->pkgpart, ": $@";
548 #change this bit to use Date::Manip?
549 #$sdate=$cust_pkg->bill || time;
550 #$sdate=$cust_pkg->bill || $time;
551 $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
552 my ($sec,$min,$hour,$mday,$mon,$year) =
553 (localtime($sdate) )[0,1,2,3,4,5];
554 $mon += $part_pkg->getfield('freq');
555 until ( $mon < 12 ) { $mon -= 12; $year++; }
556 $cust_pkg->setfield('bill',
557 timelocal($sec,$min,$hour,$mday,$mon,$year));
558 $cust_pkg_mod_flag = 1;
562 warn "setup is undefinded" unless defined($setup);
563 warn "recur is undefinded" unless defined($recur);
564 warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
566 if ( $cust_pkg_mod_flag ) {
567 $error=$cust_pkg->replace($old_cust_pkg);
568 if ( $error ) { #just in case
569 warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
571 $setup = sprintf( "%.2f", $setup );
572 $recur = sprintf( "%.2f", $recur );
573 my $cust_bill_pkg = new FS::cust_bill_pkg ({
574 'pkgnum' => $cust_pkg->pkgnum,
578 'edate' => $cust_pkg->bill,
580 push @cust_bill_pkg, $cust_bill_pkg;
581 $total_setup += $setup;
582 $total_recur += $recur;
588 my $charged = sprintf( "%.2f", $total_setup + $total_recur );
590 return '' if scalar(@cust_bill_pkg) == 0;
592 unless ( $self->getfield('tax') =~ /Y/i
593 || $self->getfield('payby') eq 'COMP'
595 my $cust_main_county = qsearchs('cust_main_county',{
596 'state' => $self->state,
597 'county' => $self->county,
598 'country' => $self->country,
600 my $tax = sprintf( "%.2f",
601 $charged * ( $cust_main_county->getfield('tax') / 100 )
603 $charged = sprintf( "%.2f", $charged+$tax );
605 my $cust_bill_pkg = new FS::cust_bill_pkg ({
612 push @cust_bill_pkg, $cust_bill_pkg;
615 my $cust_bill = new FS::cust_bill ( {
616 'custnum' => $self->getfield('custnum'),
618 'charged' => $charged,
620 $error = $cust_bill->insert;
621 #shouldn't happen, but how else to handle this? (wrap me in eval, to catch
623 die "Error creating cust_bill record: $error!\n",
624 "Check updated but unbilled packages for customer", $self->custnum, "\n"
627 my $invnum = $cust_bill->invnum;
629 foreach $cust_bill_pkg ( @cust_bill_pkg ) {
630 $cust_bill_pkg->setfield( 'invnum', $invnum );
631 $error = $cust_bill_pkg->insert;
632 #shouldn't happen, but how else tohandle this?
633 die "Error creating cust_bill_pkg record: $error!\n",
634 "Check incomplete invoice ", $invnum, "\n"
641 =item collect OPTIONS
643 (Attempt to) collect money for this customer's outstanding invoices (see
644 L<FS::cust_bill>). Usually used after the bill method.
646 Depending on the value of `payby', this may print an invoice (`BILL'), charge
647 a credit card (`CARD'), or just add any necessary (pseudo-)payment (`COMP').
649 If there is an error, returns the error, otherwise returns false.
651 Currently available options are:
653 invoice_time - Use this time when deciding when to print invoices and
654 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>
655 for conversion functions.
657 batch_card - Set this true to batch cards (see L<cust_pay_batch>). By
658 default, cards are processed immediately, which will generate an error if
659 CyberCash is not installed.
661 report_badcard - Set this true if you want bad card transactions to
662 return an error. By default, they don't.
667 my( $self, %options ) = @_;
668 my $invoice_time = $options{'invoice_time'} || time;
670 my $total_owed = $self->balance;
671 warn "collect: total owed $total_owed " if $Debug;
672 return '' unless $total_owed > 0; #redundant?????
675 local $SIG{HUP} = 'IGNORE';
676 local $SIG{INT} = 'IGNORE';
677 local $SIG{QUIT} = 'IGNORE';
678 local $SIG{TERM} = 'IGNORE';
679 local $SIG{TSTP} = 'IGNORE';
680 local $SIG{PIPE} = 'IGNORE';
682 foreach my $cust_bill (
683 qsearch('cust_bill', { 'custnum' => $self->custnum, } )
686 #this has to be before next's
687 my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
691 $total_owed = sprintf( "%.2f", $total_owed - $amount );
693 next unless $cust_bill->owed > 0;
695 next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
697 warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)" if $Debug;
699 next unless $amount > 0;
701 if ( $self->payby eq 'BILL' ) {
704 my $since = $invoice_time - ( $cust_bill->_date || 0 );
705 #warn "$invoice_time ", $cust_bill->_date, " $since";
706 if ( $since >= 0 #don't print future invoices
707 && ( $cust_bill->printed * 2592000 ) <= $since
710 #my @print_text = $cust_bill->print_text; #( date )
711 my @invoicing_list = $self->invoicing_list;
712 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
713 $ENV{SMTPHOSTS} = $smtpmachine;
714 $ENV{MAILADDRESS} = $invoice_from;
715 my $header = new Mail::Header ( [
716 "From: $invoice_from",
717 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
718 "Sender: $invoice_from",
719 "Reply-To: $invoice_from",
720 "Date: ". time2str("%a, %d %b %Y %X %z", time),
723 my $message = new Mail::Internet (
725 'Body' => [ $cust_bill->print_text ], #( date)
727 $message->smtpsend or die "Can't send invoice email!"; #die? warn?
729 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
730 open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
731 print LPR $cust_bill->print_text; #( date )
733 or die $! ? "Error closing $lpr: $!"
734 : "Exit status $? from $lpr";
737 my %hash = $cust_bill->hash;
739 my $new_cust_bill = new FS::cust_bill(\%hash);
740 my $error = $new_cust_bill->replace($cust_bill);
741 warn "Error updating $cust_bill->printed: $error" if $error;
745 } elsif ( $self->payby eq 'COMP' ) {
746 my $cust_pay = new FS::cust_pay ( {
747 'invnum' => $cust_bill->invnum,
751 'payinfo' => $self->payinfo,
754 my $error = $cust_pay->insert;
755 return 'Error COMPing invnum #' . $cust_bill->invnum .
756 ':' . $error if $error;
758 } elsif ( $self->payby eq 'CARD' ) {
760 if ( $options{'batch_card'} ne 'yes' ) {
762 return "Real time card processing not enabled!" unless $processor;
764 if ( $processor =~ /^cybercash/ ) {
766 #fix exp. date for cybercash
767 #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
768 $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
771 my $paybatch = $cust_bill->invnum.
772 '-' . time2str("%y%m%d%H%M%S", time);
774 my $payname = $self->payname ||
775 $self->getfield('first'). ' '. $self->getfield('last');
777 my $address = $self->address1;
778 $address .= ", ". $self->address2 if $self->address2;
780 my $country = 'USA' if $self->country eq 'US';
782 my @full_xaction = ( $xaction,
783 'Order-ID' => $paybatch,
784 'Amount' => "usd $amount",
785 'Card-Number' => $self->getfield('payinfo'),
786 'Card-Name' => $payname,
787 'Card-Address' => $address,
788 'Card-City' => $self->getfield('city'),
789 'Card-State' => $self->getfield('state'),
790 'Card-Zip' => $self->getfield('zip'),
791 'Card-Country' => $country,
796 if ( $processor eq 'cybercash2' ) {
797 $^W=0; #CCLib isn't -w safe, ugh!
798 %result = &CCLib::sendmserver(@full_xaction);
800 } elsif ( $processor eq 'cybercash3.2' ) {
801 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
803 return "Unkonwn real-time processor $processor\n";
806 #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
807 #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
808 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
809 my $cust_pay = new FS::cust_pay ( {
810 'invnum' => $cust_bill->invnum,
814 'payinfo' => $self->payinfo,
815 'paybatch' => "$processor:$paybatch",
817 my $error = $cust_pay->insert;
818 return 'Error applying payment, invnum #' .
819 $cust_bill->invnum. ':'. $error if $error;
820 } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
821 || $options{'report_badcard'} ) {
822 return 'Cybercash error, invnum #' .
823 $cust_bill->invnum. ':'. $result{'MErrMsg'};
829 return "Unkonwn real-time processor $processor\n";
834 my $cust_pay_batch = new FS::cust_pay_batch ( {
835 'invnum' => $cust_bill->getfield('invnum'),
836 'custnum' => $self->getfield('custnum'),
837 'last' => $self->getfield('last'),
838 'first' => $self->getfield('first'),
839 'address1' => $self->getfield('address1'),
840 'address2' => $self->getfield('address2'),
841 'city' => $self->getfield('city'),
842 'state' => $self->getfield('state'),
843 'zip' => $self->getfield('zip'),
844 'country' => $self->getfield('country'),
846 'cardnum' => $self->getfield('payinfo'),
847 'exp' => $self->getfield('paydate'),
848 'payname' => $self->getfield('payname'),
851 my $error = $cust_pay_batch->insert;
852 return "Error adding to cust_pay_batch: $error" if $error;
857 return "Unknown payment type ". $self->payby;
867 Returns the total owed for this customer on all invoices
868 (see L<FS::cust_bill>).
875 foreach my $cust_bill ( qsearch('cust_bill', {
876 'custnum' => $self->custnum,
878 $total_bill += $cust_bill->owed;
880 sprintf( "%.2f", $total_bill );
885 Returns the total credits (see L<FS::cust_credit>) for this customer.
891 my $total_credit = 0;
892 foreach my $cust_credit ( qsearch('cust_credit', {
893 'custnum' => $self->custnum,
895 $total_credit += $cust_credit->credited;
897 sprintf( "%.2f", $total_credit );
902 Returns the balance for this customer (total owed minus total credited).
908 sprintf( "%.2f", $self->total_owed - $self->total_credited );
911 =item invoicing_list [ ARRAYREF ]
913 If an arguement is given, sets these email addresses as invoice recipients
914 (see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
915 (except as warnings), so use check_invoicing_list first.
917 Returns a list of email addresses (with svcnum entries expanded).
919 Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
920 check it without disturbing anything by passing nothing.
922 This interface may change in the future.
927 my( $self, $arrayref ) = @_;
929 my @cust_main_invoice;
930 if ( $self->custnum ) {
932 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
934 @cust_main_invoice = ();
936 foreach my $cust_main_invoice ( @cust_main_invoice ) {
937 #warn $cust_main_invoice->destnum;
938 unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
939 #warn $cust_main_invoice->destnum;
940 my $error = $cust_main_invoice->delete;
941 warn $error if $error;
944 if ( $self->custnum ) {
946 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
948 @cust_main_invoice = ();
950 foreach my $address ( @{$arrayref} ) {
951 unless ( grep { $address eq $_->address } @cust_main_invoice ) {
952 my $cust_main_invoice = new FS::cust_main_invoice ( {
953 'custnum' => $self->custnum,
956 my $error = $cust_main_invoice->insert;
957 warn $error if $error;
961 if ( $self->custnum ) {
963 qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
969 =item check_invoicing_list ARRAYREF
971 Checks these arguements as valid input for the invoicing_list method. If there
972 is an error, returns the error, otherwise returns false.
976 sub check_invoicing_list {
977 my( $self, $arrayref ) = @_;
978 foreach my $address ( @{$arrayref} ) {
979 my $cust_main_invoice = new FS::cust_main_invoice ( {
980 'custnum' => $self->custnum,
983 my $error = $self->custnum
984 ? $cust_main_invoice->check
985 : $cust_main_invoice->checkdest
987 return $error if $error;
996 $Id: cust_main.pm,v 1.4 2000-02-02 20:22:18 ivan Exp $
1002 The delete method should possibly take an FS::cust_main object reference
1003 instead of a scalar customer number.
1005 Bill and collect options should probably be passed as references instead of a
1008 CyberCash v2 forces us to define some variables in package main.
1010 There should probably be a configuration file with a list of allowed credit
1013 CyberCash is the only processor.
1015 No multiple currency support (probably a larger project than just this module).
1019 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
1020 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
1021 L<FS::cust_main_county>, L<FS::cust_main_invoice>,
1022 L<FS::UID>, schema.html from the base documentation.