X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2FClientAPI%2FMyAccount.pm;h=29e374c930abdd7eaba46af3769cf45fc8c8c297;hp=895b11d9dfb7f824f4588a859f59900ef3512536;hb=d7eafc9c9aae2bf47ea19a56cc5bb1380c5874e4;hpb=c1dbb54da9699d6d347b6d8392e2108f80fb1dcf diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 895b11d9d..29e374c93 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -1,18 +1,21 @@ package FS::ClientAPI::MyAccount; +use 5.008; #require 5.8+ for Time::Local 1.05+ use strict; -use vars qw( $cache $DEBUG ); +use vars qw( $cache $DEBUG $me ); use subs qw( _cache _provision ); use Data::Dumper; use Digest::MD5 qw(md5_hex); use Date::Format; use Business::CreditCard; use Time::Duration; +use Time::Local qw(timelocal_nocheck); use FS::UI::Web::small_custview qw(small_custview); #less doh use FS::UI::Web; use FS::UI::bytecount qw( display_bytecount ); use FS::Conf; -use FS::Record qw(qsearch qsearchs); +#use FS::UID qw(dbh); +use FS::Record qw(qsearch qsearchs dbh); use FS::Msgcat qw(gettext); use FS::Misc qw(card_types); use FS::ClientAPI_SessionCache; @@ -28,16 +31,10 @@ use FS::cust_pkg; use FS::payby; use FS::acct_rt_transaction; use HTML::Entities; +use FS::TicketSystem; $DEBUG = 0; - -#false laziness with FS::cust_main -BEGIN { - eval "use Time::Local;"; - die "Time::Local minimum version 1.05 required with Perl versions before 5.6" - if $] < 5.006 && !defined($Time::Local::VERSION); - eval "use Time::Local qw(timelocal_nocheck);"; -} +$me = '[FS::ClientAPI::MyAccount]'; use vars qw( @cust_main_editable_fields ); @cust_main_editable_fields = qw( @@ -55,12 +52,83 @@ sub _cache { } ); } +sub skin_info { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + #return { 'error' => $session } if $context eq 'error'; + + my $agentnum = ''; + if ( $context eq 'customer' ) { + + my $sth = dbh->prepare('SELECT agentnum FROM cust_main WHERE custnum = ?') + or die dbh->errstr; + + $sth->execute($custnum) or die $sth->errstr; + + $agentnum = $sth->fetchrow_arrayref->[0] + or die "no agentnum for custnum $custnum"; + + #} elsif ( $context eq 'agent' ) { + } elsif ( $p->{'agentnum'} =~ /^(\d+)$/ ) { + $agentnum = $1; + } + + my $conf = new FS::Conf; + + #false laziness w/Signup.pm + + my $skin_info_cache_agent = _cache->get("skin_info_cache_agent$agentnum"); + + if ( $skin_info_cache_agent ) { + + warn "$me loading cached skin info for agentnum $agentnum\n" + if $DEBUG > 1; + + } else { + + warn "$me populating skin info cache for agentnum $agentnum\n" + if $DEBUG > 1; + + $skin_info_cache_agent = { + 'agentnum' => $agentnum, + ( map { $_ => scalar( $conf->config($_, $agentnum) ) } + qw( company_name ) ), + ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) } + qw( body_bgcolor box_bgcolor + text_color link_color vlink_color hlink_color alink_color + font title_color title_align title_size menu_bgcolor menu_fontsize + ) + ), + ( map { $_ => $conf->exists("selfservice-$_", $agentnum ) } + qw( menu_skipblanks menu_skipheadings menu_nounderline ) + ), + ( map { $_ => scalar($conf->config_binary("selfservice-$_", $agentnum)) } + qw( title_left_image title_right_image + menu_top_image menu_body_image menu_bottom_image + ) + ), + 'logo' => scalar($conf->config_binary('logo.png', $agentnum )), + ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) } + qw( head body_header body_footer company_address ) ), + }; + + _cache->set("skin_info_cache_agent$agentnum", $skin_info_cache_agent); + + } + + #{ %$skin_info_cache_agent }; + $skin_info_cache_agent; + +} + sub login_info { my $p = shift; my $conf = new FS::Conf; my %info = ( + %{ skin_info($p) }, 'phone_login' => $conf->exists('selfservice_server-phone_login'), 'single_domain'=> scalar($conf->config('selfservice_server-single_domain')), ); @@ -103,16 +171,6 @@ sub login { ); return { error => 'User not found.' } unless $svc_acct; - #my $pkg_svc = $svc_acct->cust_svc->pkg_svc; - #return { error => 'Only primary user may log in.' } - # if $conf->exists('selfservice_server-primary_only') - # && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' ); - my $cust_svc = $svc_acct->cust_svc; - my $part_pkg = $cust_svc->cust_pkg->part_pkg; - return { error => 'Only primary user may log in.' } - if $conf->exists('selfservice_server-primary_only') - && $cust_svc->svcpart != $part_pkg->svcpart('svc_acct'); - return { error => 'Incorrect password.' } unless $svc_acct->check_password($p->{'password'}); @@ -124,14 +182,28 @@ sub login { 'svcnum' => $svc_x->svcnum, }; - my $cust_pkg = $svc_x->cust_svc->cust_pkg; + my $cust_svc = $svc_x->cust_svc; + my $cust_pkg = $cust_svc->cust_pkg; if ( $cust_pkg ) { my $cust_main = $cust_pkg->cust_main; $session->{'custnum'} = $cust_main->custnum; - $session->{'pkgnum'} = $cust_pkg->pkgnum - if $conf->exists('pkg-balances'); + if ( $conf->exists('pkg-balances') ) { + my @cust_pkg = grep { $_->part_pkg->freq !~ /^(0|$)/ } + $cust_main->ncancelled_pkgs; + $session->{'pkgnum'} = $cust_pkg->pkgnum + if scalar(@cust_pkg) > 1; + } } + #my $pkg_svc = $svc_acct->cust_svc->pkg_svc; + #return { error => 'Only primary user may log in.' } + # if $conf->exists('selfservice_server-primary_only') + # && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' ); + my $part_pkg = $cust_pkg->part_pkg; + return { error => 'Only primary user may log in.' } + if $conf->exists('selfservice_server-primary_only') + && $cust_svc->svcpart != $part_pkg->svcpart([qw( svc_acct svc_phone )]); + my $session_id; do { $session_id = md5_hex(md5_hex(time(). {}. rand(). $$)) @@ -149,10 +221,59 @@ sub logout { my $p = shift; if ( $p->{'session_id'} ) { _cache->remove($p->{'session_id'}); - return { 'error' => '' }; + return { %{ skin_info($p) }, 'error' => '' }; } else { - return { 'error' => "Can't resume session" }; #better error message + return { %{ skin_info($p) }, 'error' => "Can't resume session" }; #better error message + } +} + +sub access_info { + my $p = shift; + + my $conf = new FS::Conf; + + my $info = skin_info($p); + + use vars qw( $cust_paybys ); #cache for performance + unless ( $cust_paybys ) { + + my %cust_paybys = map { $_ => 1 } + map { FS::payby->payby2payment($_) } + $conf->config('signup_server-payby'); + + $cust_paybys = [ keys %cust_paybys ]; + } + $info->{'cust_paybys'} = $cust_paybys; + + 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" }; + + $info->{hide_payment_fields} = + [ + map { my $pg = ''; + if ( FS::payby->realtime($_) ) { + $pg = $cust_main->agent->payment_gateway( + 'method' => FS::payby->payby2bop($_), + 'nofatal' => 1, + ); + } + $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; + } + @{ $info->{cust_paybys} } + ]; + + $info->{'self_suspend_reason'} = + $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum); + + return { %$info, + 'custnum' => $custnum, + 'access_pkgnum' => $session->{'pkgnum'}, + 'access_svcnum' => $session->{'svcnum'}, + }; } sub customer_info { @@ -177,21 +298,30 @@ sub customer_info { my $cust_main = qsearchs('cust_main', $search ) or return { 'error' => "unknown custnum $custnum" }; - $return{balance} = $cust_main->balance; + if ( $session->{'pkgnum'} ) { + $return{balance} = $cust_main->balance_pkgnum( $session->{'pkgnum'} ); + } else { + $return{balance} = $cust_main->balance; + } $return{tickets} = [ ($cust_main->tickets) ]; - my @open = map { - { - invnum => $_->invnum, - date => time2str("%b %o, %Y", $_->_date), - owed => $_->owed, - }; - } $cust_main->open_cust_bill; - $return{open_invoices} = \@open; + unless ( $session->{'pkgnum'} ) { + my @open = map { + { + invnum => $_->invnum, + date => time2str("%b %o, %Y", $_->_date), + owed => $_->owed, + }; + } $cust_main->open_cust_bill; + $return{open_invoices} = \@open; + } $return{small_custview} = - small_custview( $cust_main, $conf->config('countrydefault') ); + small_custview( $cust_main, + scalar($conf->config('countrydefault')), + ( $session->{'pkgnum'} ? 1 : 0 ), #nobalance + ); $return{name} = $cust_main->first. ' '. $cust_main->get('last'); @@ -230,6 +360,11 @@ sub customer_info { $return{support_services} = \@support_services; } + if ( $conf->config('prepayment_discounts-credit_type') ) { + #need to eval? + $return{discount_terms_hash} = { $cust_main->discount_terms_hash }; + } + } elsif ( $session->{'svcnum'} ) { #no customer record my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } ) @@ -283,7 +418,8 @@ sub edit_info { $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' ); - }elsif ( $payby =~ /^(CHEK|DCHK)$/ ) { + } elsif ( $payby =~ /^(CHEK|DCHK)$/ ) { + my $payinfo; $p->{'payinfo1'} =~ /^([\dx]+)$/ or return { 'error' => "illegal account number ". $p->{'payinfo1'} }; @@ -293,15 +429,15 @@ sub edit_info { my $payinfo2 = $1; $payinfo = $payinfo1. '@'. $payinfo2; - if ( $payinfo eq $cust_main->paymask ) { - $new->payinfo($cust_main->payinfo); - } else { - $new->payinfo($payinfo); - } + $new->payinfo( ($payinfo eq $cust_main->paymask) + ? $cust_main->payinfo + : $payinfo + ); $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' ); - }elsif ( $payby =~ /^(BILL)$/ ) { + } elsif ( $payby =~ /^(BILL)$/ ) { + #no-op } elsif ( $payby ) { #notyet ready return { 'error' => "unknown payby $payby" }; } @@ -331,15 +467,21 @@ sub payment_info { #generic ## + my $conf = new FS::Conf; use vars qw($payment_info); #cache for performance unless ( $payment_info ) { - my $conf = new FS::Conf; my %states = map { $_->state => 1 } qsearch('cust_main_county', { 'country' => $conf->config('countrydefault') || 'US' } ); + my %cust_paybys = map { $_ => 1 } + map { FS::payby->payby2payment($_) } + $conf->config('signup_server-payby'); + + my @cust_paybys = keys %cust_paybys; + $payment_info = { #list all counties/states/countries @@ -355,9 +497,7 @@ sub payment_info { 'paytypes' => [ @FS::cust_main::paytypes ], 'paybys' => [ $conf->config('signup_server-payby') ], - 'cust_paybys' => [ map { FS::payby->payby2payment($_) } - $conf->config('signup_server-payby') - ], + 'cust_paybys' => \@cust_paybys, 'stateid_label' => FS::Msgcat::_gettext('stateid'), 'stateid_state_label' => FS::Msgcat::_gettext('stateid_state'), @@ -365,6 +505,8 @@ sub payment_info { 'show_ss' => $conf->exists('show_ss'), 'show_stateid' => $conf->exists('show_stateid'), 'show_paystate' => $conf->exists('show_bankstate'), + + 'save_unchecked' => $conf->exists('selfservice-save_unchecked'), }; } @@ -382,17 +524,19 @@ sub payment_info { $return{hide_payment_fields} = [ - map { FS::payby->realtime($_) && - $cust_main - ->agent - ->payment_gateway( 'method' => FS::payby->payby2bop($_) ) - ->gateway_namespace - eq 'Business::OnlineThirdPartyPayment' + map { my $pg = ''; + if ( FS::payby->realtime($_) ) { + $pg = $cust_main->agent->payment_gateway( + 'method' => FS::payby->payby2bop($_), + 'nofatal' => 1, + ); + } + $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; } @{ $return{cust_paybys} } ]; - $return{balance} = $cust_main->balance; + $return{balance} = $cust_main->balance; #XXX pkg-balances? $return{payname} = $cust_main->payname || ( $cust_main->first. ' '. $cust_main->get('last') ); @@ -419,6 +563,11 @@ sub payment_info { } + if ( $conf->config('prepayment_discounts-credit_type') ) { + #need to eval? + $return{discount_terms_hash} = { $cust_main->discount_terms_hash }; + } + #doubleclick protection my $_date = time; $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32; @@ -445,6 +594,15 @@ sub process_payment { my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) or return { 'error' => "unknown custnum $custnum" }; + $p->{'amount'} =~ /^\s*(\d+(\.\d{2})?)\s*$/ + or return { 'error' => gettext('illegal_amount') }; + my $amount = $1; + return { error => 'Amount must be greater than 0' } unless $amount > 0; + + $p->{'discount_term'} =~ /^\s*(\d*)\s*$/ + or return { 'error' => gettext('illegal_discount_term'). ': '. $p->{'discount_term'} }; + my $discount_term = $1; + $p->{'payname'} =~ /^([\w \,\.\-\']+)$/ or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} }; my $payname = $1; @@ -478,6 +636,8 @@ sub process_payment { $payinfo = $p->{'payinfo'}; + #more intelligent mathing will be needed here if you change + #card_masking_method and don't remove existing paymasks $payinfo = $cust_main->payinfo if $cust_main->paymask eq $payinfo; @@ -489,7 +649,7 @@ sub process_payment { validate($payinfo) or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo return { 'error' => gettext('unknown_card_type') } - if cardtype($payinfo) eq "Unknown"; + if $payinfo !~ /^99\d{14}$/ && cardtype($payinfo) eq "Unknown"; if ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) { if ( cardtype($payinfo) eq 'American Express card' ) { @@ -513,7 +673,7 @@ sub process_payment { 'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ], ); - my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $p->{'amount'}, + my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount, 'quiet' => 1, 'payinfo' => $payinfo, 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01', @@ -521,6 +681,7 @@ sub process_payment { 'paybatch' => $paybatch, #this doesn't actually do anything 'paycvv' => $paycvv, 'pkgnum' => $session->{'pkgnum'}, + 'discount_term' => $discount_term, map { $_ => $p->{$_} } @{ $payby2fields{$payby} } ); return { 'error' => $error } if $error; @@ -531,20 +692,33 @@ sub process_payment { my $new = new FS::cust_main { $cust_main->hash }; if ($payby eq 'CARD' || $payby eq 'DCRD') { $new->set( $_ => $p->{$_} ) - foreach qw( payinfo payname paystart_month paystart_year payissue payip + foreach qw( payname paystart_month paystart_year payissue payip address1 address2 city state zip country ); $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' ); } elsif ($payby eq 'CHEK' || $payby eq 'DCHK') { $new->set( $_ => $p->{$_} ) foreach qw( payname payip paytype paystate stateid stateid_state ); - $new->set( 'payinfo' => $payinfo ); $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' ); } + $new->set( 'payinfo' => $cust_main->card_token || $payinfo ); $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' ); my $error = $new->replace($cust_main); - return { 'error' => $error } if $error; - $cust_main = $new; + if ( $error ) { + #no, this causes customers to process their payments again + #return { 'error' => $error }; + #XXX just warn verosely for now so i can figure out how these happen in + # the first place, eventually should redirect them to the "change + #address" page but indicate the payment did process?? + delete($p->{'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". + "PACKET: ". Dumper($p)."\n"; + #} else { + #not needed... + #$cust_main = $new; + } } return { 'error' => '' }; @@ -552,7 +726,6 @@ sub process_payment { } sub realtime_collect { - my $p = shift; my $session = _cache->get($p->{'session_id'}) @@ -567,10 +740,15 @@ sub realtime_collect { 'method' => $p->{'method'}, 'pkgnum' => $session->{'pkgnum'}, 'session_id' => $p->{'session_id'}, + 'apply' => 1, ); return { 'error' => $error } unless ref( $error ); - return { 'error' => '', amount => $cust_main->balance, %$error }; + my $amount = $session->{'pkgnum'} + ? $cust_main->balance_pkgnum( $session->{'pkgnum'} ) + : $cust_main->balance; + + return { 'error' => '', amount => $amount, %$error }; } sub process_payment_order_pkg { @@ -783,6 +961,7 @@ sub list_svcs { foreach my $cust_pkg ( $p->{'ncancelled'} ? $cust_main->ncancelled_pkgs : $cust_main->unsuspended_pkgs ) { + next if $session->{'pkgnum'} && $cust_pkg->pkgnum != $session->{'pkgnum'}; push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context } if ( $p->{'svcdb'} ) { @@ -798,7 +977,7 @@ sub list_svcs { # @svc_x; { - #no#'svcnum' => $session->{'svcnum'}, + 'svcnum' => $session->{'svcnum'}, 'custnum' => $custnum, 'svcs' => [ map { @@ -891,7 +1070,7 @@ sub list_support_usage { sub _list_cdr_usage { my($svc_phone, $begin, $end) = @_; map [ $_->downstream_csv('format' => 'default') ], #XXX config for format - $svc_phone->cust_svc->get_cdrs( 'begin'=>$begin, 'end'=>$end, ); + $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, ); } sub list_cdr_usage { @@ -1018,8 +1197,9 @@ sub order_pkg { my %fields = ( 'svc_acct' => [ qw( username domsvc _password sec_phrase popnum ) ], 'svc_domain' => [ qw( domain ) ], - 'svc_phone' => [ qw( phonenum pin sip_password ) ], + 'svc_phone' => [ qw( phonenum pin sip_password phone_name ) ], 'svc_external' => [ qw( id title ) ], + 'svc_pbx' => [ qw( id name ) ], ); my $svc_x = "FS::$svcdb"->new( { @@ -1076,7 +1256,9 @@ sub order_pkg { $cust_pkg->reexport; } - return { error => '', pkgnum => $cust_pkg->pkgnum }; + my $svcnum = $svc[0] ? $svc[0]->svcnum : ''; + + return { error=>'', pkgnum=>$cust_pkg->pkgnum, svcnum=>$svcnum }; } @@ -1183,7 +1365,7 @@ sub _do_bop_realtime { my $bill_error = $cust_main->bill || $cust_main->apply_payments_and_credits - || $cust_main->collect('realtime' => 1); + || $cust_main->realtime_collect; if ( $cust_main->balance > $old_balance && $cust_main->balance > 0 @@ -1274,6 +1456,32 @@ sub order_renew { } +sub suspend_pkg { + 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" }; + + my $conf = new FS::Conf; + my $reasonnum = + $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum) + or return { 'error' => 'Permission denied' }; + + my $pkgnum = $p->{'pkgnum'}; + + my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum, + 'pkgnum' => $pkgnum, } ) + or return { 'error' => "unknown pkgnum $pkgnum" }; + + my $error = $cust_pkg->suspend(reason => $reasonnum); + return { 'error' => $error }; + +} + sub cancel_pkg { my $p = shift; my $session = _cache->get($p->{'session_id'}) @@ -1290,7 +1498,7 @@ sub cancel_pkg { 'pkgnum' => $pkgnum, } ) or return { 'error' => "unknown pkgnum $pkgnum" }; - my $error = $cust_pkg->cancel( 'quiet'=>1 ); + my $error = $cust_pkg->cancel('quiet' => 1); return { 'error' => $error }; } @@ -1503,6 +1711,43 @@ sub myaccount_passwd { } +sub create_ticket { + my $p = shift; + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + warn "$me create_ticket: initializing ticket system\n" if $DEBUG; + FS::TicketSystem->init(); + + my $conf = new FS::Conf; + my $queue = $p->{'queue'} + || $conf->config('ticket_system-selfservice_queueid') + || $conf->config('ticket_system-default_queueid'); + + warn "$me create_ticket: creating ticket\n" if $DEBUG; + my $err_or_ticket = FS::TicketSystem->create_ticket( + '', #create RT session based on FS CurrentUser (fs_selfservice) + 'queue' => $queue, + 'custnum' => $custnum, + 'svcnum' => $session->{'svcnum'}, + map { $_ => $p->{$_} } qw( requestor cc subject message mime_type ) + ); + + if ( ref($err_or_ticket) ) { + warn "$me create_ticket: sucessful: ". $err_or_ticket->id. "\n" + if $DEBUG; + return { 'error' => '', + 'ticket_id' => $err_or_ticket->id, + }; + } else { + warn "$me create_ticket: unsucessful: $err_or_ticket\n" + if $DEBUG; + return { 'error' => $err_or_ticket }; + } + + +} + #-- sub _custoragent_session_custnum { @@ -1527,6 +1772,7 @@ sub _custoragent_session_custnum { $custnum = $p->{'custnum'}; } else { + $context = 'error'; return ( 'error' => "Can't resume session" ); #better error message }