aiarmada/commerce-support
Shared foundation for AI Armada's Laravel commerce packages, offering common support utilities, helpers, and base classes that keep storefront, cart, and checkout modules consistent and easy to integrate.
aiarmada/commerce-support is the foundation layer of the Commerce Suite monorepo — not a feature package. Day-to-day you rarely import it directly; instead every sibling package (commerce-catalog, commerce-orders, filament-*, etc.) inherits its conventions. Treat it as the source of truth for base models, config resolution, Filament navigation, and lifecycle columns.
First steps (in this exact order):
packages/commerce-support/CONTEXT.md — its Snapshot names the Composer package, search paths, and related packages; Read next points you at the canonical docs.docs/01-overview.md → 03-configuration.md → 04-usage.md → 99-troubleshooting.md. Skip 02-installation.md on first pass unless you are wiring it fresh.php artisan vendor:publish --tag=commerce-support-config
config/commerce-support.php. It is where json_column_type and the filament.navigation.items.* override surface live../vendor/bin/pest packages/commerce-support
First use case — runtime Filament navigation override. This is the most visible feature the support package exposes today. Instead of editing a Resource's $navigationGroup / getNavigationGroup() default, override it from your app's config:
// config/commerce-support.php
'filament' => [
'navigation' => [
'items' => [
\App\Filament\Resources\OrderResource::class => [
'group' => 'Sales',
'sort' => 10,
],
],
],
],
The engine reads the Resource's config default first, then merges your override. No code change in the Resource required.
When the support package ships a base model or model concern, adopt it rather than building your own. The contract the whole suite relies on:
use Illuminate\Database\Eloquent\Concerns\HasUuids;
class Order extends Model
{
use HasUuids;
// Do NOT declare protected $table.
public function getTable(): string
{
// Self-join safety: honour per-instance alias first.
return $this->table ?? config('commerce-support.tables.orders', 'orders');
}
}
Two non-negotiables baked into this pattern:
protected $table. getTable() must resolve from package config so tables stay prefixable and remappable.$this->table inside getTable() for self-referencing relations — Laravel aliases the inner table with setTable() and silently misbinds your join otherwise.Any migration that uses a JSON column must read the type from config, not hardcode json:
$table->{$this->jsonColumnType('payload')}('payload')->nullable();
This keeps SQLite/Postgres/MySQL portability in the hands of one config key (commerce-support.json_column_type). Hardcoding json will fail CI on SQLite and is a classic review rejection.
booted()The suite forbids database FK constraints. Do integrity in the model:
protected static function booted(): void
{
static::deleting(function (self $order) {
$order->lines()->delete();
});
}
The same rules apply to foreignUuid('order_id') columns — no ->constrained(), no ->cascadeOnDelete().
timestampTzstatus.state is reserved for geographic address regions — do not overload it.timestampTz column (e.g. completed_at, cancelled_at).CarbonImmutable — never mutable Carbon.$order->update([
'status' => OrderStatus::Completed,
'completed_at' => CarbonImmutable::now(),
]);
If you author a Resource in a filament-* package (or consume one), navigation comes from config:
public static function getNavigationGroup(): string | UnitEnum | null
{
return config('filament-orders.navigation.group');
}
No static $navigationGroup properties. This is what makes the runtime override engine work — break it and overrides silently no-op.
Long-lived workers mean no static mutable state. Bind request-scoped or container-scoped services; never cache per-request data in a static property or singleton that outlives the request.
Multi-step flows that touch transactions, side effects, and normalization should be Actions. Look for existing Actions in packages/*/src/Actions before creating a new one; support-layer helpers are usually already there.
packages/commerce-support/docs/*.md and CONTEXT.md. Read the repo, not Packagist. Budget time for that.state ≠ status. Using state for lifecycle state will collide with the geographic convention; reviewers will reject it and Filament forms that co-locate city/postcode will break.getTable() without the $this->table fallback is a silent bug. It only manifests on self-referencing relations (e.g. category trees) during existence checks. PHPStan will not catch it — write a test with a nested self-relation.json_column_type config, SQLite test runs fail with type errors on migrations. Set it in your test DB (sqlite → text) before writing the migration.commerce-support.filament.navigation.items.{FQCN} must match ::class exactly — trait-based, proxied, or aliased class names will not merge and overrides silently apply to nothing. Verify by dumping the resolved nav in a test.rg -n "constrained\(|cascadeOnDelete\(" packages/*/database is a CI check). Fix the model booted() instead.config('app.…') or a global key from inside a commerce package, the key belongs in that package's config file. Run rg -n -- "config\('pkg\." packages/*/config to audit for unused keys before merging.$navigationGroup, old form APIs) — the v4 examples are API-compatible, but v4 static properties are not../vendor/bin/pint packages/commerce-support/src and ./vendor/bin/pest packages/commerce-support. Repo-wide runs create noise and unrelated diffs.CarbonImmutable, always. Mutable Carbon will be silently accepted at runtime but violates the suite contract and breaks the "record terminal transitions in dedicated timestampTz" pattern.macro(), hasMacro(), package mixins and traits can register methods that PHPStan cannot see. Do not delete a "dead" method until you have grepped for its registration.How can I help you explore Laravel packages today?