Search code examples
wordpresswoocommerceproductcartcheckout

How to avoid buying the same product again in WooCommerce by adding products to the cart as a guest and logging in during checkout


I have WooCommerce + Tutor LMS Pro. WooCommerce shop_page and product pages are hidden, same for the WP search results (using a snippet in functions.php). Users can’t purchase more than one of the same product (course) – this is easily set up in WooCommerce. The only intended method of purchasing a product is through a Tutor course page.

During the testing I found a way how customers still can buy products already purchased in the past (which doesn’t make sense for a lifetime access to a course) – simply by adding products into the cart as a visitor and logging in after.

I don't want to force users creating new account to add courses to the cart because the buying process won't be seamless and straightforward anymore.

I tried these solutions:

Solution A (/child-theme/functions.php)

add_filter( 'woocommerce_add_cart_item_data', 'woo_check_not_bought_before' );

function woo_check_not_bought_before( $cart_item_data ) {

    global $woocommerce;

    // Some MYSQL to check if product was bought before

    if ($bought_before == 'true') {       
         $woocommerce->cart->empty_cart();
    }

    // Echo some text to explain why you can only buy the product once

    return $cart_item_data;
}

Solution B (/child-theme/loop/add-to-cart.php)

  if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

  global $product;
  $current_user = wp_get_current_user();

 if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product->id)) {
    echo 'Purchased';
    }
   else
   {
   echo apply_filters( 'woocommerce_loop_add_to_cart_link',
  sprintf( '<a href="%s" rel="nofollow" data-product_id="%s" data-product_sku="%s" class="button          %s product_type_%s">%s</a>',
    esc_url( $product->add_to_cart_url() ),
    esc_attr( $product->id ),
    esc_attr( $product->get_sku() ),
    $product->is_purchasable() ? 'add_to_cart_button' : '',
    esc_attr( $product->product_type ),
    esc_html( $product->add_to_cart_text() )
),
$product );
  } 

None of those work for the described issue. They restrict adding products to the cart from the woo product pages only. Adding product via Tutor course page as a visitor, logging in and buying after is still possible though.

Solution C (/child-theme/functions.php)

/**
 * Disables repeat purchase for products / variations
 * 
 * @param bool $purchasable true if product can be purchased
 * @param \WC_Product $product the WooCommerce product
 * @return bool $purchasable the updated is_purchasable check
 */
function sv_disable_repeat_purchase( $purchasable, $product ) {

    // Don't run on parents of variations,
    // function will already check variations separately
    if ( $product->is_type( 'variable' ) ) {
        return $purchasable;
    }

    // Get the ID for the current product (passed in)
    $product_id = $product->is_type( 'variation' ) ? $product->variation_id : $product->id; 

    // return false if the customer has bought the product / variation
    if ( wc_customer_bought_product( get_current_user()->user_email, get_current_user_id(), $product_id ) ) {
        $purchasable = false;
    }

    // Double-check for variations: if parent is not purchasable, then variation is not
    if ( $purchasable && $product->is_type( 'variation' ) ) {
        $purchasable = $product->parent->is_purchasable();
    }

    return $purchasable;
}
add_filter( 'woocommerce_is_purchasable', 'sv_disable_repeat_purchase', 10, 2 );


/**
 * Shows a "purchase disabled" message to the customer
 */
function sv_purchase_disabled_message() {

    // Get the current product to see if it has been purchased
    global $product;

    if ( $product->is_type( 'variable' ) ) {

        foreach ( $product->get_children() as $variation_id ) {
            // Render the purchase restricted message if it has been purchased
            if ( wc_customer_bought_product( get_current_user()->user_email, get_current_user_id(), $variation_id ) ) {
                sv_render_variation_non_purchasable_message( $product, $variation_id );
            }
        }

    } else {
        if ( wc_customer_bought_product( get_current_user()->user_email, get_current_user_id(), $product->id ) ) {
            echo '<div class="woocommerce"><div class="woocommerce-info wc-nonpurchasable-message">You\'ve already purchased this product! It can only be purchased once.</div></div>';
        }
    }
}
add_action( 'woocommerce_single_product_summary', 'sv_purchase_disabled_message', 31 );


/**
 * Generates a "purchase disabled" message to the customer for specific variations
 * 
 * @param \WC_Product $product the WooCommerce product
 * @param int $no_repeats_id the id of the non-purchasable product
 */
function sv_render_variation_non_purchasable_message( $product, $no_repeats_id ) {

    // Double-check we're looking at a variable product
    if ( $product->is_type( 'variable' ) && $product->has_child() ) {
        $variation_purchasable = true;
        foreach ( $product->get_available_variations() as $variation ) {
            // only show this message for non-purchasable variations matching our ID
            if ( $no_repeats_id === $variation['variation_id'] ) {
                $variation_purchasable = false; 
                echo '<div class="woocommerce"><div class="woocommerce-info wc-nonpurchasable-message js-variation-' . sanitize_html_class( $variation['variation_id'] ) . '">You\'ve already purchased this product! It can only be purchased once.</div></div>';
            }
        }
    }

    if ( ! $variation_purchasable ) {
        wc_enqueue_js("
            jQuery('.variations_form')
                .on( 'woocommerce_variation_select_change', function( event ) {
                    jQuery('.wc-nonpurchasable-message').hide();
                })
                .on( 'found_variation', function( event, variation ) {
                    jQuery('.wc-nonpurchasable-message').hide();
                    if ( ! variation.is_purchasable ) {
                        jQuery( '.wc-nonpurchasable-message.js-variation-' + variation.variation_id ).show();
                    }
                })
            .find( '.variations select' ).change();
        ");
    }
} 

This one seems to be most advanced, checking for the past purchase by customer ID and email. Using this one I get the 'error' message even if I log in as a user without any purchase history. How can I fix this to work?

Solution D

Maybe the simpliest one would be clearing the cart automatically after every login. Not sure if it won’t cause another issues though (e.g. being impossible to purchase a course for newcomers without an account). I also didn't find any snippets on this one.


Solution

  • To avoid re-buying the same product by one user in WooCommerce, first of all you can use the woocommerce_add_to_cart_validation filter hook

    function filter_woocommerce_add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = null, $variations = null ) {
        // Retrieve the current user object
        $current_user = wp_get_current_user();
        
        // Check for variantions, if you don't want this, delete this code line
        $product_id = $variation_id > 0 ? $variation_id : $product_id;
        
        // Checks if a user (by email or ID or both) has bought an item
        if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product_id ) ) {
            // Display an error message
            wc_add_notice( __( 'My custom error message', 'woocommerce' ), 'error' );
            
            $passed = false;
        }
    
        return $passed;
    }
    add_filter( 'woocommerce_add_to_cart_validation', 'filter_woocommerce_add_to_cart_validation', 10, 5 );
    

    However, this can be circumvented by adding products as a visitor to the cart and then logging in. To avoid this use the woocommerce_check_cart_items hook, which will prevent checkout.

    function action_woocommerce_check_cart_items() {
        // Retrieve the current user object
        $current_user = wp_get_current_user();
        
        // Initialize
        $flag = false;
        
        // Loop through cart items
        foreach( WC()->cart->get_cart() as $cart_item ) {
            // Check for variantions
            $product_id = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
            
            // Checks if a user (by email or ID or both) has bought an item
            if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product_id ) ) {
                // Flag becomes true
                $flag = true;
                
                // Break loop
                break;
            }
        }
        
        // True
        if ( $flag ) {
            // Clear all other notices          
            wc_clear_notices();
    
            // Avoid checkout display an error notice
            wc_add_notice( __( 'My custom error message', 'woocommerce' ), 'error' );
            
            // Optional: remove proceed to checkout button
            remove_action( 'woocommerce_proceed_to_checkout', 'woocommerce_button_proceed_to_checkout', 20 );   
        }
    }   
    add_action( 'woocommerce_check_cart_items' , 'action_woocommerce_check_cart_items', 10, 0 );