Overview
When a client pays for a hosting product, Commerce automatically creates a hosting account on the target server — no manual intervention required. This is provisioning.
Commerce communicates with hosting panels through provisioning modules: pluggable PHP classes, each implementing a standard interface. The registry auto-discovers every module in app/Provisioning/Modules/ at boot. Adding support for a new hosting provider requires dropping a single class file — no core code changes, no migrations, no service provider registration.
Architecture
Order Paid
└─▶ CreateHostingAccountJob
└─▶ ProvisioningModuleRegistry
└─▶ ProvisioningModule (e.g. OpteriusPanelModule)
└─▶ Panel API
| Component | Role |
|---|---|
ProvisioningModuleRegistry |
Singleton. Scans app/Provisioning/Modules/, loads all modules. |
ProvisioningModule interface |
Contract every module must implement. |
Server model |
Stores module type (servers.type), credentials (JSON), and max_accounts. |
ServerGroup model |
Pool of servers for a product type. Selects best server on order. |
provisioning_log table |
Records every action — success or failure — with full request/response payloads. |
How a Module Is Selected
Each Server record has a type field that matches a module's moduleId(). When a job runs, Commerce calls ProvisioningModuleRegistry::resolve($server->type) to get the right module instance, injecting the server's credentials automatically.
Built-In Module: Opterius Panel
The OpteriusPanelModule (type = opterius) is the first-class module for Opterius Panel servers. Every API request is authenticated with HMAC-SHA256 — the job sends X-Timestamp (Unix timestamp) and X-Signature (HMAC of timestamp + JSON body, keyed by api_token). This means the Panel never needs to trust a plain bearer token — each request is time-bound and body-signed.
Adding a Custom Module
- Create a class in
app/Provisioning/Modules/MyProviderModule.php. - Implement
ProvisioningModule— all required methods are defined there. - Return
ProvisioningResult::success()orProvisioningResult::failure()from each action method. - Deploy. Commerce picks it up automatically on next request.
No restart required. The registry resolves modules at runtime, so a deploy + opcache clear is sufficient.
Credential Storage
Credentials are stored in servers.credentials as a JSON blob cast to an array. Secret fields (marked secret: true in moduleFields()) are:
- Masked on the edit form (shown as empty
passwordinputs). - Never overwritten if the field is submitted blank — Commerce keeps the existing encrypted value.
- Decrypted in memory only when the module needs them.
What Happens When Provisioning Fails
Jobs retry 3 times with a 60-second backoff. After all retries are exhausted, the failure is written to provisioning_log with the full API error response. The service remains in pending status. An admin notification is triggered.
See Provisioning Log to diagnose failures and Provisioning Troubleshooting for common fixes.