use Tie::RefHash;
use Digest::SHA qw(sha512_hex);
use FS::Conf;
-use FS::Record qw(qsearch qsearchs dbdef);
+use FS::Record qw(qsearch qsearchs dbdef dbh);
use FS::CGI qw(popurl);
use FS::Msgcat qw(gettext);
use FS::Misc qw(card_types);
use FS::payby;
use FS::banned_pay;
use FS::part_tag;
+use FS::cust_payby;
$DEBUG = 1;
$me = '[FS::ClientAPI::Signup]';
+=head1 NAME
+
+FS::ClientAPI::Signup - Front-end API for signing up customers
+
+=head1 DESCRIPTION
+
+This module provides the ClientAPI functions for talking to a signup
+server. The signup server is open to the public, i.e. does not require a
+login. The back-end Freeside server creates customers, orders packages and
+services, and processes initial payments.
+
+=head1 METHODS
+
+=over 4
+
+=cut
+
+# document the rest of this as we work on it
+
sub clear_cache {
warn "$me clear_cache called\n" if $DEBUG;
my $cache = new FS::ClientAPI_SessionCache( {
'security_phrase' => $conf->exists('security_phrase'),
- 'nomadix' => $conf->exists('signup_server-nomadix'),
-
'payby' => [ $conf->config('signup_server-payby') ],
'payby_longname' => [ map { FS::payby->longname($_) }
'agentnum2part_pkg' => $agentnum2part_pkg,
'svc_acct_pop' => [ map $_->hashref, qsearch('svc_acct_pop',{} ) ],
- 'nomadix' => $conf->exists('signup_server-nomadix'),
'payby' => [ $conf->config('signup_server-payby') ],
'card_types' => card_types(),
- 'paytypes' => [ @FS::cust_main::paytypes ],
+ 'paytypes' => [ FS::cust_payby->paytypes ],
'cvv_enabled' => 1,
'require_cvv' => $conf->exists('signup-require_cvv'),
'stateid_enabled' => $conf->exists('show_stateid'),
#possibly some validation will be needed
}
- my $agentnum;
- if ( exists $packet->{'session_id'} ) {
- my $cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Agent',
- } );
- my $session = $cache->get($packet->{'session_id'});
- if ( $session ) {
- $agentnum = $session->{'agentnum'};
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
- } else {
- $agentnum = $packet->{agentnum}
- || $conf->config('signup_server-default_agentnum');
- }
+ my $agentnum = get_agentnum($packet);
+ return $agentnum if ref($agentnum);
my ($bill_hash, $ship_hash);
foreach my $f (FS::cust_main->location_fields) {
( map { $_ => $packet->{$_} } qw(
salesnum
ss stateid stateid_state
-
- payby
- payinfo paycvv paydate payname paystate paytype
- paystart_month paystart_year payissue
- payip
-
locale
-
referral_custnum comments
)
),
);
+ my %insert_options = ();
+ if ( $packet->{payby} =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) {
+ $insert_options{cust_payby} = [
+ new FS::cust_payby {
+ map { $_ => $packet->{$_} } qw(
+ payby
+ payinfo paycvv paydate payname paystate paytype
+ paystart_month paystart_year payissue
+ payip
+ ),
+ }
+ ];
+ }
+
my $template_custnum = $conf->config('signup_server-prepaid-template-custnum');
my $cust_main;
if ( $template_custnum && $packet->{prepaid_shortform} ) {
&& ! $cust_main->paycvv
&& $conf->exists('signup-require_cvv');
- $cust_main->payinfo($cust_main->daytime)
- if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
-
my @invoicing_list = $packet->{'invoicing_list'}
? split( /\s*\,\s*/, $packet->{'invoicing_list'} )
: ();
- my %insert_options = ();
-
my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
my @tax_exempt = grep { $packet->{"tax_$_"} eq 'Y' } @exempt_groups;
$insert_options{'tax_exemption'} = {
map { $_ => $packet->{$_} }
qw( username _password sec_phrase popnum domsvc ),
};
+
+ my $error = $svc->is_password_allowed($packet->{_password});
+ return { error => $error } if $error;
my @acct_snarf;
my $snarfnum = 1;
#possibly some validation will be needed
}
- my $agentnum;
- if ( exists $packet->{'session_id'} ) {
- my $cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Agent',
- } );
- my $session = $cache->get($packet->{'session_id'});
- if ( $session ) {
- $agentnum = $session->{'agentnum'};
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
- } else {
- $agentnum = $packet->{agentnum}
- || $conf->config('signup_server-default_agentnum');
- }
+ my $agentnum = get_agentnum($packet);
+ return $agentnum if ref($agentnum);
#shares some stuff with htdocs/edit/process/cust_main.cgi... take any
# common that are still here and library them.
last first company daytime night fax mobile
ss stateid stateid_state
- payby
- payinfo paycvv paydate payname paystate paytype
- paystart_month paystart_year payissue
- payip
-
locale
),
} );
+ my %opt = ();
+ if ( $packet->{payby} =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) {
+ $opt{cust_payby} = [
+ new FS::cust_payby {
+ map { $_ => $packet->{$_} } qw(
+ payby
+ payinfo paycvv paydate payname paystate paytype
+ paystart_month paystart_year payissue
+ payip
+ ),
+ }
+ ];
+ }
+
if ( grep length($packet->{$_}), FS::cust_main->location_fields ) {
my $bill_hash;
foreach my $f (FS::cust_main->location_fields) {
}
- my %opt = ();
if ( $invoicing_list[0] && $packet->{'_password'} ) {
$opt{'contact'} = [
new FS::contact { 'first' => $cust_main->first,
}
+=item get_agentnum PACKET
+
+Given a PACKET from the signup server, looks up the agentnum to use for signing
+up a customer. This will use 'session_id' if the agent is authenticated,
+otherwise 'agentnum', otherwise the 'signup_server-default_agentnum' config. If
+the agent can't be found, returns an error packet.
+
+=cut
+
+sub get_agentnum {
+ my $packet = shift;
+ my $conf = new FS::Conf;
+ my $agentnum;
+ if ( exists $packet->{'session_id'} ) {
+ my $cache = new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::Agent',
+ } );
+ my $session = $cache->get($packet->{'session_id'});
+ if ( $session ) {
+ $agentnum = $session->{'agentnum'};
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+ } else {
+ $agentnum = $packet->{agentnum}
+ || $conf->config('signup_server-default_agentnum');
+ }
+ if ( $agentnum and FS::agent->count('agentnum = ?', $agentnum) ) {
+ return $agentnum;
+ }
+ return { 'error' => 'Signup is not configured' };
+}
+
+=item new_prospect PACKET
+
+Creates a new L<FS::prospect_main> entry. PACKET must contain:
+
+- either agentnum or session_id; if not, signup_server-default_agentnum will
+be used and must not be empty
+
+- either refnum or referral_title; if not, signup_server-default_refnum will
+be used and must not be empty
+
+- last and first (names), and optionally company and title
+
+- address1, city, state, country, zip, and optionally address2
+
+- emailaddress
+
+and can also contain:
+
+- one or more of phone_daytime, phone_night, phone_mobile, and phone_fax
+
+- a 'comment' (will be attached to the contact)
+
+State and country will be normalized to Freeside state/country codes if
+necessary.
+
+=cut
+
+sub new_prospect {
+
+ my $packet = shift;
+ warn "$me new_prospect called\n".Dumper($packet) if $DEBUG;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $conf = FS::Conf->new;
+
+ my $error;
+
+ my $agentnum = get_agentnum($packet);
+ return $agentnum if ref $agentnum;
+ my $refnum;
+ if ( my $title = $packet->{referral_title} ) {
+ my $part_referral = qsearchs('part_referral', {
+ 'agentnum' => $agentnum,
+ 'title' => $title,
+ });
+ $part_referral ||= qsearchs('part_referral', {
+ 'agentnum' => '',
+ 'title' => $title,
+ });
+ if (!$part_referral) {
+ return { error => "Unknown referral type: '$title'" };
+ }
+ $refnum = $part_referral->refnum;
+ } elsif ( $packet->{refnum} ) {
+ $refnum = $packet->{refnum};
+ }
+ $refnum ||= $conf->config('signup_server-default_refnum');
+ return { error => "Signup referral type is not configured" } if !$refnum;
+
+ my $prospect = FS::prospect_main->new({
+ 'agentnum' => $agentnum,
+ 'refnum' => $refnum,
+ 'company' => $packet->{company},
+ });
+
+ my $location = FS::cust_location->new;
+ foreach ( qw(address1 address2 city county zip ) ) {
+ $location->set($_, $packet->{$_});
+ }
+ # normalize country and state if they're not already ISO codes
+ # easier than doing it on the client side--we already have the tables here
+ my $country = $packet->{country};
+ my $state = $packet->{state};
+ if (length($country) > 2) {
+ # it likes title case
+ $country = join(' ', map ucfirst, split(/\s+/, $country));
+ my $lsc = Locale::SubCountry->new($country);
+ if ($lsc) {
+ $country = uc($lsc->country_code);
+
+ if ($lsc->has_sub_countries) {
+ if ( $lsc->full_name($state) eq 'unknown' ) {
+ # then we were probably given a full name, so resolve it
+ $state = $lsc->code($state);
+ if ( $state eq 'unknown' ) {
+ # doesn't resolve as a full name either, return an error
+ $error = "Unknown state: ".$packet->{state};
+ } else {
+ $state = uc($state);
+ }
+ }
+ } # else state doesn't matter
+ } else {
+ # couldn't find the country in LSC
+ $error = "Unknown country: $country";
+ }
+ }
+ $location->set('country', $country);
+ $location->set('state', $state);
+ $prospect->set('cust_location', $location);
+
+ $error ||= $prospect->insert; # also does location
+ return { error => $error } if $error;
+
+ my $contact = FS::contact->new({
+ prospectnum => $prospect->prospectnum,
+ locationnum => $location->locationnum,
+ invoice_dest => 'Y',
+ });
+ # use emailaddress pseudo-field behavior here
+ foreach (qw(last first title emailaddress comment)) {
+ $contact->set($_, $packet->{$_});
+ }
+ $error = $contact->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return { error => $error };
+ }
+
+ foreach my $phone_type (qsearch('phone_type', {})) {
+ my $key = 'phone_' . lc($phone_type->typename);
+ my $phonenum = $packet->{$key};
+ if ( $phonenum ) {
+ # just to not have to supply country code from the other end
+ my $number = Number::Phone->new($location->country, $phonenum);
+ if (!$number) {
+ $error = 'invalid phone number';
+ } else {
+ my $phone = FS::contact_phone->new({
+ contactnum => $contact->contactnum,
+ phonenum => $phonenum,
+ countrycode => $number->country_code,
+ phonetypenum => $phone_type->phonetypenum,
+ });
+ $error = $phone->insert;
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return { error => $phone_type->typename . ' phone: ' . $error };
+ }
+ }
+ } # foreach $phone_type
+
+ $dbh->commit if $oldAutoCommit;
+ return { prospectnum => $prospect->prospectnum };
+}
+
1;