Commit 27aaa6ec142 for woocommerce

commit 27aaa6ec14201da78fbc18c1b7f1ee5a56a8f222
Author: Tom Cafferkey <tjcafferkey@gmail.com>
Date:   Tue Jul 7 14:33:15 2026 +0100

    Fix duplicate order download permissions (#66237)

    * Fix duplicate order download permissions

    * Add changefile(s) from automation for the following project(s): woocommerce

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/66237-fix-order-download-permission-duplicates b/plugins/woocommerce/changelog/66237-fix-order-download-permission-duplicates
new file mode 100644
index 00000000000..2846bbfb3f3
--- /dev/null
+++ b/plugins/woocommerce/changelog/66237-fix-order-download-permission-duplicates
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent duplicate order download permissions when granting access to a product again.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js
index b69e8d7af3d..187e7163230 100644
--- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js
+++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-order.js
@@ -1518,9 +1518,26 @@ jQuery( function ( $ ) {
 			};

 			$.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) {
-
 				if ( response && -1 !== parseInt( response ) ) {
-					$( '.order_download_permissions .wc-metaboxes' ).append( response );
+					var existingDownloads = {};
+					var $newPermissions = $( response ).filter( '.wc-metabox' );
+
+					$( '.order_download_permissions .revoke_access' ).each( function() {
+						existingDownloads[ $( this ).attr( 'rel' ) ] = true;
+					} );
+
+					$newPermissions = $newPermissions.filter( function() {
+						var downloadKey = $( this ).find( '.revoke_access' ).attr( 'rel' );
+
+						if ( existingDownloads[ downloadKey ] ) {
+							return false;
+						}
+
+						existingDownloads[ downloadKey ] = true;
+						return true;
+					} );
+
+					$( '.order_download_permissions .wc-metaboxes' ).append( $newPermissions );
 				} else {
 					window.alert( woocommerce_admin_meta_boxes.i18n_download_permission_fail );
 				}
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index 599c9d2a7ce..138cb54a75d 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -1037,6 +1037,18 @@ class WC_AJAX {
 		$data  = array();
 		$items = $order->get_items();

+		/**
+		 * Customer download data store.
+		 *
+		 * @var WC_Customer_Download_Data_Store $data_store
+		 */
+		$data_store               = WC_Data_Store::load( 'customer-download' );
+		$existing_download_access = array();
+
+		foreach ( $data_store->get_downloads( array( 'order_id' => $order_id ) ) as $download ) {
+			$existing_download_access[ $download->get_product_id() . '|' . $download->get_download_id() ] = true;
+		}
+
 		// Check against order items first.
 		foreach ( $items as $item ) {
 			$product = $item->get_product();
@@ -1065,8 +1077,15 @@ class WC_AJAX {

 			if ( ! empty( $download_data['files'] ) ) {
 				foreach ( $download_data['files'] as $download_id => $file ) {
+					$download_access_key = $product_id . '|' . $download_id;
+
+					if ( isset( $existing_download_access[ $download_access_key ] ) ) {
+						continue;
+					}
+
 					$inserted_id = wc_downloadable_file_permission( $download_id, $product->get_id(), $order, $download_data['quantity'], $download_data['order_item'] );
 					if ( $inserted_id ) {
+						$existing_download_access[ $download_access_key ] = true;
 						$download = new WC_Customer_Download( $inserted_id );
 						++$loop;
 						++$file_counter;