-
Notifications
You must be signed in to change notification settings - Fork 464
Description
I've encountered an issue where a merged cart can be returned during login, and scopeActive might leak carts from other users due to missing query grouping.
- Lunar version: 1.2.1
- Laravel Version: 12.48.1
- PHP Version: 8.4.15
- Database MySQL 8.4.2
Expected Behaviour:
When a user logs in (auth_policy=merge), the guest cart should be merged into the user's active cart and the session should resolve the unmerged "target" cart on subsequent requests/logins. Merged source carts (merged_id != null) should not be returned as the current cart.
Actual Behaviour:
After merging carts (a source cart gets merged_id set), subsequent login (without creating a new guest cart) can resolve the merged source cart as the "current" cart, resulting in missing lines/items in the frontend/session.
This seems to happen because:
- Some call sites resolve the user's cart using
->active()->first()without excluding merged carts (->unmerged()), so a merged source cart can be returned. Cart::scopeActive()useswhereDoesntHave()->orWhereHas()without grouping, which can cause the OR clause to escape other constraints (e.g. relationship constraints like user_id).
Steps To Reproduce:
- Ensure config:
lunar.cart.auth_policy = 'merge'lunar.cart_session.auto_create = false
- Log in as a user and create/ensure a user cart exists (add an item).
- Log out (ensure cart is not deleted on logout; e.g. CartSession::forget(false)).
- As guest, add an item (creates a guest cart in session).
- Log in (merge happens; source cart gets merged_id pointing to target cart).
- Log out and log in again WITHOUT adding new guest items.
- Observe that the resolved "current" cart can be the merged source cart (merged_id != null), rather than the target unmerged cart.
Notes / Proposed Fix:
- Ensure session/user cart resolution excludes merged carts, in CartSessionManager
$cartId = $user->carts()
->unmerged()
->active()
->latest('id')
->value('id');- Group OR conditions in
Cart::scopeActive():
public function scopeActive(Builder $query): Builder
{
return $query->where(function ($q) {
$q->whereDoesntHave('orders')
->orWhereHas('orders', function ($sub) {
$sub->whereNull('placed_at');
});
});
}Reactions are currently unavailable