Как испраить ошибку: SQLSTATE: Duplicate alias: имя таблицы указано больше одного раза?
Пожалуйста, называйте связи так, как называется модель, которую вы хотите получить.

items() ? чего? вы хотите получить CartProduct. назовите cartProducts()

очень редко, но бывает, когда CartProduct можно получить двумя способами. Тогда cartProductsByModel1(), cartProductsByModel2()

исключение - абстрактные паттерны, деревья. там можно назвать parent/children/treeParent/treeChild

<?php

Class Cart {
    // ...

    // вот здесь написано, что CartProduct может быть одновременно в двух и более корзинах. не могу представить себе логику, где участник заказывает на сайте товар, а потом этот же товар (который ты заморозил только что) кто-то еще заказывает
    public function items(): BelongsToMany
    {
        return $this->belongsToMany(CartProduct::class, 'cart_products');
        
        // Cart.hasMany(CartProduct) + CartProduct.belongsTo(Cart) + CartProduct.belongsTo(Product)
        // и второе
        // https://github.com/illuminate/database/blob/master/Eloquent/Concerns/HasRelationships.php#L477
        // первым параметром "тип", вторым - "через что". Вы указываете, что "связь много ко многим" должна вернуть "связь много ко многим"
        // при связи Много-Ко-Многим надо Cart.belongsToMany(Product::class, 'cart_products')
    }
}

<?php

class CartProduct {
    // ...

    // здесь написано, что CartProduct старше чем Product. Это выразится в том, что вы сможете "привязать" CartProduct только из самого Product, причем один раз. Это дважды неверно.
    public function product(): HasOne
    {
        return $this->hasOne(ProductVariant::class, 'id');

        // Во первых каждый продукт с его количеством может быть куплен много раз, а вариант всего лишь показывает на его подвид
        // Во вторых модель CartProduct создается позже чем Product, а значит писать $product.addChild($cartProduct) это снова дичь
        // Product.hasMany(ProductVariant::class) + CartProduct.belongsTo(ProductVariant::class) + $cartProduct.associate($productVariant);
    }

    // то же самое. CartProduct старше чем Cart, хотя он в ней лежит. BelongsTo задает старшинство и позволяет добавлять родителя в потомок, а hasOne указывает связь, которая нужна только для select, но не годится для insert
    // Потому что hasOne() - это возможность добавить потомка, причем в Eloquent нету метода addChild() его ещё и самому писать надо типа CartProduct.cart().insert($array) и это тут же нарушает идею отложенной записи, т.к. выполняет запрос немедленно и требует транзакции прямо в модели, тогда как транзакция относится к задаче, а не к модели
    public function cart(): HasOne
    {
        return $this->hasOne(Cart::class);
    }
}
Это лажа
public function items(): BelongsToMany
    {
        return $this->belongsToMany(CartProduct::class, 'cart_products');
    }
Вы классу CartProduct прописываете отношение к самому CartProduct

Наверное должно быть так?
public function items(): BelongsToMany
    {
        return $this->belongsToMany(ProductVariant::class, 'cart_products');
    }