package FS::cust_main;
use strict;
-use vars qw( @ISA $conf $Debug $import );
+use vars qw( @ISA $conf $DEBUG $import );
use vars qw( $realtime_bop_decline_quiet ); #ugh
use Safe;
use Carp;
$realtime_bop_decline_quiet = 0;
-$Debug = 0;
-#$Debug = 1;
+$DEBUG = 0;
+#$DEBUG = 1;
$import = 0;
$cust_main->insert( {}, [ $email, 'POST' ] );
-Currently available options are: I<noexport>
+Currently available options are: I<depend_jobnum> and I<noexport>.
-If I<noexport> is set true, no provisioning jobs (exports) are scheduled.
-(You can schedule them later with the B<reexport> method.)
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card sucessfully).
+
+The I<noexport> option is deprecated. If I<noexport> is set true, no
+provisioning jobs (exports) are scheduled. (You can schedule them later with
+the B<reexport> method.)
=cut
my $cust_pkgs = @_ ? shift : {};
my $invoicing_list = @_ ? shift : '';
my %options = @_;
+ warn "FS::cust_main::insert called with options ".
+ join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+ if $DEBUG;
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
}
# packages
- #local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
$error = $self->order_pkgs($cust_pkgs, \$seconds, %options);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
}
-=item order_pkgs HASHREF, [ , OPTION => VALUE ... ] ]
+=item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ]
Like the insert method on an existing record, this method orders a package
and included services atomicaly. Pass a Tie::RefHash data structure to this
$cust_pkg => [ $svc_acct ],
...
);
- $cust_main->order_pkgs( \%hash, 'noexport'=>1 );
+ $cust_main->order_pkgs( \%hash, \'0', 'noexport'=>1 );
+
+Currently available options are: I<depend_jobnum> and I<noexport>.
-Currently available options are: I<noexport>
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card sucessfully).
-If I<noexport> is set true, no provisioning jobs (exports) are scheduled.
-(You can schedule them later with the B<reexport> method for each
-cust_pkg object. Using the B<reexport> method on the cust_main object is not
-recommended, as existing services will also be reexported.)
+The I<noexport> option is deprecated. If I<noexport> is set true, no
+provisioning jobs (exports) are scheduled. (You can schedule them later with
+the B<reexport> method for each cust_pkg object. Using the B<reexport> method
+on the cust_main object is not recommended, as existing services will also be
+reexported.)
=cut
my $cust_pkgs = shift;
my $seconds = shift;
my %options = @_;
+ my %svc_options = ();
+ $svc_options{'depend_jobnum'} = $options{'depend_jobnum'}
+ if exists $options{'depend_jobnum'};
+ warn "FS::cust_main::order_pkgs called with options ".
+ join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+ if $DEBUG;
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
$svc_something->seconds( $svc_something->seconds + $$seconds );
$$seconds = 0;
}
- $error = $svc_something->insert;
+ $error = $svc_something->insert(%svc_options);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
#return "inserting svc_ (transaction rolled back): $error";
=item reexport
+This method is deprecated. See the I<depend_jobnum> option to the insert and
+order_pkgs methods for a better way to defer provisioning.
+
Re-schedules all exports by calling the B<reexport> method of all associated
packages (see L<FS::cust_pkg>). If there is an error, returns the error;
otherwise returns false.
sub reexport {
my $self = shift;
+ carp "warning: FS::cust_main::reexport is deprectated; ".
+ "use the depend_jobnum option to insert or order_pkgs to delay export";
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ $self->select_for_update; #mutex
+
# find the packages which are due for billing, find out how much they are
# & generate invoice database.
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ $self->select_for_update; #mutex
+
my $balance = $self->balance;
- warn "collect customer". $self->custnum. ": balance $balance" if $Debug;
+ warn "collect customer". $self->custnum. ": balance $balance" if $DEBUG;
unless ( $balance > 0 ) { #redundant?????
$dbh->rollback if $oldAutoCommit; #hmm
return '';
last if $self->balance <= 0;
warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")"
- if $Debug;
+ if $DEBUG;
foreach my $part_bill_event (
sort { $a->seconds <=> $b->seconds
|| $self->balance <= 0; # or if balance<=0
warn "calling invoice event (". $part_bill_event->eventcode. ")\n"
- if $Debug;
+ if $DEBUG;
my $cust_main = $self; #for callback
my $error;
sub realtime_bop {
my( $self, $method, $amount, %options ) = @_;
- if ( $Debug ) {
+ if ( $DEBUG ) {
warn "$self $method $amount\n";
warn " $_ => $options{$_}\n" foreach keys %options;
}
#overrides
$self->set( $_ => $options{$_} )
foreach grep { exists($options{$_}) }
- qw( payname address1 address2 city state zip payinfo paydate );
+ qw( payname address1 address2 city state zip payinfo paydate paycvv);
#load up config
my $bop_config = 'business-onlinepayment';
$conf->config($bop_config);
$action ||= 'normal authorization';
pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+ die "No real-time processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n"
+ unless $processor;
#massage data
}
#remove paycvv after initial transaction
- #make this disable-able via a config option if anyone insists?
- # (though that probably violates cardholder agreements)
+ #false laziness w/misc/process/payment.cgi - check both to make sure working
+ # correctly
if ( defined $self->dbdef_table->column('paycvv')
&& length($self->paycvv)
&& ! grep { $_ eq cardtype($self->payinfo) } $conf->config('cvv-save')
+ && ! length($options{'paycvv'})
) {
my $new = new FS::cust_main { $self->hash };
$new->paycvv('');
} );
my $error = $cust_pay->insert;
if ( $error ) {
- # gah, even with transactions.
- my $e = 'WARNING: Card/ACH debited but database not updated - '.
- 'error applying payment, invnum #' . $self->invnum.
- " ($processor): $error";
- warn $e;
- return $e;
- } else {
- return '';
+ $cust_pay->invnum(''); #try again with no specific invnum
+ my $error2 = $cust_pay->insert;
+ if ( $error2 ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card/ACH debited but database not updated - '.
+ "error inserting payment ($processor): $error2".
+ " (previously tried insert with invnum #$options{'invnum'}" .
+ ": $error )";
+ warn $e;
+ return $e;
+ }
}
+ return ''; #no error
} else {
if ( !$options{'quiet'} && !$realtime_bop_decline_quiet
&& $conf->exists('emaildecline')
&& grep { $_ ne 'POST' } $self->invoicing_list
- && ! grep { $_ eq $transaction->error_message }
+ && ! grep { $transaction->error_message =~ /$_/ }
$conf->config('emaildecline-exclude')
) {
my @templ = $conf->config('declinetemplate');
);
}
+=item paydate_monthyear
+
+Returns a two-element list consisting of the month and year of this customer's
+paydate (credit card expiration date for CARD customers)
+
+=cut
+
+sub paydate_monthyear {
+ my $self = shift;
+ if ( $self->paydate =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #Pg date format
+ ( $2, $1 );
+ } elsif ( $self->paydate =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $1, $3 );
+ } else {
+ ('', '');
+ }
+}
+
=item invoicing_list [ ARRAYREF ]
If an arguement is given, sets these email addresses as invoice recipients
qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
}
+=item select_for_update
+
+Selects this record with the SQL "FOR UPDATE" command. This can be useful as
+a mutex.
+
+=cut
+
+sub select_for_update {
+ my $self = shift;
+ qsearch('cust_main', { 'custnum' => $self->custnum }, '*', 'FOR UPDATE' );
+}
+
=back
=head1 SUBROUTINES