This lesson combines the official woocommerce/order-update-status and
woocommerce/order-add-note abilities with a custom fraud-risk heuristic. The one
non-negotiable rule for this entire lesson: automation flags, it never cancels, refunds,
or fulfills anything on its own. Every write this ability performs moves an order to a
review state and leaves a note, a human makes the actual decision.
WooCommerce 10.9+ for woocommerce/order-update-status and
woocommerce/order-add-note. A store with real order history is useful for
testing the heuristic meaningfully, a fresh install with one test order won’t surface
much.
Order records include billing addresses, customer emails, and payment method
metadata. Every ability in this lesson requires manage_woocommerce and
must never be marked meta.mcp.public true without a specific, separate
review of who can reach it and what they can see in the response.
Step 1: A conservative, explainable fraud-risk heuristic
Keep the heuristic simple and inspectable, not a black box. This one flags orders above a value threshold where the billing and shipping countries don’t match, a common, well-understood signal, not a proxy for anything else:
// File: my-plugin.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'my-plugin/scan-fraud-risk-orders',
array(
'label' => __( 'Scan for fraud-risk orders', 'my-plugin' ),
'description' => __( 'Finds processing orders above a value threshold where billing and shipping country differ. Read-only, flags candidates for human review only.', 'my-plugin' ),
'category' => 'store-inventory',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'min_total' => array( 'type' => 'number', 'default' => 200 ),
),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'flagged_orders' => array( 'type' => 'array' ),
),
),
'permission_callback' => function () {
return current_user_can( 'manage_woocommerce' );
},
'execute_callback' => function ( $input ) {
$min_total = $input['min_total'] ?? 200;
$orders = wc_get_orders( array(
'status' => 'processing',
'limit' => -1,
) );
$flagged = array();
foreach ( $orders as $order ) {
if ( (float) $order->get_total() < $min_total ) {
continue;
}
if ( $order->get_billing_country() !== $order->get_shipping_country() ) {
$flagged[] = array(
'order_id' => $order->get_id(),
'total' => $order->get_total(),
'billing_country' => $order->get_billing_country(),
'shipping_country' => $order->get_shipping_country(),
);
}
}
return array( 'flagged_orders' => $flagged );
},
'meta' => array(
'annotations' => array( 'readonly' => true ),
'mcp' => array( 'public' => false ),
),
)
);
} );
Step 2: Flagging, using the official abilities, not custom writes
Once an order is flagged, use order-add-note to record why, and
order-update-status only to move it to on-hold, never cancelled or refunded:
// File: my-plugin.php
function my_plugin_flag_order_for_review( $order_id, $reason ) {
$note_ability = wp_get_ability( 'woocommerce/order-add-note' );
$note_ability->execute( array(
'order_id' => $order_id,
'note' => sprintf( 'Flagged for manual fraud review: %s', $reason ),
) );
$status_ability = wp_get_ability( 'woocommerce/order-update-status' );
$status_ability->execute( array(
'order_id' => $order_id,
'status' => 'on-hold',
'note' => 'Automated flag, held pending manual review, not cancelled.',
) );
}
Calling both official abilities through wp_get_ability()->execute() rather than
WC_Order::update_status() directly keeps this in step with whatever validation and
hooks WooCommerce’s own abilities run internally, and it means an upgrade to those
abilities’ behavior automatically applies here too.
Step 3: Wiring the scan to the flag
// File: my-plugin.php
function my_plugin_run_fraud_review_sweep() {
$scan = wp_get_ability( 'my-plugin/scan-fraud-risk-orders' )->execute( array( 'min_total' => 200 ) );
foreach ( $scan['flagged_orders'] as $flag ) {
my_plugin_flag_order_for_review(
$flag['order_id'],
sprintf( 'Billing country %s, shipping country %s, total %s', $flag['billing_country'], $flag['shipping_country'], $flag['total'] )
);
}
}
Run this on a schedule (an Action Scheduler hook, for instance) or expose the scan ability to an admin-facing agent and let a staff member trigger flagging manually after reviewing the candidates, either is reasonable, the important part is that flagging is the ceiling of what runs automatically.
Test it
wp-env run cli wp eval '
$scan = wp_get_ability( "my-plugin/scan-fraud-risk-orders" )->execute( array( "min_total" => 50 ) );
var_dump( $scan );
'
Confirm the results are orders you’d actually want a human to look at before wiring up the flagging step against real order data.
A low min_total or an overly broad country check will flag routine international
orders (gifts shipped to a different country, for instance) as often as real risk. Treat
false positives as a tuning problem to fix in the heuristic, not something to solve by
loosening what the automation is allowed to do. The fix is a better signal, never a
wider blast radius.
Recap
Order-management automation here is strictly a detect-and-flag pipeline: a custom
read-only scan built on wc_get_orders() feeds the official
woocommerce/order-add-note and woocommerce/order-update-status abilities, and the
only status this automation ever sets is on-hold. Cancelling, refunding, or fulfilling
an order stays a human decision, permanently, not a future iteration of this feature.
Resources & further reading
- MCP & Abilities API in WooCommerce 10.9, developer.woocommerce.com
- WC_Order class reference, woocommerce.github.io
- wc_get_orders() function reference, woocommerce.github.io