github shopperlabs/shopper v3.0.0-rc.3

pre-release4 hours ago

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": true
composer require shopper/framework:^3.0.0-rc

New 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\RevalidateCartCouponAction and CancelPaymentSessionAction are removed. Use CartManager::revalidateCoupons() and CartManager::releasePaymentSession().
  • The CartManager constructor takes DiscountValidator and PaymentSessionGateway and no longer takes TaxCalculator. ResolvePurchasableAction::execute() no longer takes $currencyCode. CreateOrderFromCartAction::execute() takes honoursPayment and no longer reprices after PriceChangedException: the caller reprices.
  • shopper:store and shopper:store-auth answer 401 to a Sanctum token without the store ability, or issued for another model than auth.providers.users.model.
  • SDK: cart.transfer() returns { cart, price_changes } (CartTransfer) instead of the cart.
  • CartLine::$guarded protects is_custom_price and pricing. A custom CartLine overriding casts() must merge parent::casts().
  • Shopper\Livewire\Components\Search and the shopper-search alias are removed, replaced by the command palette add-on. Remove any shopper.components.dashboard.components.search entry 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@beta

Contributors

@mckenziearts

Full Changelog: v3.0.0-rc.2...v3.0.0-rc.3

Don't miss a new shopper release

NewReleases is sending notifications on new releases.