X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2FClientAPI%2FMyAccount.pm;h=4a878f8d27e4272dbe21cdca338e5d22a7a492f0;hp=a6bde824a6886bd19cbe5d0aba6d5242869766d5;hb=da820d8c8837dce295e7cbd61accc22c4c019e14;hpb=a68f2e9239ad5cde3bd25ca7aea6af7e0f2ce75f diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index a6bde824a..4a878f8d2 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -11,7 +11,7 @@ use Digest::SHA qw(sha512_hex); 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; @@ -401,20 +401,12 @@ sub payment_gateway { 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 { @@ -672,6 +664,29 @@ sub customer_info_short { }; } +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, + }; +} + sub billing_history { my $p = shift; @@ -825,7 +840,6 @@ sub payment_info { 'save_unchecked' => $conf->exists('selfservice-save_unchecked'), - 'credit_card_surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')), }; } @@ -853,27 +867,33 @@ sub payment_info { $return{$_} = $cust_main->bill_location->get($_) for qw(address1 address2 city state zip); - #XXX look for stored cust_payby info - # - # $return{payname} = $cust_main->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; - # - # @return{'month', 'year'} = $cust_main->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) - #} + # 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_payby->payby =~ /^(CARD|DCRD)$/ ) { + $return{card_type} = cardtype($cust_payby->payinfo); + $return{payinfo} = $cust_payby->paymask; + + @return{'month', 'year'} = $cust_payby->paydate_monthyear; + + } + + 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') ) { #need to eval? @@ -885,6 +905,8 @@ sub payment_info { $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, }; @@ -961,8 +983,12 @@ sub validate_payment { my $payinfo2 = $1; $payinfo = $payinfo1. '@'. $payinfo2; - $payinfo = $cust_main->payinfo - if $cust_main->paymask eq $payinfo; + foreach my $cust_payby ($cust_main->cust_payby('CHEK','DCHK')) { + if ( $cust_payby->paymask eq $payinfo ) { + $payinfo = $cust_payby->payinfo; + last; + } + } } elsif ( $payby eq 'CARD' || $payby eq 'DCRD' ) { @@ -972,9 +998,12 @@ sub validate_payment { #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; @@ -985,7 +1014,7 @@ sub validate_payment { 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' ) { @@ -1092,28 +1121,33 @@ sub do_process_payment { 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 }; @@ -1122,11 +1156,10 @@ sub do_process_payment { #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; } } @@ -1610,6 +1643,50 @@ sub insert_payby { } +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; @@ -1802,6 +1879,7 @@ sub list_svcs { # @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; @@ -1823,6 +1901,24 @@ sub list_svcs { # 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->$_; @@ -1895,12 +1991,19 @@ sub list_svcs { 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 } @@ -2393,7 +2496,7 @@ sub order_pkg { 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); @@ -2548,6 +2651,12 @@ sub _do_bop_realtime { 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; #? + } + ''; } @@ -2654,19 +2763,18 @@ sub cancel_pkg { 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 { @@ -2995,12 +3103,6 @@ sub myaccount_passwd { ) && ! $svc_acct->check_password($p->{'old_password'}); - # should move password length checks into is_password_allowed - $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); - $error ||= $svc_acct->is_password_allowed($p->{'new_password'}) || $svc_acct->set_password($p->{'new_password'}) || $svc_acct->replace(); @@ -3314,6 +3416,45 @@ sub process_reset_passwd { } +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);