Search code examples
phpwordpresswoocommerceproductcategories

Allow customers to buy only one product from defined WooCommerce product category


I want to let customers buy only one product from a defined category. All products are priced as 0 (zero). Only delivery fee is charged.

I referred to the same scenario and tried this code. But this code fails for not logged in users. When I try multiple purchases I have no matter of buying. Want to limit after the first successful attempt of a purchase. I am using the child theme functions.php file.

Note: customers do not need to register in the website before doing a purchase.

add_filter('woocommerce_add_to_cart_validation','filter_add_to_cart_validation',20, 2);
function filter_add_to_cart_validation($valid, $product_id){
    $current_user = wp_get_current_user();
    if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product_id) && has_term( array('free-giveaway'), 'product_cat', $product_id ) ) {
        wc_add_notice( __( 'You already bought an item. Let others to buy as well.', 'woocommerce' ), 'error' );
        $valid = false;
    }
    return $valid;
}

Solution

  • Explanation/comments related to the answer:

    • The has_term() WordPress function is used to check the product category
    • It is better to only apply these kinds of checks (has bought before) for logged in users, since usually no data is known from customers. However, it is possible that you can obtain the e-mail address via WC_Customer. (this will not always suffice, hence my comment to only apply this to logged in users)
    • Note: the has_bought() function is copied and pasted from Checking if customer has already bought something in WooCommerce answer code

    So you get:

    function filter_woocommerce_add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = null, $variations = null ) {
        // Set (multiple) categories
        $categories = array ( 'free-giveaway', 'categorie-1' );
        
        // If passed & has category
        if ( $passed && has_term( $categories, 'product_cat', $product_id ) ) {
            // Initialize
            $value = '';
    
            // User logged in
            if ( is_user_logged_in() ) {
                // Get the current user's ID 
                $value = get_current_user_id();
            } else {
                // Get billing_email
                $value = WC()->customer->get_billing_email();
    
                // When empty
                if ( empty ( $value ) ) {
                    // Get account email
                    $value = WC()->customer->get_email();   
                }
            }
    
            // NOT empty
            if ( ! empty ( $value ) ) {
                if ( has_bought( $value ) ) {
                    // Display an error message
                    wc_add_notice( __( 'My custom error message', 'woocommerce' ), 'error' );
    
                    // False
                    $passed = false;
                }
            }       
        }
    
        return $passed;
    }
    add_filter( 'woocommerce_add_to_cart_validation', 'filter_woocommerce_add_to_cart_validation', 10, 5 );
    
    // Based partially on wc_customer_bought_product(), will return a boolean value based on orders count (false for O orders and true when there is at least one paid order)
    function has_bought( $value = 0 ) {
        if ( ! is_user_logged_in() && $value === 0 ) {
            return false;
        }
    
        global $wpdb;
        
        // Based on user ID (registered users)
        if ( is_numeric( $value ) ) { 
            $meta_key   = '_customer_user';
            $meta_value = $value == 0 ? (int) get_current_user_id() : (int) $value;
        } 
        // Based on billing email (Guest users)
        else { 
            $meta_key   = '_billing_email';
            $meta_value = sanitize_email( $value );
        }
        
        $paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
    
        $count = $wpdb->get_var( $wpdb->prepare("
            SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
            INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
            WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
            AND p.post_type LIKE 'shop_order'
            AND pm.meta_key = '%s'
            AND pm.meta_value = %s
            LIMIT 1
        ", $meta_key, $meta_value ) );
    
        // Return a boolean value based on orders count
        return $count > 0;
    }