Automattic\WooCommerce\StoreApi\Routes\V1

Checkout::process_orderprivateWC 1.0

Process an order based on optimistic save approach to minimize the number of order saves.

  1. Obtain Draft Order
  2. Process Request
  3. Process Customer
  4. Validate Order
  5. Process Payment

Method of the class: Checkout{}

Returns

\WP_REST_Response|\WP_Error.

Usage

// private - for code of main (parent) class only
$result = $this->process_order( $request );
$request(WP_REST_Request) (required)
.

Checkout::process_order() code WC 11.0.0

private function process_order( \WP_REST_Request $request ) { // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint
	wc_log_order_step( '[Store API #1] Place Order flow initiated', null, false, true );

	$validation_callback = $this->validate_callback( $request );

	if ( is_wp_error( $validation_callback ) ) {
		return $validation_callback;
	}

	/**
	 * Ensure required permissions based on store settings are valid to place the order.
	 */
	$this->validate_user_can_place_order();

	/**
	 * Before triggering validation, ensure totals are current and in turn, things such as shipping costs are present.
	 * This is so plugins that validate other cart data (e.g. conditional shipping and payments) can access this data.
	 */
	$this->cart_controller->calculate_totals();

	/**
	 * Validate that the cart is not empty.
	 */
	$this->cart_controller->validate_cart_not_empty();
	wc_log_order_step( '[Store API #2] Cart validated' );

	/**
	 * Validate items and fix violations before the order is processed.
	 */
	$this->cart_controller->validate_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 );
	// Customer save-point: 1 (session-stored).
	wc_log_order_step( '[Store API #3] Updated customer data from request' );

	/**
	 * Create (or update) Draft Order and process request data.
	 */
	$this->create_or_update_draft_order( $request );
	// Order save-point: 1.
	wc_log_order_step( '[Store API #4] Created/Updated draft order', array( 'order_object' => $this->order ) );
	$this->update_order_from_request( $request, false );
	wc_log_order_step( '[Store API #5] Updated order with posted data', array( 'order_object' => $this->order ) );
	$this->process_customer( $request );
	// Customer save-point: 2 (db-stored; optional, guest -> customer transition or customer data has changed).
	wc_log_order_step( '[Store API #6] Created and/or persisted customer data from order', array( 'order_object' => $this->order ) );

	/**
	 * Validate updated order before payment is attempted.
	 */
	$this->order_controller->validate_order_before_payment( $this->order );
	wc_log_order_step( '[Store API #7] Validated order data', array( 'order_object' => $this->order ) );

	/**
	 * Hold coupons for the order as soon as the draft order is created.
	 */
	try {
		// $this->order->get_billing_email() is already validated by validate_order_before_payment()
		$this->order->hold_applied_coupons( $this->order->get_billing_email() );
	} catch ( \Exception $e ) {
		// Turn the Exception into a RouteException for the API.
		throw new RouteException(
			'woocommerce_rest_coupon_reserve_failed',
			esc_html( $e->getMessage() ),
			400
		);
	}

	/**
	 * 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_log_order_step( '[Store API #8] Reserved stock for order', array( 'order_object' => $this->order ) );

	wc_do_deprecated_action(
		'__experimental_woocommerce_blocks_checkout_order_processed',
		array(
			$this->order,
		),
		'6.3.0',
		'woocommerce_store_api_checkout_order_processed',
		'This action was deprecated in WooCommerce Blocks version 6.3.0. Please use woocommerce_store_api_checkout_order_processed instead.'
	);

	wc_do_deprecated_action(
		'woocommerce_blocks_checkout_order_processed',
		array(
			$this->order,
		),
		'7.2.0',
		'woocommerce_store_api_checkout_order_processed',
		'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_checkout_order_processed instead.'
	);

	// Set initial status to 'pending'; woocommerce_store_api_checkout_order_processed (fired below) can override it.
	// Custom statuses are preserved when no payment is needed; payment gateway statuses take precedence otherwise.
	$this->order->update_status( 'pending' );
	// Order save-point: 2.

	/**
	 * Fires before an order is processed by the Checkout Block/Store API.
	 *
	 * This hook informs extensions that $order has completed processing and is ready for payment.
	 *
	 * This is similar to existing core hook woocommerce_checkout_order_processed. We're using a new action:
	 * - To keep the interface focused (only pass $order, not passing request data).
	 * - This also explicitly indicates these orders are from checkout block/StoreAPI.
	 *
	 * @since 7.2.0
	 *
	 * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3238
	 * @example See docs/examples/checkout-order-processed.md

	 * @param \WC_Order $order Order object.
	 */
	do_action( 'woocommerce_store_api_checkout_order_processed', $this->order );

	/**
	 * Process the payment and return the results.
	 */
	$payment_result = new PaymentResult();

	if ( $this->order->needs_payment() ) {
		$this->process_payment( $request, $payment_result );
	} else {
		$this->process_without_payment( $request, $payment_result );
	}

	wc_log_order_step(
		'[Store API #9] Order processed',
		array(
			'order_object'           => $this->order,
			'processed_with_payment' => $this->order->needs_payment() ? 'yes' : 'no',
			'payment_status'         => $payment_result->status,
		),
		true
	);

	return $this->prepare_item_for_response(
		(object) [
			'order'          => wc_get_order( $this->order ),
			'payment_result' => $payment_result,
		],
		$request
	);
}