Commit 4fb682e366e for woocommerce
commit 4fb682e366e5165c8ec734fe67e91b63d2b42441
Author: Brandon Kraft <public@brandonkraft.com>
Date: Thu May 7 04:23:24 2026 -0500
Revert: Add checkout validation to payment_complete() to prevent unauthorized order completion (#64674)
* Revert "Add checkout validation to payment_complete() to prevent unauthorized… (#62843)"
This reverts commit f3b4a4ee8453d3ae69c43d71ab8ef1b24ca28139.
* chore: add changelog for revert of PR 62843
diff --git a/plugins/woocommerce/changelog/revert-62843 b/plugins/woocommerce/changelog/revert-62843
new file mode 100644
index 00000000000..a53fe7b9e73
--- /dev/null
+++ b/plugins/woocommerce/changelog/revert-62843
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Revert PR 62843 which added checkout evidence validation to WC_Order::payment_complete()
diff --git a/plugins/woocommerce/includes/class-wc-order.php b/plugins/woocommerce/includes/class-wc-order.php
index a05929a72a2..9acbf62e782 100644
--- a/plugins/woocommerce/includes/class-wc-order.php
+++ b/plugins/woocommerce/includes/class-wc-order.php
@@ -132,7 +132,7 @@ class WC_Order extends WC_Abstract_Order {
* Finally, record the date of payment.
*
* @param string $transaction_id Optional transaction id to store in post meta.
- * @return bool True on success, false if order missing or blocked for lack of checkout evidence.
+ * @return bool success
*/
public function payment_complete( $transaction_id = '' ) {
if ( ! $this->get_id() ) { // Order must exist.
@@ -140,84 +140,6 @@ class WC_Order extends WC_Abstract_Order {
}
try {
- // Validate before pre_payment_complete so blocked orders do not trigger side effects.
- $created_via = $this->get_created_via();
- $cart_hash = $this->get_cart_hash();
- $has_checkout_evidence = false;
-
- /**
- * Allowed created_via values for checkout evidence.
- *
- * @param array $allowed_created_via_values Allowed created_via values.
- * @param WC_Order $order Order object.
- * @since 10.8.0
- */
- $allowed_created_via_values = apply_filters( 'woocommerce_payment_complete_allowed_created_via_values', array( 'checkout', 'store-api', 'rest-api', 'admin', 'pos-rest-api' ), $this );
-
- if ( ! empty( $created_via ) && in_array( $created_via, $allowed_created_via_values, true ) ) {
- $has_checkout_evidence = true;
- }
-
- if ( ! empty( $cart_hash ) ) {
- $has_checkout_evidence = true;
- }
-
- if ( ! $has_checkout_evidence ) {
- /**
- * Allow payment completion without checkout evidence.
- *
- * @param bool $allow_payment_complete Whether to allow.
- * @param WC_Order $order Order object.
- * @param string $transaction_id Transaction ID.
- * @since 10.8.0
- */
- $allow_without_checkout_evidence = apply_filters( 'woocommerce_allow_payment_complete_without_checkout_evidence', false, $this, $transaction_id );
-
- if ( ! $allow_without_checkout_evidence ) {
- $logger = wc_get_logger();
- $logger->error(
- sprintf(
- 'Payment completion blocked for order #%d: Order lacks checkout session evidence (created_via: %s, cart_hash: empty)',
- $this->get_id(),
- $created_via ? $created_via : 'empty'
- ),
- array(
- 'order_id' => $this->get_id(),
- 'created_via' => $created_via,
- 'cart_hash' => 'empty',
- 'payment_method' => $this->get_payment_method(),
- )
- );
- /* translators: %s: created_via value set on the order */
- $created_via_message = empty( $created_via ) ? __( 'No created_via reference', 'woocommerce' ) : sprintf( __( 'Unexpected created_via value: %s', 'woocommerce' ), esc_html( $created_via ) );
- $cart_hash_message = __( 'No cart_hash', 'woocommerce' ); // Always empty inside this guard.
-
- $this->add_order_note(
- sprintf(
- /* translators: %1$s: created_via message, %2$s: cart_hash message */
- __( 'Payment completion blocked: Order lacks checkout session evidence (%1$s, %2$s).', 'woocommerce' ),
- $created_via_message,
- $cart_hash_message
- ),
- 0,
- false,
- array( 'note_group' => OrderNoteGroup::ERROR )
- );
-
- /**
- * Fires when payment completion is blocked due to missing checkout evidence.
- *
- * @param int $order_id Order ID.
- * @param string $created_via The order's created_via value.
- * @param string $cart_hash The order's cart_hash value.
- * @since 10.8.0
- */
- do_action( 'woocommerce_payment_complete_blocked', $this->get_id(), $created_via, $cart_hash );
-
- return false;
- }
- }
-
do_action( 'woocommerce_pre_payment_complete', $this->get_id(), $transaction_id );
if ( WC()->session ) {
diff --git a/plugins/woocommerce/includes/wc-order-functions.php b/plugins/woocommerce/includes/wc-order-functions.php
index 1045250f755..5ad099b157a 100644
--- a/plugins/woocommerce/includes/wc-order-functions.php
+++ b/plugins/woocommerce/includes/wc-order-functions.php
@@ -1137,8 +1137,6 @@ function wc_cancel_unpaid_orders() {
* Default is true for orders created via 'checkout'
* or 'store-api', false otherwise.
* @param WC_Order $order The unpaid order object.
- *
- * @see WC_Order::payment_complete() Checkout evidence allowlist.
*/
if ( apply_filters( 'woocommerce_cancel_unpaid_order', in_array( $order->get_created_via(), array( 'checkout', 'store-api' ), true ), $order ) ) {
$order->update_status( OrderStatus::CANCELLED, __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
index 7dc54c9ba52..463bc7e99bc 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
@@ -1000,9 +1000,6 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object = new WC_Order();
$this->assertFalse( $object->payment_complete() );
$object->save();
- // Set created_via to indicate legitimate checkout session.
- $object->set_created_via( 'checkout' );
- $object->save();
$this->assertTrue( $object->payment_complete( '12345' ) );
$this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
$this->assertEquals( '12345', $object->get_transaction_id() );
@@ -1017,8 +1014,6 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
public function test_payment_complete_error() {
$object = new WC_Order();
$object->save();
- $object->set_created_via( 'checkout' );
- $object->save();
add_action( 'woocommerce_payment_complete', array( $this, 'throwAnException' ) );
@@ -1035,221 +1030,6 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
remove_action( 'woocommerce_payment_complete', array( $this, 'throwAnException' ) );
}
- /**
- * Test: payment_complete blocks orders without checkout evidence
- *
- * @testdox payment_complete blocks order when no created_via or cart_hash and fires payment_complete_blocked
- * @since 10.8.0
- */
- public function test_payment_complete_blocks_orders_without_checkout_evidence() {
- $object = new WC_Order();
- $object->save();
- // No created_via or cart_hash. Payment should be blocked.
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $blocked_action_fired = false;
- $blocked_action_args = array();
- $blocked_callback = function ( $order_id, $created_via, $cart_hash ) use ( &$blocked_action_fired, &$blocked_action_args ) {
- $blocked_action_fired = true;
- $blocked_action_args = array( $order_id, $created_via, $cart_hash );
- };
- add_action( 'woocommerce_payment_complete_blocked', $blocked_callback, 10, 3 );
-
- $this->assertFalse( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::PENDING, $object->get_status() );
-
- $this->assertTrue( $blocked_action_fired );
- $this->assertEquals( $object->get_id(), $blocked_action_args[0] );
- $this->assertEquals( '', $blocked_action_args[1] );
- $this->assertEquals( '', $blocked_action_args[2] );
-
- remove_action( 'woocommerce_payment_complete_blocked', $blocked_callback, 10 );
-
- // Confirm blocked-payment order note exists.
- $notes = wc_get_order_notes(
- array(
- 'order_id' => $object->get_id(),
- )
- );
- $blocked_note = null;
- foreach ( $notes as $note ) {
- if ( strpos( $note->content, 'Payment completion blocked' ) !== false ) {
- $blocked_note = $note;
- break;
- }
- }
- $this->assertNotNull( $blocked_note );
- }
-
- /**
- * Test: payment_complete does not fire woocommerce_pre_payment_complete when blocked
- *
- * @testdox pre_payment_complete does not fire when payment is blocked for lack of checkout evidence
- * @since 10.8.0
- */
- public function test_payment_complete_does_not_fire_pre_payment_complete_when_blocked() {
- $object = new WC_Order();
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $pre_payment_complete_fired = false;
- $callback = function () use ( &$pre_payment_complete_fired ) {
- $pre_payment_complete_fired = true;
- };
- add_action( 'woocommerce_pre_payment_complete', $callback, 10, 0 );
-
- $object->payment_complete( '12345' );
-
- $this->assertFalse( $pre_payment_complete_fired );
-
- remove_action( 'woocommerce_pre_payment_complete', $callback, 10 );
- }
-
- /**
- * Test: payment_complete allows orders with created_via checkout
- *
- * @testdox payment_complete allows order with created_via checkout
- * @since 10.8.0
- */
- public function test_payment_complete_allows_orders_with_created_via_checkout() {
- $object = new WC_Order();
- $object->set_created_via( 'checkout' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
- }
-
- /**
- * Test: payment_complete allows orders with created_via store-api
- *
- * @testdox payment_complete allows order with created_via store-api
- * @since 10.8.0
- */
- public function test_payment_complete_allows_orders_with_created_via_store_api() {
- $object = new WC_Order();
- $object->set_created_via( 'store-api' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
- }
-
- /**
- * Test: payment_complete allows orders with created_via rest-api
- *
- * @testdox payment_complete allows order with created_via rest-api
- * @since 10.8.0
- */
- public function test_payment_complete_allows_orders_with_created_via_rest_api() {
- $object = new WC_Order();
- $object->set_created_via( 'rest-api' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
- }
-
- /**
- * Test: payment_complete allows orders with created_via admin
- *
- * @testdox payment_complete allows order with created_via admin
- * @since 10.8.0
- */
- public function test_payment_complete_allows_orders_with_created_via_admin() {
- $object = new WC_Order();
- $object->set_created_via( 'admin' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
- }
-
- /**
- * Test: payment_complete allows orders with created_via pos-rest-api
- *
- * @testdox payment_complete allows order with created_via pos-rest-api
- * @since 10.8.0
- */
- public function test_payment_complete_allows_orders_with_created_via_pos_rest_api() {
- $object = new WC_Order();
- $object->set_created_via( 'pos-rest-api' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
- }
-
- /**
- * Test: payment_complete allows orders with cart_hash
- *
- * @testdox payment_complete allows order with cart_hash
- * @since 10.8.0
- */
- public function test_payment_complete_allows_orders_with_cart_hash() {
- $object = new WC_Order();
- $object->set_cart_hash( 'test-cart-hash-123' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
- }
-
- /**
- * Test: payment_complete allows bypass via filter
- *
- * @testdox payment_complete allows bypass via woocommerce_allow_payment_complete_without_checkout_evidence
- * @since 10.8.0
- */
- public function test_payment_complete_allows_bypass_via_filter() {
- $object = new WC_Order();
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- // Allow this order via bypass filter.
- $bypass_callback = function ( $allow, $order ) use ( $object ) {
- return $order->get_id() === $object->get_id();
- };
- add_filter( 'woocommerce_allow_payment_complete_without_checkout_evidence', $bypass_callback, 10, 2 );
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
-
- remove_filter( 'woocommerce_allow_payment_complete_without_checkout_evidence', $bypass_callback, 10 );
- }
-
- /**
- * Test: payment_complete allows custom created_via values via filter
- *
- * @testdox payment_complete allows custom created_via via woocommerce_payment_complete_allowed_created_via_values
- * @since 10.8.0
- */
- public function test_payment_complete_allows_custom_created_via_via_filter() {
- $object = new WC_Order();
- $object->set_created_via( 'custom-integration' );
- $object->set_status( OrderStatus::PENDING );
- $object->save();
-
- // Allow custom created_via via filter.
- $created_via_callback = function ( $allowed_values ) {
- $allowed_values[] = 'custom-integration';
- return $allowed_values;
- };
- add_filter( 'woocommerce_payment_complete_allowed_created_via_values', $created_via_callback, 10, 1 );
-
- $this->assertTrue( $object->payment_complete( '12345' ) );
- $this->assertEquals( OrderStatus::COMPLETED, $object->get_status() );
-
- remove_filter( 'woocommerce_payment_complete_allowed_created_via_values', $created_via_callback, 10 );
- }
-
/**
* Test: get_formatted_order_total
*/
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php
index 82ce45b301a..a755a2f18e8 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php
@@ -305,7 +305,6 @@ class OrderHelper {
$order->set_status( OrderStatus::PENDING );
$order->set_created_via( 'unit-tests' );
- $order->set_cart_hash( '1234' );
$order->set_currency( 'COP' );
$order->set_customer_ip_address( '127.0.0.1' );