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;
|
||||
|
||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStock;
|
||||
|
||||
/**
|
||||
* 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 ( 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 );
|
||||
|
||||
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' );
|
||||
|
@ -400,7 +404,8 @@ function wc_release_stock_for_order( $order ) {
|
|||
$order = $order instanceof WC_Order ? $order : wc_get_order( $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' );
|
||||
|
|
|
@ -88,7 +88,7 @@ final class ReserveStock {
|
|||
try {
|
||||
$items = array_filter(
|
||||
$order->get_items(),
|
||||
function( $item ) {
|
||||
function ( $item ) {
|
||||
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;
|
||||
|
||||
use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidStockLevelsInCartException;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
|
||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStock;
|
||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait;
|
||||
use Automattic\WooCommerce\Utilities\RestApiUtil;
|
||||
|
@ -147,6 +145,11 @@ class Checkout extends AbstractCartRoute {
|
|||
|
||||
if ( is_wp_error( $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 );
|
||||
|
@ -181,7 +184,6 @@ class Checkout extends AbstractCartRoute {
|
|||
* 5. Process Payment
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @throws InvalidStockLevelsInCartException On error.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*
|
||||
|
@ -195,35 +197,56 @@ class Checkout extends AbstractCartRoute {
|
|||
$this->cart_controller->calculate_totals();
|
||||
|
||||
/**
|
||||
* Validate items etc are allowed in the order before the order is processed. This will fix violations and tell
|
||||
* the customer.
|
||||
* Validate items and fix violations before the order is processed.
|
||||
*/
|
||||
$this->cart_controller->validate_cart();
|
||||
|
||||
/**
|
||||
* Obtain Draft Order and process request data.
|
||||
*
|
||||
* Note: Customer data is persisted from the request first so that OrderController::update_addresses_from_cart
|
||||
* Persist customer session data from the request first so that OrderController::update_addresses_from_cart
|
||||
* uses the up to date customer address.
|
||||
*/
|
||||
$this->update_customer_from_request( $request );
|
||||
$this->create_or_update_draft_order( $request );
|
||||
$this->update_order_from_request( $request );
|
||||
|
||||
/**
|
||||
* Process customer data.
|
||||
*
|
||||
* Update order with customer details, and sign up a user account as necessary.
|
||||
* Create (or update) Draft Order and process request data.
|
||||
*/
|
||||
$this->create_or_update_draft_order( $request );
|
||||
$this->update_order_from_request( $request );
|
||||
$this->process_customer( $request );
|
||||
|
||||
/**
|
||||
* Validate order.
|
||||
*
|
||||
* This logic ensures the order is valid before payment is attempted.
|
||||
* Validate updated order before payment is attempted.
|
||||
*/
|
||||
$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(
|
||||
'__experimental_woocommerce_blocks_checkout_order_processed',
|
||||
array(
|
||||
|
@ -402,24 +425,6 @@ class Checkout extends AbstractCartRoute {
|
|||
|
||||
// Store order ID to session.
|
||||
$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