use FS::acct_rt_transaction;
use HTML::Entities;
use FS::TicketSystem;
+use Text::CSV_XS;
+use IO::Scalar;
+use Spreadsheet::WriteExcel;
$DEBUG = 0;
$me = '[FS::ClientAPI::MyAccount]';
);
return { error => 'User not found.' } unless $svc_acct;
+ if($conf->exists('selfservice_server-login_svcpart')) {
+ my @svcpart = $conf->config('selfservice_server-login_svcpart');
+ my $svcpart = $svc_acct->cust_svc->svcpart;
+ return { error => 'Invalid user.' }
+ unless grep($_ eq $svcpart, @svcpart);
+ }
+
return { error => 'Incorrect password.' }
unless $svc_acct->check_password($p->{'password'});
}
}
+sub payment_gateway {
+ # internal use only
+ # takes a cust_main and a cust_payby entry, returns the 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;
+ }
+}
+
sub access_info {
my $p = shift;
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->{'hide_payment_fields'} = [
+ map {
+ my $pg = payment_gateway($cust_main, $_);
+ $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+ } @{ $info->{cust_paybys} }
];
$info->{'self_suspend_reason'} =
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
or return { 'error' => "unknown custnum $custnum" };
- $return{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';
- }
- @{ $return{cust_paybys} }
+ $return{'hide_payment_fields'} = [
+ map {
+ my $pg = payment_gateway($cust_main, $_);
+ $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+ } @{ $return{cust_paybys} }
];
$return{balance} = $cust_main->balance; #XXX pkg-balances?
'paycvv' => $paycvv,
'pkgnum' => $session->{'pkgnum'},
'discount_term' => $discount_term,
+ 'selfservice' => 1,
map { $_ => $p->{$_} } @{ $payby2fields{$payby} }
);
return { 'error' => $error } if $error;
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
or return { 'error' => "unknown custnum $custnum" };
+ my $amount;
+ if ( $p->{'amount'} ) {
+ $amount = $p->{'amount'};
+ }
+ elsif ( $session->{'pkgnum'} ) {
+ $amount = $cust_main->balance_pkgnum( $session->{'pkgnum'} );
+ }
+ else {
+ $amount = $cust_main->balance;
+ }
+
my $error = $cust_main->realtime_collect(
'method' => $p->{'method'},
+ 'amount' => $amount,
'pkgnum' => $session->{'pkgnum'},
'session_id' => $p->{'session_id'},
'apply' => 1,
+ 'selfservice'=> 1,
);
return { 'error' => $error } unless ref( $error );
- my $amount = $session->{'pkgnum'}
- ? $cust_main->balance_pkgnum( $session->{'pkgnum'} )
- : $cust_main->balance;
-
return { 'error' => '', amount => $amount, %$error };
}
my $cust_main = qsearchs('cust_main', $search )
or return { 'error' => "unknown custnum $custnum" };
- #return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
-
my $conf = new FS::Conf;
+
+# the duplication below is necessary:
+# 1. to maintain the current buggy behaviour wrt the cust_pkg and part_pkg
+# hashes overwriting each other (setup and no_auto fields). Fixing that is a
+# non-backwards-compatible change breaking the software of anyone using the API
+# instead of the stock selfservice
+# 2. to return cancelled packages as well - for wholesale and non-wholesale
+ if( $conf->exists('selfservice_server-view-wholesale') ) {
+ return { 'svcnum' => $session->{'svcnum'},
+ 'custnum' => $custnum,
+ 'cust_pkg' => [ map {
+ { $_->hash,
+ part_pkg => [ map $_->hashref, $_->part_pkg ],
+ part_svc =>
+ [ map $_->hashref, $_->available_part_svc ],
+ cust_svc =>
+ [ map { my $ref = { $_->hash,
+ label => [ $_->label ],
+ };
+ $ref->{_password} = $_->svc_x->_password
+ if $context eq 'agent'
+ && $conf->exists('agent-showpasswords')
+ && $_->part_svc->svcdb eq 'svc_acct';
+ $ref;
+ } $_->cust_svc
+ ],
+ };
+ } $cust_main->cust_pkg
+ ],
+ 'small_custview' =>
+ small_custview( $cust_main, $conf->config('countrydefault') ),
+ 'wholesale_view' => 1,
+ 'login_svcpart' => [ $conf->config('selfservice_server-login_svcpart') ],
+ 'date_format' => $conf->config('date_format') || '%m/%d/%Y',
+ };
+ }
{ 'svcnum' => $session->{'svcnum'},
'custnum' => $custnum,
my $bill_error = $cust_main->bill
|| $cust_main->apply_payments_and_credits
- || $cust_main->realtime_collect;
+ || $cust_main->realtime_collect('selfservice' => 1);
if ( $cust_main->balance > $old_balance
&& $cust_main->balance > 0
}
+sub provision_phone {
+ my $p = shift;
+ my @bulkdid = @{$p->{'bulkdid'}};
+ unless (scalar(@bulkdid)) {
+ return _provision( 'FS::svc_phone',
+ [qw(phonenum countrycode)],
+ [qw(phonenum countrycode)],
+ $p,
+ @_
+ );
+ }
+#XXX: finish bulk orders
+}
+
sub provision_acct {
my $p = shift;
warn "provision_acct called\n"
}
+sub did_report {
+ my $p = shift;
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ return { error => 'requested format not implemented' }
+ unless ($p->{'format'} eq 'csv' || $p->{'format'} eq 'xls');
+
+ my $conf = new FS::Conf;
+ my $age_threshold = 0;
+ $age_threshold = time() - $conf->config('selfservice-recent-did-age')
+ if ($p->{'recentonly'} && $conf->exists('selfservice-recent-did-age'));
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+# does it make more sense to just run one sql query for this instead of all the
+# insanity below? would increase performance greately for large data sets?
+ my @svc_phone = ();
+ foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) {
+ my @part_svc = $cust_pkg->part_svc;
+ foreach my $part_svc ( @part_svc ) {
+ if($part_svc->svcdb eq 'svc_phone'){
+ my @cust_pkg_svc = @{$part_svc->cust_pkg_svc};
+ foreach my $cust_pkg_svc ( @cust_pkg_svc ) {
+ push @svc_phone, $cust_pkg_svc->svc_x
+ if $cust_pkg_svc->date_inserted >= $age_threshold;
+ }
+ }
+ }
+ }
+
+ my $csv;
+ my $xls;
+ my($xls_r,$xls_c) = (0,0);
+ my $xls_workbook;
+ my $content = '';
+ my @fields = qw( countrycode phonenum pin sip_password phone_name );
+ if($p->{'format'} eq 'csv') {
+ $csv = new Text::CSV_XS { 'always_quote' => 1,
+ 'eol' => "\n",
+ };
+ return { 'error' => 'Unable to create CSV' } unless $csv->combine(@fields);
+ $content .= $csv->string;
+ }
+ elsif($p->{'format'} eq 'xls') {
+ my $XLS1 = new IO::Scalar \$content;
+ $xls_workbook = Spreadsheet::WriteExcel->new($XLS1)
+ or return { 'error' => "Error opening .xls file: $!" };
+ $xls = $xls_workbook->add_worksheet('DIDs');
+ foreach ( @fields ) {
+ $xls->write(0,$xls_c++,$_);
+ }
+ $xls_r++;
+ }
+
+ foreach my $svc_phone ( @svc_phone ) {
+ my @cols = map { $svc_phone->$_ } @fields;
+ if($p->{'format'} eq 'csv') {
+ return { 'error' => 'Unable to create CSV' }
+ unless $csv->combine(@cols);
+ $content .= $csv->string;
+ }
+ elsif($p->{'format'} eq 'xls') {
+ $xls_c = 0;
+ foreach ( @cols ) {
+ $xls->write($xls_r,$xls_c++,$_);
+ }
+ $xls_r++;
+ }
+ }
+
+ $xls_workbook->close() if $p->{'format'} eq 'xls';
+
+ { content => $content, format => $p->{'format'}, };
+}
+
+sub get_ticket {
+ my $p = shift;
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ warn "$me get_ticket: initializing ticket system\n" if $DEBUG;
+ FS::TicketSystem->init();
+
+ if(length($p->{'reply'})) {
+# currently this allows anyone to correspond on any ticket as fs_selfservice
+# probably bad...
+ my @err_or_res = FS::TicketSystem->correspond_ticket(
+ '', #create RT session based on FS CurrentUser (fs_selfservice)
+ 'ticket_id' => $p->{'ticket_id'},
+ 'content' => $p->{'reply'},
+ );
+
+ return { 'error' => 'unable to reply to ticket' }
+ unless ( $err_or_res[0] != 0 && defined $err_or_res[2] );
+ }
+
+ warn "$me get_ticket: getting ticket\n" if $DEBUG;
+ my $err_or_ticket = FS::TicketSystem->get_ticket(
+ '', #create RT session based on FS CurrentUser (fs_selfservice)
+ 'ticket_id' => $p->{'ticket_id'},
+ );
+
+ if ( ref($err_or_ticket) ) {
+
+# since we're bypassing the RT security/permissions model by always using
+# fs_selfservice as the RT user (as opposed to a requestor, which we
+# can't do since we want all tickets linked to a cust), we check below whether
+# the requested ticket was actually linked to this customer
+ my @custs = @{$err_or_ticket->{'custs'}};
+ my @txns = @{$err_or_ticket->{'txns'}};
+
+ return { 'error' => 'no customer' } unless ( $custnum && scalar(@custs) );
+
+ return { 'error' => 'invalid ticket requested' }
+ unless grep($_ eq $custnum, @custs);
+
+ warn "$me get_ticket: sucessful: \n"
+ if $DEBUG;
+ return { 'error' => '',
+ 'transactions' => \@txns,
+ 'ticket_id' => $p->{'ticket_id'},
+ };
+ } else {
+ warn "$me create_ticket: unsucessful: $err_or_ticket\n"
+ if $DEBUG;
+ return { 'error' => $err_or_ticket };
+ }
+}
+
+
#--
sub _custoragent_session_custnum {