diff options
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/ClientAPI/MyAccount.pm | 37 | ||||
-rw-r--r-- | FS/FS/Conf.pm | 7 | ||||
-rw-r--r-- | FS/FS/cust_main.pm | 236 | ||||
-rw-r--r-- | FS/FS/svc_acct.pm | 77 |
4 files changed, 310 insertions, 47 deletions
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 478973396..60d1f64d9 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -6,6 +6,7 @@ use subs qw(_cache); use Digest::MD5 qw(md5_hex); use Date::Format; use Business::CreditCard; +use Time::Duration; use FS::CGI qw(small_custview); #doh use FS::Conf; use FS::Record qw(qsearch qsearchs); @@ -135,12 +136,16 @@ sub customer_info { $return{'postal_invoicing'} = 0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list ); - } else { #no customer record + } elsif ( $session->{'svcnum'} ) { #no customer record my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } ) or die "unknown svcnum"; $return{name} = $svc_acct->email; + } else { + + return { 'error' => 'Expired session' }; #XXX redirect to login w/this err! + } return { 'error' => '', @@ -357,6 +362,36 @@ sub process_payment { } +sub process_prepay { + + my $p = shift; + + my $session = _cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my %return; + + my $custnum = $session->{'custnum'}; + + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return { 'error' => "unknown custnum $custnum" }; + + my( $amount, $seconds ) = ( 0, 0 ); + my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'}, + \$amount, + \$seconds + ); + + return { 'error' => $error } if $error; + + return { 'error' => '', + 'amount' => $amount, + 'seconds' => $seconds, + 'duration' => duration_exact($seconds), + }; + +} + sub invoice { my $p = shift; my $session = _cache->get($p->{'session_id'}) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index ea2785bdf..a1fdd034e 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1531,6 +1531,13 @@ httemplate/docs/config.html 'type' => 'checkbox', }, + { + 'key' => 'svc_acct-usage_unsuspend', + 'section' => 'billing', + 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds is incremented from 0 or below to a positive value (accounts with an empty seconds value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.', + 'type' => 'checkbox', + }, + ); 1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 1edd319cb..775523a56 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1,7 +1,7 @@ package FS::cust_main; use strict; -use vars qw( @ISA @EXPORT_OK $DEBUG $conf @encrypted_fields +use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf @encrypted_fields $import $skip_fuzzyfiles ); use vars qw( $realtime_bop_decline_quiet ); #ugh use Safe; @@ -51,7 +51,7 @@ use FS::Msgcat qw(gettext); $realtime_bop_decline_quiet = 0; $DEBUG = 0; -#$DEBUG = 1; +$me = '[FS::cust_main]'; $import = 0; $skip_fuzzyfiles = 0; @@ -349,33 +349,21 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $prepay_credit = ''; - my $seconds = 0; + my $prepay_identifier = ''; + my( $amount, $seconds ) = ( 0, 0 ); if ( $self->payby eq 'PREPAY' ) { + $self->payby('BILL'); - $prepay_credit = qsearchs( - 'prepay_credit', - { 'identifier' => $self->payinfo }, - '', - 'FOR UPDATE' - ); - unless ( $prepay_credit ) { - $dbh->rollback if $oldAutoCommit; - return "Invalid prepaid card: ". $self->payinfo; - } - $seconds = $prepay_credit->seconds; - if ( $prepay_credit->agentnum ) { - if ( $self->agentnum && $self->agentnum != $prepay_credit->agentnum ) { - $dbh->rollback if $oldAutoCommit; - return "prepaid card not valid for agent ". $self->agentnum; - } - $self->agentnum($prepay_credit->agentnum); - } - my $error = $prepay_credit->delete; + $prepay_identifier = $self->payinfo; + $self->payinfo(''); + + my $error = $self->get_prepay($prepay_identifier, \$amount, \$seconds); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "removing prepay_credit (transaction rolled back): $error"; + #return "error applying prepaid card (transaction rolled back): $error"; + return $error; } + } my $error = $self->SUPER::insert; @@ -407,15 +395,8 @@ sub insert { return "No svc_acct record to apply pre-paid time"; } - if ( $prepay_credit && $prepay_credit->amount ) { - my $cust_pay = new FS::cust_pay { - 'custnum' => $self->custnum, - 'paid' => $prepay_credit->amount, - #'_date' => #date the prepaid card was purchased??? - 'payby' => 'PREP', - 'payinfo' => $prepay_credit->identifier, - }; - $error = $cust_pay->insert; + if ( $amount ) { + $error = $self->insert_prepay($amount, $prepay_identifier); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "inserting prepayment (transaction rolled back): $error"; @@ -526,6 +507,195 @@ sub order_pkgs { ''; #no error } +=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF ] + +Recharges this (existing) customer with the specified prepaid card (see +L<FS::prepay_credit>), specified either by I<identifier> or as an +FS::prepay_credit object. If there is an error, returns the error, otherwise +returns false. + +Optionally, two scalar references can be passed as well. They will have their +values filled in with the amount and number of seconds applied by this prepaid +card. + +=cut + +sub recharge_prepay { + my( $self, $prepay_credit, $amountref, $secondsref ) = @_; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my( $amount, $seconds ) = ( 0, 0 ); + + my $error = $self->get_prepay($prepay_credit, \$amount, \$seconds) + || $self->increment_seconds($seconds) + || $self->insert_cust_pay_prepay( $amount, + ref($prepay_credit) + ? $prepay_credit->identifier + : $prepay_credit + ); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + if ( defined($amountref) ) { $$amountref = $amount; } + if ( defined($secondsref) ) { $$secondsref = $seconds; } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=item get_prepay IDENTIFIER | PREPAY_CREDIT_OBJ , AMOUNTREF, SECONDSREF + +Looks up and deletes a prepaid card (see L<FS::prepay_credit>), +specified either by I<identifier> or as an FS::prepay_credit object. + +References to I<amount> and I<seconds> scalars should be passed as arguments +and will be incremented by the values of the prepaid card. + +If the prepaid card specifies an I<agentnum> (see L<FS::agent>), it is used to +check or set this customer's I<agentnum>. + +If there is an error, returns the error, otherwise returns false. + +=cut + + +sub get_prepay { + my( $self, $prepay_credit, $amountref, $secondsref ) = @_; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + unless ( ref($prepay_credit) ) { + + my $identifier = $prepay_credit; + + $prepay_credit = qsearchs( + 'prepay_credit', + { 'identifier' => $prepay_credit }, + '', + 'FOR UPDATE' + ); + + unless ( $prepay_credit ) { + $dbh->rollback if $oldAutoCommit; + return "Invalid prepaid card: ". $identifier; + } + + } + + if ( $prepay_credit->agentnum ) { + if ( $self->agentnum && $self->agentnum != $prepay_credit->agentnum ) { + $dbh->rollback if $oldAutoCommit; + return "prepaid card not valid for agent ". $self->agentnum; + } + $self->agentnum($prepay_credit->agentnum); + } + + my $error = $prepay_credit->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "removing prepay_credit (transaction rolled back): $error"; + } + + $$amountref += $prepay_credit->amount; + $$secondsref += $prepay_credit->seconds; + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=item increment_seconds SECONDS + +Updates this customer's single or primary account (see L<FS::svc_acct>) by +the specified number of seconds. If there is an error, returns the error, +otherwise returns false. + +=cut + +sub increment_seconds { + my( $self, $seconds ) = @_; + warn "$me increment_seconds called: $seconds seconds\n" + if $DEBUG; + + my @cust_pkg = grep { $_->part_pkg->svcpart('svc_acct') } + $self->ncancelled_pkgs; + + if ( ! @cust_pkg ) { + return 'No packages with primary or single services found'. + ' to apply pre-paid time'; + } elsif ( scalar(@cust_pkg) > 1 ) { + #maybe have a way to specify the package/account? + return 'Multiple packages found to apply pre-paid time'; + } + + my $cust_pkg = $cust_pkg[0]; + warn " found package pkgnum ". $cust_pkg->pkgnum. "\n" + if $DEBUG; + + my @cust_svc = + $cust_pkg->cust_svc( $cust_pkg->part_pkg->svcpart('svc_acct') ); + + if ( ! @cust_svc ) { + return 'No account found to apply pre-paid time'; + } elsif ( scalar(@cust_svc) > 1 ) { + return 'Multiple accounts found to apply pre-paid time'; + } + + my $svc_acct = $cust_svc[0]->svc_x; + warn " found service svcnum ". $svc_acct->pkgnum. + ' ('. $svc_acct->email. ")\n" + if $DEBUG; + + $svc_acct->increment_seconds($seconds); + +} + +=item insert_cust_pay_prepay AMOUNT [ PAYINFO ] + +Inserts a prepayment in the specified amount for this customer. An optional +second argument can specify the prepayment identifier for tracking purposes. +If there is an error, returns the error, otherwise returns false. + +=cut + +sub insert_cust_pay_prepay { + my( $self, $amount ) = splice(@_, 0, 2); + my $payinfo = scalar(@_) ? shift : ''; + + my $cust_pay = new FS::cust_pay { + 'custnum' => $self->custnum, + 'paid' => $amount, + #'_date' => #date the prepaid card was purchased??? + 'payby' => 'PREP', + 'payinfo' => $payinfo, + }; + $cust_pay->insert; + +} + =item reexport This method is deprecated. See the I<depend_jobnum> option to the insert and diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index a0451938a..496312062 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -37,7 +37,6 @@ use FS::svc_www; @ISA = qw( FS::svc_Common ); $DEBUG = 0; -#$DEBUG = 1; $me = '[FS::svc_acct]'; #ask FS::UID to run this stuff for us later @@ -1117,12 +1116,45 @@ sub acct_snarf { =item decrement_seconds SECONDS -Decrements the I<seconds> field of this record by the given amount. +Decrements the I<seconds> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. =cut sub decrement_seconds { - my( $self, $seconds ) = @_; + shift->_op_seconds('-', @_); +} + +=item increment_seconds SECONDS + +Increments the I<seconds> field of this record by the given amount. If there +is an error, returns the error, otherwise returns false. + +=cut + +sub increment_seconds { + shift->_op_seconds('+', @_); +} + + +my %op2action = ( + '-' => 'suspend', + '+' => 'unsuspend', +); +my %op2condition = ( + '-' => sub { my($self, $seconds) = @_; + $self->seconds - $seconds <= 0; + }, + '+' => sub { my($self, $seconds) = @_; + $self->seconds + $seconds > 0; + }, +); + +sub _op_seconds { + my( $self, $op, $seconds ) = @_; + warn "$me _op_seconds called for svcnum ". $self->svcnum. + ' ('. $self->email. "): $op $seconds\n" + if $DEBUG; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -1134,22 +1166,41 @@ sub decrement_seconds { my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; - - my $sth = dbh->prepare( - 'UPDATE svc_acct SET seconds = seconds - ? WHERE svcnum = ?' - ) or die dbh->errstr;; - $sth->execute($seconds, $self->svcnum) or die $sth->errstr; - if ( $conf->exists('svc_acct-usage_suspend') - && $self->seconds - $seconds <= 0 ) { - #my $error = $self->suspend; - my $error = $self->cust_svc->cust_pkg->suspend; - die $error if $error; + + my $sql = "UPDATE svc_acct SET seconds = ". + " CASE WHEN seconds IS NULL THEN 0 ELSE seconds END ". #$seconds||0 + " $op ? WHERE svcnum = ?"; + warn "$me $sql\n" + if $DEBUG; + + my $sth = $dbh->prepare( $sql ) + or die "Error preparing $sql: ". $dbh->errstr; + my $rv = $sth->execute($seconds, $self->svcnum); + die "Error executing $sql: ". $sth->errstr + unless defined($rv); + die "Can't update seconds for svcnum". $self->svcnum + if $rv == 0; + + my $action = $op2action{$op}; + + if ( $conf->exists("svc_acct-usage_$action") + && &{$op2condition{$op}}($self, $seconds) ) { + #my $error = $self->$action(); + my $error = $self->cust_svc->cust_pkg->$action(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error ${action}ing: $error"; + } } + warn "$me update sucessful; committing\n" + if $DEBUG; $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; } + =item seconds_since TIMESTAMP Returns the number of seconds this account has been online since TIMESTAMP, |