WooCommerce - Prevent Multiple Orders for Same Product

I’m working on a custom checkout function and need help preventing duplicate orders in WooCommerce. Here’s my current implementation:

public function handle_order_submission() {
    try {
        $security_token = wc_get_var( $_REQUEST['woocommerce-checkout-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) );
        $session_expired_msg = sprintf(
            __( 'Your session expired. <a href="%s">Go back to store</a>', 'woocommerce' ),
            esc_url( wc_get_page_permalink( 'shop' ) )
        );

        if ( empty( $security_token ) || ! wp_verify_nonce( $security_token, 'woocommerce-checkout' ) ) {
            if ( WC()->cart->is_empty() ) {
                throw new Exception( $session_expired_msg );
            }
            WC()->session->set( 'refresh_totals', true );
            throw new Exception( __( 'Order processing failed, please retry.', 'woocommerce' ) );
        }

        wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
        wc_set_time_limit( 0 );

        do_action( 'woocommerce_before_checkout_process' );

        if ( WC()->cart->is_empty() ) {
            throw new Exception( $session_expired_msg );
        }

        do_action( 'woocommerce_checkout_process' );

        $validation_errors = new WP_Error();
        $form_data = $this->get_submitted_data();

        $this->refresh_session( $form_data );
        $this->validate_order_data( $form_data, $validation_errors );

        foreach ( $validation_errors->errors as $error_code => $error_messages ) {
            $error_data = $validation_errors->get_error_data( $error_code );
            foreach ( $error_messages as $error_message ) {
                wc_add_notice( $error_message, 'error', $error_data );
            }
        }

        if ( empty( $form_data['woocommerce_update_checkout'] ) && 0 === wc_notice_count( 'error' ) ) {
            $this->handle_customer_data( $form_data );
            $new_order_id = $this->generate_order( $form_data );
            $current_order = wc_get_order( $new_order_id );

            if ( is_wp_error( $new_order_id ) ) {
                throw new Exception( $new_order_id->get_error_message() );
            }

            if ( ! $current_order ) {
                throw new Exception( __( 'Order creation failed.', 'woocommerce' ) );
            }

            do_action( 'woocommerce_order_creation_complete', $new_order_id, $form_data, $current_order );

            if ( apply_filters( 'woocommerce_payment_required', $current_order->needs_payment(), WC()->cart ) ) {
                $this->handle_payment_processing( $new_order_id, $form_data['payment_method'] );
            } else {
                $this->complete_free_order( $new_order_id );
            }
        }
    } catch ( Exception $error ) {
        wc_add_notice( $error->getMessage(), 'error' );
    }
    $this->return_ajax_error();
}

This is from the WooCommerce core for testing purposes. I’ll implement this using proper hooks later.

I need this function to check if a customer already has an existing order for the same product. If they do, it should either block the new order creation or update the existing order instead of creating a duplicate.

What’s the best approach to implement this duplicate order prevention?

I’d recommend adding the duplicate check right before your generate_order call since you already have the cart data and customer info available at that point. You can query existing orders using wc_get_orders with customer_id parameter and then loop through each order’s line items to compare product IDs against your current cart contents. Make sure to check order statuses like ‘pending’, ‘processing’, and ‘completed’ - don’t just check completed orders because pending payments might still go through. Also consider if you want to prevent duplicates entirely or just within a certain timeframe, because legitimate repeat purchases are pretty common in most stores.

I ran into this exact issue when building a booking system where duplicate orders would cause inventory problems. The cleanest solution I found was implementing a transient-based lock mechanism combined with database checking. Before calling generate_order, set a transient key using the customer ID and product hash that expires after 30 seconds - this prevents rapid-fire submissions during processing. Then query the database for existing orders using get_posts with meta queries on customer ID and order status. Check the line items against your cart contents and if duplicates exist, either merge quantities into the existing order or block with a custom error message. The transient approach solved the double-click submission issue that traditional database-only checks missed due to timing.

easiest way is to hook into woocommerce_checkout_process and check existing orders by customer id + product ids. something like checking order status ‘processing’ or ‘completed’ then throw exception if match found. dont overthink it mate