Store Projects

Project: Order-Management Automation

⏱ 17 min

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.

What you'll learn in this lesson
A custom read-only fraud-risk scan
Built on wc_get_orders(), checking a conservative, explainable heuristic.
Calling official abilities to flag, not to cancel
order-add-note for the flag, order-update-status only ever to "on-hold."
Why "never auto-cancel" is a hard rule here
The cost asymmetry between a false positive and a wrongly cancelled real order.
Locking this down to store-management capabilities only
This ability touches payment-adjacent data and must never be public.
Prerequisites

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.

This lesson touches payment-adjacent order data

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 heuristic tuned too aggressively becomes noise

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

← Project: AI Shopping Assistant Project: Sales-Reporting Assistant →