Commit 7a9f016a80b for woocommerce

commit 7a9f016a80be3a52a0a84fae5ac52db7ff3089a5
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date:   Thu Mar 19 02:37:23 2026 +0300

    [WOOPLUG-6363] add: typed PHP API methods for tracking number and carrier management (#63573)

    * [WOOPLUG-6363] add: typed PHP API methods for tracking number and carrier management

    * Add typed helper methods for tracking number, shipping provider, and tracking URL to Fulfillment model and update docs

    * Add string safety checks to new getters

    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

    * Fix conflicts after merge

    * Address PR review comments: add @since tags, fix docblocks, use typed accessors

    - Add @since 10.7.0 to all 6 new public methods in Fulfillment.php
    - Fix missing * on blank docblock lines for get_shipping_provider and get_tracking_url
    - Replace raw get_meta('_tracking_*') calls with typed accessors in
      FulfillmentOrderNotes, FulfillmentUtils, and FulfillmentsRenderer

    ---------

    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

diff --git a/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md b/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md
index e631dbc512b..ddf17abd4a0 100644
--- a/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md
+++ b/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md
@@ -119,7 +119,7 @@ add_action( 'woocommerce_fulfillment_created_notification', 'send_sms_notificati

 function send_sms_notification( $order_id, $fulfillment, $order ) {
     $phone = $order->get_billing_phone();
-    $tracking = $fulfillment->get_meta( '_tracking_number', true );
+    $tracking = $fulfillment->get_tracking_number();

     if ( $phone && $tracking ) {
         // Send SMS notification
@@ -278,7 +278,7 @@ Called after the fulfillment items table in emails.
 add_action( 'woocommerce_email_after_fulfillment_table', 'add_tracking_info', 10, 5 );

 function add_tracking_info( $order, $fulfillment, $sent_to_admin, $plain_text, $email ) {
-    $tracking = $fulfillment->get_meta( '_tracking_number', true );
+    $tracking = $fulfillment->get_tracking_number();
     if ( $tracking ) {
         echo '<p>Track your package: ' . esc_html( $tracking ) . '</p>';
     }
diff --git a/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md b/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md
index b98291dd3a4..29a6d1be903 100644
--- a/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md
+++ b/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md
@@ -652,6 +652,8 @@ Metadata objects have the following structure:
 -   `_is_locked` - Whether the fulfillment is locked for merchant modification
 -   `_lock_message` - What to show as the lock message for a locked fulfillment

+**Note:** The tracking number, shipping provider, and tracking URL metadata keys have typed convenience methods on the `Fulfillment` object: `get_tracking_number()` / `set_tracking_number()`, `get_shipping_provider()` / `set_shipping_provider()`, and `get_tracking_url()` / `set_tracking_url()`. These are recommended over direct `get_meta()` / `update_meta_data()` calls for better type safety and IDE autocompletion.
+
 ### Items Structure

 The `_items` metadata must be an array of objects with the following structure:
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/Fulfillment.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/Fulfillment.php
index 3792632eae9..1960986852a 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/Fulfillment.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/Fulfillment.php
@@ -264,6 +264,84 @@ class Fulfillment extends \WC_Data {
 		$this->update_meta_data( '_items', array_values( $items ) );
 	}

+	/**
+	 * Get the tracking number.
+	 *
+	 * @since 10.7.0
+	 * @return string|null Tracking number.
+	 */
+	public function get_tracking_number(): ?string {
+		$value = $this->get_meta( '_tracking_number', true );
+		if ( ! is_scalar( $value ) ) {
+			return null;
+		}
+
+		$value = (string) $value;
+		return '' !== $value ? $value : null;
+	}
+
+	/**
+	 * Set the tracking number.
+	 *
+	 * @since 10.7.0
+	 * @param string $tracking_number Tracking number.
+	 */
+	public function set_tracking_number( string $tracking_number ): void {
+		$this->update_meta_data( '_tracking_number', $tracking_number );
+	}
+
+	/**
+	 * Get the shipping provider.
+	 *
+	 * @since 10.7.0
+	 * @return string|null Shipping provider slug.
+	 */
+	public function get_shipping_provider(): ?string {
+		$value = $this->get_meta( '_shipping_provider', true );
+		if ( ! is_scalar( $value ) ) {
+			return null;
+		}
+
+		$value = (string) $value;
+		return '' !== $value ? $value : null;
+	}
+
+	/**
+	 * Set the shipping provider.
+	 *
+	 * @since 10.7.0
+	 * @param string $shipping_provider Shipping provider slug.
+	 */
+	public function set_shipping_provider( string $shipping_provider ): void {
+		$this->update_meta_data( '_shipping_provider', $shipping_provider );
+	}
+
+	/**
+	 * Get the tracking URL.
+	 *
+	 * @since 10.7.0
+	 * @return string|null Tracking URL.
+	 */
+	public function get_tracking_url(): ?string {
+		$value = $this->get_meta( '_tracking_url', true );
+		if ( ! is_scalar( $value ) ) {
+			return null;
+		}
+
+		$value = (string) $value;
+		return '' !== $value ? $value : null;
+	}
+
+	/**
+	 * Set the tracking URL.
+	 *
+	 * @since 10.7.0
+	 * @param string $tracking_url Tracking URL.
+	 */
+	public function set_tracking_url( string $tracking_url ): void {
+		$this->update_meta_data( '_tracking_url', $tracking_url );
+	}
+
 	/**
 	 * Get the order associated with this fulfillment.
 	 *
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentOrderNotes.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentOrderNotes.php
index 2ea9aaf1ee9..57aeb0b7c45 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentOrderNotes.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentOrderNotes.php
@@ -323,17 +323,17 @@ class FulfillmentOrderNotes {
 	 * @return string The formatted tracking information, or empty string if no tracking number is present.
 	 */
 	private function format_tracking( Fulfillment $fulfillment ): string {
-		$tracking_number   = $fulfillment->get_meta( '_tracking_number', true );
-		$shipping_provider = $fulfillment->get_meta( '_shipping_provider', true );
-		$tracking_url      = $fulfillment->get_meta( '_tracking_url', true );
+		$tracking_number   = $fulfillment->get_tracking_number();
+		$shipping_provider = $fulfillment->get_shipping_provider();
+		$tracking_url      = $fulfillment->get_tracking_url();

-		if ( ! is_string( $tracking_number ) || '' === $tracking_number ) {
+		if ( null === $tracking_number ) {
 			return '';
 		}

 		$parts = array( $tracking_number );

-		if ( is_string( $shipping_provider ) && '' !== $shipping_provider ) {
+		if ( null !== $shipping_provider ) {
 			$parts[] = sprintf(
 				/* translators: %s: shipping provider name */
 				__( 'Provider: %s', 'woocommerce' ),
@@ -341,7 +341,7 @@ class FulfillmentOrderNotes {
 			);
 		}

-		if ( is_string( $tracking_url ) && '' !== $tracking_url ) {
+		if ( null !== $tracking_url ) {
 			$parts[] = sprintf(
 				/* translators: %s: tracking URL */
 				__( 'URL: %s', 'woocommerce' ),
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php
index a638da56317..ecc4580544a 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php
@@ -209,9 +209,9 @@ class FulfillmentUtils {
 	 */
 	public static function get_tracking_info_html( Fulfillment $fulfillment ): string {
 		$tracking_html   = '';
-		$tracking_url    = $fulfillment->get_meta( '_tracking_url', true );
-		$tracking_number = $fulfillment->get_meta( '_tracking_number', true );
-		if ( ! empty( $tracking_url ) && ! empty( $tracking_number ) ) {
+		$tracking_url    = $fulfillment->get_tracking_url();
+		$tracking_number = $fulfillment->get_tracking_number();
+		if ( null !== $tracking_url && null !== $tracking_number ) {
 			$tracking_html .= '<a href="' . esc_url( $tracking_url ) . '" target="_blank" rel="noopener noreferrer">';
 			$tracking_html .= esc_html( $tracking_number );
 			$tracking_html .= '</a>';
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php
index ea3933ca9ce..29cdb79cc08 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php
@@ -203,7 +203,7 @@ class FulfillmentsRenderer {
 	private function render_shipment_tracking_column_row_data( WC_Order $order, array $fulfillments ) {
 		$tracking = array();
 		foreach ( $fulfillments as $fulfillment ) {
-			$tracking[] = $fulfillment->get_meta( '_tracking_number' ) ?? null;
+			$tracking[] = $fulfillment->get_tracking_number();
 		}

 		$tracking = array_filter(