Commit ecfa8f9c6b1 for woocommerce

commit ecfa8f9c6b17ccdc9897cdd89515560ed2274069
Author: Vasily Belolapotkov <vasily.belolapotkov@automattic.com>
Date:   Thu Jul 9 18:31:11 2026 +0200

    Keep the subscriptions-engine Core zone WordPress-free in BillingPolicy (#66463)

    fix: keep subscriptions-engine Core zone WordPress-free in BillingPolicy

    BillingPolicy::from_array() called wp_json_encode() in its trial_duration
    error branch - the only WordPress symbol in the WordPress-free Core zone.
    On that path in a WP-free context (the Core unit tests stub no WP
    functions) it fatals with "Call to undefined function" instead of throwing
    the intended DomainException.

    Use gettype() instead, matching every sibling validation in the file, and
    add a unit test covering the previously-untested branch so the WP-free
    test bootstrap guards it against a future regression.

diff --git a/packages/php/woocommerce-subscriptions-engine/changelog/fix-billing-policy-core-zone-purity b/packages/php/woocommerce-subscriptions-engine/changelog/fix-billing-policy-core-zone-purity
new file mode 100644
index 00000000000..e87155cf114
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/changelog/fix-billing-policy-core-zone-purity
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Keep the WordPress-free Core zone pure: use gettype() instead of wp_json_encode() in the BillingPolicy trial_duration validation message, and cover the previously-untested error branch with a unit test.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php
index 25d45f173e6..130bbd226e5 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php
@@ -115,7 +115,7 @@ final class BillingPolicy {
 		$trial = $data['trial_duration'] ?? null;
 		if ( null !== $trial && ! is_array( $trial ) ) {
 			throw new DomainException(
-				sprintf( 'BillingPolicy: trial_duration must be null or an array, got %s.', wp_json_encode( $trial ) )
+				sprintf( 'BillingPolicy: trial_duration must be null or an array, got %s.', gettype( $trial ) )
 			);
 		}

diff --git a/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/BillingPolicyTest.php b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/BillingPolicyTest.php
index 3d9dbcf826e..f087a5a534e 100644
--- a/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/BillingPolicyTest.php
+++ b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/BillingPolicyTest.php
@@ -128,6 +128,19 @@ class BillingPolicyTest extends TestCase {
 		$policy->compute_next_renewal_from( new DateTimeImmutable( '2026-01-01', new DateTimeZone( 'UTC' ) ) );
 	}

+	public function test_non_array_trial_duration_throws(): void {
+		$this->expectException( DomainException::class );
+		$this->expectExceptionMessage( 'BillingPolicy: trial_duration must be null or an array, got string.' );
+
+		BillingPolicy::from_array(
+			array(
+				'period'         => 'month',
+				'interval'       => 1,
+				'trial_duration' => 'P7D',
+			)
+		);
+	}
+
 	/**
 	 * @dataProvider provide_min_and_max_cycles_validation_cases
 	 * @param string|null $expected_exception_message The expected exception message, or null if no exception is expected.