Payments

Processing Payments Through an Agent: Where to Draw the Line

⏱ 15 min

Say this as directly as possible: this lesson does not teach an AI agent how to initiate a new charge, and it will not, anywhere in this course. Payment gateways, Stripe, PayPal, and every other processor WooCommerce integrates with, handle actual money movement, and most are subject to PCI-DSS requirements around how card data is handled, transmitted, and stored. Getting this wrong is not a bug you patch later, it’s the kind of mistake that costs real money, breaks compliance obligations, and erodes customer trust in ways a refund can’t undo. This course does not endorse, and will not walk you through, unattended AI-initiated payment processing. What it teaches instead is the responsible pattern: an agent can request a payment action, described clearly, and a human, working through your store’s own trusted admin tools or your gateway’s own dashboard, decides whether to actually carry it out.

What you'll learn in this lesson
Why payment processing is the highest-risk topic in this course
Real money movement, PCI-DSS scope, and consequences a refund can't reverse.
The request-and-confirm pattern for payment actions
An ability that describes and queues a payment request, never one that calls a gateway's charge API.
Why raw card data should never appear in an ability's input_schema
A concrete line: if you're tempted to add a card field, stop.
What actually executes a payment action, and why it's never your execute_callback
A human, in WooCommerce's own admin screens or the gateway's dashboard.
Prerequisites

Lesson 3’s refund pattern, since this lesson draws a direct contrast against it. This lesson is conceptually dense and code-light on purpose, read it in full before building anything payment-adjacent.

No code in this lesson calls a payment gateway to initiate a charge

Every example below stops at describing and recording a requested action. If you find yourself extending this pattern to actually call a gateway’s charge or capture API from an execute_callback, you have left the boundary this lesson draws, and you are now building something this course explicitly does not endorse.

Step 1: Why this is the highest-risk topic in the whole course

A refund, from Lesson 3, moves money that was already collected, back to a customer who already trusts your store. A new charge is the reverse: money moving from a customer’s account based on an authorization that may or may not still be valid, for an amount they may or may not have explicitly agreed to in this exact moment. Add to that the fact that most WooCommerce payment gateways are subject to PCI-DSS, the Payment Card Industry Data Security Standard, which governs how card data is handled, transmitted, and stored, specifically to prevent exactly the kind of casual, code-level access this course has otherwise been building toward for orders and refunds. An AI agent with the ability to initiate a charge is an AI agent operating inside that compliance boundary, whether or not anyone designed it to.

Step 2: The line this course draws, request versus execute

What an agent may do
Describe a payment action in plain terms
"Charge the remaining balance of $40 on order #1204 using the card on file" is a description, not an action.
Record that request for a human to review
A queued, auditable entry a staff member sees and acts on deliberately.
Reference an existing, already-tokenized payment method
Never raw card data, only what WooCommerce and the gateway already store as a token.
What an agent must never do in this course's patterns
Call a gateway's charge, capture, or tokenize-a-new-card API directly
That belongs to a human using the gateway's own trusted, compliant interface.
Accept raw card fields in an input_schema
Number, expiry, CVV, anything of that shape has no reason to ever reach an ability's execute_callback.
Execute a payment action from the same call that requested it
No propose/confirm shortcut collapses request and execution into one step here, unlike some Tier 2 actions.

Step 3: Building the request-only ability

This ability writes a reviewable request. It never touches a gateway.

// File: my-plugin.php
wp_register_ability( 'my-plugin/request-payment-action', array(
	'label'       => __( 'Request a payment action for human review', 'my-plugin' ),
	'description' => __( 'Records a requested payment action (capturing an authorized charge, or charging an additional amount on the existing payment method) for a staff member to review and carry out manually in WooCommerce\'s own order screen or the gateway dashboard. This ability never contacts a payment gateway and never initiates a charge itself.', 'my-plugin' ),
	'input_schema' => array(
		'type'       => 'object',
		'properties' => array(
			'order_id'    => array( 'type' => 'integer' ),
			'action_type' => array( 'type' => 'string', 'enum' => array( 'capture_authorized_payment', 'charge_additional_amount' ) ),
			'amount'      => array( 'type' => 'number' ),
			'reason'      => array( 'type' => 'string' ),
		),
		'required' => array( 'order_id', 'action_type', 'reason' ),
	),
	'permission_callback' => function () {
		return current_user_can( 'manage_woocommerce' );
	},
	'execute_callback' => function ( $input ) {
		$order = wc_get_order( $input['order_id'] );
		if ( ! $order ) {
			return new WP_Error( 'not_found', 'No order with that ID exists.' );
		}

		$order->update_meta_data( '_agent_payment_request_pending', array(
			'action_type' => $input['action_type'],
			'amount'      => $input['amount'] ?? null,
			'reason'      => sanitize_text_field( $input['reason'] ),
			'requested_by' => get_current_user_id(),
			'requested_at' => current_time( 'mysql' ),
		) );
		$order->save();

		$order->add_order_note( sprintf(
			'Payment action requested for staff review: %s. Reason: %s. No charge has been made.',
			$input['action_type'],
			$input['reason']
		) );

		return array(
			'queued'  => true,
			'order_id' => $order->get_id(),
			'message'  => 'Request recorded. No payment has been processed. A staff member must carry this out manually in WooCommerce\'s order screen or the gateway dashboard.',
		);
	},
	'meta' => array( 'mcp' => array( 'public' => false ) ),
) );

Nothing in execute_callback calls WC_Payment_Gateway::process_payment(), a gateway’s REST API, or any tokenized-charge helper. It writes order meta and a note, full stop. The order note’s own wording, “no charge has been made,” is deliberate: it’s the first thing a staff member or a support ticket reviewer sees.

Step 4: What actually carries the request out

The human step is not another ability, it’s a person, using tools that are already PCI-compliant because they were built to be: WooCommerce’s own order screen, which surfaces a gateway’s native capture or charge action when the gateway supports it, or the gateway’s own merchant dashboard (Stripe’s, PayPal’s, or whichever processor you use). That interface already handles the compliance boundary correctly. Nothing you build in an ability needs to, or should try to, re-implement it.

A staff member reviewing a queued request
Sees the order note and the _agent_payment_request_pending meta
Clear record of what was requested and why, with no ambiguity about whether it already happened.
Opens the order in WooCommerce's own admin screen
Or the gateway's dashboard directly, whichever is the normal, compliant path for that action.
Carries out the action themselves, through that trusted interface
Then clears the pending meta and adds a note confirming what actually happened.

Test it

Confirm the ability genuinely never touches money: search your codebase for any call to a gateway class or REST endpoint inside this ability’s file, there should be none. Then confirm the human path works as documented:

wp-env run cli wp eval '
$result = wp_get_ability( "my-plugin/request-payment-action" )->execute( array(
	"order_id"    => 101,
	"action_type" => "charge_additional_amount",
	"amount"      => 15.00,
	"reason"      => "Shipping overage on oversized package",
) );
print_r( $result );
'

Then check the order in wp-admin: the note should read clearly as a request, not a confirmation of payment, and the order’s payment status should be completely unchanged.

Treating 'queued' as equivalent to a confirm step

It’s tempting to reuse Lesson 2 and 3’s propose/confirm machinery here and think you’ve built a safe payment-processing ability. You haven’t, if the confirm step’s execute_callback calls a gateway. The propose/confirm pattern is about who authorizes an action your own code then performs. This lesson’s pattern is different and stricter: no code path your ability owns ever performs the payment action at all, only a human, outside your ability entirely, does.

Recap

Payment processing is where this course draws its hardest line. An agent may describe and queue a requested payment action, clearly, auditably, and it may never call a gateway’s charge or capture API itself. That boundary exists because payment gateways move real money and sit inside PCI-DSS’s compliance scope, and no amount of confirmation-token machinery changes that calculus. If your product genuinely needs AI-initiated payment automation beyond what’s described here, that is a conversation with a PCI-qualified security assessor, not a pattern this course provides.

Resources & further reading

← Building Return and Refund Abilities Safely Cart Recovery and Upsell Flows →