Skip to content

Commit 081fd02

Browse files
committed
Merge release/10.5.1 into trunk
2 parents a1f755f + 82a7afc commit 081fd02

9 files changed

+222
-55
lines changed

changelog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
*** WooPayments Changelog ***
22

3+
= 10.5.1 - 2026-02-11 =
4+
* Fix - Cache the affected orders check for the canceled auth fee remediation note to avoid an expensive query on every admin page load
5+
36
= 10.5.0 - 2026-02-05 =
47
* Add - Add "Other" and "Booking/Reservation" product type support for dispute evidence (feature flag gated)
58
* Add - Add ability to specify preferred communications email.

includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ class WC_Payments_Remediate_Canceled_Auth_Fees {
4848
*/
4949
const DRY_RUN_ACTION_HOOK = 'wcpay_remediate_canceled_authorization_fees_dry_run';
5050

51+
/**
52+
* Action Scheduler hook for the async affected orders check.
53+
*/
54+
const CHECK_AFFECTED_ORDERS_HOOK = 'wcpay_check_affected_auth_fee_orders';
55+
56+
/**
57+
* Option key for tracking the affected orders check state.
58+
*
59+
* Possible values:
60+
* - false (option doesn't exist): not yet checked.
61+
* - 'scheduled': async check is scheduled or running.
62+
* - 'has_affected_orders': affected orders were found.
63+
* - 'no_affected_orders': no affected orders found.
64+
*/
65+
const CHECK_STATE_OPTION_KEY = 'wcpay_has_affected_auth_fee_orders';
66+
5167
/**
5268
* Option key for tracking dry run mode.
5369
*/
@@ -98,6 +114,7 @@ public function __construct() {
98114
public function init(): void {
99115
add_action( self::ACTION_HOOK, [ $this, 'process_batch' ] );
100116
add_action( self::DRY_RUN_ACTION_HOOK, [ $this, 'process_batch_dry_run' ] );
117+
add_action( self::CHECK_AFFECTED_ORDERS_HOOK, [ $this, 'check_and_cache_affected_orders' ] );
101118
}
102119

103120
/**
@@ -294,35 +311,33 @@ private function get_affected_orders_hpos( int $limit ): array {
294311
$orders_table = $wpdb->prefix . 'wc_orders';
295312
$meta_table = $wpdb->prefix . 'wc_orders_meta';
296313

297-
// Build the SQL query to find orders with canceled intent status that have either:
298-
// 1. Incorrect fee metadata (_wcpay_transaction_fee or _wcpay_net), OR
299-
// 2. Refund objects (which shouldn't exist for never-captured authorizations), OR
300-
// 3. Incorrect order status of 'wc-refunded' (should be 'wc-cancelled').
301-
$sql = "
302-
SELECT DISTINCT o.id
303-
FROM {$orders_table} o
304-
INNER JOIN {$meta_table} pm_status ON o.id = pm_status.order_id
305-
LEFT JOIN {$meta_table} pm_fee ON o.id = pm_fee.order_id
306-
AND pm_fee.meta_key IN ('_wcpay_transaction_fee', '_wcpay_net')
307-
LEFT JOIN {$orders_table} refunds ON o.id = refunds.parent_order_id
308-
AND refunds.type = 'shop_order_refund'
309-
WHERE o.type = 'shop_order'
310-
AND o.date_created_gmt >= %s
311-
AND pm_status.meta_key = '_intention_status'
312-
AND pm_status.meta_value = %s
313-
AND (pm_fee.order_id IS NOT NULL OR refunds.id IS NOT NULL OR o.status = 'wc-refunded')
314-
";
315-
316-
$params = [ self::BUG_START_DATE, Intent_Status::CANCELED ];
314+
$sql = "SELECT orders.id
315+
FROM {$orders_table} orders
316+
INNER JOIN {$meta_table} status_meta ON orders.id = status_meta.order_id AND status_meta.meta_key = '_intention_status' AND status_meta.meta_value = %s
317+
LEFT JOIN {$meta_table} fees_meta ON orders.id = fees_meta.order_id AND fees_meta.meta_key = '_wcpay_transaction_fee'
318+
WHERE orders.type = 'shop_order'
319+
AND orders.date_created_gmt >= %s
320+
AND (
321+
-- Refunded with or without a refund.
322+
orders.status = 'wc-refunded'
323+
324+
-- Cancelled with fees.
325+
OR (
326+
orders.status = 'wc-cancelled'
327+
AND fees_meta.order_id IS NOT NULL
328+
)
329+
)";
330+
331+
$params = [ Intent_Status::CANCELED, self::BUG_START_DATE ];
317332

318333
// Add offset based on last order ID.
319334
if ( $last_order_id > 0 ) {
320-
$sql .= ' AND o.id > %d';
335+
$sql .= ' AND orders.id > %d';
321336
$params[] = $last_order_id;
322337
}
323338

324339
// Add ordering and limit.
325-
$sql .= ' ORDER BY o.id ASC LIMIT %d';
340+
$sql .= ' ORDER BY orders.id ASC LIMIT %d';
326341
$params[] = $limit;
327342

328343
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@@ -346,31 +361,33 @@ private function get_affected_orders_cpt( int $limit ): array {
346361
// 1. Incorrect fee metadata (_wcpay_transaction_fee or _wcpay_net), OR
347362
// 2. Refund objects (which shouldn't exist for never-captured authorizations), OR
348363
// 3. Incorrect order status of 'wc-refunded' (should be 'wc-cancelled').
349-
$sql = "
350-
SELECT DISTINCT p.ID
351-
FROM {$wpdb->posts} p
352-
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
353-
LEFT JOIN {$wpdb->postmeta} pm_fee ON p.ID = pm_fee.post_id
354-
AND pm_fee.meta_key IN ('_wcpay_transaction_fee', '_wcpay_net')
355-
LEFT JOIN {$wpdb->posts} refunds ON p.ID = refunds.post_parent
356-
AND refunds.post_type = 'shop_order_refund'
357-
WHERE p.post_type IN ('shop_order', 'shop_order_placeholder')
358-
AND p.post_date >= %s
359-
AND pm_status.meta_key = '_intention_status'
360-
AND pm_status.meta_value = %s
361-
AND (pm_fee.post_id IS NOT NULL OR refunds.ID IS NOT NULL OR p.post_status = 'wc-refunded')
362-
";
363-
364-
$params = [ self::BUG_START_DATE, Intent_Status::CANCELED ];
364+
$sql = "SELECT orders.ID
365+
FROM {$wpdb->posts} orders
366+
INNER JOIN {$wpdb->postmeta} status_meta ON orders.ID = status_meta.post_id AND status_meta.meta_key = '_intention_status' AND status_meta.meta_value = %s
367+
LEFT JOIN {$wpdb->postmeta} fees_meta ON orders.ID = fees_meta.post_id AND fees_meta.meta_key = '_wcpay_transaction_fee'
368+
WHERE orders.post_type IN ('shop_order', 'shop_order_placeholder')
369+
AND orders.post_date >= %s
370+
AND (
371+
-- Refunded with or without a refund.
372+
orders.post_status = 'wc-refunded'
373+
374+
-- Cancelled with fees
375+
OR (
376+
orders.post_status = 'wc-cancelled'
377+
AND fees_meta.post_id IS NOT NULL
378+
)
379+
)";
380+
381+
$params = [ Intent_Status::CANCELED, self::BUG_START_DATE ];
365382

366383
// Add offset based on last order ID.
367384
if ( $last_order_id > 0 ) {
368-
$sql .= ' AND p.ID > %d';
385+
$sql .= ' AND orders.ID > %d';
369386
$params[] = $last_order_id;
370387
}
371388

372389
// Add ordering and limit.
373-
$sql .= ' ORDER BY p.ID ASC LIMIT %d';
390+
$sql .= ' ORDER BY orders.ID ASC LIMIT %d';
374391
$params[] = $limit;
375392

376393
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@@ -427,6 +444,9 @@ public function process_batch(): void {
427444
return;
428445
}
429446

447+
// This can affect the order transitions by unnecessarily reaching out to Stripe.
448+
remove_action( 'woocommerce_order_status_cancelled', [ WC_Payments::get_order_service(), 'cancel_authorizations_on_order_status_change' ] );
449+
430450
$start_time = microtime( true );
431451
$batch_size = $this->get_batch_size();
432452
$orders = $this->get_affected_orders( $batch_size );
@@ -885,4 +905,21 @@ public function has_affected_orders(): bool {
885905
$orders = $this->get_affected_orders( 1 );
886906
return ! empty( $orders );
887907
}
908+
909+
/**
910+
* Run the affected orders query and cache the result.
911+
*
912+
* Called by Action Scheduler in a separate request.
913+
*
914+
* @return void
915+
*/
916+
public function check_and_cache_affected_orders(): void {
917+
$result = $this->has_affected_orders();
918+
919+
update_option(
920+
self::CHECK_STATE_OPTION_KEY,
921+
$result ? 'has_affected_orders' : 'no_affected_orders',
922+
true
923+
);
924+
}
888925
}

includes/notes/class-wc-payments-notes-canceled-auth-remediation.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class WC_Payments_Notes_Canceled_Auth_Remediation {
3737
* @return bool
3838
*/
3939
public static function can_be_added() {
40+
include_once WCPAY_ABSPATH . 'includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php';
41+
4042
// Don't show if remediation is already complete.
4143
if ( 'completed' === get_option( 'wcpay_fee_remediation_status', '' ) ) {
4244
return false;
@@ -88,12 +90,47 @@ public static function get_note() {
8890
/**
8991
* Check if there are orders that need remediation.
9092
*
93+
* Uses a state machine backed by an option to avoid running the expensive
94+
* query inline. On the first call, schedules an async Action Scheduler job
95+
* and returns false. The note will be added on a subsequent admin_init
96+
* once the async check completes.
97+
*
9198
* @return bool
9299
*/
93100
private static function has_affected_orders() {
94-
include_once WCPAY_ABSPATH . 'includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php';
95-
$remediation = new WC_Payments_Remediate_Canceled_Auth_Fees();
96-
return $remediation->has_affected_orders();
101+
$state = get_option( WC_Payments_Remediate_Canceled_Auth_Fees::CHECK_STATE_OPTION_KEY );
102+
103+
if ( false === $state ) {
104+
self::schedule_check();
105+
return false;
106+
}
107+
108+
if ( 'has_affected_orders' === $state ) {
109+
return true;
110+
}
111+
112+
// 'scheduled', 'no_affected_orders', or any unexpected value.
113+
return false;
114+
}
115+
116+
/**
117+
* Schedule the async affected orders check via Action Scheduler.
118+
*
119+
* @return void
120+
*/
121+
private static function schedule_check() {
122+
if ( ! function_exists( 'as_schedule_single_action' ) ) {
123+
return;
124+
}
125+
126+
update_option( WC_Payments_Remediate_Canceled_Auth_Fees::CHECK_STATE_OPTION_KEY, 'scheduled', true );
127+
128+
as_schedule_single_action(
129+
time() + 10,
130+
WC_Payments_Remediate_Canceled_Auth_Fees::CHECK_AFFECTED_ORDERS_HOOK,
131+
[],
132+
'woocommerce-payments'
133+
);
97134
}
98135

99136
/**
@@ -106,7 +143,6 @@ private static function is_remediation_running() {
106143
return false;
107144
}
108145

109-
include_once WCPAY_ABSPATH . 'includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php';
110146
return as_has_scheduled_action( WC_Payments_Remediate_Canceled_Auth_Fees::ACTION_HOOK );
111147
}
112148
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "woocommerce-payments",
3-
"version": "10.5.0",
3+
"version": "10.5.1",
44
"main": "webpack.config.js",
55
"author": "Automattic",
66
"license": "GPL-3.0-or-later",

readme.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment
44
Requires at least: 6.0
55
Tested up to: 6.9
66
Requires PHP: 7.3
7-
Stable tag: 10.5.0
7+
Stable tag: 10.5.1
88
License: GPLv2 or later
99
License URI: http://www.gnu.org/licenses/gpl-2.0.html
1010

@@ -87,6 +87,10 @@ You can read our Terms of Service and other policies [here](https://woocommerce.
8787

8888
== Changelog ==
8989

90+
= 10.5.1 - 2026-02-11 =
91+
* Fix - Cache the affected orders check for the canceled auth fee remediation note to avoid an expensive query on every admin page load
92+
93+
9094
= 10.5.0 - 2026-02-05 =
9195
* Add - Add "Other" and "Booking/Reservation" product type support for dispute evidence (feature flag gated)
9296
* Add - Add ability to specify preferred communications email.

0 commit comments

Comments
 (0)