# Mercury Provisioning

Provisions Creatia organisations, users, product rights and Mercury bandwidth in
response to WHMCS service lifecycle events.

## The `gsl` external identity

Organisations are located by their `gsl` external identity, and that value is **the CXID
the network side dereferences** — not a free WHMCS reference:

- tunnel microservice: `GET|DELETE /api/v1/tunnels/by-cxid/{identity}`
- BGP scanner: `GET /scan/cxid/{identity}`
- `broadcast:mercury-rates`: per-tunnel BGP state and billing rates, keyed on it

**The value is `tblclients.id`** — the WHMCS client ID. The Creatia button writes the same
thing under the same key, so both modules and the network side agree on one namespace.

Note for anyone reading the git history: committed versions of this file from `0e47a77`
onward used `clientsdetails.owner_user_id`, a `tblusers` ID from a different sequence.
**That version was never deployed** — production has always written the client ID.
Shipping it would have started filing organisations under a second, colliding namespace
and orphaned every existing one. The current code restores parity with production.

## Rights and bandwidth are organisation-level

Product rights and bandwidth belong to the Creatia organisation, not to the individual
WHMCS service, and one organisation can hold several client identities — the Creatia
button adds one whenever it links a client to an organisation, and CreateAccount adds one
when it links a customer's existing login (below).

So anything deciding "has this customer any Mercury service left" has to consider every
client the organisation holds, which is what `mercury_organisationClientIds()` is for.
Scoping to the acting client alone would report no siblings while another client's service
is still live, revoke the right, and take every tunnel on the organisation down with it —
`remove_product_right` deletes by every CXID the organisation carries.

Bandwidth is likewise rebuilt as the highest live tier across those services rather than
last-write-wins, so a smaller or duplicate order cannot drag an existing customer down.

## A customer who already has a Creatia login

When the client has no organisation under `gsl` but their billing address is already a
Creatia login, CreateAccount links the organisation that login belongs to — adding `gsl` =
the client ID alongside whatever identities it already holds — and provisions Mercury
there, instead of refusing or creating a second organisation. The link happens only from a
verified address (below); otherwise Module Create fails asking for verification, and a
retry once verified completes it. Each link is written to the client's activity log.

The link goes through `internal/v2/organisation/{id}` in `add` mode. The v1 endpoint behind
`updateOrganisation()` overwrites the organisation's existing `gsl` identity, which would
detach the client already filed there and the CXID its tunnels are keyed on.

Bandwidth is rebuilt across every client the organisation will hold before anything is
written, so linking a small plan onto an organisation already running Mercury under another
client does not drag it down — and an unreadable sibling stops the link, not just the update.

A client already filed under one organisation whose address is a login in another is still
refused: linking would leave the identity held by two organisations.

## Email verification, and when it applies

`mercury_requireVerifiedAddress()` runs when adding a user to an organisation that
**already existed**, and before linking an existing login's organisation to the client. It
does not run on a first-time provision, where the organisation and
its first user are created together: the invitation goes to the address in question, so an
unproven one gains nothing, and requiring it would fail the ordinary case of a customer
signing up and ordering in one sitting.

It is also **not** applied to Terminate or Change Package. Those act on an organisation
that already exists and send nothing; refusing there would leave the product right and the
tunnels live after billing had stopped, which is worse than what it prevents.

## Only GSL sells Mercury

This module hardcodes the `gsl` identity key, as do the tunnel teardown in
`remove_product_right` and every other CXID reader. The Creatia button's key is
configurable per install (`gsl`, `whmcs_godedicated`, `whmcs_streamline`).

That is consistent only because Mercury is sold on the GSL install alone. If Mercury is
ever offered on a brand install, this module will not find organisations the button created
there — it will make duplicates, and tunnel teardown will find no CXID to delete by.

## config.json is web-served

The module directory is under the web root, and unlike `.php` a `.json` file is served as
content. `.htaccess` in this directory denies `config.json`; confirm it is honoured:

```
curl -sI https://<host>/modules/servers/mercury/config.json | head -1
```

That must be 403 or 404. If the server ignores `.htaccess` (nginx, or `AllowOverride None`)
add an equivalent rule to the server config instead.

## Deploying

Order matters: **Laravel API first, then Mercury, then the button.** `get_user`'s
case-insensitive match is what makes Mercury's pre-create check sound — against the old
exact match, a customer whose Creatia address differs only in case reads as "address free"
and a duplicate organisation is created.

Both modules are deployed by copying files; there is no CI or deploy script in this repo.
`mercury.php` and `PhpCreatia.php` must be copied **together** — the constructor changed
from zero arguments to three, and `mercury.php` calls `addExternalIdentity()`, which only
the current `PhpCreatia.php` has. Target is `modules/servers/mercury/`.

Verify on staging with a **Terminate or Change Package against a client that already has a
Creatia organisation**. A fresh-client CreateAccount exercises a different path and will
not catch a fault in organisation resolution.

## Deployment prerequisite

This version reads its credentials from `config.json` in the module directory. **The
deployed module has no such file** — it carries the endpoint, identity and secret
hardcoded in `PhpCreatia.php`. Create `config.json` from `config.json.example` before
deploying, or every lifecycle event fails at `mercury_getApi()`.

Those hardcoded credentials are also in git history. Rotate them.
