Commit d77c01ce462 for woocommerce

commit d77c01ce46285af05ed7ce0bf14b5569971f671a
Author: daledupreez <dale@automattic.com>
Date:   Wed Jun 24 20:39:13 2026 +0200

    Rename plan_groups app_id column to extension_slug in subscriptions engine (#65959)

    * Rename app_id to extension_slug in plan_groups
    * Changelog
    * Fix indentation

diff --git a/packages/php/woocommerce-subscriptions-engine/changelog/fix-subscriptions-engine-plan-group-column-name b/packages/php/woocommerce-subscriptions-engine/changelog/fix-subscriptions-engine-plan-group-column-name
new file mode 100644
index 00000000000..a76434e6a0b
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/changelog/fix-subscriptions-engine-plan-group-column-name
@@ -0,0 +1,4 @@
+Significance: major
+Type: dev
+
+Rename app_id column to extension slug for plan groups
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php
index a3714b72349..b0b62a2b537 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php
@@ -4,7 +4,7 @@
  *
  * `merchant_code` is an optional stable external identifier; when present it is
  * unique at the storage layer and is the deduplication key consumers use to
- * make group creation idempotent. `app_id` scopes a group to a solution family.
+ * make group creation idempotent. `extension_slug` scopes a group to a specific extension.
  *
  * @package Automattic\WooCommerce\SubscriptionsEngine\Core\Entity
  */
@@ -56,11 +56,11 @@ final class PlanGroup {
 	private $options_display;

 	/**
-	 * Solution-family scope, e.g. a third-party app slug.
+	 * Extension slug, e.g. a third-party extension slug.
 	 *
 	 * @var string|null
 	 */
-	private $app_id;
+	private $extension_slug;

 	/**
 	 * Use {@see self::create()} or {@see self::from_storage()}.
@@ -69,14 +69,14 @@ final class PlanGroup {
 	 * @param string            $name            Display name.
 	 * @param string|null       $merchant_code   Optional stable external identifier.
 	 * @param array<int, mixed> $options_display Display ordering metadata.
-	 * @param string|null       $app_id          Solution-family scope.
+	 * @param string|null       $extension_slug  Extension slug.
 	 */
-	private function __construct( ?int $id, string $name, ?string $merchant_code, array $options_display, ?string $app_id ) {
+	private function __construct( ?int $id, string $name, ?string $merchant_code, array $options_display, ?string $extension_slug ) {
 		$this->id              = $id;
 		$this->name            = $name;
 		$this->merchant_code   = $merchant_code;
 		$this->options_display = $options_display;
-		$this->app_id          = $app_id;
+		$this->extension_slug  = $extension_slug;
 	}

 	/**
@@ -90,7 +90,7 @@ final class PlanGroup {
 			self::coerce_string( $args['name'] ?? null ),
 			self::coerce_nullable_string( $args['merchant_code'] ?? null ),
 			is_array( $args['options_display'] ?? null ) ? $args['options_display'] : array(),
-			self::coerce_nullable_string( $args['app_id'] ?? null )
+			self::coerce_nullable_string( $args['extension_slug'] ?? null )
 		);
 	}

@@ -105,7 +105,7 @@ final class PlanGroup {
 			self::coerce_string( $row['name'] ?? null ),
 			self::coerce_nullable_string( $row['merchant_code'] ?? null ),
 			is_array( $row['options_display'] ?? null ) ? $row['options_display'] : array(),
-			self::coerce_nullable_string( $row['app_id'] ?? null )
+			self::coerce_nullable_string( $row['extension_slug'] ?? null )
 		);
 	}

@@ -167,10 +167,10 @@ final class PlanGroup {
 	}

 	/**
-	 * Solution-family scope.
+	 * Extension slug.
 	 */
-	public function get_app_id(): ?string {
-		return $this->app_id;
+	public function get_extension_slug(): ?string {
+		return $this->extension_slug;
 	}

 	/**
@@ -183,7 +183,7 @@ final class PlanGroup {
 			'name'            => $this->name,
 			'merchant_code'   => $this->merchant_code,
 			'options_display' => $this->options_display,
-			'app_id'          => $this->app_id,
+			'extension_slug'  => $this->extension_slug,
 		);
 	}
 }
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanGroupRepository.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanGroupRepository.php
index e52589418c9..c93c2126b0c 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanGroupRepository.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanGroupRepository.php
@@ -40,7 +40,7 @@ final class PlanGroupRepository {
 				'name'             => $data['name'],
 				'merchant_code'    => $data['merchant_code'],
 				'options_display'  => wp_json_encode( $data['options_display'] ),
-				'app_id'           => $data['app_id'],
+				'extension_slug'   => $data['extension_slug'],
 				'date_created_gmt' => $now,
 				'date_updated_gmt' => $now,
 			)
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php
index 878140b7af9..7b288ba0f89 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php
@@ -32,13 +32,14 @@ final class SchemaInstaller {
 	 * 2.0.0 - cycle-chain model: contract as live source of truth (schedule, snapshot
 	 *         references, totals, stamps); immutable cycle records keyed on
 	 *         `(contract_id, kind)`; per-contract snapshots deduped by copy-forward.
+	 * 2.1.0 - rename `app_id` to `extension_slug` in plan_groups table.
 	 *
 	 * Pre-freeze, tables are recreated rather than migrated. dbDelta adds columns but
 	 * does not change an existing column's nullability or drop unused ones, so a dev box
 	 * on an earlier schema must drop and recreate the tables (and clear VERSION_OPTION)
 	 * to pick up such changes - in-place ALTERs and backfills arrive with the freeze.
 	 */
-	const VERSION = '2.0.0';
+	const VERSION = '2.1.0';

 	/**
 	 * Option key tracking the installed schema version.
@@ -179,12 +180,12 @@ final class SchemaInstaller {
   name VARCHAR(255) NOT NULL,
   merchant_code VARCHAR(64) NULL,
   options_display JSON NULL,
-  app_id VARCHAR(64) NULL,
+  extension_slug VARCHAR(64) NULL,
   date_created_gmt DATETIME NOT NULL,
   date_updated_gmt DATETIME NOT NULL,
   PRIMARY KEY  (id),
   UNIQUE KEY merchant_code (merchant_code),
-  KEY app_id (app_id)
+  KEY extension_slug (extension_slug)
 ) {$collate};";

 		// `extension_slug` records the creating extension's registered slug. Nullable
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/PlanRepositoryTest.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/PlanRepositoryTest.php
index 5a5d0fb0758..17f93ac5de5 100644
--- a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/PlanRepositoryTest.php
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/PlanRepositoryTest.php
@@ -43,7 +43,7 @@ class PlanRepositoryTest extends EngineIntegrationTestCase {
 					'name'            => 'Boxes',
 					'merchant_code'   => 'boxes',
 					'options_display' => array( array( 'name' => 'Size' ) ),
-					'app_id'          => 'wc-subscriptions',
+					'extension_slug'  => 'wc-subscriptions',
 				)
 			)
 		);
@@ -54,7 +54,7 @@ class PlanRepositoryTest extends EngineIntegrationTestCase {
 		$this->assertSame( $id, $fetched->get_id() );
 		$this->assertSame( 'Boxes', $fetched->get_name() );
 		$this->assertSame( 'boxes', $fetched->get_merchant_code() );
-		$this->assertSame( 'wc-subscriptions', $fetched->get_app_id() );
+		$this->assertSame( 'wc-subscriptions', $fetched->get_extension_slug() );
 		$this->assertSame( array( array( 'name' => 'Size' ) ), $fetched->get_options_display() );
 	}