Improve "sold individually" validation (https://github.com/woocommerce/woocommerce-blocks/pull/2234)
* API - Block qty > 1 for sold individually products * Add item validation for sold individually to block checkout * Fix validation check * Add client side limit if sold individually * Prevent never ending loading on failed add to cart * Change cart button for sold individually errors
This commit is contained in:
parent
2dfb8365de
commit
990949b302
|
@ -35,26 +35,26 @@ const getQuantityFromCartItems = ( cartItems, productId ) => {
|
|||
* to add to cart functionality.
|
||||
*/
|
||||
export const useStoreAddToCart = ( productId ) => {
|
||||
const [ addingToCart, setAddingToCart ] = useState( false );
|
||||
const { addItemToCart } = useDispatch( storeKey );
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
|
||||
const [ addingToCart, setAddingToCart ] = useState( false );
|
||||
const currentCartItemQuantity = useRef(
|
||||
getQuantityFromCartItems( cartItems, productId )
|
||||
);
|
||||
const { addItemToCart } = useDispatch( storeKey );
|
||||
|
||||
const addToCart = () => {
|
||||
setAddingToCart( true );
|
||||
addItemToCart( productId );
|
||||
addItemToCart( productId ).finally( () => {
|
||||
setAddingToCart( false );
|
||||
} );
|
||||
};
|
||||
|
||||
useEffect( () => {
|
||||
if ( addingToCart ) {
|
||||
const quantity = getQuantityFromCartItems( cartItems, productId );
|
||||
const quantity = getQuantityFromCartItems( cartItems, productId );
|
||||
|
||||
if ( quantity !== currentCartItemQuantity.current ) {
|
||||
currentCartItemQuantity.current = quantity;
|
||||
setAddingToCart( false );
|
||||
}
|
||||
if ( quantity !== currentCartItemQuantity.current ) {
|
||||
currentCartItemQuantity.current = quantity;
|
||||
}
|
||||
}, [ cartItems, productId ] );
|
||||
|
||||
|
|
|
@ -28,11 +28,21 @@ import Dinero from 'dinero.js';
|
|||
* @param {boolean} backOrdersAllowed Whether to allow backorders or not.
|
||||
* @param {number|null} lowStockAmount If present the number of stock
|
||||
* remaining.
|
||||
* @param {boolean} soldIndividually Whether an item is sold individually or not.
|
||||
*
|
||||
* @return {number} The maximum number value for the quantity input.
|
||||
*/
|
||||
const getMaximumQuantity = ( backOrdersAllowed, lowStockAmount ) => {
|
||||
const getMaximumQuantity = (
|
||||
backOrdersAllowed,
|
||||
lowStockAmount,
|
||||
soldIndividually
|
||||
) => {
|
||||
if ( soldIndividually ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const maxQuantityLimit = getSetting( 'quantitySelectLimit', 99 );
|
||||
|
||||
if ( backOrdersAllowed || ! lowStockAmount ) {
|
||||
return maxQuantityLimit;
|
||||
}
|
||||
|
@ -59,6 +69,7 @@ const CartLineItemRow = ( { lineItem } ) => {
|
|||
summary = '',
|
||||
low_stock_remaining: lowStockRemaining = null,
|
||||
backorders_allowed: backOrdersAllowed = false,
|
||||
sold_individually: soldIndividually = false,
|
||||
permalink = '',
|
||||
images = [],
|
||||
variation = [],
|
||||
|
@ -133,7 +144,8 @@ const CartLineItemRow = ( { lineItem } ) => {
|
|||
quantity={ quantity }
|
||||
maximum={ getMaximumQuantity(
|
||||
backOrdersAllowed,
|
||||
lowStockRemaining
|
||||
lowStockRemaining,
|
||||
soldIndividually
|
||||
) }
|
||||
onChange={ changeQuantity }
|
||||
itemName={ name }
|
||||
|
|
|
@ -3,3 +3,5 @@ export const PRODUCT_NOT_PURCHASABLE =
|
|||
'woocommerce_rest_cart_product_is_not_purchasable';
|
||||
export const PRODUCT_NOT_ENOUGH_STOCK =
|
||||
'woocommerce_rest_cart_product_no_stock';
|
||||
export const PRODUCT_SOLD_INDIVIDUALLY =
|
||||
'woocommerce_rest_cart_product_sold_individually';
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
PRODUCT_OUT_OF_STOCK,
|
||||
PRODUCT_NOT_PURCHASABLE,
|
||||
PRODUCT_NOT_ENOUGH_STOCK,
|
||||
PRODUCT_SOLD_INDIVIDUALLY,
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
|
@ -61,7 +62,8 @@ const ErrorTitle = ( { errorData } ) => {
|
|||
if (
|
||||
errorData.code === PRODUCT_NOT_ENOUGH_STOCK ||
|
||||
errorData.code === PRODUCT_NOT_PURCHASABLE ||
|
||||
errorData.code === PRODUCT_OUT_OF_STOCK
|
||||
errorData.code === PRODUCT_OUT_OF_STOCK ||
|
||||
errorData.code === PRODUCT_SOLD_INDIVIDUALLY
|
||||
) {
|
||||
heading = __(
|
||||
'There is a problem with your cart',
|
||||
|
@ -85,7 +87,8 @@ const ErrorMessage = ( { errorData } ) => {
|
|||
if (
|
||||
errorData.code === PRODUCT_NOT_ENOUGH_STOCK ||
|
||||
errorData.code === PRODUCT_NOT_PURCHASABLE ||
|
||||
errorData.code === PRODUCT_OUT_OF_STOCK
|
||||
errorData.code === PRODUCT_OUT_OF_STOCK ||
|
||||
errorData.code === PRODUCT_SOLD_INDIVIDUALLY
|
||||
) {
|
||||
message =
|
||||
message +
|
||||
|
@ -111,7 +114,8 @@ const ErrorButton = ( { errorData } ) => {
|
|||
if (
|
||||
errorData.code === PRODUCT_NOT_ENOUGH_STOCK ||
|
||||
errorData.code === PRODUCT_NOT_PURCHASABLE ||
|
||||
errorData.code === PRODUCT_OUT_OF_STOCK
|
||||
errorData.code === PRODUCT_OUT_OF_STOCK ||
|
||||
errorData.code === PRODUCT_SOLD_INDIVIDUALLY
|
||||
) {
|
||||
buttonText = __( 'Edit your cart', 'woo-gutenberg-products-block' );
|
||||
buttonUrl = CART_URL;
|
||||
|
|
|
@ -98,14 +98,9 @@ class CartItemsByKey extends AbstractRoute {
|
|||
protected function get_route_update_response( \WP_REST_Request $request ) {
|
||||
$controller = new CartController();
|
||||
$cart = $controller->get_cart_instance();
|
||||
$cart_item = $controller->get_cart_item( $request['key'] );
|
||||
|
||||
if ( ! $cart_item ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woo-gutenberg-products-block' ), 404 );
|
||||
}
|
||||
|
||||
if ( isset( $request['quantity'] ) ) {
|
||||
$cart->set_quantity( $request['key'], $request['quantity'] );
|
||||
$controller->set_cart_item_quantity( $request['key'], $request['quantity'] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->prepare_item_for_response( $controller->get_cart_item( $request['key'] ), $request ) );
|
||||
|
|
|
@ -59,14 +59,9 @@ class CartUpdateItem extends AbstractCartRoute {
|
|||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
$controller = new CartController();
|
||||
$cart = $controller->get_cart_instance();
|
||||
$cart_item = $controller->get_cart_item( $request['key'] );
|
||||
|
||||
if ( ! $cart_item ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item no longer exists or is invalid.', 'woo-gutenberg-products-block' ), 409 );
|
||||
}
|
||||
|
||||
if ( isset( $request['quantity'] ) ) {
|
||||
$cart->set_quantity( $request['key'], $request['quantity'] );
|
||||
$controller->set_cart_item_quantity( $request['key'], $request['quantity'] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
|
|
|
@ -151,6 +151,42 @@ class CartController {
|
|||
return $cart_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on core `set_quantity` method, but validates if an item is sold individually first.
|
||||
*
|
||||
* @throws RouteException Exception if invalid data is detected.
|
||||
*
|
||||
* @param string $item_id Cart item id.
|
||||
* @param integer $quantity Cart quantity.
|
||||
*/
|
||||
public function set_cart_item_quantity( $item_id, $quantity = 1 ) {
|
||||
$cart_item = $this->get_cart_item( $item_id );
|
||||
|
||||
if ( ! $cart_item ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woo-gutenberg-products-block' ), 404 );
|
||||
}
|
||||
|
||||
$product = $cart_item['data'];
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_product', __( 'Cart item is invalid.', 'woo-gutenberg-products-block' ), 404 );
|
||||
}
|
||||
|
||||
if ( $product->is_sold_individually() && $quantity > 1 ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_product_sold_individually',
|
||||
sprintf(
|
||||
/* translators: %s: product name */
|
||||
__( '"%s" is already inside your cart.', 'woo-gutenberg-products-block' ),
|
||||
$product->get_name()
|
||||
),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
wc()->cart->set_quantity( $item_id, $quantity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all items in the cart and check for errors.
|
||||
*
|
||||
|
@ -160,13 +196,7 @@ class CartController {
|
|||
$cart_items = $this->get_cart_items();
|
||||
|
||||
foreach ( $cart_items as $cart_item_key => $cart_item ) {
|
||||
$product = $cart_item['data'];
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->validate_cart_item( $product );
|
||||
$this->validate_cart_item( $cart_item );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,14 +210,8 @@ class CartController {
|
|||
$cart_items = $this->get_cart_items();
|
||||
|
||||
foreach ( $cart_items as $cart_item_key => $cart_item ) {
|
||||
$product = $cart_item['data'];
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validate_cart_item( $product );
|
||||
$this->validate_cart_item( $cart_item );
|
||||
} catch ( RouteException $error ) {
|
||||
$errors[] = new \WP_Error( $error->getErrorCode(), $error->getMessage() );
|
||||
}
|
||||
|
@ -201,9 +225,15 @@ class CartController {
|
|||
*
|
||||
* @throws RouteException Exception if invalid data is detected.
|
||||
*
|
||||
* @param \WC_Product $product Product object associated with the cart item.
|
||||
* @param array $cart_item Cart item data.
|
||||
*/
|
||||
public function validate_cart_item( \WC_Product $product ) {
|
||||
protected function validate_cart_item( $cart_item ) {
|
||||
$product = $cart_item['data'];
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $product->is_purchasable() ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_product_is_not_purchasable',
|
||||
|
@ -216,6 +246,18 @@ class CartController {
|
|||
);
|
||||
}
|
||||
|
||||
if ( $product->is_sold_individually() && $cart_item['quantity'] > 1 ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_product_sold_individually',
|
||||
sprintf(
|
||||
/* translators: %s: product name */
|
||||
__( 'There are too many "%s" in the cart. Only 1 can be purchased.', 'woo-gutenberg-products-block' ),
|
||||
$product->get_name()
|
||||
),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $product->is_in_stock() ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_product_no_stock',
|
||||
|
|
Loading…
Reference in New Issue