Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
*** WooPayments Changelog ***

= 10.5.1 - 2026-02-11 =
* Fix - Cache the affected orders check for the canceled auth fee remediation note to avoid an expensive query on every admin page load

= 10.5.0 - 2026-02-05 =
* Add - Add "Other" and "Booking/Reservation" product type support for dispute evidence (feature flag gated)
* Add - Add ability to specify preferred communications email.
Expand Down
117 changes: 77 additions & 40 deletions includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ class WC_Payments_Remediate_Canceled_Auth_Fees {
*/
const DRY_RUN_ACTION_HOOK = 'wcpay_remediate_canceled_authorization_fees_dry_run';

/**
* Action Scheduler hook for the async affected orders check.
*/
const CHECK_AFFECTED_ORDERS_HOOK = 'wcpay_check_affected_auth_fee_orders';

/**
* Option key for tracking the affected orders check state.
*
* Possible values:
* - false (option doesn't exist): not yet checked.
* - 'scheduled': async check is scheduled or running.
* - 'has_affected_orders': affected orders were found.
* - 'no_affected_orders': no affected orders found.
*/
const CHECK_STATE_OPTION_KEY = 'wcpay_has_affected_auth_fee_orders';

/**
* Option key for tracking dry run mode.
*/
Expand Down Expand Up @@ -98,6 +114,7 @@ public function __construct() {
public function init(): void {
add_action( self::ACTION_HOOK, [ $this, 'process_batch' ] );
add_action( self::DRY_RUN_ACTION_HOOK, [ $this, 'process_batch_dry_run' ] );
add_action( self::CHECK_AFFECTED_ORDERS_HOOK, [ $this, 'check_and_cache_affected_orders' ] );
}

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

// Build the SQL query to find orders with canceled intent status that have either:
// 1. Incorrect fee metadata (_wcpay_transaction_fee or _wcpay_net), OR
// 2. Refund objects (which shouldn't exist for never-captured authorizations), OR
// 3. Incorrect order status of 'wc-refunded' (should be 'wc-cancelled').
$sql = "
SELECT DISTINCT o.id
FROM {$orders_table} o
INNER JOIN {$meta_table} pm_status ON o.id = pm_status.order_id
LEFT JOIN {$meta_table} pm_fee ON o.id = pm_fee.order_id
AND pm_fee.meta_key IN ('_wcpay_transaction_fee', '_wcpay_net')
LEFT JOIN {$orders_table} refunds ON o.id = refunds.parent_order_id
AND refunds.type = 'shop_order_refund'
WHERE o.type = 'shop_order'
AND o.date_created_gmt >= %s
AND pm_status.meta_key = '_intention_status'
AND pm_status.meta_value = %s
AND (pm_fee.order_id IS NOT NULL OR refunds.id IS NOT NULL OR o.status = 'wc-refunded')
";

$params = [ self::BUG_START_DATE, Intent_Status::CANCELED ];
$sql = "SELECT orders.id
FROM {$orders_table} orders
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
LEFT JOIN {$meta_table} fees_meta ON orders.id = fees_meta.order_id AND fees_meta.meta_key = '_wcpay_transaction_fee'
WHERE orders.type = 'shop_order'
AND orders.date_created_gmt >= %s
AND (
-- Refunded with or without a refund.
orders.status = 'wc-refunded'

-- Cancelled with fees.
OR (
orders.status = 'wc-cancelled'
AND fees_meta.order_id IS NOT NULL
)
)";

$params = [ Intent_Status::CANCELED, self::BUG_START_DATE ];

// Add offset based on last order ID.
if ( $last_order_id > 0 ) {
$sql .= ' AND o.id > %d';
$sql .= ' AND orders.id > %d';
$params[] = $last_order_id;
}

// Add ordering and limit.
$sql .= ' ORDER BY o.id ASC LIMIT %d';
$sql .= ' ORDER BY orders.id ASC LIMIT %d';
$params[] = $limit;

// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
Expand All @@ -346,31 +361,33 @@ private function get_affected_orders_cpt( int $limit ): array {
// 1. Incorrect fee metadata (_wcpay_transaction_fee or _wcpay_net), OR
// 2. Refund objects (which shouldn't exist for never-captured authorizations), OR
// 3. Incorrect order status of 'wc-refunded' (should be 'wc-cancelled').
$sql = "
SELECT DISTINCT p.ID
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
LEFT JOIN {$wpdb->postmeta} pm_fee ON p.ID = pm_fee.post_id
AND pm_fee.meta_key IN ('_wcpay_transaction_fee', '_wcpay_net')
LEFT JOIN {$wpdb->posts} refunds ON p.ID = refunds.post_parent
AND refunds.post_type = 'shop_order_refund'
WHERE p.post_type IN ('shop_order', 'shop_order_placeholder')
AND p.post_date >= %s
AND pm_status.meta_key = '_intention_status'
AND pm_status.meta_value = %s
AND (pm_fee.post_id IS NOT NULL OR refunds.ID IS NOT NULL OR p.post_status = 'wc-refunded')
";

$params = [ self::BUG_START_DATE, Intent_Status::CANCELED ];
$sql = "SELECT orders.ID
FROM {$wpdb->posts} orders
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
LEFT JOIN {$wpdb->postmeta} fees_meta ON orders.ID = fees_meta.post_id AND fees_meta.meta_key = '_wcpay_transaction_fee'
WHERE orders.post_type IN ('shop_order', 'shop_order_placeholder')
AND orders.post_date >= %s
AND (
-- Refunded with or without a refund.
orders.post_status = 'wc-refunded'

-- Cancelled with fees
OR (
orders.post_status = 'wc-cancelled'
AND fees_meta.post_id IS NOT NULL
)
)";

$params = [ Intent_Status::CANCELED, self::BUG_START_DATE ];

// Add offset based on last order ID.
if ( $last_order_id > 0 ) {
$sql .= ' AND p.ID > %d';
$sql .= ' AND orders.ID > %d';
$params[] = $last_order_id;
}

// Add ordering and limit.
$sql .= ' ORDER BY p.ID ASC LIMIT %d';
$sql .= ' ORDER BY orders.ID ASC LIMIT %d';
$params[] = $limit;

// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
Expand Down Expand Up @@ -427,6 +444,9 @@ public function process_batch(): void {
return;
}

// This can affect the order transitions by unnecessarily reaching out to Stripe.
remove_action( 'woocommerce_order_status_cancelled', [ WC_Payments::get_order_service(), 'cancel_authorizations_on_order_status_change' ] );

$start_time = microtime( true );
$batch_size = $this->get_batch_size();
$orders = $this->get_affected_orders( $batch_size );
Expand Down Expand Up @@ -885,4 +905,21 @@ public function has_affected_orders(): bool {
$orders = $this->get_affected_orders( 1 );
return ! empty( $orders );
}

/**
* Run the affected orders query and cache the result.
*
* Called by Action Scheduler in a separate request.
*
* @return void
*/
public function check_and_cache_affected_orders(): void {
$result = $this->has_affected_orders();

update_option(
self::CHECK_STATE_OPTION_KEY,
$result ? 'has_affected_orders' : 'no_affected_orders',
true
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class WC_Payments_Notes_Canceled_Auth_Remediation {
* @return bool
*/
public static function can_be_added() {
include_once WCPAY_ABSPATH . 'includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php';

// Don't show if remediation is already complete.
if ( 'completed' === get_option( 'wcpay_fee_remediation_status', '' ) ) {
return false;
Expand Down Expand Up @@ -88,12 +90,47 @@ public static function get_note() {
/**
* Check if there are orders that need remediation.
*
* Uses a state machine backed by an option to avoid running the expensive
* query inline. On the first call, schedules an async Action Scheduler job
* and returns false. The note will be added on a subsequent admin_init
* once the async check completes.
*
* @return bool
*/
private static function has_affected_orders() {
include_once WCPAY_ABSPATH . 'includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php';
$remediation = new WC_Payments_Remediate_Canceled_Auth_Fees();
return $remediation->has_affected_orders();
$state = get_option( WC_Payments_Remediate_Canceled_Auth_Fees::CHECK_STATE_OPTION_KEY );

if ( false === $state ) {
self::schedule_check();
return false;
}

if ( 'has_affected_orders' === $state ) {
return true;
}

// 'scheduled', 'no_affected_orders', or any unexpected value.
return false;
}

/**
* Schedule the async affected orders check via Action Scheduler.
*
* @return void
*/
private static function schedule_check() {
if ( ! function_exists( 'as_schedule_single_action' ) ) {
return;
}

update_option( WC_Payments_Remediate_Canceled_Auth_Fees::CHECK_STATE_OPTION_KEY, 'scheduled', true );

as_schedule_single_action(
time() + 10,
WC_Payments_Remediate_Canceled_Auth_Fees::CHECK_AFFECTED_ORDERS_HOOK,
[],
'woocommerce-payments'
);
}

/**
Expand All @@ -106,7 +143,6 @@ private static function is_remediation_running() {
return false;
}

include_once WCPAY_ABSPATH . 'includes/migrations/class-wc-payments-remediate-canceled-auth-fees.php';
return as_has_scheduled_action( WC_Payments_Remediate_Canceled_Auth_Fees::ACTION_HOOK );
}
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "woocommerce-payments",
"version": "10.5.0",
"version": "10.5.1",
"main": "webpack.config.js",
"author": "Automattic",
"license": "GPL-3.0-or-later",
Expand Down
6 changes: 5 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: woocommerce payments, apple pay, credit card, google pay, payment, payment
Requires at least: 6.0
Tested up to: 6.9
Requires PHP: 7.3
Stable tag: 10.5.0
Stable tag: 10.5.1
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

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

== Changelog ==

= 10.5.1 - 2026-02-11 =
* Fix - Cache the affected orders check for the canceled auth fee remediation note to avoid an expensive query on every admin page load


= 10.5.0 - 2026-02-05 =
* Add - Add "Other" and "Booking/Reservation" product type support for dispute evidence (feature flag gated)
* Add - Add ability to specify preferred communications email.
Expand Down
Loading
Loading