The Client package provides a robust customer lifecycle management system. It handles standalone clients, reseller-managed portfolios, and deep integration with licensing and support systems.
- Multi-Tenant Lifecycle: Manage standalone clients and those owned by resellers (Allies).
- Binding & Identity: Optional 1:1 binding between client entities and system
Useraccounts. - Reference-Based Security: Automatic generation of secure
refids for sensitive public exposure. - State Machine: Fluent API for transitioning between Active, Pending, Suspended, and Inactive states.
- Extensible Metadata: JSON-based storage for industry-specific or custom client attributes.
- Reseller Support: Built-in logic for attribution and management via the reseller network.
Client is a package that requires installation before use.
php dock package:install Client --packagesThis will automatically:
- Run the migration for Client tables.
- Register the
ClientServiceProvider. - Publish the configuration file.
Configuration file: App/Config/client.php
use Client\Enums\ClientStatus;
return [
'default_status' => ClientStatus::PENDING,
'refid_prefix' => 'CL-',
'auto_bind_user' => false,
];Use the Client facade for a fluent registration experience:
use Client\Client;
$client = Client::make()
->name('Acme Corporation')
->email('billing@acme.com')
->metadata(['segment' => 'Enterprise'])
->create();Link a client to a managing reseller (Ally):
$client = Client::make()
->name('Global Logistics')
->email('ops@global.log')
->reseller($allyId)
->create();Client::activate($clientId);
Client::suspend($clientId);Efficiently filter your client database using built-in model scopes:
use Client\Models\Client as ClientModel;
// Status Scopes
$active = ClientModel::active()->get();
$pending = ClientModel::pending()->get();
$suspended = ClientModel::suspended()->get();
$inactive = ClientModel::inactive()->get();
// Ownership Scopes
$standalone = ClientModel::standalone()->get(); // No reseller
$reselled = ClientModel::reselled()->get(); // Managed by resellerMonitor acquisition trends and portfolio health using a fluent, chainable API:
$analytics = Client::analytics();
// 1. Fluent Growth Trends
$growth = $analytics->monthly()->signupTrends('2023-01-01', '2023-06-30');
// 2. Scoped Analytics (View growth for a specific reseller)
$resellerGrowth = $analytics->forReseller($id)->daily()->signupTrends($start, $end);
// 3. Portfolio Health
$breakdown = $analytics->statusBreakdown();
// Returns: ['active' => 120, 'pending' => 45, 'suspended' => 5]
$segments = $analytics->segmentation();
/* Returns:
[
'standalone' => ['count' => 50, 'percentage' => 33.33],
'resold' => ['count' => 100, 'percentage' => 66.67]
]
*/| Method | Description |
|---|---|
make() |
Returns a fluent ClientBuilder. |
findByRefid($id) |
Locates a client by secure reference string. |
getByReseller($id) |
Retrieves all clients managed by a specific Ally. |
activate($id) |
Transitions a client to the Active state. |
analytics() |
Returns the AnalyticsManager service. |
| Method | Description |
|---|---|
statusBreakdown($start, $end) |
Returns client counts grouped by status. |
growth($start, $end) |
Returns total new clients in period. |
segmentation($start, $end) |
Returns standalone vs. resold client metrics. |
signupTrends($start, $end) |
Returns time-series signup data. Supports fluent intervals. |
daily(), monthly(), yearly() |
Chainable methods to set trend aggregation interval. |
forReseller($id) |
Chainable method to scope analytics to a specific reseller/owner. |
| Method | Description |
|---|---|
name($name) |
Sets the corporate or individual name. |
email($email) |
Sets the primary billing/contact email. |
reseller($id) |
Attributes the client to a reseller. |
metadata($data) |
Merges custom JSON data into the record. |
create() |
Persists the client and generates a refid. |
| Attribute | Type | Description |
|---|---|---|
refid |
string |
Unique, non-sequential public identifier. |
status |
Enum |
Active, Pending, Suspended, Inactive. |
owner_id |
integer |
ID of the managing Ally (if applicable). |
The Client package is designed to be the central identity hub for other Anchor packages.
Clients can own multiple licenses. To retrieve licenses for a client, use the Forge facade:
use Forge\Forge;
$licences = Forge::licences()->getByClient($clientId);Billing history and active subscriptions are managed via Wave:
use Wave\Wave;
$invoices = Wave::invoices()->getByClient($clientId);
$subscriptions = Wave::subscriptions()->getByClient($clientId);If a client is managed by a reseller, the owner_id links it to an Ally. Resellers can provision services for their clients using their own credit balance.
use Ally\Ally;
use Forge\Forge;
// 1. A reseller provisions a license for their client
$reseller = Ally::findByUser($authUserId);
$cost = 500; // 500 credit units
if (Ally::provision($reseller, $cost, 'Pro License for ' . $client->name)) {
// 2. If credits are successfully deducted, mint the license contextually
Forge::make()
->client($client)
->product($productId)
->create();
}While the Client package focuses on current state, integrations allow you to reconstruct historical activity:
- Activation History: Use
Forge::analytics()->forClient($id)->activationTrends()for time-series data. - Billing History: Use
Wave::analytics()->forClient($id)->revenueTrends()for historical spend. - Audit Logs: The framework's internal audit system tracks all status changes (e.g., from
PendingtoActive).
Yes. While many clients are onboarded via resellers (Allies), the Client package supports "Standalone" entities. You can create a client and mint licenses for them directly without any reseller attribution.
The Workflow:
- Creation: Omit the
reseller()call. - Payment: Use Wave or Pay directly for the invoice (bypassing the Ally credit system).
- Fulfillment: Mint the license via
Forgeonce payment is confirmed.
use Client\Client;
use Forge\Forge;
use Wave\Wave;
// 1. Create a standalone client
$client = Client::make()
->name('Independent Corp')
->email('info@independent.com')
->create();
// 2. Create invoice for a one-time product
$invoice = Wave::invoices()->make()
->for($client)
->amount(150)
->currency('USD')
->create();
// 3. Attempt payment (Wallet fallback or External Checkout)
if (Wave::invoices()->attemptPayment($invoice)) {
// 4. Fulfillment: Activate client and mint/activate the license
Client::activate($client->id);
$licence = Forge::make()
->client($client)
->product($productId)
->create();
Forge::activate($licence->id, $client);
}| Error/Log | Cause | Solution |
|---|---|---|
| "Duplicate Email" | Email is already registered to a client. | Verify client directory or use findByEmail. |
| "Invalid Reseller" | Provided owner_id does not exist. |
Ensure Ally is registered before assignment. |
| "Status Transition Denied" | Illegal state change detected. | Check ClientStatus transitions logic. |
- Always Use Refids: Never expose numeric
idcolumns in URLs or APIs; userefidto prevent ID enumeration. - Verify Ownership: When build reseller dashboards, always scope queries with
owner_idto prevent cross-tenant access. - Sanitize Metadata: Avoid storing sensitive credentials or raw PII in the
metadataJSON field without encryption.