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.
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 );