Documentation Index
Fetch the complete documentation index at: https://docs.laravelshopper.dev/llms.txt
Use this file to discover all available pages before exploring further.
Estimated Upgrade Time: 5 to 15 minutes. v2.8 is a pure security release with no breaking API changes.
Updating Dependencies
Update the following dependency in your application’scomposer.json file:
shopper/framework to ^2.8
Then run:
Database Migration
v2.8 ships one additive migration. It adds five nullable columns to theorders table to snapshot the discount that was redeemed at order placement time:
| Column | Type | Purpose |
|---|---|---|
discount_id | bigint FK (nullOnDelete) | Link to the discount that was redeemed |
discount_code | string | Snapshot of the code used (kept even if the discount is later edited or deleted) |
discount_type | string(32) | Snapshot of the discount type (percentage / fixed_amount) |
discount_value_at_apply | unsigned int | Snapshot of the discount value at apply time |
discount_currency_code | char(3) | Currency the discount was applied in |
High Impact Changes
Discount Limit Enforcement
Likelihood of Impact: High if you callCreateOrderFromCartAction directly.
Affects any storefront code that converts carts to orders outside the default checkout flow.
Two production bugs around Discount.usage_limit are fixed in v2.8. Both can now throw a new exception from CreateOrderFromCartAction::execute().
The global usage counter (total_use) is now incremented atomically inside the order-creation transaction. The discount row is locked with lockForUpdate, then a conditional UPDATE increments total_use only if it is still below usage_limit. If zero rows are affected, the limit was reached between cart validation and commit and DiscountLimitReachedException::global() is thrown. The order is not created and the transaction rolls back.
The per-user limit (usage_limit_per_user) previously read from DiscountDetail.total_use, a counter that was never incremented. It now counts prior orders on the new orders.discount_id column. The check runs in two places: DiscountValidator rejects the code at cart-apply time (so the customer is not surprised at checkout), and CreateOrderFromCartAction re-checks at commit and throws DiscountLimitReachedException::perUser() if the customer redeemed it between cart apply and commit.
Update your checkout controller to catch the new exception:
Medium Impact Changes
Custom Livewire Admin Components
Likelihood of Impact: Medium Affects applications with custom Livewire components extending Shopper’s admin surfaces. v2.8 tightens authorization across every Shopper admin Livewire component. If you have custom components that extend or mimic Shopper’s order, product, customer, or settings surfaces, audit them against the new patterns. Skipping this audit leaves the same bypass surface that v2.8 closed in Shopper itself.Chain authorize() on Filament actions
Filament Action handlers should declare their ability on the action itself, not inside the closure. This hides the button when the user lacks the ability, instead of throwing a 403 on click:
Authorize Livewire methods on the first line
Every public method that mutates state must call$this->authorize() first:
Lock public Eloquent properties
Anypublic Model $x property on a Livewire component must be marked #[Locked] so it cannot be rebound from the browser. This is the pattern Shopper now applies to Order, OrderShipping, Discount, Review, ShopperUser, Role, Inventory, Legal, and Product:
Allow-list input on store() methods
When persisting user input from the browser, use an explicit Arr::only(...) allow-list instead of Arr::except(...). The latter is what allowed the _password hidden-field leak that v2.8 closed in Customers/Create::store():
Settings Permissions Tightened
Likelihood of Impact: Medium Affects custom roles that previously relied on the loose permission scope. Five settings surfaces moved fromview_users to access_setting. If you have custom roles, audit them to make sure the right people retain access:
| Surface | Before | After |
|---|---|---|
Settings/Team/Index::createRole | view_users | access_setting |
Settings/Team/Index DeleteAction | view_users | access_setting |
Settings/Team/RolePermission save/generate/create | view_users | access_setting |
PaymentMethods / Currencies / Carriers ToggleColumn, record actions, bulk actions | view_users | access_setting |
Settings/Team/Permissions::togglePermission / removePermission | view_users | access_setting (and guards against null lookups) |
access_setting granted.
Low Impact Changes
Product Barcode Validation
Product barcodes are now validated against^[A-Za-z0-9\-]*$ server-side. This closes a stored XSS surface on the admin barcode renderer. If you have existing products with non-alphanumeric barcodes, they will fail re-validation on next save, so clean them up before upgrading.
Discount Snapshot on Orders
Orders now snapshot the discount at placement time. If you have storefront pages or admin extensions that read the discount tied to an order, prefer the new snapshot columns overOrder::discount for historical accuracy. The relationship is still there but will return null if the originating discount has been deleted.
Migration Checklist
Catch DiscountLimitReachedException
Wrap
CreateOrderFromCartAction::execute() calls in your checkout controller to surface a friendly message to the customer when a discount limit is hit at commit time.Audit custom Livewire components
For every custom Livewire admin component, chain
->authorize() on Filament actions, call $this->authorize() on Livewire method handlers, and mark public Model $x properties with #[Locked].Audit custom roles
Grant
access_setting to roles that need to manage payment methods, currencies, carriers, team members, or permissions.Sanitize existing barcodes
Find products with non-alphanumeric barcodes and clean them up before saving.