+Returns a hash reference. On errors, it contains a single key, B<error>, with
+the error message. Otherwise, contains a single key, B<dates>, pointing to
+an array refernce of hash references. Each hash reference contains the
+following keys:
+
+=over 4
+
+=item bill_date
+
+(Future) Bill date. Indicates a future date for which billing could be run.
+Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
+function.
+
+=item bill_date_pretty
+
+(Future) Bill date as a human-readable string. (Convenience for display;
+subject to change, so best not to parse for the date.)
+
+=item amount
+
+Base amount which will be charged if renewed early as of this date.
+
+=item renew_date
+
+Renewal date; i.e. even-futher future date at which the customer will be paid
+through if the early renewal is completed with the given B<bill-date>.
+Specified as a integer UNIX timestamp.
+
+=item renew_date_pretty
+
+Renewal date as a human-readable string. (Convenience for display;
+subject to change, so best not to parse for the date.)
+
+=item pkgnum
+
+Package that will be renewed.
+
+=item expire_date
+
+Expiration date of the package that will be renewed.
+
+=item expire_date_pretty
+
+Expiration date of the package that will be renewed, as a human-readable
+string. (Convenience for display; subject to change, so best not to parse for
+the date.)
+
+=back
+
+=item order_renew
+
+Renews this customer early; i.e. runs billing for this customer in advance.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+Session identifier
+
+=item date
+
+Integer date as returned by the B<renew_info> function, indicating the advance
+date for which to run billing.
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+=item cancel_pkg
+
+Cancels a package for this customer.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+Session identifier
+
+=item pkgpart
+
+pkgpart of package to cancel
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+=back
+
+=head1 SIGNUP FUNCTIONS
+
+=over 4
+
+=item signup_info HASHREF
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id - Optional agent/reseller interface session
+
+=back
+
+Returns a hash reference containing information that may be useful in
+displaying a signup page. The hash reference contains the following keys:
+
+=over 4
+
+=item cust_main_county
+
+County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>). Note these are not FS::cust_main_county objects, but hash references of columns and values.
+
+=item part_pkg
+
+Available packages - array reference of hash references, each of which has the fields of a part_pkg record (see L<FS::part_pkg>). Each hash reference also has an additional 'payby' field containing an array reference of acceptable payment types specific to this package (see below and L<FS::part_pkg/payby>). Note these are not FS::part_pkg objects, but hash references of columns and values. Requires the 'signup_server-default_agentnum' configuration value to be set, or
+an agentnum specified explicitly via reseller interface session_id in the
+options.
+
+=item agent
+
+Array reference of hash references, each of which has the fields of an agent record (see L<FS::agent>). Note these are not FS::agent objects, but hash references of columns and values.
+
+=item agentnum2part_pkg
+
+Hash reference; keys are agentnums, values are array references of available packages for that agent, in the same format as the part_pkg arrayref above.
+
+=item svc_acct_pop
+
+Access numbers - array reference of hash references, each of which has the fields of an svc_acct_pop record (see L<FS::svc_acct_pop>). Note these are not FS::svc_acct_pop objects, but hash references of columns and values.
+
+=item security_phrase
+
+True if the "security_phrase" feature is enabled
+
+=item payby
+
+Array reference of acceptable payment types for signup
+
+=over 4
+
+=item CARD
+
+credit card - automatic
+
+=item DCRD
+
+credit card - on-demand - version 1.5+ only
+
+=item CHEK
+
+electronic check - automatic
+
+=item DCHK
+
+electronic check - on-demand - version 1.5+ only
+
+=item LECB
+
+Phone bill billing
+
+=item BILL
+
+billing, not recommended for signups
+
+=item COMP
+
+free, definitely not recommended for signups
+
+=item PREPAY
+
+special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL
+
+=back
+
+=item cvv_enabled
+
+True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
+
+=item msgcat
+
+Hash reference of message catalog values, to support error message customization. Currently available keys are: passwords_dont_match, invalid_card, unknown_card_type, and not_a (as in "Not a Discover card"). Values are configured in the web interface under "View/Edit message catalog".
+
+=item statedefault
+
+Default state
+
+=item countrydefault
+
+Default country
+
+=back
+
+=item new_customer HASHREF
+
+Creates a new customer. Takes a hash reference as parameter with the
+following keys:
+
+=over 4
+
+=item first
+
+first name (required)
+
+=item last
+
+last name (required)
+
+=item ss
+
+(not typically collected; mostly used for ACH transactions)
+
+=item company
+
+Company name
+
+=item address1 (required)
+
+Address line one
+
+=item address2
+
+Address line two
+
+=item city (required)
+
+City
+
+=item county
+
+County
+
+=item state (required)
+
+State
+
+=item zip (required)
+
+Zip or postal code
+
+=item daytime
+
+Daytime phone number
+
+=item night
+
+Evening phone number
+
+=item fax
+
+Fax number
+
+=item payby
+
+CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
+
+=item payinfo
+
+Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
+
+=item paycvv
+
+Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
+
+=item paydate
+
+Expiration date for CARD/DCRD
+
+=item payname
+
+Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
+
+=item invoicing_list
+
+comma-separated list of email addresses for email invoices. The special value 'POST' is used to designate postal invoicing (it may be specified alone or in addition to email addresses),
+
+=item referral_custnum
+
+referring customer number
+
+=item agentnum
+
+Agent number
+
+=item pkgpart
+
+pkgpart of initial package
+
+=item username
+
+Username
+
+=item _password
+
+Password
+
+=item sec_phrase
+
+Security phrase
+
+=item popnum
+
+Access number (index, not the literal number)
+
+=item countrycode
+
+Country code (to be provisioned as a service)
+
+=item phonenum
+
+Phone number (to be provisioned as a service)
+
+=item pin
+
+Voicemail PIN
+
+=back
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors. The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under Configuration | View/Edit message catalog)
+
+=back
+
+=item regionselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item selected_county
+
+Currently selected county
+
+=item selected_state
+
+Currently selected state
+
+=item selected_country
+
+Currently selected country
+
+=item prefix
+
+Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
+
+=item onchange
+
+Specify a javascript subroutine to call on changes
+
+=item default_state
+
+Default state
+
+=item default_country
+
+Default country
+
+=item locales
+
+An arrayref of hash references specifying regions. Normally you can just pass the value of the I<cust_main_county> field returned by B<signup_info>.
+
+=back
+
+Returns a list consisting of three HTML fragments for county selection,
+state selection and country selection, respectively.
+
+=cut
+
+#false laziness w/FS::cust_main_county (this is currently the "newest" version)
+sub regionselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+ $param->{'selected_country'} ||= $param->{'default_country'};
+ $param->{'selected_state'} ||= $param->{'default_state'};
+
+ my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
+
+ my $countyflag = 0;
+
+ my %cust_main_county;
+
+# unless ( @cust_main_county ) { #cache
+ #@cust_main_county = qsearch('cust_main_county', {} );
+ #foreach my $c ( @cust_main_county ) {
+ foreach my $c ( @{ $param->{'locales'} } ) {
+ #$countyflag=1 if $c->county;
+ $countyflag=1 if $c->{county};
+ #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
+ #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
+ $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
+ }
+# }
+ $countyflag=1 if $param->{selected_county};
+
+ my $script_html = <<END;
+ <SCRIPT>
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+ function ${prefix}country_changed(what) {
+ country = what.options[what.selectedIndex].text;
+ for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
+ what.form.${prefix}state.options[i] = null;
+END
+ #what.form.${prefix}state.options[0] = new Option('', '', false, true);
+
+ foreach my $country ( sort keys %cust_main_county ) {
+ $script_html .= "\nif ( country == \"$country\" ) {\n";
+ foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
+ my $text = $state || '(n/a)';
+ $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
+ }
+ $script_html .= "}\n";
+ }
+
+ $script_html .= <<END;
+ }
+ function ${prefix}state_changed(what) {
+END
+
+ if ( $countyflag ) {
+ $script_html .= <<END;
+ state = what.options[what.selectedIndex].text;
+ country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
+ for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
+ what.form.${prefix}county.options[i] = null;
+END
+
+ foreach my $country ( sort keys %cust_main_county ) {
+ $script_html .= "\nif ( country == \"$country\" ) {\n";
+ foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
+ $script_html .= "\nif ( state == \"$state\" ) {\n";
+ #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
+ foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
+ my $text = $county || '(n/a)';
+ $script_html .=
+ qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
+ }
+ $script_html .= "}\n";
+ }
+ $script_html .= "}\n";
+ }
+ }
+
+ $script_html .= <<END;
+ }
+ </SCRIPT>
+END
+
+ my $county_html = $script_html;
+ if ( $countyflag ) {
+ $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
+ foreach my $county (
+ sort keys %{ $cust_main_county{$param->{'selected_country'}}{$param->{'selected_state'}} }
+ ) {
+ my $text = $county || '(n/a)';
+ $county_html .= qq!<OPTION VALUE="$county"!.
+ ($county eq $param->{'selected_county'} ?
+ ' SELECTED>' :
+ '>'
+ ).
+ $text.
+ '</OPTION>';
+ }
+ $county_html .= '</SELECT>';
+ } else {
+ $county_html .=
+ qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
+ }
+
+ my $state_html = qq!<SELECT NAME="${prefix}state" !.
+ qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
+ foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
+ my $text = $state || '(n/a)';
+ my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
+ $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
+ }
+ $state_html .= '</SELECT>';
+
+ my $country_html = '';
+ if ( scalar( keys %cust_main_county ) > 1 ) {
+
+ $country_html = qq(<SELECT NAME="${prefix}country" ).
+ qq(onChange="${prefix}country_changed(this); ).
+ $param->{'onchange'}.
+ '"'.
+ '>';
+ my $countrydefault = $param->{default_country} || 'US';
+ foreach my $country (
+ sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
+ keys %cust_main_county
+ ) {
+ my $selected = $country eq $param->{'selected_country'}
+ ? ' SELECTED'
+ : '';
+ $country_html .= "\n<OPTION$selected>$country</OPTION>"
+ }
+ $country_html .= '</SELECT>';
+ } else {
+
+ $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
+ ' VALUE="'. (keys %cust_main_county )[0]. '">';
+
+ }
+
+ ($county_html, $state_html, $country_html);
+
+}
+
+sub regionselector_hashref {
+ my ($county_html, $state_html, $country_html) = regionselector(@_);
+ {
+ 'county_html' => $county_html,
+ 'state_html' => $state_html,
+ 'country_html' => $country_html,
+ };
+}
+
+=item location_form HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item session_id
+
+Current customer session_id
+
+=item no_asterisks
+
+Omit red asterisks from required fields.
+
+=item address1_label
+
+Label for first address line.
+
+=back
+
+Returns an HTML fragment for a location form (address, city, state, zip,
+country)
+
+=cut
+
+sub location_form {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+
+ my $session_id = delete $param->{'session_id'};
+
+ my $rv = mason_comp( 'session_id' => $session_id,
+ 'comp' => '/elements/location.html',
+ 'args' => [ %$param ],
+ );
+
+ #hmm.
+ $rv->{'error'} || $rv->{'output'};
+
+}
+
+
+#=item expselect HASHREF | LIST
+#
+#Takes as input a hashref or list of key/value pairs with the following keys:
+#
+#=over 4
+#
+#=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
+#
+#=item date - current date, in yyyy-mm-dd or m-d-yyyy format
+#
+#=back
+
+=item expselect PREFIX [ DATE ]
+
+Takes as input a unique prefix string and the current expiration date, in
+yyyy-mm-dd or m-d-yyyy format
+
+Returns an HTML fragments for expiration date selection.
+
+=cut
+
+sub expselect {
+ #my $param;
+ #if ( ref($_[0]) ) {
+ # $param = shift;
+ #} else {
+ # $param = { @_ };
+ #my $prefix = $param->{'prefix'};
+ #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
+ #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
+ my $prefix = shift;
+ my $date = scalar(@_) ? shift : '';
+
+ my( $m, $y ) = ( 0, 0 );
+ if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
+ ( $m, $y ) = ( $2, $1 );
+ } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $m, $y ) = ( $1, $3 );
+ }
+ my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
+ for ( 1 .. 12 ) {
+ $return .= qq!<OPTION VALUE="$_"!;
+ $return .= " SELECTED" if $_ == $m;
+ $return .= ">$_";
+ }
+ $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
+ my @t = localtime;
+ my $thisYear = $t[5] + 1900;
+ for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
+ $return .= qq!<OPTION VALUE="$_"!;
+ $return .= " SELECTED" if $_ == $y;
+ $return .= ">$_";
+ }
+ $return .= "</SELECT>";
+
+ $return;
+}
+
+=item popselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item popnum
+
+Access number number
+
+=item pops
+
+An arrayref of hash references specifying access numbers. Normally you can just pass the value of the I<svc_acct_pop> field returned by B<signup_info>.
+
+=back
+
+Returns an HTML fragment for access number selection.
+
+=cut
+
+#horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
+sub popselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+ my $popnum = $param->{'popnum'};
+ my $pops = $param->{'pops'};
+
+ return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
+ return $pops->[0]{city}. ', '. $pops->[0]{state}.
+ ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
+ '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
+ if scalar(@$pops) == 1;
+
+ my %pop = ();
+ my %popnum2pop = ();
+ foreach (@$pops) {
+ push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
+ $popnum2pop{$_->{popnum}} = $_;
+ }
+
+ my $text = <<END;
+ <SCRIPT>
+ function opt(what,href,text) {
+ var optionName = new Option(text, href, false, false)
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+END
+
+ my $init_popstate = $param->{'init_popstate'};
+ if ( $init_popstate ) {
+ $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
+ $init_popstate. '">';
+ } else {
+ $text .= <<END;
+ function acstate_changed(what) {
+ state = what.options[what.selectedIndex].text;
+ what.form.popac.options.length = 0
+ what.form.popac.options[0] = new Option("Area code", "-1", false, true);
+END
+ }
+
+ my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
+ foreach my $state ( sort { $a cmp $b } @states ) {
+ $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
+
+ foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
+ $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
+ if ($ac eq $param->{'popac'}) {
+ $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
+ }
+ }
+ $text .= "}\n" unless $init_popstate;
+ }
+ $text .= "popac_changed(what.form.popac)}\n";
+
+ $text .= <<END;
+ function popac_changed(what) {
+ ac = what.options[what.selectedIndex].text;
+ what.form.popnum.options.length = 0;
+ what.form.popnum.options[0] = new Option("City", "-1", false, true);
+
+END
+
+ foreach my $state ( @states ) {
+ foreach my $popac ( keys %{ $pop{$state} } ) {
+ $text .= "\nif ( ac == \"$popac\" ) {\n";
+
+ foreach my $pop ( @{$pop{$state}->{$popac}}) {
+ my $o_popnum = $pop->{popnum};
+ my $poptext = $pop->{city}. ', '. $pop->{state}.
+ ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
+
+ $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
+ if ($popnum == $o_popnum) {
+ $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
+ }
+ }
+ $text .= "}\n";
+ }
+ }
+
+
+ $text .= "}\n</SCRIPT>\n";
+
+ $param->{'acstate'} = '' unless defined($param->{'acstate'});
+
+ $text .=
+ qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
+ qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
+ $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
+ ">$_" foreach sort { $a cmp $b } @states;
+ $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
+
+ $text .=
+ qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
+ qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
+
+ $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
+
+
+ #comment this block to disable initial list polulation
+ my @initial_select = ();
+ if ( scalar( @$pops ) > 100 ) {
+ push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
+ } else {
+ @initial_select = @$pops;
+ }
+ foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
+ $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
+ ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
+ $pop->{city}. ', '. $pop->{state}.
+ ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
+ }
+
+ $text .= qq!</SELECT></TD></TR></TABLE>!;
+
+ $text;
+
+}
+
+=item domainselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item pkgnum
+
+Package number
+
+=item domsvc
+
+Service number of the selected item.
+
+=back
+
+Returns an HTML fragment for domain selection.
+
+=cut
+
+sub domainselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+ my $domsvc= $param->{'domsvc'};
+ my $rv =
+ domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
+ my $domains = $rv->{'domains'};
+ $domsvc = $rv->{'domsvc'} unless $domsvc;
+
+ return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
+ unless scalar(keys %$domains);
+
+ if (scalar(keys %$domains) == 1) {
+ my $key;
+ foreach(keys %$domains) {
+ $key = $_;
+ }
+ return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
+ '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
+ }
+
+ my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
+
+
+ foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
+ $text .= qq!<OPTION VALUE="!. $domain. '"'.
+ ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
+ $domains->{$domain};
+ }
+
+ $text .= qq!</SELECT></TD></TR>!;
+
+ $text;
+
+}
+
+=item didselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item field
+
+Field name for the returned HTML fragment.
+
+=item svcpart
+
+Service definition (see L<FS::part_svc>)
+
+=back
+
+Returns an HTML fragment for DID selection.
+
+=cut
+
+sub didselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+
+ my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
+ 'args'=>[ %$param ],
+ );
+
+ #hmm.
+ $rv->{'error'} || $rv->{'output'};
+
+}
+
+=back
+
+=head1 RESELLER FUNCTIONS
+
+Note: Resellers can also use the B<signup_info> and B<new_customer> functions
+with their active session, and the B<customer_info> and B<order_pkg> functions
+with their active session and an additional I<custnum> parameter.
+
+For the most part, development of the reseller web interface has been
+superceded by agent-virtualized access to the backend.
+
+=over 4
+
+=item agent_login
+
+Agent login
+
+=item agent_info
+
+Agent info
+
+=item agent_list_customers
+
+List agent's customers.