github shopperlabs/shopper v2.6.0
v2.6.0: Cart Engine, Tax System & Addon Hooks

8 hours ago

Highlights

A new cart package

This release ships shopper/cart a standalone package that manages the full shopping cart lifecycle. It handles item management, coupon application, address storage, and price calculation through a pipeline system.

The entry point is the Cart facade backed by a session-based manager:

use Shopper\Cart\Facades\Cart;
use Shopper\Cart\CartManager;
use Shopper\Core\Enum\AddressType;

// Get the current session cart, or create one
$cart = Cart::current() ?? Cart::create();

// Associate with the authenticated user
Cart::associate($user);

// Add items
$manager = resolve(CartManager::class);

try {
    $manager->add($cart, $product, quantity: 2);
} catch (InsufficientStockException $e) {
    // Not enough stock
} catch (CartCompletedException $e) {
    // Cart already converted to an order
}

// Apply a coupon
try {
    $manager->applyCoupon($cart, 'SUMMER20');
} catch (InvalidDiscountException $e) {
    // Code not found, expired, usage limit reached, etc.
}

// Add a shipping address
$manager->addAddress($cart, AddressType::Shipping, [
    'first_name' => 'John',
    'last_name' => 'Doe',
    'address_1' => '123 Main St',
    'city' => 'New York',
    'postal_code' => '10001',
    'country_id' => $country->id,
]);

// Calculate totals through the pipeline
$context = $manager->calculate($cart);
// $context->subtotal, $context->discountTotal, $context->taxTotal, $context->total

Totals flow through an ordered pipeline defined in config/shopper/cart.php:

'pipelines' => [
    'cart' => [
        Shopper\Cart\Pipelines\CalculateLines::class,
        Shopper\Cart\Pipelines\ApplyDiscounts::class,
        Shopper\Cart\Pipelines\CalculateTax::class,
        Shopper\Cart\Pipelines\Calculate::class,
    ],
],

Each stage receives a CartPipelineContext and passes it to the next. You can insert your own stages anywhere in the array. When the cart is ready, CreateOrderFromCartAction converts it to an Order in a single database transaction — creating order items, snapshotting tax lines, incrementing discount usage counts, and marking the cart as completed.

The package also ships a shopper:prune-carts command to clean up old incomplete carts:

php artisan shopper:prune-carts
php artisan shopper:prune-carts --days=7

The pruning threshold is configurable:

// config/shopper/cart.php
'abandoned_after_minutes' => 60,
'prune_after_days'        => 30,

Tax zones, rates, and overrides

The core package now includes a complete tax system. Tax zones map a country or a specific country + province to one or more tax rates. Rates can be marked as default or combinable, and can be scoped to specific products, product types, or categories through override rules.

When CalculateTax runs in the cart pipeline, it resolves the applicable zone from the cart's shipping address, computes a tax line per cart line, and stores the result in CartLineTaxLine. At order creation those lines are snapshotted into OrderTaxLine records so historical orders always reflect the rate that applied at the time of purchase, regardless of any future rate changes.

Tax zones and rates are fully manageable from the admin without touching code.

v2.6 ships 15 new migrations across two packages. Run php artisan migrate after updating.


Abandoned carts page

A new page at Orders → Abandoned Carts lists carts that have lines, were never completed, and have been inactive longer than the threshold defined in config/shopper/cart.php. Each entry shows the customer (or guest), the number of items, the currency, the channel, and the time since last activity. A slide-over provides the full cart detail.


Addon system

Shopper now has a first-class addon API for packaging extensions. An addon implements the ShopperAddon contract and can register routes, Livewire components, sidebar entries, views, settings items, styles, and permissions as a single unit:

use Shopper\Contracts\ShopperAddon;
use Shopper\ShopperPanel;

class MyAddon implements ShopperAddon
{
    public function getId(): string { return 'my-addon'; }
    public function getName(): string { return 'My Addon'; }
    public function isEnabled(): bool { return true; }

    public function register(ShopperPanel $panel): void
    {
        $panel
            ->addonRoutes(fn () => require __DIR__.'/../routes/addon.php')
            ->addonLivewireComponents(['my-addon::widget' => Widget::class])
            ->addonSidebar(MyAddonSidebar::class)
            ->addonPermissions(['my-addon.view']);
    }

    public function boot(ShopperPanel $panel): void {}
}

Register it in your service provider:

ShopperPanel::make()->addon(new MyAddon);

Addons can be toggled without touching code from config/shopper/addons.php:

return [
    'my-addon' => false,
];

Render hooks — 62 named positions across the entire admin

The old RenderHook enum from v2.5 has been replaced by seven scoped classes organized by business domain. Each class exposes named constants for every injectable position in that section:

use Shopper\View\ProductRenderHook;
use Shopper\View\OrderRenderHook;
use Shopper\View\LayoutRenderHook;

Shopper::renderHook(ProductRenderHook::EDIT_TABS_END, fn () => view('my-addon::extra-tab'));
Shopper::renderHook(OrderRenderHook::DETAIL_SIDEBAR_AFTER, fn () => view('my-addon::order-widget'));
Shopper::renderHook(LayoutRenderHook::HEAD_END, fn () => view('my-addon::scripts'));
Class Domain
LayoutRenderHook Head, body, header, content, dashboard, settings, account
ProductRenderHook Product index, edit page, tabs, variant page
OrderRenderHook Order index, order detail, shipments, abandoned carts
CatalogRenderHook Categories, brands, tags, attributes, reviews
CollectionRenderHook Collection index and edit
CustomerRenderHook Customer index and show
SalesRenderHook Discounts, suppliers

If you used RenderHook from v2.5, see the upgrade guide for the migration table.


Dashboard analytics

The dashboard now shows five new components: a 12-month revenue bar chart, stat cards with month-over-month trends, a recent orders table, a top-selling products table, and an interactive setup guide. All data is cached with stale-while-revalidate semantics.


Auth rebuilt on Filament forms

The login and password reset screens are now powered by Filament's form components. Password reset can be disabled from config/shopper/auth.php — when disabled the link is hidden from the login page and the reset routes are not registered:

'password_reset' => false,

Server-side authorization enforced on all admin components

All admin Livewire pages and slide-overs now enforce permissions server-side via $this->authorize(). Previously, many components relied only on sidebar visibility to restrict access.

If you have custom roles, assign the relevant permissions before upgrading. See the upgrade guide for the full table of permissions per component.


Breaking changes

Auth models and traits moved from shopper/core to shopper/admin

Before (v2.5) After (v2.6)
Shopper\Core\Models\Role Shopper\Models\Role
Shopper\Core\Models\Permission Shopper\Models\Permission
Shopper\Core\Models\Contracts\ShopperUser Shopper\Models\Contracts\ShopperUser
Shopper\Core\Traits\ShopperUser Shopper\Traits\InteractsWithShopper
Shopper\Core\Models\Traits\HasProfilePhoto Shopper\Traits\HasProfilePhoto

The old namespaces are kept as deprecated aliases until v3.0.

The role config key has also moved:

// Before
config('shopper.core.roles.admin')

// After
config('shopper.admin.roles.admin')

Two order events renamed

Before (v2.5) After (v2.6)
Shopper\Core\Events\Orders\OrderCancel Shopper\Core\Events\Orders\OrderCancelled
Shopper\Core\Events\Orders\AddNoteToOrder Shopper\Core\Events\Orders\OrderNoteAdded

RenderHook enum replaced by scoped classes

The Shopper\Enum\RenderHook enum from v2.5 no longer exists. Replace all usages with the equivalent LayoutRenderHook constant. See the upgrade guide for the full mapping.

Shopper enums now implement Shopper\Core\Contracts instead of Filament\Support\Contracts

HasLabel, HasIcon, HasColor, and HasDescription have been extracted from Filament's interfaces into Shopper's own contracts, removing the Filament dependency from shopper/core. The method signatures are identical no change is required in most applications.


Features

  • feat: add cart package with manager, pipelines, discounts, taxes, and session handling by @mckenziearts in #421
  • feat: tax system, order tax lines, and CI optimizations by @mckenziearts in #420
  • feat: abandoned carts UI and addon system by @mckenziearts in #423
  • feat: improve UI with dashboard analytics and onboarding by @mckenziearts in #422
  • feat: add scoped render hooks system for addon extensibility by @mckenziearts in #425
  • feat: modernize auth UI with Filament forms and configurable password reset by @mckenziearts in #428
  • feat: improve customer show page with URL tabs and Filament orders table by @mckenziearts in #427
  • feat: remove Auth dependency on core package and upgrade dependency by @mckenziearts in #432
  • feat(typescript): add cart and tax types by @mckenziearts in #435

Bug Fixes

  • fix: resolve PostgreSQL compatibility and XSS in RevenueChart by @mckenziearts in #431
  • fix: add wire:navigate to internal links for SPA navigation by @mckenziearts in #424
  • fix: allow bacon/bacon-qr-code v3 in admin package by @mckenziearts

Security

  • security: enforce server-side authorization on all admin components by @mckenziearts in #434

Refactoring

Don't miss a new shopper release

NewReleases is sending notifications on new releases.