Commit 39be92243ab for woocommerce

commit 39be92243ab0167ded779e08bcb9a78b2190c04b
Author: Ján Mikláš <neosinner@gmail.com>
Date:   Tue May 19 12:55:37 2026 +0200

    Add offer-level product price currency (#65123)

    * Add offer-level structured data price currency

    * Cover sale-price case for offer-level priceCurrency

    Add a structured-data test for the on-sale path so the offer-level
    `price` is locked to the sale price (matching what `priceSpecification[0]`
    records). Add a changelog fragment.

    * Fix structured data PHPStan warning

    * Fix structured data PHPStan guard

diff --git a/plugins/woocommerce/changelog/wooplug-5442-product-offer-price-currency b/plugins/woocommerce/changelog/wooplug-5442-product-offer-price-currency
new file mode 100644
index 00000000000..d38f11b9fa0
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-5442-product-offer-price-currency
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Promote the simple-product offer's price and currency to the top of the JSON-LD offer so Google Search Console accepts the structured data.
diff --git a/plugins/woocommerce/includes/class-wc-structured-data.php b/plugins/woocommerce/includes/class-wc-structured-data.php
index 316504715b0..d0318c43b58 100644
--- a/plugins/woocommerce/includes/class-wc-structured-data.php
+++ b/plugins/woocommerce/includes/class-wc-structured-data.php
@@ -418,11 +418,14 @@ class WC_Structured_Data {
 					'url'   => $shop_url,
 				),
 			);
+			if ( 'Offer' === $markup_offer['@type'] && empty( $markup_offer['price'] ) && ! empty( $markup_offer['priceSpecification'][0]['price'] ) ) {
+				$markup_offer['price'] = $markup_offer['priceSpecification'][0]['price'];
+			}
 			if (
 				( ! empty( $markup_offer['price'] ) ||
 					! empty( $markup_offer['lowPrice'] ) ||
 					! empty( $markup_offer['highPrice'] )
-				) && empty( $markup_offer['priceCurrency'] )
+				)
 			) {
 				$markup_offer['priceCurrency'] = $currency;
 			}
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 0432d84946c..cd79245e2b4 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -15660,12 +15660,6 @@ parameters:
 			count: 1
 			path: includes/class-wc-structured-data.php

-		-
-			message: '#^Offset ''priceCurrency'' on array\{priceValidUntil\: string, availability\: ''https\://schema\.org…'', url\: mixed, seller\: array\{''@type''\: ''Organization'', name\: mixed, url\: mixed\}, ''@type''\: ''AggregateOffer'', lowPrice\: mixed, highPrice\: mixed, offerCount\: int\<0, max\>, \.\.\.\} in empty\(\) does not exist\.$#'
-			identifier: empty.offset
-			count: 1
-			path: includes/class-wc-structured-data.php
-
 		-
 			message: '#^Parameter \#1 \$attachment_id of function wp_get_attachment_url expects int, string given\.$#'
 			identifier: argument.type
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php b/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php
index 652d1199e92..a0a25c41367 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php
@@ -80,4 +80,48 @@ class WC_Structured_Data_Test extends \WC_Unit_Test_Case {
 		$this->assertEquals( $this->structured_data->prepare_gtin( '+12345678' ), '12345678' );
 		$this->assertEquals( $this->structured_data->prepare_gtin( '123.4e-5' ), '12345' );
 	}
+
+	/**
+	 * Test simple product offer structured data includes offer-level price currency.
+	 *
+	 * @return void
+	 */
+	public function test_simple_product_offer_includes_offer_level_price_currency(): void {
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_regular_price( '97' );
+		$product->set_price( '97' );
+		$product->save();
+
+		$this->structured_data->generate_product_data( $product );
+
+		$data  = $this->structured_data->get_data();
+		$offer = $data[0]['offers'][0];
+
+		$this->assertEquals( '97.00', $offer['price'] );
+		$this->assertEquals( get_woocommerce_currency(), $offer['priceCurrency'] );
+		$this->assertEquals( get_woocommerce_currency(), $offer['priceSpecification'][0]['priceCurrency'] );
+	}
+
+	/**
+	 * Test on-sale simple product offer reports the sale price at the offer top level.
+	 *
+	 * @return void
+	 */
+	public function test_simple_product_offer_on_sale_uses_sale_price_at_offer_level(): void {
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_regular_price( '100' );
+		$product->set_sale_price( '70' );
+		$product->set_price( '70' );
+		$product->save();
+
+		$this->structured_data->generate_product_data( $product );
+
+		$data  = $this->structured_data->get_data();
+		$offer = $data[0]['offers'][0];
+
+		// The offer-level `price` should reflect the sale price, matching `priceSpecification[0]['price']`.
+		$this->assertEquals( '70.00', $offer['price'] );
+		$this->assertEquals( '70.00', $offer['priceSpecification'][0]['price'] );
+		$this->assertEquals( get_woocommerce_currency(), $offer['priceCurrency'] );
+	}
 }