Avoid reserving stock for draft orders under payment is attempted (#49446)
* Use wc_reserve_stock_for_order only before taking payment * Release stock on exception * changelog
This commit is contained in:
parent
f3e003c236
commit
6d52388b8b
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: enhancement
|
||||||
|
|
||||||
|
Updated block checkout and Store API stock handling so stock is only reserved when attempting payment for an order.
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Checkout\Helpers\ReserveStock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a product's stock amount.
|
* Update a product's stock amount.
|
||||||
*
|
*
|
||||||
|
@ -348,7 +350,8 @@ function wc_get_held_stock_quantity( WC_Product $product, $exclude_order_id = 0
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->get_reserved_stock( $product, $exclude_order_id );
|
$reserve_stock = new ReserveStock();
|
||||||
|
return $reserve_stock->get_reserved_stock( $product, $exclude_order_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -374,7 +377,8 @@ function wc_reserve_stock_for_order( $order ) {
|
||||||
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
|
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
|
||||||
|
|
||||||
if ( $order ) {
|
if ( $order ) {
|
||||||
( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->reserve_stock_for_order( $order );
|
$reserve_stock = new ReserveStock();
|
||||||
|
$reserve_stock->reserve_stock_for_order( $order );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_action( 'woocommerce_checkout_order_created', 'wc_reserve_stock_for_order' );
|
add_action( 'woocommerce_checkout_order_created', 'wc_reserve_stock_for_order' );
|
||||||
|
@ -400,7 +404,8 @@ function wc_release_stock_for_order( $order ) {
|
||||||
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
|
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
|
||||||
|
|
||||||
if ( $order ) {
|
if ( $order ) {
|
||||||
( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->release_stock_for_order( $order );
|
$reserve_stock = new ReserveStock();
|
||||||
|
$reserve_stock->release_stock_for_order( $order );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_action( 'woocommerce_checkout_order_exception', 'wc_release_stock_for_order' );
|
add_action( 'woocommerce_checkout_order_exception', 'wc_release_stock_for_order' );
|
||||||
|
|
|
@ -88,7 +88,7 @@ final class ReserveStock {
|
||||||
try {
|
try {
|
||||||
$items = array_filter(
|
$items = array_filter(
|
||||||
$order->get_items(),
|
$order->get_items(),
|
||||||
function( $item ) {
|
function ( $item ) {
|
||||||
return $item->is_type( 'line_item' ) && $item->get_product() instanceof \WC_Product && $item->get_quantity() > 0;
|
return $item->is_type( 'line_item' ) && $item->get_product() instanceof \WC_Product && $item->get_quantity() > 0;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,11 +2,9 @@
|
||||||
namespace Automattic\WooCommerce\StoreApi\Routes\V1;
|
namespace Automattic\WooCommerce\StoreApi\Routes\V1;
|
||||||
|
|
||||||
use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
|
use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
|
||||||
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidStockLevelsInCartException;
|
|
||||||
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException;
|
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException;
|
||||||
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
|
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
|
||||||
use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
|
use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
|
||||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStock;
|
|
||||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException;
|
use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException;
|
||||||
use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait;
|
use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait;
|
||||||
use Automattic\WooCommerce\Utilities\RestApiUtil;
|
use Automattic\WooCommerce\Utilities\RestApiUtil;
|
||||||
|
@ -147,6 +145,11 @@ class Checkout extends AbstractCartRoute {
|
||||||
|
|
||||||
if ( is_wp_error( $response ) ) {
|
if ( is_wp_error( $response ) ) {
|
||||||
$response = $this->error_to_response( $response );
|
$response = $this->error_to_response( $response );
|
||||||
|
|
||||||
|
// If we encountered an exception, free up stock.
|
||||||
|
if ( $this->order ) {
|
||||||
|
wc_release_stock_for_order( $this->order );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->add_response_headers( $response );
|
return $this->add_response_headers( $response );
|
||||||
|
@ -181,7 +184,6 @@ class Checkout extends AbstractCartRoute {
|
||||||
* 5. Process Payment
|
* 5. Process Payment
|
||||||
*
|
*
|
||||||
* @throws RouteException On error.
|
* @throws RouteException On error.
|
||||||
* @throws InvalidStockLevelsInCartException On error.
|
|
||||||
*
|
*
|
||||||
* @param \WP_REST_Request $request Request object.
|
* @param \WP_REST_Request $request Request object.
|
||||||
*
|
*
|
||||||
|
@ -195,35 +197,56 @@ class Checkout extends AbstractCartRoute {
|
||||||
$this->cart_controller->calculate_totals();
|
$this->cart_controller->calculate_totals();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate items etc are allowed in the order before the order is processed. This will fix violations and tell
|
* Validate items and fix violations before the order is processed.
|
||||||
* the customer.
|
|
||||||
*/
|
*/
|
||||||
$this->cart_controller->validate_cart();
|
$this->cart_controller->validate_cart();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain Draft Order and process request data.
|
* Persist customer session data from the request first so that OrderController::update_addresses_from_cart
|
||||||
*
|
|
||||||
* Note: Customer data is persisted from the request first so that OrderController::update_addresses_from_cart
|
|
||||||
* uses the up to date customer address.
|
* uses the up to date customer address.
|
||||||
*/
|
*/
|
||||||
$this->update_customer_from_request( $request );
|
$this->update_customer_from_request( $request );
|
||||||
$this->create_or_update_draft_order( $request );
|
|
||||||
$this->update_order_from_request( $request );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process customer data.
|
* Create (or update) Draft Order and process request data.
|
||||||
*
|
|
||||||
* Update order with customer details, and sign up a user account as necessary.
|
|
||||||
*/
|
*/
|
||||||
|
$this->create_or_update_draft_order( $request );
|
||||||
|
$this->update_order_from_request( $request );
|
||||||
$this->process_customer( $request );
|
$this->process_customer( $request );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate order.
|
* Validate updated order before payment is attempted.
|
||||||
*
|
|
||||||
* This logic ensures the order is valid before payment is attempted.
|
|
||||||
*/
|
*/
|
||||||
$this->order_controller->validate_order_before_payment( $this->order );
|
$this->order_controller->validate_order_before_payment( $this->order );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserve stock for the order.
|
||||||
|
*
|
||||||
|
* In the shortcode based checkout, when POSTing the checkout form the order would be created and fire the
|
||||||
|
* `woocommerce_checkout_order_created` action. This in turn would trigger the `wc_reserve_stock_for_order`
|
||||||
|
* function so that stock would be held pending payment.
|
||||||
|
*
|
||||||
|
* Via the block based checkout and Store API we already have a draft order, but when POSTing to the /checkout
|
||||||
|
* endpoint we do the same; reserve stock for the order to allow time to process payment.
|
||||||
|
*
|
||||||
|
* Note, stock is only "held" while the order has the status wc-checkout-draft or pending. Stock is freed when
|
||||||
|
* the order changes status, or there is an exception.
|
||||||
|
*
|
||||||
|
* @see ReserveStock::get_query_for_reserved_stock()
|
||||||
|
*
|
||||||
|
* @since 9.2 Stock is no longer held for all draft orders, nor on non-POST requests. See https://github.com/woocommerce/woocommerce/issues/44231
|
||||||
|
* @since 9.2 Uses wc_reserve_stock_for_order() instead of using the ReserveStock class directly.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
wc_reserve_stock_for_order( $this->order );
|
||||||
|
} catch ( ReserveStockException $e ) {
|
||||||
|
throw new RouteException(
|
||||||
|
esc_html( $e->getErrorCode() ),
|
||||||
|
esc_html( $e->getMessage() ),
|
||||||
|
esc_html( $e->getCode() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
wc_do_deprecated_action(
|
wc_do_deprecated_action(
|
||||||
'__experimental_woocommerce_blocks_checkout_order_processed',
|
'__experimental_woocommerce_blocks_checkout_order_processed',
|
||||||
array(
|
array(
|
||||||
|
@ -402,24 +425,6 @@ class Checkout extends AbstractCartRoute {
|
||||||
|
|
||||||
// Store order ID to session.
|
// Store order ID to session.
|
||||||
$this->set_draft_order_id( $this->order->get_id() );
|
$this->set_draft_order_id( $this->order->get_id() );
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to reserve stock for the order.
|
|
||||||
*
|
|
||||||
* If creating a draft order on checkout entry, set the timeout to 10 mins.
|
|
||||||
* If POSTing to the checkout (attempting to pay), set the timeout to 60 mins (using the woocommerce_hold_stock_minutes option).
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
$reserve_stock = new ReserveStock();
|
|
||||||
$duration = $request->get_method() === 'POST' ? (int) get_option( 'woocommerce_hold_stock_minutes', 60 ) : 10;
|
|
||||||
$reserve_stock->reserve_stock_for_order( $this->order, $duration );
|
|
||||||
} catch ( ReserveStockException $e ) {
|
|
||||||
throw new RouteException(
|
|
||||||
$e->getErrorCode(),
|
|
||||||
$e->getMessage(),
|
|
||||||
$e->getCode()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue