* 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:
Mike Jolley 2020-04-17 10:24:44 +01:00 committed by GitHub
parent 2dfb8365de
commit 990949b302
7 changed files with 92 additions and 42 deletions

View File

@ -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 ] );

View File

@ -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 }

View File

@ -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';

View File

@ -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;

View File

@ -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 ) );

View File

@ -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 ) );

View File

@ -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',