use Date::Format;
use Time::Duration;
use Time::Local qw(timelocal_nocheck);
-use Business::CreditCard;
+use Business::CreditCard 0.35;
use HTML::Entities;
use Text::CSV_XS;
use Spreadsheet::WriteExcel;
@cust_main_editable_fields = qw(
first last company daytime night fax mobile
locale
- payby payinfo payname paystart_month paystart_year payissue payip
- ss paytype paystate stateid stateid_state
+ ss stateid stateid_state
);
@location_editable_fields = qw(
address1 address2 city county state zip country
my $conf = new FS::Conf;
my $cust_main = shift;
my $cust_payby = shift;
- my $gatewaynum = $conf->config('selfservice-payment_gateway');
- if ( $gatewaynum ) {
- my $pg = qsearchs('payment_gateway', { gatewaynum => $gatewaynum });
- die "configured gatewaynum $gatewaynum not found!" if !$pg;
- return $pg;
- }
- else {
- return '' if ! FS::payby->realtime($cust_payby);
- my $pg = $cust_main->agent->payment_gateway(
- 'method' => FS::payby->payby2bop($cust_payby),
- 'nofatal' => 1
- );
- return $pg;
- }
+ return '' if ! FS::payby->realtime($cust_payby);
+ my $pg = $cust_main->agent->payment_gateway(
+ 'method' => FS::payby->payby2bop($cust_payby),
+ 'nofatal' => 1
+ );
+ return $pg;
}
sub access_info {
or return { 'error' => "customer_info_short: unknown custnum $custnum" };
$return{display_custnum} = $cust_main->display_custnum;
+ $return{max_invnum} = $cust_main->max_invnum;
if ( $session->{'pkgnum'} ) {
$return{balance} = $cust_main->balance_pkgnum( $session->{'pkgnum'} );
$return{'last'} = $cust_main->get('last');
$return{name} = $cust_main->first. ' '. $cust_main->get('last');
- $return{payby} = $cust_main->payby;
-
#none of these are terribly expensive if we want 'em...
for (@cust_main_editable_fields) {
$return{$_} = $cust_main->get($_);
if $cust_main->ship_locationnum;
}
- if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- $return{payinfo} = $cust_main->paymask;
- @return{'month', 'year'} = $cust_main->paydate_monthyear;
- }
-
my @invoicing_list = $cust_main->invoicing_list;
$return{'invoicing_list'} =
join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list );
}
+ # this is here because this routine is called by both fs_ and ng_ main pages, where it appears
+ # it is not customer-specific, though it is only shown to authenticated customers
+ # it is not currently agent-specific, though at some point it might be
+ $return{'announcement'} = join(' ',$conf->config('selfservice-announcement')) || '';
+
+ return { 'error' => '',
+ 'custnum' => $custnum,
+ %return,
+ };
+}
+
+sub customer_recurring {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my %return;
+
+ my $conf = new FS::Conf;
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "customer_info_short: unknown custnum $custnum" };
+
+ $return{'display_recurring'} = [ $cust_main->display_recurring ];
+
return { 'error' => '',
'custnum' => $custnum,
%return,
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
or return { 'error' => "unknown custnum $custnum" };
+ my $conf = new FS::Conf;
+ if (($p->{payby} eq "CHEK" || $p->{payby} eq "DCHEK") && $conf->exists('selfservice-ACH_info_readonly')) {
+ return { 'error' => "You do not have authority to add a bank account" };
+ }
+
my $new = new FS::cust_main { $cust_main->hash };
$new->set( $_ => $p->{$_} )
# but if it hasn't been passed in at all, leave ship_location alone--
# DON'T change it to match bill_location.
- my $payby = '';
- if (exists($p->{'payby'})) {
- $p->{'payby'} =~ /^([A-Z]{4})$/
- or return { 'error' => "illegal_payby " . $p->{'payby'} };
- $payby = $1;
- }
-
- my $conf = new FS::Conf;
-
- if ( $payby =~ /^(CARD|DCRD)$/ ) {
-
- $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
-
- if ( $new->payinfo eq $cust_main->paymask ) {
- $new->payinfo($cust_main->payinfo);
- $new->paycvv( $p->{'paycvv'} || $cust_main->paycvv );
- } else {
- $new->payinfo($p->{'payinfo'});
- return { 'error' => 'CVV2 is required' }
- if ! $p->{'paycvv'} && $conf->exists('selfservice-onfile_require_cvv');
- $new->paycvv( $p->{'paycvv'} )
- }
-
- $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
-
- } elsif ( $payby =~ /^(CHEK|DCHK)$/ ) {
-
- my $payinfo;
- $p->{'payinfo1'} =~ /^([\dx]+)$/
- or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
- my $payinfo1 = $1;
- $p->{'payinfo2'} =~ /^([\dx\.]+)$/ # . turned on by echeck-country CA ?
- or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
- my $payinfo2 = $1;
- $payinfo = $payinfo1. '@'. $payinfo2;
-
- $new->payinfo( ($payinfo eq $cust_main->paymask)
- ? $cust_main->payinfo
- : $payinfo
- );
-
- $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
-
- } elsif ( $payby =~ /^(BILL)$/ ) {
- #no-op
- } elsif ( $payby ) { #notyet ready
- return { 'error' => "unknown payby $payby" };
- }
-
my @invoicing_list;
if ( exists $p->{'invoicing_list'} || exists $p->{'postal_invoicing'} ) {
#false laziness with httemplate/edit/process/cust_main.cgi
'show_paystate' => $conf->exists('show_bankstate'),
'save_unchecked' => $conf->exists('selfservice-save_unchecked'),
+ 'ach_read_only' => $conf->exists('selfservice-ACH_info_readonly'),
- 'credit_card_surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')),
};
}
my %return = %$payment_info;
+ delete $return{'cust_main_county'} if $p->{'omit_cust_main_county'};
+
my $custnum = $session->{'custnum'};
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
$return{balance} = $cust_main->balance; #XXX pkg-balances?
- $return{payname} = $cust_main->payname
- || ( $cust_main->first. ' '. $cust_main->get('last') );
-
$return{$_} = $cust_main->bill_location->get($_)
for qw(address1 address2 city state zip);
- $return{payby} = $cust_main->payby;
- $return{stateid_state} = $cust_main->stateid_state;
+ # look for stored cust_payby info
+ # only if we've been given a clear payment_payby (to avoid payname conflicts)
+ if ($p->{'payment_payby'} =~ /^(CARD|CHEK)$/) {
+ my @search_payby = ($p->{'payment_payby'} eq 'CARD') ? ('CARD','DCRD') : ('CHEK','DCHK');
+ my ($cust_payby) = $cust_main->cust_payby(@search_payby);
+ if ($cust_payby) {
+ $return{payname} = $cust_payby->payname
+ || ( $cust_main->first. ' '. $cust_main->get('last') );
- if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- $return{card_type} = cardtype($cust_main->payinfo);
- $return{payinfo} = $cust_main->paymask;
+ if ( $cust_payby->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{card_type} = cardtype($cust_payby->payinfo);
+ $return{payinfo} = $cust_payby->paymask;
- @return{'month', 'year'} = $cust_main->paydate_monthyear;
+ @return{'month', 'year'} = $cust_payby->paydate_monthyear;
- }
+ }
- if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
- my ($payinfo1, $payinfo2) = split '@', $cust_main->paymask;
- $return{payinfo1} = $payinfo1;
- $return{payinfo2} = $payinfo2;
- $return{paytype} = $cust_main->paytype;
- $return{paystate} = $cust_main->paystate;
- $return{payname} = $cust_main->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
+ if ( $cust_payby->payby =~ /^(CHEK|DCHK)$/ ) {
+ my ($payinfo1, $payinfo2) = split '@', $cust_payby->paymask;
+ $return{payinfo1} = $payinfo1;
+ $return{payinfo2} = $payinfo2;
+ $return{paytype} = $cust_payby->paytype;
+ $return{paystate} = $cust_payby->paystate;
+ $return{payname} = $cust_payby->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
+ }
+ }
}
if ( $conf->config('prepayment_discounts-credit_type') ) {
$return{payunique} = "webui-MyAccount-$_date-$$-". rand() * 2**32; #new
$return{paybatch} = $return{payunique}; #back compat
+ $return{credit_card_surcharge_percentage} = $conf->config('credit-card-surcharge-percentage', $cust_main->agentnum);
+
return { 'error' => '',
%return,
};
my $payinfo2 = $1;
$payinfo = $payinfo1. '@'. $payinfo2;
- $payinfo = $cust_main->payinfo
- if $cust_main->paymask eq $payinfo;
+ my $achonfile = 0;
+ foreach my $cust_payby ($cust_main->cust_payby('CHEK','DCHK')) {
+ if ( $cust_payby->paymask eq $payinfo ) {
+ $payinfo = $cust_payby->payinfo;
+ $achonfile = 1;
+ last;
+ }
+ }
+
+ if ($conf->exists('selfservice-ACH_info_readonly') && !$achonfile) {
+ return { 'error' => "You are not allowed to change your payment information." };
+ }
} elsif ( $payby eq 'CARD' || $payby eq 'DCRD' ) {
#more intelligent matching will be needed here if you change
#card_masking_method and don't remove existing paymasks
- if ( $cust_main->paymask eq $payinfo ) {
- $payinfo = $cust_main->payinfo;
- $onfile = 1;
+ foreach my $cust_payby ($cust_main->cust_payby('CARD','DCRD')) {
+ if ( $cust_payby->paymask eq $payinfo ) {
+ $payinfo = $cust_payby->payinfo;
+ $onfile = 1;
+ last;
+ }
}
$payinfo =~ s/\D//g;
- $payinfo =~ /^(\d{13,16}|\d{8,9})$/
+ $payinfo =~ /^(\d{13,19}|\d{8,9})$/
or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
$payinfo = $1;
validate($payinfo)
or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
return { 'error' => gettext('unknown_card_type') }
- if $payinfo !~ /^99\d{14}$/ && cardtype($payinfo) eq "Unknown";
+ if !$cust_main->tokenized($payinfo) && cardtype($payinfo) eq "Unknown";
if ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) {
if ( cardtype($payinfo) eq 'American Express card' ) {
my $payby = delete $validate->{'payby'};
if ( $validate->{'save'} ) {
- my $new = new FS::cust_main { $cust_main->hash };
- if ($payby eq 'CARD' || $payby eq 'DCRD') {
- $new->set( $_ => $validate->{$_} )
- foreach qw( payname paystart_month paystart_year payissue payip );
- $new->set( 'payby' => $validate->{'auto'} ? 'CARD' : 'DCRD' );
+ my %saveopt;
+ foreach my $field ( qw( auto payinfo paymask payname payip ) ) {
+ $saveopt{$field} = $validate->{$field};
+ }
+
+ if ( $payby eq 'CARD' ) {
my $bill_location = FS::cust_location->new({
map { $_ => $validate->{$_} }
qw(address1 address2 city state country zip)
- }); # county?
- $new->set('bill_location' => $bill_location);
- # but don't allow the service address to change this way.
-
- } elsif ($payby eq 'CHEK' || $payby eq 'DCHK') {
- $new->set( $_ => $validate->{$_} )
- foreach qw( payname payip paytype paystate
- stateid stateid_state );
- $new->set( 'payby' => $validate->{'auto'} ? 'CHEK' : 'DCHK' );
+ });
+ $saveopt{'bill_location'} = $bill_location;
+ foreach my $field ( qw( paydate paystart_month paystart_year payissue ) ) {
+ $saveopt{$field} = $validate->{$field};
+ }
+ } else {
+ # stateid/stateid_state won't be saved, might be broken as of 4.x
+ foreach my $field ( qw( paytype paystate ) ) {
+ $saveopt{$field} = $validate->{$field};
+ }
}
- $new->payinfo( $validate->{'payinfo'} ); #to properly set paymask
- $new->set( 'paydate' => $validate->{'paydate'} );
- my $error = $new->replace($cust_main);
+
+ my $error = $cust_main->save_cust_payby(
+ 'payment_payby' => $payby,
+ %saveopt
+ );
+
if ( $error ) {
#no, this causes customers to process their payments again
#return { 'error' => $error };
#address" page but indicate if the payment processed?
delete($validate->{'payinfo'}); #don't want to log this!
warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n".
- "NEW: ". Dumper($new)."\n".
- "OLD: ". Dumper($cust_main)."\n".
+ "PAYBY: $payby\n".
+ "SAVEOPT: ".Dumper(\%saveopt)."\n".
+ "CUST_MAIN: ". Dumper($cust_main)."\n".
"PACKET: ". Dumper($validate)."\n";
- } else {
- $cust_main = $new;
}
}
#and generate an invoice for it now too
$error = $cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
- return { 'error' => "payment processed and fee ordered sucessfully, but error billing fee: $error" }
+ return { 'error' => "payment processed and fee ordered successfully, but error billing fee: $error" }
if $error;
}
};
}
-
sub list_invoices {
my $p = shift;
my $session = _cache->get($p->{'session_id'})
};
}
+sub list_payments {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ return { 'error' => '',
+ 'balance' => $cust_main->balance,
+ 'money_char' => FS::Conf->new->config("money_char") || '$',
+ 'payments' => [ map $_->SSAPI_getinfo, $cust_main->cust_pay ],
+ };
+}
+
+sub payment_receipt {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_pay = qsearchs('cust_pay', { 'custnum' => $custnum,
+ 'paynum' => $p->{'paynum'},
+ }
+ )
+ or return { 'error' => "unknown payment ". $p->{'paynum'} };
+
+ return {
+ 'error' => '',
+ %{ $cust_pay->SSAPI_getinfo },
+ };
+}
+
+sub list_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ return {
+ 'payby' => [ map {
+ my $cust_payby = $_;
+ +{
+ map { $_ => $cust_payby->$_ }
+ qw( custpaybynum weight payby paymask paydate
+ payname paystate paytype
+ )
+ };
+ }
+ $cust_main->cust_payby
+ ],
+ };
+}
+
+sub insert_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ #XXX payinfo1 + payinfo2 for CHEK?
+ #or take the opportunity to use separate, more well- named fields?
+ # my $payinfo;
+ # $p->{'payinfo1'} =~ /^([\dx]+)$/
+ # or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
+ # my $payinfo1 = $1;
+ # $p->{'payinfo2'} =~ /^([\dx\.]+)$/ # . turned on by echeck-country CA ?
+ # or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
+ # my $payinfo2 = $1;
+ # $payinfo = $payinfo1. '@'. $payinfo2;
+
+ my $cust_payby = new FS::cust_payby {
+ 'custnum' => $custnum,
+ map { $_ => $p->{$_} } qw( weight payby payinfo paycvv paydate payname
+ paystate paytype payip
+ ),
+ };
+
+ my $error = $cust_payby->insert;
+ if ( $error ) {
+ return { 'error' => $error };
+ } else {
+ return { 'custpaybynum' => $cust_payby->custpaybynum };
+ }
+
+}
+
+sub update_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_payby = qsearchs('cust_payby', {
+ 'custnum' => $custnum,
+ 'custpaybynum' => $p->{'custpaybynum'},
+ })
+ or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
+
+ foreach my $field (
+ qw( weight payby payinfo paycvv paydate payname paystate paytype payip )
+ ) {
+ next unless exists($p->{$field});
+ $cust_payby->set($field,$p->{$field});
+ }
+
+ my $error = $cust_payby->replace;
+ if ( $error ) {
+ return { 'error' => $error };
+ } else {
+ return { 'custpaybynum' => $cust_payby->custpaybynum };
+ }
+
+}
+
+sub verify_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_payby = qsearchs('cust_payby', {
+ 'custnum' => $custnum,
+ 'custpaybynum' => $p->{'custpaybynum'},
+ })
+ or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
+
+ return { 'error' => $cust_payby->verify };
+
+}
+
+sub delete_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_payby = qsearchs('cust_payby', {
+ 'custnum' => $custnum,
+ 'custpaybynum' => $p->{'custpaybynum'},
+ })
+ or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
+
+ my $conf = new FS::Conf;
+ if (($cust_payby->payby eq "DCHK" || $cust_payby->payby eq "CHEK") && $conf->exists('selfservice-ACH_info_readonly')) {
+ return { 'error' => "Sorry you do not have permission to delete bank information." };
+ }
+ else {
+ return { 'error' => $cust_payby->delete };
+ }
+}
+
sub cancel {
my $p = shift;
my $session = _cache->get($p->{'session_id'})
}
+sub pkg_info {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $pkg = qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
+ 'hashref' => {
+ 'custnum' => $custnum,
+ 'pkgnum' => $p->{'pkgnum'},
+ },
+ })
+ or return {'error' => 'unknown pkg num $pkgnum'};
+
+ return {
+ pkg_label => $pkg->pkg,
+ pkgpart => $pkg->pkgpart,
+ classnum => $pkg->classnum,
+ };
+
+}
+
sub list_pkgs {
my $p = shift;
# @svc_x;
my @svcs; # stuff to return to the client
+ my %bytes_used_total; # for _used columns only
foreach my $cust_svc (@cust_svc) {
my $svc_x = $cust_svc->svc_x;
my($label, $value) = $cust_svc->label;
# would it make sense to put this in a svc_* method?
+ if (!$hide_usage and grep(/^$svcdb$/, qw(svc_acct svc_broadband)) and $part_svc->part_export_usage) {
+ my $last_bill = $cust_pkg->last_bill || 0;
+ my $now = time;
+ my $up_used = $cust_svc->attribute_since_sqlradacct($last_bill,$now,'AcctInputOctets');
+ my $down_used = $cust_svc->attribute_since_sqlradacct($last_bill,$now,'AcctOutputOctets');
+ %hash = (
+ %hash,
+ 'seconds_used' => $cust_svc->seconds_since_sqlradacct($last_bill,$now),
+ 'upbytes_used' => display_bytecount($up_used),
+ 'downbytes_used' => display_bytecount($down_used),
+ 'totalbytes_used' => display_bytecount($up_used + $down_used)
+ );
+ $bytes_used_total{'seconds_used'} += $hash{'seconds_used'};
+ $bytes_used_total{'upbytes_used'} += $up_used;
+ $bytes_used_total{'downbytes_used'} += $down_used;
+ $bytes_used_total{'totalbytes_used'} += $up_used + $down_used;
+ }
+
if ( $svcdb eq 'svc_acct' ) {
foreach (qw(username email finger seconds)) {
$hash{$_} = $svc_x->$_;
push @svcs, \%hash;
} # foreach $cust_svc
+ foreach my $field (keys %bytes_used_total) {
+ if ($field =~ /bytes/) {
+ $bytes_used_total{$field} = display_bytecount($bytes_used_total{$field});
+ }
+ }
+
return {
'svcnum' => $session->{'svcnum'},
'custnum' => $custnum,
'date_format' => $conf->config('date_format') || '%m/%d/%Y',
'view_usage_nodomain' => $conf->exists('selfservice-view_usage_nodomain'),
'svcs' => \@svcs,
+ 'bytes_used_total' => \%bytes_used_total,
'usage_pools' => [
map { $usage_pools{$_} }
sort { $a cmp $b }
my $conf = new FS::Conf;
if ( $conf->exists('signup_server-realtime') ) {
- my $bill_error = _do_bop_realtime( $cust_main, $status );
+ my $bill_error = _do_bop_realtime( $cust_main, $status, 'collect'=>$p->{run_bill_events} );
if ($bill_error) {
$cust_pkg->cancel('quiet'=>1);
my $err_or_cust_pkg = $cust_pkg->change( 'pkgpart' => $p->{'pkgpart'},
'quantity' => $p->{'quantity'} || 1,
);
+
+ my $new_pkg = qsearchs('part_pkg', { 'pkgpart' => $p->{pkgpart} } )
+ or return { 'error' => "unknown package $p->{pkgpart}" };
return { error=>$err_or_cust_pkg, pkgnum=>$cust_pkg->pkgnum }
unless ref($err_or_cust_pkg);
+
if ( $conf->exists('signup_server-realtime') ) {
my $bill_error = _do_bop_realtime( $cust_main, $status, 'no_invoice_void'=>1 );
$err_or_cust_pkg->reexport;
}
- return { error => '', pkgnum => $cust_pkg->pkgnum };
+ return { error => '', pkg => $new_pkg->pkg, pkgnum => $err_or_cust_pkg->pkgnum };
}
sub _do_bop_realtime {
my ($cust_main, $status, %opt) = @_;
- my $old_balance = $cust_main->balance;
+ my $old_balance = $cust_main->balance;
- my @cust_bill;
- my $bill_error = $cust_main->bill(
- 'return_bill' => \@cust_bill,
- );
-
- $bill_error ||= $cust_main->apply_payments_and_credits;
+ my @cust_bill;
+ my $bill_error = $cust_main->bill(
+ 'return_bill' => \@cust_bill,
+ );
- $bill_error ||= $cust_main->realtime_collect('selfservice' => 1)
- if $cust_main->payby =~ /^(CARD|CHEK)$/;
+ $bill_error ||= $cust_main->apply_payments_and_credits;
- if ( $cust_main->balance > $old_balance
- && $cust_main->balance > 0
- && ( $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/
- || $status eq 'suspended'
- )
- )
- {
- unless ( $opt{'no_invoice_void'} ) {
+ $bill_error ||= $cust_main->realtime_collect('selfservice' => 1);
- #this used to apply a credit, but now we can void invoices...
- foreach my $cust_bill (@cust_bill) {
- my $voiderror = $cust_bill->void('automatic payment failed');
- warn "Error voiding cust bill after decline: $voiderror" if $voiderror;
- }
+ if ( $cust_main->balance > $old_balance
+ && $cust_main->balance > 0
+ && ( $cust_main->has_cust_payby_auto || $status eq 'suspended' )
+ )
+ {
+ unless ( $opt{'no_invoice_void'} ) {
+ #this used to apply a credit, but now we can void invoices...
+ foreach my $cust_bill (@cust_bill) {
+ my $voiderror = $cust_bill->void('automatic payment failed');
+ warn "Error voiding cust bill after decline: $voiderror" if $voiderror;
}
- return { 'error' => '_decline', 'bill_error' => $bill_error };
}
- '';
+ return { 'error' => '_decline', 'bill_error' => $bill_error };
+ }
+
+ if ( $opt{'collect'} ) {
+ my $collect_error = $cust_main->collect();
+ return { 'error' => '_decline', 'bill_error' => $collect_error }
+ if $collect_error; #?
+ }
+
+ '';
}
sub renew_info {
or return { 'error' => "Can't resume session" }; #better error message
my $custnum = $session->{'custnum'};
-
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
or return { 'error' => "unknown custnum $custnum" };
my $pkgnum = $p->{'pkgnum'};
-
my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
'pkgnum' => $pkgnum, } )
or return { 'error' => "unknown pkgnum $pkgnum" };
- my $error = $cust_pkg->cancel('quiet' => 1);
+ my $error = $cust_pkg->cancel( 'quiet' => 1,
+ 'date' => $p->{'date'},
+ );
return { 'error' => $error };
-
}
sub provision_phone {
)
&& ! $svc_acct->check_password($p->{'old_password'});
- $error = 'Password too short.'
- if length($p->{'new_password'}) < ($conf->config('passwordmin') || 6);
- $error = 'Password too long.'
- if length($p->{'new_password'}) > ($conf->config('passwordmax') || 8);
-
- $svc_acct->set_password($p->{'new_password'});
- $error ||= $svc_acct->replace();
+ $error ||= $svc_acct->is_password_allowed($p->{'new_password'})
+ || $svc_acct->set_password($p->{'new_password'})
+ || $svc_acct->replace();
#regular pw change in self-service should change contact pw too, otherwise its
#way too confusing. hell its confusing they're separate at all, but alas.
)
) {
#svc_acct was successful but this one returns an error? "shouldn't happen"
+ #don't recheck is_password_allowed here; if the svc_acct password was
+ #legal, that's good enough
$error ||= $contact->change_password($p->{'new_password'});
}
if ( $svc_acct ) {
- $svc_acct->set_password($p->{'new_password'});
- my $error = $svc_acct->replace();
+ my $error ||= $svc_acct->is_password_allowed($p->{'new_password'})
+ || $svc_acct->set_password($p->{'new_password'})
+ || $svc_acct->replace();
return { %$info, 'error' => $error } if $error;
if ( $contact ) {
- my $error = $contact->change_password($p->{'new_password'});
+ my $error = $contact->is_password_allowed($p->{'new_password'})
+ || $contact->change_password($p->{'new_password'});
return { %$info, 'error' => $error }; # if $error;
}
+sub validate_passwd {
+ my $p = shift;
+
+ my %result;
+ %result = ( 'fieldid' => $p->{'fieldid'} )
+ if $p->{'fieldid'} =~ /^\w+$/;
+
+ return { %result, 'password_invalid' => 'Enter new password' }
+ unless length($p->{'check_password'});
+
+ my $svc_acct;
+ if ($p->{'svcnum'}) {
+ # false laziness with myaccount_passwd
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { %result, 'error' => $session } if $context eq 'error';
+
+ $custnum =~ /^(\d+)$/ or die "illegal custnum";
+ my $search = " AND custnum = $1";
+ $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent';
+
+ $svc_acct = qsearchs( {
+ 'table' => 'svc_acct',
+ 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '.
+ 'LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ 'LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $p->{'svcnum'}, },
+ 'extra_sql' => $search, #important
+ } )
+ or return { %result, 'error' => "Service not found" };
+ # end false laziness
+ }
+
+ $svc_acct ||= new FS::svc_acct {};
+
+ my $error = $svc_acct->is_password_allowed($p->{'check_password'});
+ return { %result, 'password_invalid' => $error } if $error;
+ return { %result, 'password_valid' => 1 };
+}
+
sub list_tickets {
my $p = shift;
my($context, $session, $custnum) = _custoragent_session_custnum($p);