Important
This is the third release candidate of Shopper 3.0. Cart lines now take their price from the PriceResolver, so tiered and customer prices from an add-on reach the cart, the Store API and the order, and a cart holding a payment session can no longer change once the customer may have paid. It changes public APIs of the cart, the Store API authentication and the SDK: read the breaking changes before updating.
Installation
"minimum-stability": "RC",
"prefer-stable": truecomposer require shopper/framework:^3.0.0-rcNew Features
- feat(cart): reprice cart lines and freeze carts under a payment session (#679)
Cart lines take their price from the PriceResolver on every change that can move it: add, quantity update, removal, merge, customer attach, currency or zone change, sibling variants of the same product included. Tiered and customer prices from an add-on reach the cart, the Store API and the order. The core gains four contracts for add-ons: ProductPriceIndex (price sort, price filters and price_range on listings), QuantityRuleResolver, PreloadsPrices and PaymentSessionGateway.
A cart holding a provider payment session is frozen. Every mutation that changes the total first releases an unpaid session, and answers 409 payment_session_collected when the payment may already be collected. Completion honours a price, quantity rule, promotion term or campaign budget that moved only when the payment is collected. shopper:payments:reconcile dispatches PaymentOrphaned for a collected payment still without an order after orphan_after_minutes: the cart it paid for is completed when it can be, otherwise the payment is reported, and refunded when orphans is refund.
'reconciliation' => [
// ...
'orphan_after_minutes' => env('PAYMENT_ORPHAN_AFTER_MINUTES', 30),
'orphans' => env('PAYMENT_ORPHANS', 'alert'),
],PATCH /store/carts/{cartId} accepts zone_code. Products and variants expose calculated_price and quantity_rule, and errors carry a meta object with the details of a domain error, such as the rule behind quantity_rule_violated. New error codes: payment_session_collected (409), payment_released, quantity_rule_violated, cart_line_metadata_conflict, shipping_method_required and shipping_price_changed on payment session, shipping_provider_unavailable (503). A line quantity is capped at 1,000,000 and line metadata at 50 keys and 4,096 characters. In the admin, the listing tabs move into the table toolbar as a segmented control.
- feat(admin): shared design tokens for add-ons and confirmed order actions (#677)
The Tailwind theme (fonts, gray scale, sh-* colors and the dark variant) moves to resources/css/tokens.css. An add-on shipping its own stylesheet imports it and gets the same sh-* utilities as the admin, theme overrides included.
@import '../../vendor/shopper/framework/resources/css/tokens.css';startProcessing, markPaid, markComplete and cancelOrder now ask for a confirmation that describes what happens: stock returned and no refund on cancel, a new order moved to processing when marked as paid. Actions that could only fail are hidden: markPaid on an authorized payment, where capturePayment applies, capturePayment on cancelled and archived orders, cancelOrder on completed orders and archive on archived orders. PaymentProcessingService::capture() refuses cancelled and archived orders with PaymentException::captureNotAllowed(). The order page opens the action given in ?action=, so a link can open an action modal directly. The Evergreen theme now defines the settings sidebar hover in dark mode.
Other Changes
- chore(admin): format the admin resources with prettier (#680)
- docs(types): fix the package name and install instructions (#681)
Breaking Changes
Shopper\Api\Actions\RevalidateCartCouponActionandCancelPaymentSessionActionare removed. UseCartManager::revalidateCoupons()andCartManager::releasePaymentSession().- The
CartManagerconstructor takesDiscountValidatorandPaymentSessionGatewayand no longer takesTaxCalculator.ResolvePurchasableAction::execute()no longer takes$currencyCode.CreateOrderFromCartAction::execute()takeshonoursPaymentand no longer reprices afterPriceChangedException: the caller reprices. shopper:storeandshopper:store-authanswer 401 to a Sanctum token without thestoreability, or issued for another model thanauth.providers.users.model.- SDK:
cart.transfer()returns{ cart, price_changes }(CartTransfer) instead of the cart. CartLine::$guardedprotectsis_custom_priceandpricing. A customCartLineoverridingcasts()must mergeparent::casts().Shopper\Livewire\Components\Searchand theshopper-searchalias are removed, replaced by the command palette add-on. Remove anyshopper.components.dashboard.components.searchentry from a published config.
Upgrading
composer update "shopper/*"
php artisan migrate
php artisan vendor:publish --tag=shopper-cart-translations --tag=shopper-api-translations --tag=shopper-core-translations --force--force overwrites your changes to these translation files. Add orphan_after_minutes and orphans to the reconciliation block of a published config/shopper/payment.php.
The migration merges duplicate cart lines before creating a unique index on cart_lines (cart_id, purchasable): the oldest line keeps the summed quantity and its metadata. On Postgres the index build blocks writes on cart_lines. Create lines through CartManager::add(), never $cart->lines()->create(). New columns: pricing on cart_lines and order_items, order_items.metadata (null for older orders), carts.payment_reference (indexed, backfilled for open carts holding a session), type, value and campaign_id on cart_promotions, and payment_webhook_events.orphaned_at.
Cart mutations serialize on the cart:payment-session:{public_id} cache lock (180 s), so the cache store must be shared across app servers. A direct CartManager caller can get a LockTimeoutException after 3 s.
TaxCalculator and TaxCalculationProvider are scoped instead of singletons. The shopper.api.currency.* cache is gone and the admin country cache moves to shopper.admin.countries.*; old keys expire on their own. Each package now declares the illuminate/* components it imports.
@shopperlabs/shopper-types 3.0.0-rc.3 and @shopperlabs/shopper-sdk 1.0.0-rc.2 are published under the beta tag. The SDK requires the types ^3.0.0-rc.3.
npm install @shopperlabs/shopper-sdk@beta @shopperlabs/shopper-types@betaContributors
Full Changelog: v3.0.0-rc.2...v3.0.0-rc.3