I'm using WooCommerce to restrict a coupon to the user role "gold-member" which works fine but I'm trying to change the message displayed when an invalid user tries to use the coupon:
Sorry, coupon code "goldmember10" is not valid with your customer role.
My code
add_filter( 'woocommerce_coupon_error','coupon_error_message_change',10,3 );
function coupon_error_message_change($err, $err_code, $parm ) {
switch ( $err_code ) {
case 100:
$err = sprintf( __( 'Sorry, coupon code "%s" is not valid.', 'woocommerce' ), $parm->get_code() );
break;
}
return $err;
}
$err_code matches 100 per class-wc-coupon.php but the message does not get updated.
This should suffice:
function filter_woocommerce_coupon_error( $err, $err_code, $coupon ) {
// Compare
if ( intval( $err_code ) === WC_COUPON::E_WC_COUPON_INVALID_FILTERED ) {
// Message
$err = sprintf( __( 'Sorry, coupon code "%s" is not valid.', 'woocommerce' ), $coupon->get_code() );
} else {
$err = 'DEBUG INFORMATION, error code number = ' . intval( $err_code );
}
return $err;
}
add_filter( 'woocommerce_coupon_error', 'filter_woocommerce_coupon_error', 10, 3 );
Related
How to send order notification to a business partner when a specific coupon is used?
I found a solution for the instance when the coupon is applied here :
Send an email notification when a specific coupon code is applied in WooCommerce
However, I need to find a solution for when after order is submitted, because the order is not always submitted after coupon is applied.
Each coupon will have its own email address.
First we add a setting field to admin Coupon pages, to set an email recipient for for a coupon:
// Add a custom field to Admin coupon settings pages
add_action( 'woocommerce_coupon_options', 'add_coupon_text_field', 10 );
function add_coupon_text_field() {
woocommerce_wp_text_input( array(
'id' => 'email_recipient',
'label' => __( 'Email recipient', 'woocommerce' ),
'placeholder' => '',
'description' => __( 'Send an email notification to a defined recipient' ),
'desc_tip' => true, // Or false
) );
}
// Save the custom field value from Admin coupon settings pages
add_action( 'woocommerce_coupon_options_save', 'save_coupon_text_field', 10, 2 );
function save_coupon_text_field( $post_id, $coupon ) {
if( isset( $_POST['email_recipient'] ) ) {
$coupon->update_meta_data( 'email_recipient', sanitize_text_field( $_POST['email_recipient'] ) );
$coupon->save();
}
}
Then email is sent for each applied coupon to a submitted order, if the email recipient has been set for the applied coupon.
Caution! choose only one of the following functions:
For woocommerce versions Up to 4.3 (new hook)
// For Woocommerce version 4.3+
add_action( 'woocommerce_checkout_order_created', 'custom_email_for_orders_with_applied_coupon' );
function custom_email_for_orders_with_applied_coupon( $order ){
$used_coupons = $order->get_used_coupons();
if( ! empty($used_coupons) ){
foreach ( $used_coupons as $coupon_code ) {
$coupon = new WC_Coupon( $coupon_code ); // WC_Coupon Object
$recipient = $coupon->get_meta('email_recipient'); // get recipient
if( ! empty($recipient) ) {
$subject = sprintf( __('Coupon "%s" has been applied'), $coupon_code );
$content = sprintf( __('The coupon code "%s" has been applied by a customer'), $coupon_code );
wp_mail( $recipient, $subject, $content ); // Send email
}
}
}
}
Or for all WooCommerce versions (since version 3.0)
// For all Woocommerce versions (since 3.0)
add_action( 'woocommerce_checkout_update_order_meta', 'custom_email_for_orders_with_applied_coupon' );
function custom_email_for_orders_with_applied_coupon( $order_id ){
$order = wc_get_order( $order_id );
$used_coupons = $order->get_used_coupons();
if( ! empty($used_coupons) ){
foreach ( $used_coupons as $coupon_code ) {
$coupon = new WC_Coupon( $coupon_code ); // WC_Coupon Object
$recipient = $coupon->get_meta('email_recipient'); // get recipient
if( ! empty($recipient) ) {
$subject = sprintf( __('Coupon "%s" has been applied'), $coupon_code );
$content = sprintf( __('The coupon code "%s" has been applied by a customer'), $coupon_code );
wp_mail( $recipient, $subject, $content ); // Send email
}
}
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
I need to display customer Bio in WooCommerce admin order edit pages after the billing address.
Actually I only succeeded to display in a column like that:
With this code:
// Adding a custom new column to admin orders list
add_filter( 'manage_edit-shop_order_columns', 'custom_column_eldest_players', 20 );
function custom_column_eldest_players($columns)
{
$reordered_columns = array();
// Inserting columns to a specific location
foreach( $columns as $key => $column){
$reordered_columns[$key] = $column;
if( $key == 'order_status' ){
// Inserting after "Status" column
$reordered_columns['user-bio'] = __( 'Note client', 'woocommerce');
}
}
return $reordered_columns;
}
// Adding custom fields meta data for the column
add_action( 'manage_shop_order_posts_custom_column' , 'custom_orders_list_column_content', 20, 2 );
function custom_orders_list_column_content( $column, $post_id ) {
if ( 'user-bio' === $column ) {
global $the_order;
echo ( $user = $the_order->get_user() ) ? $user->description : 'n/c';
}
}
But I don't know how to insert in WooCommerce admin order edit pages. Any advice?
Do display the user description on the admin order pages after billing adress you can use the woocommerce_admin_order_data_after_billing_address acton hook.
So you get:
// Display on admin order pages after billing adress
function action_woocommerce_admin_order_data_after_billing_address( $order ) {
// Get user
$user = $order->get_user();
// Initialize
$output = __( 'Bio: ', 'woocommerce' );
// Is a WP user
if ( is_a( $user, 'WP_User' ) ) {
! empty( $user->description ) ? $output .= $user->description : $output .= __( 'n/c', 'woocommerce' );
} else {
$output .= __( 'n/c', 'woocommerce' );
}
// Output
echo $output;
}
add_action( 'woocommerce_admin_order_data_after_billing_address', 'action_woocommerce_admin_order_data_after_billing_address', 10, 1 );
This has proven to be a challenge I can't figure out. I handled the renaming of "Coupon" to "Promo" pretty much everywhere else. However, the renaming of the WC LABEL that calls the name of the coupon and the LABEL is a lot harder.
Here's a screenshot of what I'm talking about exactly:
I've pretty much renamed all the uses of "coupon" to "promo" site-wide. This one is making my hair fall out. Has anyone ever successfully changed this? What am I missing?
Rename "Coupon" to "Promo" code below
// rename the coupon field on the cart page
function woocommerce_rename_coupon_field_on_cart( $translated_text, $text, $text_domain ) {
// bail if not modifying frontend woocommerce text
if ( is_admin() || 'woocommerce' !== $text_domain ) {
return $translated_text;
}
if ( 'Coupon code:' === $text ) {
$translated_text = 'Promo Code:';
}
return $translated_text;
}
add_filter( 'gettext', 'woocommerce_rename_coupon_field_on_cart', 10, 3 );
// rename the "Have a Coupon?" message on the checkout page
function woocommerce_rename_coupon_message_on_checkout() {
return 'Have a Customer Code?' . ' <a href="#" class="showcoupon">' . __(
'Click here to enter your code', 'woocommerce' ) . '</a>';
}
add_filter( 'woocommerce_checkout_coupon_message',
'woocommerce_rename_coupon_message_on_checkout' );
// rename the coupon field on the checkout page
function woocommerce_rename_coupon_field_on_checkout( $translated_text, $text, $text_domain ) {
// bail if not modifying frontend woocommerce text
if ( is_admin() || 'woocommerce' !== $text_domain ) {
return $translated_text;
}
if ( 'Coupon code' === $text ) {
$translated_text = 'Promo Code';
} elseif ( 'Apply coupon' === $text ) {
$translated_text = 'Apply Code';
}
return $translated_text;
}
add_filter( 'gettext', 'woocommerce_rename_coupon_field_on_checkout', 10, 3 );
WC Coupon Notices/Message code below
function woocommerce_coupon_message( $translated_text, $text, $domain ) {
switch ( $translated_text ) {
case 'Coupon' :
$translated_text = __( 'Promo', 'woocommerce' );
break;
case 'promo' :
$translated_text = __( 'promo', 'woocommerce' );
break;
case 'Please enter a coupon code.' :
$translated_text = __( 'Please enter a Promo code.', 'woocommerce' );
break;
case 'Coupon code already applied!' :
$translated_text = __( 'Promo code already applied!', 'woocommerce' );
break;
case 'Coupon has been removed.' :
$translated_text = __( 'Promo has been removed', 'woocommerce' );
break;
case 'Coupon code applied successfully.' :
$translated_text = __( 'Promo code applied successfully.', 'woocommerce' );
break;
}
return $translated_text;
}
add_filter( 'gettext', 'woocommerce_coupon_message', 20, 3 );
// Hide 'Coupon: CODE' in cart totals and instead return generic 'Promo Code Applied'
add_filter( 'woocommerce_cart_totals_coupon_label', 'woocommerce_change_coupon_label' );
function woocommerce_change_coupon_label() {
echo 'Promo Code Applied';
}
add_filter( 'woocommerce_cart_totals_coupon_label', 'woocommerce_change_coupon_label' );
function woocommerce_change_coupon_label($sprintf, $coupon) {
return 'Promo ' . $coupon->code;
}
I found a solutions for all needs.
Works with WooCommerce v7.
function custom_cart_totals_coupon_label($sprintf, $coupon)
{
$coupon_code = $coupon->get_code();
$sprintf = '<span class="discount_label">'.__('Coupon', 'veganwebagency').':</span> <span class="discount_code">'.$coupon_code.'</span>';
return $sprintf;
}
add_filter('woocommerce_cart_totals_coupon_label', 'custom_cart_totals_coupon_label', 10, 2);
I am using the following code to add new stock statuses in WooCommerce 4+
The new statuses are:
Preorder
Contact us
function add_custom_stock_type() {
?>
<script type="text/javascript">
jQuery(function(){
jQuery('._stock_status_field').not('.custom-stock-status').remove();
});
</script>
<?php
woocommerce_wp_select( array( 'id' => '_stock_status', 'wrapper_class' => 'custom-stock-status', 'label' => __( 'Stock status', 'woocommerce' ), 'options' => array(
'instock' => __( 'Available', 'woocommerce' ), //changed the name
'outofstock' => __( 'Sold out', 'woocommerce' ), //changed the name
'onbackorder' => __( 'Preorder : Pending Distributor release', 'woocommerce' ), //changed the name
'contact' => __( 'Contact us for Availability', 'woocommerce' ), //added new one
'preorder' => __( 'On Preorder: Pending Distributor release', 'woocommerce' ), //added new one
), 'desc_tip' => true, 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ) ) );
}
add_action('woocommerce_product_options_stock_status', 'add_custom_stock_type');
function save_custom_stock_status( $product_id ) {
update_post_meta( $product_id, '_stock_status', wc_clean( $_POST['_stock_status'] ) );
}
add_action('woocommerce_process_product_meta', 'save_custom_stock_status',99,1);
function woo_add_custom_general_fields_save_two( $post_id ){
// Select
$woocommerce_select = $_POST['_stock_status'];
if( !empty( $woocommerce_select ) )
update_post_meta( $post_id, '_stock_status', esc_attr( $woocommerce_select ) );
else
update_post_meta( $post_id, '_stock_status', '' );
}
function woocommerce_get_custom_availability( $data, $product ) {
switch( $product->stock_status ) {
case 'instock':
$data = array( 'availability' => __( 'Available', 'woocommerce' ), 'class' => 'in-stock' ); //changed name
break;
case 'outofstock':
$data = array( 'availability' => __( 'Sold Out', 'woocommerce' ), 'class' => 'out-of-stock' ); //changed name
break;
case 'onbackorder':
$data = array( 'availability' => __( 'Preorder : Pending Distributor release', 'woocommerce' ), 'class' => 'onbackorder' ); //changed name
break;
case 'contact':
$data = array( 'availability' => __( 'Contact us for Availability', 'woocommerce' ), 'class' => 'contact' ); //added new one
break;
case 'preorder':
$data = array( 'availability' => __( 'On Preorder : Pending Distributor release', 'woocommerce' ), 'class' => 'preorder' ); //added new one
break;
}
return $data;
}
add_action('woocommerce_get_availability', 'woocommerce_get_custom_availability', 10, 4);
Works:
Backend: The new status is added in the dropdown menu, I can select the status I want
Does not work:
Front end: on single product page is not showing the correct status
Backend: Display the new status on the admin products list table
Someone who can assist me with this?
Last update: 04/22 - Tested in WordPress 5.9.2 & WooCommerce 6.3.1
Code goes in functions.php file of the active child theme (or active theme).
Use woocommerce_product_stock_status_options
instead of woocommerce_product_options_stock_status.
This way you can immediately add a status instead of replace the existing dropdown
Also use woocommerce_get_availability_text & woocommerce_get_availability_class
opposite woocommerce_get_availability.
This way you don't have to add the existing statuses again
// Add new stock status options
function filter_woocommerce_product_stock_status_options( $status ) {
// Add new statuses
$status['pre_order'] = __( 'Pre order', 'woocommerce' );
$status['contact_us'] = __( 'Contact us', 'woocommerce' );
return $status;
}
add_filter( 'woocommerce_product_stock_status_options', 'filter_woocommerce_product_stock_status_options', 10, 1 );
// Availability text
function filter_woocommerce_get_availability_text( $availability, $product ) {
// Get stock status
switch( $product->get_stock_status() ) {
case 'pre_order':
$availability = __( 'Pre order', 'woocommerce' );
break;
case 'contact_us':
$availability = __( 'Contact us', 'woocommerce' );
break;
}
return $availability;
}
add_filter( 'woocommerce_get_availability_text', 'filter_woocommerce_get_availability_text', 10, 2 );
// Availability CSS class
function filter_woocommerce_get_availability_class( $class, $product ) {
// Get stock status
switch( $product->get_stock_status() ) {
case 'pre_order':
$class = 'pre-order';
break;
case 'contact_us':
$class = 'contact-us';
break;
}
return $class;
}
add_filter( 'woocommerce_get_availability_class', 'filter_woocommerce_get_availability_class', 10, 2 );
Use woocommerce_admin_stock_html to display the new stock status on the admin products list table
// Admin stock html
function filter_woocommerce_admin_stock_html( $stock_html, $product ) {
// Simple
if ( $product->is_type( 'simple' ) ) {
// Get stock status
$product_stock_status = $product->get_stock_status();
// Variable
} elseif ( $product->is_type( 'variable' ) ) {
foreach( $product->get_visible_children() as $variation_id ) {
// Get product
$variation = wc_get_product( $variation_id );
// Get stock status
$product_stock_status = $variation->get_stock_status();
/*
Currently the status of the last variant in the loop will be displayed.
So from here you need to add your own logic, depending on what you expect from your custom stock status.
By default, for the existing statuses. The status displayed on the admin products list table for variable products is determined as:
- Product should be in stock if a child is in stock.
- Product should be out of stock if all children are out of stock.
- Product should be on backorder if all children are on backorder.
- Product should be on backorder if at least one child is on backorder and the rest are out of stock.
*/
}
}
// Stock status
switch( $product_stock_status ) {
case 'pre_order':
$stock_html = '<mark class="pre-order" style="background:transparent none;color:#33ccff;font-weight:700;line-height:1;">' . __( 'Pre order', 'woocommerce' ) . '</mark>';
break;
case 'contact_us':
$stock_html = '<mark class="contact-us" style="background:transparent none;color:#cc33ff;font-weight:700;line-height:1;">' . __( 'Contact us', 'woocommerce' ) . '</mark>';
break;
}
return $stock_html;
}
add_filter( 'woocommerce_admin_stock_html', 'filter_woocommerce_admin_stock_html', 10, 2 );
Optional: if desired, the custom stock status can be used in hooks where you already have access to the $product object or you can use global $product.
1) No access to the $product object, use global $product as is the case for example with the woocommerce_shop_loop_item_title or the woocommerce_single_product_summary hook
function woocommerce_my_callback() {
// An example based on global $product
// Get the global product object
global $product;
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// Get stock status
$product_stock_status = $product->get_stock_status();
// Use this line during testing, delete afterwards!
echo '<p style="color:red;font-size:20px;">DEBUG INFORMATION = ' . $product_stock_status . '</p>';
// Compare
if ( $product_stock_status == 'My custom stock status' ) {
// Etc..
}
}
}
add_action( 'woocommerce_shop_loop_item_title', 'woocommerce_my_callback', 10 );
add_action( 'woocommerce_single_product_summary', 'woocommerce_my_callback', 10 );
2) Access to the $product object, because it's passed by default to the callback function. As is the case for example with the woocommerce_get_price_html hook
function filter_woocommerce_get_price_html( $price, $product ) {
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// Get stock status
$product_stock_status = $product->get_stock_status();
// Use this line during testing, delete afterwards!
echo '<p style="color:red;font-size:20px;">DEBUG INFORMATION = ' . $product_stock_status . '</p>';
// Compare
if ( $product_stock_status == 'My custom stock status' ) {
// Etc..
// $price .= ' my text';
}
}
return $price;
}
add_filter( 'woocommerce_get_price_html', 'filter_woocommerce_get_price_html', 10, 2 );
In addition to filters provided by 7uc1f3r woocommerce_product_export_product_column_stock_status filter is required to display custom stock status in exported products CSV file:
function add_custom_stock_csv_data( $_, $product ) {
$status = $product->get_stock_status( 'edit' );
switch( $status ) {
case 'pre_order':
case 'contact_us':
return $status;
case 'onbackorder':
return 'backorder';
case 'instock':
return 1;
default:
return 0;
}
}
add_filter( 'woocommerce_product_export_product_column_stock_status', 'add_custom_stock_csv_data', 10, 2 );
What Woocommerce Does is...
When products are Sold Individually, and when the product already exists in the Cart and customer clicks on Add to Cart, Woocommerce shows Error Message "You cannot add another to your cart.....View Cart"
Instead of the above flow I want..
When customer clicks on Add to Cart and if the product already exists in the Cart then woocommerce should redirect to Checkout page straightway.
I think this can be achieved by editing few lines of code in class-wc-cart.php of Woocommerce Plugin.
The is given below:
// Force quantity to 1 if sold individually and check for existing item in cart
if ( $product_data->is_sold_individually() ) {
$quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
$in_cart_quantity = $cart_item_key ? $this->cart_contents[ $cart_item_key ]['quantity'] : 0;
if ( $in_cart_quantity > 0 ) {
throw new Exception( sprintf( '%s %s', wc_get_cart_url(), __( 'View Cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_title() ) ) );
}
}
I was just looking into how to do this myself. user1072884's answer is okay, however, it only works for one productID at a time. Not ideal if you have multiple products.
Here's a simplified solution that will work for all products.
Simply add the following code to your child theme functions.php and swap the placeholder text with your checkout page URL.
/** If multiple items are in the cart redirect to the checkout page instead of throwing an error **/
add_filter( 'woocommerce_add_to_cart_validation', 'check_cart' );
function check_cart( $cart_item_data ) {
global $woocommerce;
if ( $woocommerce->cart->cart_contents_count > 0 ) {
$direct_url = home_url( 'xyz' ); // Change the value of your actual redirect url.
wp_redirect( $direct_url );
exit();
}
}
Old thread, but maybe someone else will find this helpful.
Try this it will work:
add_filter( 'woocommerce_add_to_cart_sold_individually_quantity', 'vipcomment_change_quantity_to_zero', 10, 5 );
function vipcomment_change_quantity_to_zero( $one, $quantity, $product_id, $variation_id, $cart_item_data ) {
$your_product_id = 96;
if ( $product_id == $your_product_id ) {
$product_cart_id = WC()->cart->generate_cart_id( $product_id );
$in_cart = WC()->cart->find_product_in_cart( $product_cart_id );
if ( $in_cart ) {
return 0;
} else {
return $quantity;
}
} else {
return $quantity;
}
}
add_filter( 'woocommerce_add_to_cart_sold_individually_found_in_cart', 'vipcomment_is_product_exist_in_cart', 10, 5 );
function vipcomment_is_product_exist_in_cart( $exist, $product_id, $variation_id, $cart_item_data, $cart_id ) {
$your_product_id = 96;
if ( $product_id == $your_product_id ) {
return false;
} else {
return $exist;
}
}
Simplest way would be:
throw new Exception( sprintf( '%s %s',
wc_get_cart_url(), __( 'View cart', 'woocommerce' ),
header( "Location: https://www.example.com/cart/" ) ) );
instead of that error. Be advised: this will redirect the user to the cart page ( where the product should already be in place ).