Commit 1b320629104 for woocommerce

commit 1b320629104564c935f014fdc4a1ca6779c481a9
Author: Anuj Singh <80690679+Anuj-Rathore24@users.noreply.github.com>
Date:   Thu Jun 4 20:17:03 2026 +0530

    Fix: `woocommerce_adjust_non_base_location_prices` not respected for manual backend orders (#63744)

    Co-authored-by: Nestor Soriano <konamiman@konamiman.com>

diff --git a/plugins/woocommerce/changelog/63744-fix-adjust-non-base-location-prices-manual-orders b/plugins/woocommerce/changelog/63744-fix-adjust-non-base-location-prices-manual-orders
new file mode 100644
index 00000000000..9907ef17814
--- /dev/null
+++ b/plugins/woocommerce/changelog/63744-fix-adjust-non-base-location-prices-manual-orders
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix: woocommerce_adjust_non_base_location_prices not respected for manual backend orders
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index aa23accb00a..37130e5a108 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -1634,13 +1634,22 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 	public function add_product( $product, $qty = 1, $args = array() ) {
 		if ( $product ) {
 			$order = ArrayUtil::get_value_or_default( $args, 'order' );
-			$total = wc_get_price_excluding_tax(
-				$product,
-				array(
-					'qty'   => $qty,
-					'order' => $order,
-				)
-			);
+
+			if ( $this->has_fixed_end_prices() ) {
+				// Note: storing inclusive price as-is relies on the filter being a
+				// code-level constant. If the filter changes at runtime, existing
+				// line totals will be misinterpreted on recalculate since the gross
+				// price is reused without re-deriving from the product.
+				$total = (float) $product->get_price() * $qty;
+			} else {
+				$total = wc_get_price_excluding_tax(
+					$product,
+					array(
+						'qty'   => $qty,
+						'order' => $order,
+					)
+				);
+			}

 			$default_args = array(
 				'name'         => $product->get_name(),
@@ -1877,7 +1886,9 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {

 		$is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );

-		// Trigger tax recalculation for all items.
+		if ( $this->has_fixed_end_prices() ) {
+			$calculate_tax_for['prices_include_tax'] = true;
+		}
 		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
 			if ( ! $is_vat_exempt ) {
 				$item->calculate_taxes( $calculate_tax_for );
@@ -2003,6 +2014,30 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 		);
 	}

+	/**
+	 * Whether this store uses fixed end-prices across tax jurisdictions.
+	 * True when prices include tax and woocommerce_adjust_non_base_location_prices is false.
+	 *
+	 * @return bool
+	 */
+	private function has_fixed_end_prices(): bool {
+		/**
+		 * Filters if taxes should be removed from locations outside the store base location.
+		 *
+		 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing
+		 * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10
+		 * regardless of location and taxes.
+		 *
+		 * @since 2.4.7
+		 *
+		 * @param bool $adjust_non_base_location_prices True by default.
+		 */
+		$adjust_non_base_location_prices = apply_filters( 'woocommerce_adjust_non_base_location_prices', true );
+
+		return 'yes' === get_option( 'woocommerce_prices_include_tax' )
+			&& ! $adjust_non_base_location_prices;
+	}
+
 	/**
 	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
 	 *
@@ -2048,6 +2083,11 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 			$this->calculate_taxes();
 		}

+		// Re-read cart totals after calculate_taxes().
+		// Negative fees may have been capped while calculating totals.
+		$cart_subtotal = $this->get_cart_subtotal_for_order();
+		$cart_total    = (float) $this->get_cart_total_for_order();
+
 		// Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp.
 		foreach ( $this->get_items() as $item ) {
 			$taxes = $item->get_taxes();
@@ -2061,6 +2101,12 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 			}
 		}

+		// Fixed end-price orders keep inclusive item totals; compare net values and add tax back below.
+		if ( $this->has_fixed_end_prices() ) {
+			$cart_subtotal = $cart_subtotal - $cart_subtotal_tax;
+			$cart_total    = $cart_total - $cart_total_tax;
+		}
+
 		$this->set_discount_total( NumberUtil::round( $cart_subtotal - $cart_total, $price_decimals ) );
 		$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
 		$this->set_total( NumberUtil::round( $cart_total + $fees_total + (float) $this->get_shipping_total() + (float) $this->get_cart_tax() + (float) $this->get_shipping_tax(), $price_decimals ) );
@@ -2100,6 +2146,37 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
 	}

+	/**
+	 * Get the items subtotal amount to display in the admin order screen.
+	 *
+	 * For stores with fixed end-prices (prices entered including tax with the
+	 * woocommerce_adjust_non_base_location_prices adjustment disabled), the cart
+	 * tax is removed so the displayed subtotal matches the ex-tax cart display.
+	 *
+	 * @return float
+	 */
+	public function get_subtotal_amount_to_display() {
+		return $this->has_fixed_end_prices()
+			? (float) $this->get_subtotal() - (float) $this->get_cart_tax()
+			: (float) $this->get_subtotal();
+	}
+
+	/**
+	 * Get the per-unit item subtotal amount to display in the admin order screen.
+	 *
+	 * For stores with fixed end-prices (prices entered including tax with the
+	 * woocommerce_adjust_non_base_location_prices adjustment disabled), the item
+	 * tax is removed so the displayed amount matches the ex-tax cart display.
+	 *
+	 * @param object $item Item to get the subtotal from.
+	 * @return float
+	 */
+	public function get_item_subtotal_to_display( $item ) {
+		return ( $this->has_fixed_end_prices() && $item instanceof WC_Order_Item_Product && $item->get_quantity() )
+			? NumberUtil::round( ( (float) $item->get_subtotal() - (float) $item->get_subtotal_tax() ) / $item->get_quantity(), wc_get_price_decimals() )
+			: $this->get_item_subtotal( $item, false, true );
+	}
+
 	/**
 	 * Get line subtotal - this is the cost before discount.
 	 *
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-item.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-item.php
index 88383ec4cbc..5348d3e88fc 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-item.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-item.php
@@ -80,10 +80,10 @@ $item_name = apply_filters( 'woocommerce_order_item_name', $item->get_name(), $i
 			</div>
 		</td>
 	<?php endif; ?>
-	<td class="item_cost" width="1%" data-sort-value="<?php echo esc_attr( $order->get_item_subtotal( $item, false, true ) ); ?>">
+	<td class="item_cost" width="1%" data-sort-value="<?php echo esc_attr( $order->get_item_subtotal_to_display( $item ) ); ?>">
 		<div class="view">
 			<?php
-			echo wc_price( $order->get_item_subtotal( $item, false, true ), $wc_price_arg ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+			echo wc_price( $order->get_item_subtotal_to_display( $item ), $wc_price_arg ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 			?>
 		</div>
 	</td>
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-items.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-items.php
index b39f119d40e..1c2160da276 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-items.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-order-items.php
@@ -171,7 +171,7 @@ if ( wc_tax_enabled() ) {
 				<td class="label"><?php esc_html_e( 'Items Subtotal:', 'woocommerce' ); ?></td>
 				<td width="1%"></td>
 				<td class="total">
-					<?php echo wc_price( $order->get_subtotal(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+					<?php echo wc_price( $order->get_subtotal_amount_to_display(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
 				</td>
 			</tr>
 		<?php if ( 0 < $order->get_total_discount() ) : ?>
diff --git a/plugins/woocommerce/includes/class-wc-order-item-fee.php b/plugins/woocommerce/includes/class-wc-order-item-fee.php
index a49f5de03ca..ecbbfc927b1 100644
--- a/plugins/woocommerce/includes/class-wc-order-item-fee.php
+++ b/plugins/woocommerce/includes/class-wc-order-item-fee.php
@@ -93,6 +93,7 @@ class WC_Order_Item_Fee extends WC_Order_Item {
 		}
 		// Use regular calculation unless the fee is negative.
 		if ( 0 <= $this->get_total() ) {
+			unset( $calculate_tax_for['prices_include_tax'] );
 			return parent::calculate_taxes( $calculate_tax_for );
 		}

@@ -111,7 +112,7 @@ class WC_Order_Item_Fee extends WC_Order_Item {
 					$cart_discount_proportion       = $this->get_total() * $proportion;
 					$calculate_tax_for['tax_class'] = $tax_class;
 					$tax_rates                      = WC_Tax::find_rates( $calculate_tax_for );
-					$discount_taxes                 = wc_array_merge_recursive_numeric( $discount_taxes, WC_Tax::calc_tax( $cart_discount_proportion, $tax_rates ) );
+					$discount_taxes                 = wc_array_merge_recursive_numeric( $discount_taxes, WC_Tax::calc_tax( $cart_discount_proportion, $tax_rates, ! empty( $calculate_tax_for['prices_include_tax'] ) ) );
 				}
 			}
 			$this->set_taxes( array( 'total' => $discount_taxes ) );
diff --git a/plugins/woocommerce/includes/class-wc-order-item.php b/plugins/woocommerce/includes/class-wc-order-item.php
index 22858fcd59a..3ad6b58ce9b 100644
--- a/plugins/woocommerce/includes/class-wc-order-item.php
+++ b/plugins/woocommerce/includes/class-wc-order-item.php
@@ -283,19 +283,23 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
 		if ( ! isset( $calculate_tax_for['country'], $calculate_tax_for['state'], $calculate_tax_for['postcode'], $calculate_tax_for['city'] ) ) {
 			return false;
 		}
+
+		$inc_tax = ! empty( $calculate_tax_for['prices_include_tax'] );
+
 		if ( '0' !== $this->get_tax_class() && ProductTaxStatus::TAXABLE === $this->get_tax_status() && wc_tax_enabled() ) {
 			$calculate_tax_for['tax_class'] = $this->get_tax_class();
 			$tax_rates                      = WC_Tax::find_rates( $calculate_tax_for );
-			$taxes                          = WC_Tax::calc_tax( $this->get_total(), $tax_rates, false );
+			$taxes                          = WC_Tax::calc_tax( $this->get_total(), $tax_rates, $inc_tax );

 			if ( method_exists( $this, 'get_subtotal' ) ) {
-				$subtotal_taxes = WC_Tax::calc_tax( $this->get_subtotal(), $tax_rates, false );
+				$subtotal_taxes = WC_Tax::calc_tax( $this->get_subtotal(), $tax_rates, $inc_tax );
 				$this->set_taxes(
 					array(
 						'total'    => $taxes,
 						'subtotal' => $subtotal_taxes,
 					)
 				);
+
 			} else {
 				$this->set_taxes( array( 'total' => $taxes ) );
 			}
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
index dbd21a31a3d..c4a46a9a868 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
@@ -2175,4 +2175,295 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {

 		$this->assertEquals( 20.50, $order->get_total_fees() );
 	}
+
+	/**
+	 * @testdox calculate_taxes() respects woocommerce_adjust_non_base_location_prices
+	 *          for manual backend orders.
+	 */
+	public function test_calculate_taxes_fixed_end_price_non_base_country(): void {
+		$context = $this->create_manual_order_tax_context();
+		add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
+		$order = $this->create_manual_order_for_tax_context( $context, '24' );
+
+		try {
+			$order->calculate_totals();
+
+			$this->assertEquals( 24.00, round( (float) $order->get_total(), 2 ) );
+			$this->assertEquals( 0.00, round( (float) $order->get_discount_total(), 2 ) );
+
+			$item = $this->get_first_order_item( $order );
+			$this->assertEquals(
+				1.98,
+				round( (float) $item->get_subtotal_tax(), 2 ),
+				'Line subtotal tax must be 24/1.09*0.09 from the customer NL rate.'
+			);
+		} finally {
+			$this->cleanup_manual_order_tax_context( $context, $order );
+		}
+	}
+
+	/**
+	 * @testdox calculate_taxes() without the filter still adjusts price for non-base
+	 *          country (default WooCommerce behaviour must be preserved).
+	 */
+	public function test_calculate_taxes_adjusts_price_without_filter(): void {
+		$context = $this->create_manual_order_tax_context();
+		$order   = $this->create_manual_order_for_tax_context( $context );
+
+		try {
+			$order->calculate_totals();
+
+			$this->assertGreaterThan( 24.00, (float) $order->get_total() );
+			$this->assertEquals(
+				24.68,
+				round( (float) $order->get_total(), 2 ),
+				'Without the filter, NL total should be ~24.68.'
+			);
+		} finally {
+			$this->cleanup_manual_order_tax_context( $context, $order );
+		}
+	}
+
+	/**
+	 * @testdox calculate_taxes() with adjust_non_base_location_prices false is a
+	 *          no-op for prices-exclusive-of-tax stores.
+	 */
+	public function test_calculate_taxes_fixed_price_filter_noop_for_excl_tax_store(): void {
+		$context = $this->create_manual_order_tax_context( 'no' );
+		add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
+		$order = $this->create_manual_order_for_tax_context( $context );
+
+		try {
+			$order->calculate_totals();
+
+			$this->assertEquals(
+				26.16,
+				round( (float) $order->get_total(), 2 ),
+				'For excl-tax stores the filter must be a no-op; NL 9% applies on top of 24.'
+			);
+		} finally {
+			$this->cleanup_manual_order_tax_context( $context, $order );
+		}
+	}
+
+	/**
+	 * @testdox calculate_totals() is idempotent, calling it twice produces
+	 *          the same result when adjust_non_base_location_prices is false.
+	 */
+	public function test_calculate_taxes_fixed_price_idempotent(): void {
+		$context = $this->create_manual_order_tax_context();
+		add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
+		$order = $this->create_manual_order_for_tax_context( $context, '24' );
+
+		try {
+			$order->calculate_totals();
+			$total_first = $order->get_total();
+
+			$order->calculate_totals();
+			$total_second = $order->get_total();
+
+			$this->assertEquals(
+				round( (float) $total_first, 2 ),
+				round( (float) $total_second, 2 ),
+				'Calling calculate_totals() twice must produce the same total.'
+			);
+			$this->assertEquals( 24.00, round( (float) $total_second, 2 ) );
+		} finally {
+			$this->cleanup_manual_order_tax_context( $context, $order );
+		}
+	}
+
+	/**
+	 * @testdox calculate_totals() keeps positive fees tax-exclusive in fixed end-price mode.
+	 */
+	public function test_calculate_taxes_fixed_price_positive_fee_remains_tax_exclusive(): void {
+		$context = $this->create_manual_order_tax_context();
+		add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
+		$order = $this->create_manual_order_for_tax_context( $context, '24' );
+
+		$fee = new WC_Order_Item_Fee();
+		$fee->set_props(
+			array(
+				'name'       => 'Handling',
+				'tax_status' => ProductTaxStatus::TAXABLE,
+				'tax_class'  => $context['tax_class_slug'],
+				'total'      => 10,
+			)
+		);
+		$order->add_item( $fee );
+
+		try {
+			$order->calculate_totals();
+
+			$this->assertEquals( 34.90, round( (float) $order->get_total(), 2 ) );
+			$this->assertEquals( 0.90, round( (float) $fee->get_total_tax(), 2 ) );
+		} finally {
+			$this->cleanup_manual_order_tax_context( $context, $order );
+		}
+	}
+
+	/**
+	 * @testdox calculate_totals() extracts tax from negative fees in fixed end-price mode.
+	 */
+	public function test_calculate_taxes_fixed_price_negative_fee_uses_inclusive_tax(): void {
+		$context = $this->create_manual_order_tax_context();
+		add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
+		$order = $this->create_manual_order_for_tax_context( $context, '24' );
+
+		$fee = new WC_Order_Item_Fee();
+		$fee->set_props(
+			array(
+				'name'       => 'Manual discount',
+				'tax_status' => ProductTaxStatus::TAXABLE,
+				'tax_class'  => $context['tax_class_slug'],
+				'total'      => -10,
+			)
+		);
+		$order->add_item( $fee );
+
+		try {
+			$order->calculate_totals();
+
+			$this->assertEquals( -0.83, round( (float) $fee->get_total_tax(), 2 ) );
+			$this->assertEquals( 1.15, round( (float) $order->get_cart_tax(), 2 ) );
+			$this->assertEquals( 13.17, round( (float) $order->get_total(), 2 ) );
+		} finally {
+			$this->cleanup_manual_order_tax_context( $context, $order );
+		}
+	}
+
+	/**
+	 * Creates tax rates and a product for manual order tax tests.
+	 *
+	 * @param string $prices_include_tax Whether prices include tax.
+	 * @return array<string,mixed>
+	 */
+	private function create_manual_order_tax_context( string $prices_include_tax = 'yes' ): array {
+		$tax_class_slug    = 'books-vat';
+		$tax_class_created = false;
+
+		$context = array(
+			'original_prices_include_tax' => get_option( 'woocommerce_prices_include_tax' ),
+			'original_calc_taxes'         => get_option( 'woocommerce_calc_taxes' ),
+			'original_tax_based_on'       => get_option( 'woocommerce_tax_based_on' ),
+			'original_base_country'       => get_option( 'woocommerce_default_country' ),
+			'tax_class_slug'              => $tax_class_slug,
+		);
+
+		update_option( 'woocommerce_prices_include_tax', $prices_include_tax );
+		update_option( 'woocommerce_calc_taxes', 'yes' );
+		update_option( 'woocommerce_tax_based_on', 'billing' );
+		update_option( 'woocommerce_default_country', 'BE' );
+
+		if ( ! in_array( $tax_class_slug, WC_Tax::get_tax_class_slugs(), true ) ) {
+			WC_Tax::create_tax_class( 'Books VAT', $tax_class_slug );
+			$tax_class_created = true;
+		}
+
+		$context['tax_class_created'] = $tax_class_created;
+		$context['be_rate_id']        = $this->insert_manual_order_tax_rate( 'BE', '6.0000', 'Belgium VAT', $tax_class_slug );
+		$context['nl_rate_id']        = $this->insert_manual_order_tax_rate( 'NL', '9.0000', 'Netherlands VAT', $tax_class_slug );
+
+		WC_Cache_Helper::invalidate_cache_group( 'taxes' );
+
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_price( '24' );
+		$product->set_regular_price( '24' );
+		$product->set_tax_status( ProductTaxStatus::TAXABLE );
+		$product->set_tax_class( $tax_class_slug );
+		$product->save();
+
+		$context['product'] = $product;
+
+		return $context;
+	}
+
+	/**
+	 * Inserts a tax rate for manual order tax tests.
+	 *
+	 * @param string $country Tax rate country.
+	 * @param string $rate Tax rate percentage.
+	 * @param string $name Tax rate name.
+	 * @param string $tax_class_slug Tax class slug.
+	 * @return int
+	 */
+	private function insert_manual_order_tax_rate( string $country, string $rate, string $name, string $tax_class_slug ): int {
+		return WC_Tax::_insert_tax_rate(
+			array(
+				'tax_rate_country'  => $country,
+				'tax_rate_state'    => '',
+				'tax_rate'          => $rate,
+				'tax_rate_name'     => $name,
+				'tax_rate_priority' => '1',
+				'tax_rate_compound' => '0',
+				'tax_rate_shipping' => '1',
+				'tax_rate_order'    => '1',
+				'tax_rate_class'    => $tax_class_slug,
+			)
+		);
+	}
+
+	/**
+	 * Creates a manual order for the tax context.
+	 *
+	 * @param array<string,mixed> $context Tax test context.
+	 * @param string|null         $line_total Optional line total override.
+	 * @return WC_Order
+	 */
+	private function create_manual_order_for_tax_context( array $context, ?string $line_total = null ): WC_Order {
+		$order = wc_create_order();
+		$order->set_billing_country( 'NL' );
+		$order->set_shipping_country( 'NL' );
+		$item_id = $order->add_product( $context['product'], 1 );
+
+		if ( null !== $line_total ) {
+			$item = $order->get_item( $item_id );
+			$item->set_subtotal( $line_total );
+			$item->set_total( $line_total );
+			$item->save();
+		}
+
+		$order->save();
+
+		return $order;
+	}
+
+	/**
+	 * Cleans up manual order tax test data.
+	 *
+	 * @param array<string,mixed> $context Tax test context.
+	 * @param WC_Order|null       $order Order to delete.
+	 */
+	private function cleanup_manual_order_tax_context( array $context, ?WC_Order $order = null ): void {
+		remove_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
+		WC_Tax::_delete_tax_rate( $context['be_rate_id'] );
+		WC_Tax::_delete_tax_rate( $context['nl_rate_id'] );
+
+		if ( $context['tax_class_created'] ) {
+			WC_Tax::delete_tax_class_by( 'slug', $context['tax_class_slug'] );
+		}
+
+		WC_Cache_Helper::invalidate_cache_group( 'taxes' );
+		WC_Helper_Product::delete_product( $context['product']->get_id() );
+
+		if ( $order ) {
+			$order->delete( true );
+		}
+
+		update_option( 'woocommerce_prices_include_tax', $context['original_prices_include_tax'] );
+		update_option( 'woocommerce_calc_taxes', $context['original_calc_taxes'] );
+		update_option( 'woocommerce_tax_based_on', $context['original_tax_based_on'] );
+		update_option( 'woocommerce_default_country', $context['original_base_country'] );
+	}
+
+	/**
+	 * Gets the first item from an order.
+	 *
+	 * @param WC_Order $order Order object.
+	 * @return WC_Order_Item
+	 */
+	private function get_first_order_item( WC_Order $order ): WC_Order_Item {
+		$items = $order->get_items();
+		return reset( $items );
+	}
 }