summaryrefslogtreecommitdiff
path: root/FS/FS/ClientAPI
diff options
context:
space:
mode:
Diffstat (limited to 'FS/FS/ClientAPI')
-rw-r--r--FS/FS/ClientAPI/Bulk.pm384
-rw-r--r--FS/FS/ClientAPI/MasonComponent.pm75
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm375
-rw-r--r--FS/FS/ClientAPI/SGNG.pm277
-rw-r--r--FS/FS/ClientAPI/Signup.pm193
5 files changed, 1206 insertions, 98 deletions
diff --git a/FS/FS/ClientAPI/Bulk.pm b/FS/FS/ClientAPI/Bulk.pm
new file mode 100644
index 0000000..ec617df
--- /dev/null
+++ b/FS/FS/ClientAPI/Bulk.pm
@@ -0,0 +1,384 @@
+package FS::ClientAPI::Bulk;
+
+use strict;
+
+use vars qw( $DEBUG $cache );
+use Date::Parse;
+use FS::Record qw( qsearchs );
+use FS::Conf;
+use FS::ClientAPI_SessionCache;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::svc_external;
+use FS::cust_recon;
+use Data::Dumper;
+
+$DEBUG = 1;
+
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache ( {
+ 'namespace' => 'FS::ClientAPI::Agent', #yes, share session_ids
+ } );
+}
+
+sub _izoom_ftp_row_fixup {
+ my $hash = shift;
+
+ my @addr_fields = qw( address1 address2 city state zip );
+ my @fields = ( qw( agent_custid username _password first last ),
+ @addr_fields,
+ map { "ship_$_" } @addr_fields );
+
+ $hash->{$_} =~ s/[&\/\*'"]/_/g foreach @fields;
+
+ #$hash->{action} = '' if $hash->{action} eq 'R'; #unsupported for ftp
+
+ $hash->{refnum} = 1; #ahem
+ $hash->{country} = 'US';
+ $hash->{ship_country} = 'US';
+ $hash->{payby} = 'LECB';
+ $hash->{payinfo} = $hash->{daytime};
+ $hash->{ship_fax} = '' if ( !$hash->{sms} || $hash->{sms} eq 'F' );
+
+ my $has_ship =
+ grep { $hash->{"ship_$_"} &&
+ (! $hash->{$_} || $hash->{"ship_$_"} ne $hash->{$_} )
+ }
+ ( @addr_fields, 'fax' );
+
+ if ( $has_ship ) {
+ foreach ( @addr_fields, qw( first last ) ) {
+ $hash->{"ship_$_"} = $hash->{$_} unless $hash->{"ship_$_"};
+ }
+ }
+
+ delete $hash->{sms};
+
+ '';
+
+};
+
+sub _izoom_ftp_result {
+ my ($hash, $error) = @_;
+ my $cust_main =
+ qsearchs( 'cust_main', { 'agent_custid' => $hash->{agent_custid},
+ 'agentnum' => $hash->{agentnum}
+ }
+ );
+
+ my $custnum = $cust_main ? $cust_main->custnum : '';
+ my @response = ( $hash->{action}, $hash->{agent_custid}, $custnum );
+
+ if ( $error ) {
+ push @response, ( 'ERROR', $error );
+ } else {
+ push @response, ( 'OK', 'OK' );
+ }
+
+ join( ',', @response );
+
+}
+
+sub _izoom_ftp_badaction {
+ "Invalid action: $_[0] record: @_ ";
+}
+
+sub _izoom_soap_row_fixup { _izoom_ftp_row_fixup(@_) };
+
+sub _izoom_soap_result {
+ my ($hash, $error) = @_;
+
+ if ( $hash->{action} eq 'R' ) {
+ if ( $error ) {
+ return "Please check errors:\n $error"; # odd extra space
+ } else {
+ return join(' ', "Everything ok.", $hash->{pkg}, $hash->{adjourn} );
+ }
+ }
+
+ my $pkg = $hash->{pkg} || $hash->{saved_pkg} || '';
+ if ( $error ) {
+ return join(' ', $hash->{agent_custid}, $error );
+ } else {
+ return join(' ', $hash->{agent_custid}, $pkg, $hash->{adjourn} );
+ }
+
+}
+
+sub _izoom_soap_badaction {
+ "Unknown action '$_[13]' ";
+}
+
+my %format = (
+ 'izoom-ftp' => {
+ 'fields' => [ qw ( action agent_custid username _password
+ daytime ship_fax sms first last
+ address1 address2 city state zip
+ pkg adjourn ship_address1 ship_address2
+ ship_city ship_state ship_zip ) ],
+ 'fixup' => sub { _izoom_ftp_row_fixup(@_) },
+ 'result' => sub { _izoom_ftp_result(@_) },
+ 'action' => sub { _izoom_ftp_badaction(@_) },
+ },
+ 'izoom-soap' => {
+ 'fields' => [ qw ( agent_custid username _password
+ daytime first last address1 address2
+ city state zip pkg action adjourn
+ ship_fax sms ship_address1 ship_address2
+ ship_city ship_state ship_zip ) ],
+ 'fixup' => sub { _izoom_soap_row_fixup(@_) },
+ 'result' => sub { _izoom_soap_result(@_) },
+ 'action' => sub { _izoom_soap_badaction(@_) },
+ },
+);
+
+sub processrow {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $conf = new FS::Conf;
+ my $format = $conf->config('selfservice-bulk_format', $session->{agentnum})
+ || 'izoom-soap';
+ my ( @row ) = @{ $p->{row} };
+
+ warn "processrow called with '". join("' '", @row). "'\n" if $DEBUG;
+
+ return { 'error' => "unknown format: $format" }
+ unless exists $format{$format};
+
+ return { 'error' => "Invalid record record length: ". scalar(@row).
+ "record: @row " #sic
+ }
+ unless scalar(@row) == scalar(@{$format{$format}{fields}});
+
+ my %hash = ( 'agentnum' => $session->{agentnum} );
+ my $error;
+
+ foreach my $field ( @{ $format{ $format }{ fields } } ) {
+ $hash{$field} = shift @row;
+ }
+
+ $error ||= &{ $format{ $format }{ fixup } }( \%hash );
+
+ # put in the fixup routine?
+ if ( 'R' eq $hash{action} ) {
+ warn "processing reconciliation\n" if $DEBUG;
+ $error ||= process_recon($hash{agentnum}, $hash{agent_custid});
+ } elsif ( 'P' eq $hash{action} ) {
+ # do nothing
+ } elsif( 'D' eq $hash{action} ) {
+ $hash{promo_pkg} = 'disk-1-'. $session->{agent};
+ } elsif ( 'S' eq $hash{action} ) {
+ $hash{promo_pkg} = 'disk-2-'. $session->{agent};
+ $hash{saved_pkg} = $hash{pkg};
+ $hash{pkg} = '';
+ } else {
+ $error ||= &{ $format{ $format }{ action } }( @row );
+ }
+
+ warn "processing provision\n" if ($DEBUG && !$error && $hash{action} ne 'R');
+ $error ||= provision( %hash ) unless $hash{action} eq 'R';
+
+ my $result = &{ $format{ $format }{ result } }( \%hash, $error );
+
+ warn "processrow returning '". join("' '", $result, $error). "'\n"
+ if $DEBUG;
+
+ return { 'error' => $error, 'message' => $result };
+
+}
+
+sub provision {
+ my %args = ( @_ );
+
+ delete $args{action};
+
+ my $cust_main =
+ qsearchs( 'cust_main',
+ { map { $_ => $args{$_} } qw ( agent_custid agentnum ) },
+ );
+
+ unless ( $cust_main ) {
+ $cust_main = new FS::cust_main { %args };
+ my $error = $cust_main->insert;
+ return $error if $error;
+ }
+
+ my @pkgs = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs;
+ if ( scalar(@pkgs) > 1 ) {
+ return "Invalid account, should not be more then one active package ". #sic
+ "but found: ". scalar(@pkgs). " packages.";
+ }
+
+ my $part_pkg = qsearchs( 'part_pkg', { 'pkg' => $args{pkg} } )
+ or return "Unknown pkgpart: $args{pkg}"
+ if $args{pkg};
+
+
+ my $create_package = $args{pkg};
+ if ( scalar(@pkgs) && $create_package ) {
+ my $pkg = pop(@pkgs);
+
+ if ( $part_pkg->pkgpart != $pkg->pkgpart ) {
+ my @cust_bill_pkg = $pkg->cust_bill_pkg();
+ if ( 1 == scalar(@cust_bill_pkg) ) {
+ my $cbp= pop(@cust_bill_pkg);
+ my $cust_bill = $cbp->cust_bill;
+ $cust_bill->delete(); #really? wouldn't a credit be better?
+ }
+ $pkg->cancel();
+ } else {
+ $create_package = '';
+ $pkg->setfield('adjourn', str2time($args{adjourn}));
+ my $error = $pkg->replace();
+ return $error if $error;
+ }
+ }
+
+ if ( $create_package ) {
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'pkgpart' => $part_pkg->pkgpart,
+ 'adjourn' => str2time( $args{adjourn} ),
+ } );
+
+ my $svcpart = $part_pkg->svcpart('svc_acct');
+
+ my $svc_acct = new FS::svc_acct ( {
+ 'svcpart' => $svcpart,
+ 'username' => $args{username},
+ '_password' => $args{_password},
+ } );
+
+ my $error = $cust_main->order_pkg( cust_pkg => $cust_pkg,
+ svcs => [ $svc_acct ],
+ );
+ return $error if $error;
+ }
+
+ if ( $args{promo_pkg} ) {
+ my $part_pkg =
+ qsearchs( 'part_pkg', { 'promo_code' => $args{promo_pkg} } )
+ or return "unknown pkgpart: $args{promo_pkg}";
+
+ my $svcpart = $part_pkg->svcpart('svc_external')
+ or return "unknown svcpart: svc_external";
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'svcpart' => $svcpart,
+ 'pkgpart' => $part_pkg->pkgpart,
+ } );
+
+ my $svc_ext = new FS::svc_external ( { 'svcpart' => $svcpart } );
+
+ my $ticket_subject = 'Send setup disk to customer '. $cust_main->custnum;
+ my $error = $cust_main->order_pkg ( cust_pkg => $cust_pkg,
+ svcs => [ $svc_ext ],
+ noexport => 1,
+ ticket_subject => $ticket_subject,
+ ticket_queue => "disk-$args{agentnum}",
+ );
+ return $error if $error;
+ }
+
+ my $error = $cust_main->bill();
+ return $error if $error;
+}
+
+sub process_recon {
+ my ( $agentnum, $id ) = @_;
+ my @recs = split /;/, $id;
+ my $err = '';
+ foreach my $rec ( @recs ) {
+ my @record = split /,/, $rec;
+ my $result = process_recon_record(@record, $agentnum);
+ $err .= "$result\n" if $result;
+ }
+ return $err;
+}
+
+sub process_recon_record {
+ my ( $agent_custid, $username, $_password, $daytime, $first, $last, $address1, $address2, $city, $state, $zip, $pkg, $adjourn, $agentnum) = @_;
+
+ warn "process_recon_record called with '". join("','", @_). "'\n" if $DEBUG;
+
+ my ($cust_pkg, $package);
+
+ my $cust_main =
+ qsearchs( 'cust_main',
+ { 'agent_custid' => $agent_custid, 'agentnum' => $agentnum },
+ );
+
+ my $comments = '';
+ if ( $cust_main ) {
+ my @cust_pkg = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs;
+ if ( scalar(@cust_pkg) == 1) {
+ $cust_pkg = pop(@cust_pkg);
+ $package = $cust_pkg->part_pkg->pkg;
+ $comments = "$agent_custid wrong package, expected: $pkg found: $package"
+ if ( $pkg ne $package );
+ } else {
+ $comments = "invalid account, should be one active package but found: ".
+ scalar(@cust_pkg). " packages.";
+ }
+ } else {
+ $comments =
+ "Customer not found agent_custid=$agent_custid, agentnum=$agentnum";
+ }
+
+ my $cust_recon = new FS::cust_recon( {
+ 'recondate' => time,
+ 'agentnum' => $agentnum,
+ 'first' => $first,
+ 'last' => $last,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'custnum' => $cust_main ? $cust_main->custnum : '', #really?
+ 'status' => $cust_main ? $cust_main->status : '',
+ 'pkg' => $package,
+ 'adjourn' => $cust_pkg ? $cust_pkg->adjourn : '',
+ 'agent_custid' => $agent_custid, # redundant?
+ 'agent_pkg' => $pkg,
+ 'agent_adjourn' => str2time($adjourn),
+ 'comments' => $comments,
+ } );
+
+ warn Dumper($cust_recon) if $DEBUG;
+ my $error = $cust_recon->insert;
+ return $error if $error;
+
+ warn "process_recon_record returning $comments\n" if $DEBUG;
+
+ $comments;
+
+}
+
+sub check_username {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $svc_domain = qsearchs( 'svc_domain', { 'domain' => $p->{domain} } )
+ or return { 'error' => 'Unknown domain '. $p->{domain} };
+
+ my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{user},
+ 'domsvc' => $svc_domain->svcnum,
+ },
+ );
+
+ return { 'error' => $p->{user}. '@'. $p->{domain}. " alerady in use" } # sic
+ if $svc_acct;
+
+ return { 'error' => '',
+ 'message' => $p->{user}. '@'. $p->{domain}. " is free"
+ };
+}
+
+1;
diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm
index 78ea9bd..88baf07 100644
--- a/FS/FS/ClientAPI/MasonComponent.pm
+++ b/FS/FS/ClientAPI/MasonComponent.pm
@@ -1,9 +1,14 @@
package FS::ClientAPI::MasonComponent;
use strict;
-use vars qw($DEBUG $me);
+use vars qw( $cache $DEBUG $me );
+use subs qw( _cache );
use FS::Mason qw( mason_interps );
use FS::Conf;
+use FS::ClientAPI_SessionCache;
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main;
+use FS::part_pkg;
$DEBUG = 0;
$me = '[FS::ClientAPI::MasonComponent]';
@@ -13,6 +18,54 @@ my %allowed_comps = map { $_=>1 } qw(
/misc/areacodes.cgi
/misc/exchanges.cgi
/misc/phonenums.cgi
+ /misc/states.cgi
+ /misc/counties.cgi
+ /misc/svc_acct-domains.cgi
+ /misc/part_svc-columns.cgi
+);
+
+my %session_comps = map { $_=>1 } qw(
+ /elements/location.html
+ /edit/cust_main/first_pkg/select-part_pkg.html
+);
+
+my %session_callbacks = (
+
+ '/elements/location.html' => sub {
+ my( $custnum, $argsref ) = @_;
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return "unknown custnum $custnum";
+ my %args = @$argsref;
+ $args{object} = $cust_main;
+ @$argsref = ( %args );
+ return ''; #no error
+ },
+
+ '/edit/cust_main/first_pkg/select-part_pkg.html' => sub {
+ my( $custnum, $argsref ) = @_;
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return "unknown custnum $custnum";
+
+ my $pkgpart = $cust_main->agent->pkgpart_hashref;
+
+ #false laziness w/ edit/cust_main/first_pkg.html
+ my @first_svc = ( 'svc_acct', 'svc_phone' );
+
+ my @part_pkg =
+ grep { $_->svcpart(\@first_svc)
+ && ( $pkgpart->{ $_->pkgpart }
+ || ( $_->agentnum && $_->agentnum == $cust_main->agentnum )
+ )
+ }
+ qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
+
+ my %args = @$argsref;
+ $args{part_pkg} = \@part_pkg;
+ @$argsref = ( %args );
+ return ''; #no error
+
+ },
+
);
my $outbuf;
@@ -24,12 +77,23 @@ sub mason_comp {
warn "$me mason_comp called on $packet\n" if $DEBUG;
my $comp = $packet->{'comp'};
- unless ( $allowed_comps{$comp} ) {
+ unless ( $allowed_comps{$comp} || $session_comps{$comp} ) {
return { 'error' => 'Illegal component' };
}
my @args = $packet->{'args'} ? @{ $packet->{'args'} } : ();
+ if ( $session_comps{$comp} ) {
+
+ my $session = _cache->get($packet->{'session_id'})
+ or return ( 'error' => "Can't resume session" ); #better error message
+ my $custnum = $session->{'custnum'};
+
+ my $error = &{ $session_callbacks{$comp} }( $custnum, \@args );
+ return { 'error' => $error } if $error;
+
+ }
+
my $conf = new FS::Conf;
$FS::Mason::Request::FSURL = $conf->config('selfservice_server-base_url');
$FS::Mason::Request::QUERY_STRING = $packet->{'query_string'} || '';
@@ -43,4 +107,11 @@ sub mason_comp {
}
+#hmm
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount',
+ } );
+}
+
1;
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index c0586af..26cd76f 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -10,7 +10,7 @@ use Business::CreditCard;
use Time::Duration;
use FS::UI::Web::small_custview qw(small_custview); #less doh
use FS::UI::Web;
-use FS::UI::bytecount;
+use FS::UI::bytecount qw( display_bytecount );
use FS::Conf;
use FS::Record qw(qsearch qsearchs);
use FS::Msgcat qw(gettext);
@@ -55,12 +55,35 @@ sub _cache {
} );
}
+sub skin_info {
+ #my $p = shift;
+
+ my $conf = new FS::Conf;
+
+ use vars qw($skin_info); #cache for performance.
+ #agentnum eventually...? but if they're not logged in yet.. ?
+
+ $skin_info ||= {
+ 'head' => join("\n", $conf->config('selfservice-head') ),
+ 'body_header' => join("\n", $conf->config('selfservice-body_header') ),
+ 'body_footer' => join("\n", $conf->config('selfservice-body_footer') ),
+ 'body_bgcolor' => scalar( $conf->config('selfservice-body_bgcolor') ),
+ 'box_bgcolor' => scalar( $conf->config('selfservice-box_bgcolor') ),
+
+ 'company_name' => scalar($conf->config('company_name')),
+ };
+
+ $skin_info;
+
+}
+
sub login_info {
my $p = shift;
my $conf = new FS::Conf;
my %info = (
+ %{ skin_info() },
'phone_login' => $conf->exists('selfservice_server-phone_login'),
'single_domain'=> scalar($conf->config('selfservice_server-single_domain')),
);
@@ -103,16 +126,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,12 +137,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;
+ 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(). $$))
@@ -147,12 +176,59 @@ sub logout {
my $p = shift;
if ( $p->{'session_id'} ) {
_cache->remove($p->{'session_id'});
- return { 'error' => '' };
+ return { %{ skin_info() }, 'error' => '' };
} else {
- return { 'error' => "Can't resume session" }; #better error message
+ return { %{ skin_info() }, '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} }
+ ];
+
+ return { %$info,
+ 'custnum' => $custnum,
+ 'pkgnum' => $session->{'pkgnum'},
+ 'svcnum' => $session->{'svcnum'},
+ 'nonprimary' => $session->{'nonprimary'},
+ };
+}
+
sub customer_info {
my $p = shift;
@@ -175,21 +251,32 @@ 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
+ );
+
+ warn $return{small_custview};
$return{name} = $cust_main->first. ' '. $cust_main->get('last');
@@ -281,7 +368,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'} };
@@ -291,15 +379,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" };
}
@@ -338,6 +426,12 @@ sub payment_info {
'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
@@ -353,6 +447,7 @@ sub payment_info {
'paytypes' => [ @FS::cust_main::paytypes ],
'paybys' => [ $conf->config('signup_server-payby') ],
+ 'cust_paybys' => \@cust_paybys,
'stateid_label' => FS::Msgcat::_gettext('stateid'),
'stateid_state_label' => FS::Msgcat::_gettext('stateid_state'),
@@ -375,7 +470,21 @@ sub payment_info {
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
or return { 'error' => "unknown custnum $custnum" };
- $return{balance} = $cust_main->balance;
+ $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{balance} = $cust_main->balance; #XXX pkg-balances?
$return{payname} = $cust_main->payname
|| ( $cust_main->first. ' '. $cust_main->get('last') );
@@ -436,6 +545,7 @@ sub process_payment {
or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
my $paybatch = $1;
+ $p->{'payby'} ||= 'CARD';
$p->{'payby'} =~ /^([A-Z]{4})$/
or return { 'error' => "illegal_payby " . $p->{'payby'} };
my $payby = $1;
@@ -460,6 +570,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;
@@ -490,7 +602,8 @@ sub process_payment {
}
my %payby2fields = (
- 'CARD' => [ qw( paystart_month paystart_year payissue address1 address2 city state zip payip ) ],
+ 'CARD' => [ qw( paystart_month paystart_year payissue payip
+ address1 address2 city state zip country ) ],
'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ],
);
@@ -501,6 +614,7 @@ sub process_payment {
'payname' => $payname,
'paybatch' => $paybatch, #this doesn't actually do anything
'paycvv' => $paycvv,
+ 'pkgnum' => $session->{'pkgnum'},
map { $_ => $p->{$_} } @{ $payby2fields{$payby} }
);
return { 'error' => $error } if $error;
@@ -512,15 +626,15 @@ sub process_payment {
if ($payby eq 'CARD' || $payby eq 'DCRD') {
$new->set( $_ => $p->{$_} )
foreach qw( payname paystart_month paystart_year payissue payip
- address1 address2 city state zip payinfo );
+ 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' => $payinfo );
$new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
my $error = $new->replace($cust_main);
return { 'error' => $error } if $error;
@@ -531,6 +645,32 @@ sub process_payment {
}
+sub realtime_collect {
+
+ 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 $error = $cust_main->realtime_collect(
+ 'method' => $p->{'method'},
+ 'pkgnum' => $session->{'pkgnum'},
+ 'session_id' => $p->{'session_id'},
+ );
+ return { 'error' => $error } unless ref( $error );
+
+ my $amount = $session->{'pkgnum'}
+ ? $cust_main->balance_pkgnum( $session->{'pkgnum'} )
+ : $cust_main->balance;
+
+ return { 'error' => '', amount => $amount, %$error };
+}
+
sub process_payment_order_pkg {
my $p = shift;
@@ -617,7 +757,14 @@ sub invoice_logo {
#sessioning for this? how do we get the session id to the backend invoice
# template so it can add it to the link, blah
- my $templatename = $p->{'templatename'};
+ my $agentnum = '';
+ if ( $p->{'invnum'} ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $p->{'invnum'} } )
+ or return { 'error' => 'unknown invnum' };
+ $agentnum = $cust_bill->cust_main->agentnum;
+ }
+
+ my $templatename = $p->{'template'} || $p->{'templatename'};
#false laziness-ish w/view/cust_bill-logo.cgi
@@ -631,7 +778,7 @@ sub invoice_logo {
my $filename = "logo$templatename.png";
return { 'error' => '',
- 'logo' => $conf->config_binary($filename),
+ 'logo' => $conf->config_binary($filename, $agentnum),
'content_type' => 'image/png', #should allow gif, jpg too
};
}
@@ -734,10 +881,17 @@ 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
}
- @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc
- if $p->{'svcdb'};
+ if ( $p->{'svcdb'} ) {
+ my $svcdb = ref($p->{'svcdb'}) eq 'HASH'
+ ? $p->{'svcdb'}
+ : ref($p->{'svcdb'}) eq 'ARRAY'
+ ? { map { $_=>1 } @{ $p->{'svcdb'} } }
+ : { $p->{'svcdb'} => 1 };
+ @cust_svc = grep $svcdb->{ $_->part_svc->svcdb }, @cust_svc
+ }
#@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username }
# @svc_x;
@@ -745,30 +899,51 @@ sub list_svcs {
{
#no#'svcnum' => $session->{'svcnum'},
'custnum' => $custnum,
- 'svcs' => [ map {
- my $svc_x = $_->svc_x;
- my($label, $value) = $_->label;
- my $part_pkg = $svc_x->cust_svc->cust_pkg->part_pkg;
-
- { 'svcnum' => $_->svcnum,
- 'label' => $label,
- 'value' => $value,
- 'username' => $svc_x->username,
- 'email' => $svc_x->email,
- 'seconds' => $svc_x->seconds,
- 'upbytes' => FS::UI::bytecount::display_bytecount($svc_x->upbytes),
- 'downbytes' => FS::UI::bytecount::display_bytecount($svc_x->downbytes),
- 'totalbytes'=> FS::UI::bytecount::display_bytecount($svc_x->totalbytes),
- 'recharge_amount' => $part_pkg->option('recharge_amount', 1),
- 'recharge_seconds' => $part_pkg->option('recharge_seconds', 1),
- 'recharge_upbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_upbytes', 1)),
- 'recharge_downbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_downbytes', 1)),
- 'recharge_totalbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_totalbytes', 1)),
- # more...
- };
- }
- @cust_svc
- ],
+ 'svcs' => [
+ map {
+ my $svc_x = $_->svc_x;
+ my($label, $value) = $_->label;
+ my $svcdb = $_->part_svc->svcdb;
+ my $part_pkg = $_->cust_pkg->part_pkg;
+
+ my %hash = (
+ 'svcnum' => $_->svcnum,
+ 'svcdb' => $svcdb,
+ 'label' => $label,
+ 'value' => $value,
+ );
+
+ if ( $svcdb eq 'svc_acct' ) {
+ %hash = (
+ %hash,
+ 'username' => $svc_x->username,
+ 'email' => $svc_x->email,
+ 'seconds' => $svc_x->seconds,
+ 'upbytes' => display_bytecount($svc_x->upbytes),
+ 'downbytes' => display_bytecount($svc_x->downbytes),
+ 'totalbytes' => display_bytecount($svc_x->totalbytes),
+
+ 'recharge_amount' => $part_pkg->option('recharge_amount',1),
+ 'recharge_seconds' => $part_pkg->option('recharge_seconds',1),
+ 'recharge_upbytes' =>
+ display_bytecount($part_pkg->option('recharge_upbytes',1)),
+ 'recharge_downbytes' =>
+ display_bytecount($part_pkg->option('recharge_downbytes',1)),
+ 'recharge_totalbytes' =>
+ display_bytecount($part_pkg->option('recharge_totalbytes',1)),
+ # more...
+ );
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+ %hash = (
+ %hash,
+ );
+ }
+
+ \%hash;
+ }
+ @cust_svc
+ ],
};
}
@@ -778,9 +953,8 @@ sub _list_svc_usage {
my @usage = ();
foreach my $part_export (
map { qsearch ( 'part_export', { 'exporttype' => $_ } ) }
- qw (sqlradius sqlradius_withdomain')
+ qw( sqlradius sqlradius_withdomain )
) {
-
push @usage, @ { $part_export->usage_sessions($begin, $end, $svc_acct) };
}
(@usage);
@@ -813,29 +987,50 @@ sub list_support_usage {
_usage_details(\&_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, );
+}
+
+sub list_cdr_usage {
+ my $p = shift;
+ _usage_details( \&_list_cdr_usage, $p,
+ 'svcdb' => 'svc_phone',
+ );
+}
+
sub _usage_details {
- my ($callback, $p) = (shift,shift);
+ my($callback, $p, %opt) = @_;
my($context, $session, $custnum) = _custoragent_session_custnum($p);
return { 'error' => $session } if $context eq 'error';
my $search = { 'svcnum' => $p->{'svcnum'} };
$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $svc_acct = qsearchs ( 'svc_acct', $search );
+
+ my $svcdb = $opt{'svcdb'} || 'svc_acct';
+
+ my $svc_x = qsearchs( $svcdb, $search );
return { 'error' => 'No service selected in list_svc_usage' }
- unless $svc_acct;
+ unless $svc_x;
- my $freq = $svc_acct->cust_svc->cust_pkg->part_pkg->freq;
- my $start = $svc_acct->cust_svc->cust_pkg->setup;
- #my $end = $svc_acct->cust_svc->cust_pkg->bill; # or time?
- my $end = time;
+ my $header = $svcdb eq 'svc_phone'
+ ? [ split(',', FS::cdr::invoice_header('default') ) ] #XXX
+ : [];
- unless($p->{beginning}){
- $p->{beginning} = $svc_acct->cust_svc->cust_pkg->last_bill;
- $p->{ending} = $end;
+ my $cust_pkg = $svc_x->cust_svc->cust_pkg;
+ my $freq = $cust_pkg->part_pkg->freq;
+ my $start = $cust_pkg->setup;
+ #my $end = $cust_pkg->bill; # or time?
+ my $end = time;
+
+ unless ( $p->{beginning} ) {
+ $p->{beginning} = $cust_pkg->last_bill;
+ $p->{ending} = $end;
}
- my (@usage) = &$callback($svc_acct,$p->{beginning},$p->{ending});
+ my (@usage) = &$callback($svc_x, $p->{beginning}, $p->{ending});
#kinda false laziness with FS::cust_main::bill, but perhaps
#we should really change this bit to DateTime and DateTime::Duration
@@ -878,6 +1073,7 @@ sub _usage_details {
'ending' => $p->{ending},
'previous' => ($previous > $start) ? $previous : $start,
'next' => ($next < $end) ? $next : $end,
+ 'header' => $header,
'usage' => \@usage,
};
}
@@ -921,7 +1117,7 @@ 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 ) ],
);
@@ -1124,14 +1320,18 @@ sub renew_info {
my $total = $cust_main->balance;
my @array = map {
- $total += $_->part_pkg->base_recur;
+ my $bill = $_->bill;
+ $total += $_->part_pkg->base_recur($_, \$bill);
my $renew_date = $_->part_pkg->add_freq($_->bill);
{
- 'bill_date' => $_->bill,
- 'bill_date_pretty' => time2str('%x', $_->bill),
- 'renew_date' => $renew_date,
- 'renew_date_pretty' => time2str('%x', $renew_date),
- 'amount' => sprintf('%.2f', $total),
+ 'pkgnum' => $_->pkgnum,
+ 'amount' => sprintf('%.2f', $total),
+ 'bill_date' => $_->bill,
+ 'bill_date_pretty' => time2str('%x', $_->bill),
+ 'renew_date' => $renew_date,
+ 'renew_date_pretty' => time2str('%x', $renew_date),
+ 'expire_date' => $_->expire,
+ 'expire_date_pretty' => time2str('%x', $_->expire),
};
}
@cust_pkg;
@@ -1140,6 +1340,15 @@ sub renew_info {
}
+sub payment_info_renew_info {
+ my $p = shift;
+ my $renew_info = renew_info($p);
+ my $payment_info = payment_info($p);
+ return { %$renew_info,
+ %$payment_info,
+ };
+}
+
sub order_renew {
my $p = shift;
diff --git a/FS/FS/ClientAPI/SGNG.pm b/FS/FS/ClientAPI/SGNG.pm
new file mode 100644
index 0000000..7f784dc
--- /dev/null
+++ b/FS/FS/ClientAPI/SGNG.pm
@@ -0,0 +1,277 @@
+#this stuff is SG-specific (i.e. multi-customer company username hack)
+
+package FS::ClientAPI::SGNG;
+
+use strict;
+use vars qw( $cache $DEBUG );
+use Time::Local qw(timelocal timelocal_nocheck);
+use Business::CreditCard;
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::ClientAPI::MyAccount; #qw( payment_info process_payment )
+
+$DEBUG = 0;
+
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount', #yes, share session_ids
+ } );
+}
+
+sub ping {
+ #my $p = shift;
+
+ return { 'pong' => '1' };
+
+}
+
+#this might almost be general-purpose
+sub decompify_pkgs {
+ 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' => 'Not a complimentary customer' }
+ unless $cust_main->payby eq 'COMP';
+
+ my $paydate =
+ $cust_main->paydate =~ /^\S+$/ ? $cust_main->paydate : '2037-12-31';
+
+ my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
+
+ my $date = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+
+ foreach my $cust_pkg (
+ qsearch({ 'table' => 'cust_pkg',
+ 'hashref' => { 'custnum' => $custnum,
+ 'bill' => '',
+ },
+ 'extra_sql' => ' AND '. FS::cust_pkg->active_sql,
+ })
+ ) {
+ $cust_pkg->set('bill', $date);
+ my $error = $cust_pkg->replace;
+ return { 'error' => $error } if $error;
+ }
+
+ return { 'error' => '' };
+
+}
+
+#find old payment info
+# (should work just like MyAccount::payment_info, except returns previous info
+# too)
+# definitly sg-specific, no one else stores past customer records like this
+sub previous_payment_info {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $payment_info = FS::ClientAPI::MyAccount::payment_info($p);
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ #?
+ return $payment_info if $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/;
+
+ foreach my $prev_cust_main (
+ reverse _previous_cust_main( 'custnum' => $custnum,
+ 'username' => $cust_main->company,
+ 'with_payments' => 1,
+ )
+ ) {
+
+ next unless $prev_cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/;
+
+ if ( $prev_cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+
+ #card expired?
+ my ($payyear,$paymonth,$payday) = split (/-/, $cust_main->paydate);
+
+ my $expdate = timelocal_nocheck(0,0,0,1,$paymonth,$payyear);
+
+ next if $expdate < time;
+
+ } elsif ( $prev_cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+
+ #any check? or just skip these in favor of cards?
+
+ }
+
+ return { %$payment_info,
+ #$prev_cust_main->payment_info
+ _cust_main_payment_info( $prev_cust_main ),
+ 'previous_custnum' => $prev_cust_main->custnum,
+ };
+
+ }
+
+ #still nothing? return an error?
+ return $payment_info;
+
+}
+
+#this is really FS::cust_main::payment_info, but here for now
+sub _cust_main_payment_info {
+ my $self = shift;
+
+ my %return = ();
+
+ $return{balance} = $self->balance;
+
+ $return{payname} = $self->payname
+ || ( $self->first. ' '. $self->get('last') );
+
+ $return{$_} = $self->get($_) for qw(address1 address2 city state zip);
+
+ $return{payby} = $self->payby;
+ $return{stateid_state} = $self->stateid_state;
+
+ if ( $self->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{card_type} = cardtype($self->payinfo);
+ $return{payinfo} = $self->paymask;
+
+ @return{'month', 'year'} = $self->paydate_monthyear;
+
+ }
+
+ if ( $self->payby =~ /^(CHEK|DCHK)$/ ) {
+ my ($payinfo1, $payinfo2) = split '@', $self->paymask;
+ $return{payinfo1} = $payinfo1;
+ $return{payinfo2} = $payinfo2;
+ $return{paytype} = $self->paytype;
+ $return{paystate} = $self->paystate;
+
+ }
+
+ #doubleclick protection
+ my $_date = time;
+ $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
+
+ %return;
+
+}
+
+#find old cust_main records (with payments)
+sub _previous_cust_main {
+
+ #safety check! return nothing unless we're enabled explicitly
+ return () unless FS::Conf->new->exists('sg-multicustomer_hack');
+
+ my %opt = @_;
+ my $custnum = $opt{'custnum'};
+ my $username = $opt{'username'};
+
+ my %search = ();
+ if ( $opt{'with_payments'} ) {
+ $search{'extra_sql'} =
+ ' AND 0 < ( SELECT COUNT(*) FROM cust_pay
+ WHERE cust_pay.custnum = cust_main.custnum
+ )
+ ';
+ }
+
+ qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'company' => { op => 'ILIKE', value => $opt{'username'} },
+ 'custnum' => { op => '!=', value => $opt{'custnum'} },
+ },
+ 'order_by' => 'ORDER BY custnum',
+ %search,
+ } );
+
+}
+
+#since we could be passing masked old CC data, need to look that up and
+#replace it (like regular process_payment does) w/info from old customer record
+sub previous_process_payment {
+ my $p = shift;
+
+ return FS::ClientAPI::MyAccount::process_payment($p)
+ unless $p->{'previous_custnum'}
+ && ( ( $p->{'payby'} =~ /^(CARD|DCRD)$/ && $p->{'payinfo'} =~ /x/i )
+ || ( $p->{'payby'} =~ /^(CHEK|DCHK)$/ && $p->{'payinfo1'} =~ /x/i )
+ );
+
+ 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" };
+
+ #make sure this is really a previous custnum of this customer
+ my @previous_cust_main =
+ grep { $_->custnum == $p->{'previous_custnum'} }
+ _previous_cust_main( 'custnum' => $custnum,
+ 'username' => $cust_main->company,
+ 'with_payments' => 1,
+ );
+
+ my $previous_cust_main = $previous_cust_main[0];
+
+ #causes problems with old data w/old masking method
+ #if $previous_cust_main->paymask eq $payinfo;
+
+ if ( $p->{'payby'} =~ /^(CHEK|DCHK)$/ && $p->{'payinfo1'} =~ /x/i ) {
+ ( $p->{'payinfo1'}, $p->{'payinfo2'} ) =
+ split('@', $previous_cust_main->payinfo);
+ } elsif ( $p->{'payby'} =~ /^(CARD|DCRD)$/ && $p->{'payinfo'} =~ /x/i ) {
+ $p->{'payinfo'} = $previous_cust_main->payinfo;
+ }
+
+ FS::ClientAPI::MyAccount::process_payment($p);
+
+}
+
+sub previous_payment_info_renew_info {
+ my $p = shift;
+ my $renew_info = renew_info($p);
+ my $payment_info = previous_payment_info($p);
+ return { %$renew_info,
+ %$payment_info,
+ };
+}
+
+sub previous_process_payment_order_pkg {
+ my $p = shift;
+
+ my $hr = previous_process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_pkg($p);
+}
+
+sub previous_process_payment_change_pkg {
+ my $p = shift;
+
+ my $hr = previous_process_payment($p);
+ return $hr if $hr->{'error'};
+
+ change_pkg($p);
+}
+
+sub previous_process_payment_order_renew {
+ my $p = shift;
+
+ my $hr = previous_process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_renew($p);
+}
+
+1;
+
diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm
index 5569dfb..c376476 100644
--- a/FS/FS/ClientAPI/Signup.pm
+++ b/FS/FS/ClientAPI/Signup.pm
@@ -6,6 +6,7 @@ use Data::Dumper;
use Tie::RefHash;
use FS::Conf;
use FS::Record qw(qsearch qsearchs dbdef);
+use FS::CGI qw(popurl);
use FS::Msgcat qw(gettext);
use FS::Misc qw(card_types);
use FS::ClientAPI_SessionCache;
@@ -20,6 +21,7 @@ use FS::svc_phone;
use FS::acct_snarf;
use FS::queue;
use FS::reg_code;
+use FS::payby;
$DEBUG = 0;
$me = '[FS::ClientAPI::Signup]';
@@ -59,7 +61,9 @@ sub signup_info {
} }
grep { $_->svcpart($svc_x)
&& ( $href->{ $_->pkgpart }
- || $_->agentnum == $agent->agentnum
+ || ( $_->agentnum
+ && $_->agentnum == $agent->agentnum
+ )
)
}
qsearch( 'part_pkg', { 'disabled' => '' } )
@@ -103,6 +107,8 @@ sub signup_info {
'security_phrase' => $conf->exists('security_phrase'),
+ 'nomadix' => $conf->exists('signup_server-nomadix'),
+
'payby' => [ $conf->config('signup_server-payby') ],
'card_types' => card_types(),
@@ -276,6 +282,32 @@ sub signup_info {
if ( $agentnum ) {
+ warn "$me setting agent-specific payment flag\n" if $DEBUG > 1;
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ warn "$me has agent $agent\n" if $DEBUG > 1;
+ if ( $agent ) { #else complain loudly?
+ $signup_info->{'hide_payment_fields'} = [];
+ foreach my $payby (@{$signup_info->{payby}}) {
+ warn "$me checking $payby payment fields\n" if $DEBUG > 1;
+ my $hide = 0;
+ if ( FS::payby->realtime($payby) ) {
+ my $payment_gateway =
+ $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby),
+ 'nofatal' => 1,
+ );
+ if ( $payment_gateway
+ && $payment_gateway->gateway_namespace
+ eq 'Business::OnlineThirdPartyPayment'
+ ) {
+ warn "$me hiding $payby payment fields\n" if $DEBUG > 1;
+ $hide = 1;
+ }
+ }
+ push @{$signup_info->{'hide_payment_fields'}}, $hide;
+ }
+ }
+ warn "$me done setting agent-specific payment flag\n" if $DEBUG > 1;
+
warn "$me setting agent-specific package list\n" if $DEBUG > 1;
$signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
unless @{ $signup_info->{'part_pkg'} };
@@ -295,8 +327,6 @@ sub signup_info {
];
warn "$me done setting agent-specific adv. source list\n" if $DEBUG > 1;
- my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
-
$signup_info->{'agent_name'} = $agent->agent;
$signup_info->{'company_name'} = $conf->config('company_name', $agentnum);
@@ -436,6 +466,21 @@ sub new_customer {
unless grep { $_ eq $packet->{'payby'} }
$conf->config('signup_server-payby');
+ if (FS::payby->realtime($packet->{payby})) {
+ my $payby = $packet->{payby};
+
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum });
+ return { 'error' => "Unknown reseller" }
+ unless $agent;
+
+ my $gw = $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby),
+ 'nofatal' => 1,
+ );
+
+ $cust_main->payby('BILL') # MCRD better?
+ if $gw && $gw->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+ }
+
$cust_main->payinfo($cust_main->daytime)
if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
@@ -469,14 +514,14 @@ sub new_customer {
#return { 'error' => $error } if $error;
#should be all auto-magic and shit
- my $svc;
+ my @svc = ();
if ( $svc_x eq 'svc_acct' ) {
- $svc = new FS::svc_acct ( {
+ my $svc = new FS::svc_acct {
'svcpart' => $svcpart,
map { $_ => $packet->{$_} }
qw( username _password sec_phrase popnum ),
- } );
+ };
my @acct_snarf;
my $snarfnum = 1;
@@ -493,21 +538,48 @@ sub new_customer {
}
$svc->child_objects( \@acct_snarf );
+ push @svc, $svc;
+
} elsif ( $svc_x eq 'svc_phone' ) {
- $svc = new FS::svc_phone ( {
+ my $svc = new FS::svc_phone ( {
'svcpart' => $svcpart,
map { $_ => $packet->{$_} }
qw( countrycode phonenum sip_password pin ),
} );
+ push @svc, $svc;
+
} else {
die "unknown signup service $svc_x";
}
-
- my $y = $svc->setdefault; # arguably should be in new method
+ my $y = $svc[0]->setdefault; # arguably should be in new method
return { 'error' => $y } if $y && !ref($y);
+ if ($packet->{'mac_addr'} && $conf->exists('signup_server-mac_addr_svcparts'))
+ {
+
+ my %mac_addr_svcparts = map { $_ => 1 }
+ $conf->config('signup_server-mac_addr_svcparts');
+ my @pkg_svc = grep { $_->quantity && $mac_addr_svcparts{$_->svcpart} }
+ $cust_pkg->part_pkg->pkg_svc;
+
+ return { 'error' => 'No service defined to assign mac address' }
+ unless @pkg_svc;
+
+ my $svc = new FS::svc_acct {
+ 'svcpart' => $pkg_svc[0]->svcpart, #multiple matches? alas..
+ 'username' => $packet->{'mac_addr'},
+ '_password' => '', #blank as requested (set passwordmin to 0)
+ };
+
+ my $y = $svc->setdefault; # arguably should be in new method
+ return { 'error' => $y } if $y && !ref($y);
+
+ push @svc, $svc;
+
+ }
+
#$error = $svc->check;
#return { 'error' => $error } if $error;
@@ -521,7 +593,7 @@ sub new_customer {
use Tie::RefHash;
tie my %hash, 'Tie::RefHash';
- %hash = ( $cust_pkg => [ $svc ] );
+ %hash = ( $cust_pkg => \@svc );
#msgcat
$error = $cust_main->insert(
\%hash,
@@ -547,10 +619,26 @@ sub new_customer {
# " new customer: $bill_error"
# if $bill_error;
- $bill_error = $cust_main->collect('realtime' => 1);
+ if ($cust_main->_new_bop_required()) {
+ $bill_error = $cust_main->realtime_collect(
+ method => FS::payby->payby2bop( $packet->{payby} ),
+ depend_jobnum => $placeholder->jobnum,
+ );
+ } else {
+ $bill_error = $cust_main->collect('realtime' => 1);
+ }
#warn "[fs_signup_server] error collecting from new customer: $bill_error"
# if $bill_error;
+ if ($bill_error && ref($bill_error) eq 'HASH') {
+ return { 'error' => '_collect',
+ ( map { $_ => $bill_error->{$_} }
+ qw(popup_url reference collectitems)
+ ),
+ amount => $cust_main->balance,
+ };
+ }
+
if ( $cust_main->balance > 0 ) {
#this makes sense. credit is "un-doing" the invoice
@@ -589,9 +677,9 @@ sub new_customer {
);
if ( $svc_x eq 'svc_acct' ) {
- $return{$_} = $svc->$_() for qw( username _password );
+ $return{$_} = $svc[0]->$_() for qw( username _password );
} elsif ( $svc_x eq 'svc_phone' ) {
- $return{$_} = $svc->$_() for qw( countrycode phonenum sip_password pin );
+ $return{$_} = $svc[0]->$_() for qw( countrycode phonenum sip_password pin );
} else {
die "unknown signup service $svc_x";
}
@@ -600,4 +688,83 @@ sub new_customer {
}
+sub capture_payment {
+ my $packet = shift;
+
+ warn "$me capture_payment called on $packet\n" if $DEBUG;
+
+ ###
+ # identify processor/gateway from called back URL
+ ###
+
+ my $conf = new FS::Conf;
+
+ my $url = $packet->{url};
+ my $payment_gateway =
+ qsearchs('payment_gateway', { 'gateway_callback_url' => popurl(0, $url) } );
+
+ unless ($payment_gateway) {
+
+ my ( $processor, $login, $password, $action, @bop_options ) =
+ $conf->config('business-onlinepayment');
+ $action ||= 'normal authorization';
+ pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+ die "No real-time processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n"
+ unless $processor;
+
+ $payment_gateway = new FS::payment_gateway( {
+ gateway_namespace => $conf->config('business-onlinepayment-namespace'),
+ gateway_module => $processor,
+ gateway_username => $login,
+ gateway_password => $password,
+ gateway_action => $action,
+ options => [ ( @bop_options ) ],
+ });
+
+ }
+
+ die "No real-time third party processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n*"
+ unless $payment_gateway->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+
+ ###
+ # locate pending transaction
+ ###
+
+ eval "use Business::OnlineThirdPartyPayment";
+ die $@ if $@;
+
+ my $transaction =
+ new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
+ @{ [ $payment_gateway->options ] },
+ );
+
+ my $paypendingnum = $transaction->reference($packet->{data});
+
+ my $cust_pay_pending =
+ qsearchs('cust_pay_pending', { paypendingnum => $paypendingnum } );
+
+ unless ($cust_pay_pending) {
+ my $bill_error = "No payment is being processed with id $paypendingnum".
+ "; Transaction aborted.";
+ return { error => '_decline', bill_error => $bill_error };
+ }
+
+ if ($cust_pay_pending->status ne 'pending') {
+ my $bill_error = "Payment with id $paypendingnum is not pending, but ".
+ $cust_pay_pending->status. "; Transaction aborted.";
+ return { error => '_decline', bill_error => $bill_error };
+ }
+
+ my $cust_main = $cust_pay_pending->cust_main;
+ my $bill_error =
+ $cust_main->realtime_botpp_capture( $cust_pay_pending, %{$packet->{data}} );
+
+ return { 'error' => ( $bill_error->{bill_error} ? '_decline' : '' ),
+ %$bill_error,
+ };
+
+}
+
1;