Skip to content

feat(orders): free subdomains for hosting orders - #179

Merged
Panelica merged 7 commits into
Panelica:mainfrom
ertugrulkeyvan:feat/free-subdomain-reserved
Oct 12, 2026
Merged

Panelica merged 7 commits into
Panelica:mainfrom
ertugrulkeyvan:feat/free-subdomain-reserved

Conversation

@ertugrulkeyvan

Copy link
Copy Markdown
Contributor

What

A hosting order can take a name under one of the shop's own domains, such as yourname.example-sites.com, instead of a domain of its own.

WHMCS calls these "Free Subdomains". They suit trial plans and customers who have not chosen a domain yet. Many shops offer them, and we are about to.

Today the only way is to type the full name as "use my own domain", and nothing checks it. A customer can order www.<shop domain>, mail.<shop domain>, or a name another customer already has. The panel then either refuses at provisioning or, worse, serves the shop's own host from a customer account.

After this:

  • Setting: Setup → General → Domains gets two fields.
    • Free subdomain domains: the parent domains offered, one per line.
    • Names nobody may take: the first labels kept back. Left empty, a built-in list is used: www, mail, ns1, autodiscover, admin, api and the like.
  • Order form: when domains are set, the hosting order form shows a fourth option, Free subdomain from us. The customer types the first label and picks the domain from a list beside the field. Without domains set, nothing changes.
  • Refused:
    • a name that is not a single label (a.b, or one with a leading hyphen);
    • a domain that is not offered;
    • a reserved name;
    • a name a live service or a domain record already has. Terminated, cancelled and fraud services free their names again.
  • Checked at checkout: the name is checked again when the order is placed, since another order may have taken it while it waited in the cart.
  • Typed as an own domain: the same checks apply when such a name is typed under "use my own domain", so the form is not the only way in. A customer's own domain is not touched by any of this.
  • No order note: a free subdomain does not get the "[Domain … requested]" note that register and transfer orders get.

How

  • App\Support\FreeSubdomains: the offered parents, the reserved labels (the operator's list or the default one), parentOf(), problem() (an error message or null) and taken().
  • CartController::addToCart():
    • domain_option=subdomain with subdomain_parent builds the full name;
    • any name under an offered parent goes through FreeSubdomains::problem();
    • errors come back on the domain field, as other domain errors do.
  • CartService::checkout(): re-checks a free subdomain before the order is created, and only notes register and transfer.
  • Order form: client/cart/configure gets the option and the parent list. Its script swaps the placeholder and skips the price quote for this option.
  • Admin: the two settings join GENERAL_KEYS.
  • Text: in en, tr, de, pl and zh.
  • Docs: a "Free subdomains" section in docs/guides/sell-domains.md, with the wildcard DNS record the domains need.

The work is split into seven commits: the rules, the order, the form, the admin fields, the translations, the docs and the tests.

Tests

tests/Feature/FreeSubdomainTest.php, 16 tests:

  • Setting: parents are read one per line, without a leading dot. Empty turns the option off.
  • Order: a free name goes into the cart as the full name, with no order note.
  • Reserved: www, mail, admin, ns1 and autodiscover are refused. The operator's own list replaces the default one.
  • Taken: a name another service has is refused. Once that service is terminated, the name is free again.
  • Invalid: two labels, a leading hyphen, and a domain not offered are refused.
  • Own domain: typed as an own domain, a reserved or taken name is refused the same way. A customer's own domain is unaffected.
  • Checkout: a name taken while it waited in the cart stops the order.
  • Form: offers the option only when domains are set.
  • Admin: the settings page has both fields.

They fail on main. HardcodedViewTextTest and the translation tests pass.

Full suite: 4102 passed. The one failure is the CurrencyAndBackupTest db backup test, which fails on main too in my local setup.

@Panelica
Panelica merged commit 42faa1b into Panelica:main Oct 12, 2026
1 check passed
Panelica pushed a commit that referenced this pull request Oct 12, 2026
feat(orders): free subdomains for hosting orders

lang/*/admin.php and lang/*/client.php: the keys #180, #184 and #185 added and the ones added here are all kept, in that order in every language.
@Panelica

Copy link
Copy Markdown
Owner

Thank you, @ertugrulkeyvan, merged. A free subdomain checked for reserved and taken names, both in the cart and again at checkout, is a careful piece of work.

On top of your commits there is one more (727fedc): with no free subdomain domains set, the order form is the page it was before. The flex row, the placeholder attribute and the script lines are written only when the option is offered, and the script's check is safe on order forms without a domain section, such as VPS products. When merging, the new texts follow the ones #180, #184 and #185 had just added to lang/*/admin.php and lang/*/client.php.

Full suite green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants