I'm looking for a way to programmatically add a switch between two Woocommerce subscription variations to the cart.
We're building a headless WP site, so I don't want to do it with a link, as described in this question
I've tried the following simple way, but it won't allow it that way, since the user is already subscribed:
WC()->cart->add_to_cart( 1907, 1, 1908 );
I want to mimic what is happening when the user goes to the upgrade/downgrade page, selects a new variation and presses Switch subscription. Usually that means that the user is sent to checkout with the switch in cart, I just want it to be added to the cart, since we have our own checkout.
The main issue is that Woocommerce thinks that a subscription product is unpurchaseable when it is added to the cart any other way than through the subscription switch form. The form sends a couple of GET-parameters which sends WC through some extra validation, and then allows the item to be added to the cart if everything else is valid.
My solution to this is to mimic this validation with some functions of my own, mostly stolen from the subscription plugin.
/**
* Must be set to true before programmatically adding subscription switch to cart.
*
* #var bool
*/
$kbhl_adding_switch_to_cart = false;
/**
* Array filled with data about current subscription.
* Should only be retrieved by kbhl_get_current_subscription_data().
*
* #var array
*/
$kbhl_global_current_subscription_data = array();
/**
* Cache whether a given product is purchasable or not to save running lots of queries for the same product in the same request
*
* $is_purchasable_cache taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #var array
*/
$kbhl_purchasable_cache = array();
/**
* $user_subscriptions_to_product taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #var array
*/
$kbhl_user_subscriptions_to_product = array();
/**
* If a product is being marked as not purchasable because it is limited and the customer has a subscription,
* but the current request is to switch the subscription, then mark it as purchasable.
*
* Function is_purchasable_switch() taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #param bool $is_purchasable Current purchasable status.
* #param obj $product Product being checked.
*
* #return bool New purchasable status.
*/
function kbhl_is_purchasable_switch( $is_purchasable, $product ) {
global $kbhl_purchasable_cache;
$kbhl_current_subscription_data = kbhl_get_current_subscription_data();
// Only process this filter if running custom add switch function.
if ( ! empty( $kbhl_current_subscription_data['id'] ) ) {
return $is_purchasable;
}
$product_key = wcs_get_canonical_product_id( $product );
// Set an empty cache if one isn't set yet.
if ( ! isset( $kbhl_purchasable_cache[ $product_key ] ) ) {
$kbhl_purchasable_cache[ $product_key ] = array();
}
// Exit early if we've already determined this product's purchasability via switching.
if ( isset( $kbhl_purchasable_cache[ $product_key ]['switch'] ) ) {
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
// If the product is already purchasble, we don't need to determine it's purchasibility via switching/auto-switching.
if ( true === $is_purchasable || ! is_user_logged_in() || ! wcs_is_product_switchable_type( $product ) || ! WC_Subscriptions_Product::is_subscription( $product->get_id() ) ) {
$kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
$user_id = get_current_user_id();
$product_limitation = wcs_get_product_limitation( $product );
if ( 'no' == $product_limitation || ! wcs_user_has_subscription( $user_id, $product->get_id(), wcs_get_product_limitation( $product ) ) ) {
$kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
// Adding to cart.
if ( array_key_exists( $kbhl_current_subscription_data['id'], kbhl_get_user_subscriptions_to_product( $product, $user_id, $product_limitation ) ) ) {
$is_purchasable = true;
}
$kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
add_filter( 'woocommerce_subscription_is_purchasable', 'kbhl_is_purchasable_switch', 13, 2 );
/**
* Gets a list of the customer subscriptions to a product with a particular limited status.
*
* Function get_user_subscriptions_to_product() taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #param WC_Product|int $product The product object or product ID.
* #param int $user_id The user's ID.
* #param string $limit_status The limit status.
*
* #return WC_Subscription[] An array of a customer's subscriptions with a specific status and product.
*/
function kbhl_get_user_subscriptions_to_product( $product, $user_id, $limit_status ) {
global $user_subscriptions_to_product;
$product_id = is_object( $product ) ? $product->get_id() : $product;
$cache_key = "{$product_id}_{$user_id}_{$limit_status}";
if ( ! isset( $user_subscriptions_to_product[ $cache_key ] ) ) {
// Getting all the customers subscriptions and removing ones without the product is more performant than querying for subscriptions with the product.
$subscriptions = wcs_get_subscriptions(
array(
'customer_id' => $user_id,
'status' => $limit_status,
)
);
foreach ( $subscriptions as $subscription_id => $subscription ) {
if ( ! $subscription->has_product( $product_id ) ) {
unset( $subscriptions[ $subscription_id ] );
}
}
$user_subscriptions_to_product[ $cache_key ] = $subscriptions;
}
return $user_subscriptions_to_product[ $cache_key ];
}
/**
* When a subscription switch is added to the cart, store a record of pertinent meta about the switch.
*
* #since 1.4
*/
/**
* When a subscription switch is added to the cart, store a record of pertinent meta about the switch.
*
* Function set_switch_details_in_cart() taken from plugins\woocommerce-subscriptions\includes\class-wc-subscriptions-switcher.php
*
* #param array $cart_item_data Current cart item data.
* #param int $product_id ID of current product.
* #param int $variation_id ID of current product variation.
*
* #return array Updated cart item data.
*/
function kbhl_set_switch_details_in_cart( $cart_item_data, $product_id, $variation_id ) {
try {
$kbhl_current_subscription_data = kbhl_get_current_subscription_data();
if ( empty( $kbhl_current_subscription_data['id'] ) || empty( $kbhl_current_subscription_data['item'] ) ) {
return $cart_item_data;
}
$subscription = wcs_get_subscription( $kbhl_current_subscription_data['id'] );
// Requesting a switch for someone elses subscription.
if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) {
wc_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' );
WC()->cart->empty_cart( true );
return array();
}
$item = wcs_get_order_item( absint( $kbhl_current_subscription_data['item'] ), $subscription );
// Else it's a valid switch.
$product = wc_get_product( $item['product_id'] );
$parent_products = WC_Subscriptions_Product::get_parent_ids( $product );
$child_products = array();
if ( ! empty( $parent_products ) ) {
foreach ( $parent_products as $parent_id ) {
$child_products = array_unique( array_merge( $child_products, wc_get_product( $parent_id )->get_children() ) );
}
}
if ( $product_id != $item['product_id'] && ! in_array( $item['product_id'], $child_products ) ) {
return $cart_item_data;
}
$next_payment_timestamp = $subscription->get_time( 'next_payment' );
// If there are no more payments due on the subscription, because we're in the last billing period, we need to use the subscription's expiration date, not next payment date.
if ( false == $next_payment_timestamp ) {
$next_payment_timestamp = $subscription->get_time( 'end' );
}
$cart_item_data['subscription_switch'] = array(
'subscription_id' => $subscription->get_id(),
'item_id' => absint( $kbhl_current_subscription_data['item'] ),
'next_payment_timestamp' => $next_payment_timestamp,
'upgraded_or_downgraded' => '',
);
return $cart_item_data;
} catch ( Exception $e ) {
wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' );
WC()->cart->empty_cart( true );
return array();
}
}
add_filter( 'woocommerce_add_cart_item_data', 'kbhl_set_switch_details_in_cart', 11, 3 );
/**
* Gets subscription data for current user.
*
* #return array Subscription data, also stored to global variable $kbhl_global_current_subscription_data
*/
function kbhl_get_current_subscription_data() {
global $kbhl_adding_switch_to_cart, $kbhl_global_current_subscription_data;
if ( ! $kbhl_adding_switch_to_cart ) {
return array();
}
if ( ! empty( $kbhl_global_current_subscription_data ) ) {
return $kbhl_global_current_subscription_data;
}
$subscription_data = array();
$subs = wcs_get_users_subscriptions();
if ( ! empty( $subs ) ) {
foreach ( $subs as $sub ) {
$subscription_data['id'] = $sub->get_id();
foreach ( $sub->get_items() as $item_id => $item ) {
$subscription_data['item'] = $item_id;
break; // There should only be 1 order item.
}
break; // There should only be 1 subscription.
}
}
$kbhl_global_current_subscription_data = $subscription_data;
return $kbhl_global_current_subscription_data;
}
With this added you can add the switch to the cart as follows:
global $kbhl_adding_switch_to_cart; // If run inside a function.
WC()->cart->empty_cart( true );
$kbhl_adding_switch_to_cart = true;
WC()->cart->add_to_cart( 1907, 1, 1927 );
$kbhl_adding_switch_to_cart = false; // Reset after to get back to default validation.
Related
Can't seem to figure out the PHP code to add a new column on the woocommcerce orders admin page to show the customers user role?
Thanks for your time
Darren
Strugglinjg to get any PHP snippets ive found to work
Here is the code snippet you can add to your functions.php:
/**
* Add custom column.
*
* #param array $columns Columns.
* #return array
*/
function set_custom_edit_shop_order_columns( $columns ) {
$columns['user_role'] = __( 'Role', 'your-text-domain' );
return $columns;
}
add_filter( 'manage_shop_order_posts_columns', 'set_custom_edit_shop_order_columns', 100, 1 );
/**
* Add data to custom column.
*
* #param string $column Column slug.
* #param id $post_id Post ID.
*/
function custom_shop_order_column( $column, $post_id ) {
if ( 'user_role' === $column ) {
$order_user = get_post_meta( $post_id, '_customer_user', true );
if ( ! empty( $order_user ) ) {
$user = get_user_by( 'ID', $order_user );
if ( ! empty( $user ) ) {
echo implode( ', ', ( array ) $user->roles );
}
}
}
}
add_action( 'manage_shop_order_posts_custom_column', 'custom_shop_order_column', 100, 2 );
This will only work if you have not opted in for High-Performance Order Storage (HPOS), otherwise you may have to tweak custom_shop_order_column() method to retrieve data from new custom order meta table.
first of all thx to everyone who put answers here and to all the staff of stackoverflow.
I write a class(copy paste and mod) of WC_duplicate_product off woocommerce in order to put a meta on duplicates and to generate a slug wit base as "https://website.com/slug-base/product-name", but I cant acomplish the part of slug-base. If anybody can help me i'll appreciate.
This is my code, feel free to use and mod:
/**
* Function to create the duplicate of the product.
*
* #param WC_Product $product The product to duplicate.
* #return WC_Product The duplicate.
*/
public function product_duplicate( $product ) {
/**
* Filter to allow us to exclude meta keys from product duplication..
*
* #param array $exclude_meta The keys to exclude from the duplicate.
* #param array $existing_meta_keys The meta keys that the product already has.
* #since 2.6
*/
$meta_to_exclude = array_filter(
apply_filters(
'woocommerce_duplicate_product_exclude_meta',
array(),
array_map(
function ( $datum ) {
return $datum->key;
},
$product->get_meta_data()
)
)
);
$duplicate = clone $product;
$duplicate->set_id( 0 );
$char = 47;
/* translators: %s contains the name of the original product. */
$duplicate->set_name( sprintf( '%s', $duplicate->get_name()) );
$duplicate->set_total_sales( 0 );
if ( '' !== $product->get_sku( 'edit' ) ) {
$duplicate->set_sku( wc_product_generate_unique_sku( 0, $product->get_sku( 'edit' ) ) );
}
$duplicate->set_status( 'draft' );
$duplicate->set_date_created( null );
$duplicate->set_slug( 'vendor'.sprintf( '%c', $char).'' );
$duplicate->set_rating_counts( 0 );
$duplicate->set_average_rating( 0 );
$duplicate->set_review_count( 0 );
$duplicate->add_meta_data( '_is_clone', 'true' );
foreach ( $meta_to_exclude as $meta_key ) {
$duplicate->delete_meta_data( $meta_key );
}
/**
* This action can be used to modify the object further before it is created - it will be passed by reference.
*
* #since 3.0
*/
do_action( 'woocommerce_product_duplicate_before_save', $duplicate, $product );
// Save parent product.
$duplicate->save();
// Duplicate children of a variable product.
if ( ! apply_filters( 'woocommerce_duplicate_product_exclude_children', false, $product ) && $product->is_type( 'variable' ) ) {
foreach ( $product->get_children() as $child_id ) {
$child = wc_get_product( $child_id );
$child_duplicate = clone $child;
$child_duplicate->set_parent_id( $duplicate->get_id() );
$child_duplicate->set_id( 0 );
$child_duplicate->set_date_created( null );
// If we wait and let the insertion generate the slug, we will see extreme performance degradation
// in the case where a product is used as a template. Every time the template is duplicated, each
// variation will query every consecutive slug until it finds an empty one. To avoid this, we can
// optimize the generation ourselves, avoiding the issue altogether.
$this->generate_unique_slug( $child_duplicate );
if ( '' !== $child->get_sku( 'edit' ) ) {
$child_duplicate->set_sku( wc_product_generate_unique_sku( 0, $child->get_sku( 'edit' ) ) );
}
foreach ( $meta_to_exclude as $meta_key ) {
$child_duplicate->delete_meta_data( $meta_key );
}
/**
* This action can be used to modify the object further before it is created - it will be passed by reference.
*
* #since 3.0
*/
do_action( 'woocommerce_product_duplicate_before_save', $child_duplicate, $child );
$child_duplicate->save();
}
// Get new object to reflect new children.
$duplicate = wc_get_product( $duplicate->get_id() );
}
return $duplicate;
}
this is teh part of code that I need to polish to get the desired slug, so when i run this clas, always obtain https://website.com/slug-baseproduct-name
Maybe someone could give me a hand resolving this challenge.
I was looking for a solution which would allow me to add products to cart by SKU's instead of WooCommerce generated ID's as I would like to use the same products across different CMS systems.
I have stumbled upon the below code, but it seems not to be compatible anymore? Any advice?
<?php
/**
* Plugin Name: WooCommerce: Add Product to Cart by SKU
* Plugin URI: http://remicorson.com
* Description: Just a demo!
* Version: 1.0
* Author: Remi Corson
* Author URI: http://remicorson.com/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WC Product Add to Cart by SKU class
*/
class WC_Add_to_Cart_by_SKU {
/**
* Constructor
*/
public function __construct() {
define( 'WC_ADD_TO_CART_BY_SKU_VERSION', '1.0' );
define( 'WC_ADD_TO_CART_BY_SKU_PATH', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
define( 'WC_ADD_TO_CART_BY_SKU_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
}
/**
* get_product_id_by_product_sku()
*
* Return product ID from product SKU
*/
public function get_product_id_by_product_sku( $add_to_cart ) {
global $wpdb;
$product_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_sku' AND meta_value='%s' LIMIT 1", $add_to_cart ) );
if ( $product_id ) return $product_id;
return $add_to_cart;
}
}
add_filter( 'woocommerce_add_to_cart_product_id', array( new WC_Add_to_Cart_by_SKU(), 'get_product_id_by_product_sku' ) );
Source: https://gist.github.com/corsonr/c02b46bd34a8471327bbf3adee6507c8
There is no need to use a custom SQL query, as you can use the wc_get_product_id_by_sku() WooCommerce function.
Using this function is much more lighter and effective but also takes into account products that are not in trash. Something your current code doesn't do.
So, this snippet will suffice:
function filter_woocommerce_add_to_cart_product_id( $product_id ) {
// Retrieves the post type of the current post or of a given post
if ( get_post_type( $product_id ) === 'product' ) {
return $product_id;
} else {
$sku = $product_id;
}
// Get product ID by SKU
$product_id = wc_get_product_id_by_sku( $sku );
return $product_id;
}
add_filter( 'woocommerce_add_to_cart_product_id', 'filter_woocommerce_add_to_cart_product_id', 10, 1 );
Note: SKU is assumed to be a numerical value
As you can read in the note, the above answer will only work for numerical values, to make this work for all SKU values, you can use as custom query string = /?add-to-cart-sku=THE-SKU
So you get:
function action_wp_loaded( $url = false ) {
// Make sure WC is installed and add-to-cart-sku query arg exists
if ( ! class_exists( 'WC_Form_Handler' ) || ! isset( $_REQUEST['add-to-cart-sku'] ) || ! is_string( $_REQUEST['add-to-cart-sku'] ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
wc_nocache_headers();
$product_id = wc_get_product_id_by_sku( wp_unslash( $_REQUEST['add-to-cart-sku'] ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
if ( 'variable' === $add_to_cart_handler || 'variation' === $add_to_cart_handler ) {
$was_added_to_cart = woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_variable', $product_id );
} elseif ( 'grouped' === $add_to_cart_handler ) {
$was_added_to_cart = woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_grouped', $product_id );
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ) {
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url ); // Custom handler.
} else {
$was_added_to_cart = woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_simple', $product_id );
}
// If we added the product to the cart we can now optionally do a redirect.
if ( $was_added_to_cart && 0 === wc_notice_count( 'error' ) ) {
$url = apply_filters( 'woocommerce_add_to_cart_redirect', $url, $adding_to_cart );
if ( $url ) {
wp_safe_redirect( $url );
exit;
} elseif ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
wp_safe_redirect( wc_get_cart_url() );
exit;
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'action_wp_loaded', 15 );
/**
* Invoke class private method
*
* #since 0.1.0
*
* #param string $class_name
* #param string $methodName
*
* #return mixed
*/
function woo_hack_invoke_private_method( $class_name, $methodName ) {
if ( version_compare( phpversion(), '5.3', '<' ) ) {
throw new Exception( 'PHP version does not support ReflectionClass::setAccessible()' );
}
$args = func_get_args();
unset( $args[0], $args[1] );
$reflection = new ReflectionClass( $class_name );
$method = $reflection->getMethod( $methodName );
$method->setAccessible( true );
$args = array_merge( array( $reflection ), $args );
return call_user_func_array( array( $method, 'invoke' ), $args );
}
Based on: Allow adding multiple products to the cart via the add-to-cart query string & /includes/class-wc-form-handler.php
please try this one, i have include the filter call inside __constructor:
<?php
/**
* Plugin Name: WooCommerce: Add Product to Cart by SKU
* Plugin URI: http://remicorson.com
* Description: Just a demo!
* Version: 1.0
* Author: Remi Corson
* Author URI: http://remicorson.com/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WC Product Add to Cart by SKU class
*/
class WC_Add_to_Cart_by_SKU {
/**
* Constructor
*/
public function __construct() {
define( 'WC_ADD_TO_CART_BY_SKU_VERSION', '1.0' );
define( 'WC_ADD_TO_CART_BY_SKU_PATH', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
define( 'WC_ADD_TO_CART_BY_SKU_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
add_filter( 'woocommerce_add_to_cart_product_id', array(
$this,
'get_product_id_by_product_sku'
) );
}
/**
* get_product_id_by_product_sku()
*
* Return product ID from product SKU
*/
public function get_product_id_by_product_sku( $add_to_cart ) {
global $wpdb;
$product_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_sku' AND meta_value='%s' LIMIT 1", $add_to_cart ) );
if ( $product_id ) {
return $product_id;
}
return $add_to_cart;
}
}
new WC_Add_to_Cart_by_SKU();
I am creating an LMS using LearnDash + WooCommerce. So far so good. Now, I'd like to hide a Product when the logged-in user has already purchased it to a) prevent the user from purchasing it twice, and b) so that I can display a product grid for them without the already purchased item(s).
Another example: if a user who purchased ITEM A goes to the shop, ITEM A should not even be displayed for them.
Thanks so much!
So, I ended up with a nice workaround:
On the product page, I created a CUSTOM FIELD where I can input the COURSE ID.
Then, with PHP, I check the Product and retrieve the custom field for the COURSE ID.
LearnDash has a shortcode for students... so after retrieving the course ID, I can display a message/button for users who have registered already for this course.
This is the code:
<?php
global $product;
$postid = $product->get_id();
$courseid = get_post_meta( $product->get_id(), 'ID_del_curso', true );
$html = '"]<center><div class="alreadyPurchased"><i class="fas fa-graduation-cap paddingRightM"></i> Ya te has registrado</div></center>';
$openIT = '[student course_id="';
$closeIT = '[/student]';
echo do_shortcode( $openIT . $courseid . $html . $closeIT );
?>
Hope it helps!
There is a simpler solution.
Use this LearnDash shortcode...
[visitor]
Hello visitor.
Put your "Buy now" button here.
[/visitor]
/**
* 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->get_id();
// return false if the customer has bought the product / variation
if ( wc_customer_bought_product( wp_get_current_user()->user_email, get_current_user_id(), $product_id ) ) {
$purchasable = false;
}
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() {
global $product; // get the current product to see if it has been purchased
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( wp_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( wp_get_current_user()->user_email, get_current_user_id(), $product->get_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();
");
}
}
Gist snippet got from here - please check
I added an override function of the extended class of WC_Gateway_BACS. The function will update the status of the order from on-hold to processing. The problem is the email is missing the bank details now. Before I have the bank account number included on the email but after the customization, the email does not include it and I think it is because the order status is now processing.
Has anyone here did the same thing and come up with a solution? I included some images here of on-hold and processing emails. I like to add the account number to processing-email
class WC_Gateway_BACS_custom extends WC_Gateway_BACS {
/**
* Process the payment and return the result
*
* #access public
* #param int $order_id
* #return array
*/
function process_payment( $order_id ) {
global $woocommerce;
$order = new WC_Order( $order_id );
// Mark as processing (or anything you want)
$order->update_status('processing', __( 'Awaiting BACS payment', 'woocommerce' ));
// Reduce stock levels
$order->reduce_order_stock();
// Remove cart
$woocommerce->cart->empty_cart();
// Return thankyou redirect
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
}
/**
* Add content to the WC emails.
*
* #param WC_Order $order
* #param bool $sent_to_admin
* #param bool $plain_text
*/
// public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
// if ( ! $sent_to_admin && 'bacs' === $order->payment_method && ($order->has_status( 'on-hold' ) || $order->has_status( 'processing' )) ) {
// if ( $this->instructions ) {
// echo wpautop( wptexturize( $this->instructions ) ) . PHP_EOL;
// }
// $this->bank_details( $order->id );
// }
// }
}
on-hold email
processing email
I actually ran into the same issue today. I came up with two possible solutions:
Extending the class, as you did above:
/* override gateway for BACS */
function my_core_gateways($methods)
{
foreach ($methods as &$method){
if($method == 'WC_Gateway_BACS')
{
$method = 'WC_Gateway_BACS_custom';
}
}
return $methods;
}
/* custom gateway processor for BACS */
class WC_Gateway_BACS_custom extends WC_Gateway_BACS
{
/**
* Add content to the WC emails.
*
* #param WC_Order $order
* #param bool $sent_to_admin
* #param bool $plain_text
*/
public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
if ( ! $sent_to_admin && 'bacs' === $order->payment_method && $order->has_status( 'processing' ) ) {
if ( $this->instructions ) {
echo wpautop( wptexturize( $this->instructions ) ) . PHP_EOL;
}
/* dirty hack to get access to bank_details */
$reflector = new ReflectionObject($this);
$method = $reflector->getMethod('bank_details');
$method->setAccessible(true);
$result = $method->invoke($this, $order->id);
}
}
/**
* Process the payment and return the result.
*
* #param int $order_id
* #return array
*/
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
// Mark as on-hold (we're awaiting the payment)
$order->update_status( 'processing', __( 'Awaiting BACS payment', 'woocommerce' ) );
// Reduce stock levels
$order->reduce_order_stock();
// Remove cart
WC()->cart->empty_cart();
// Return thankyou redirect
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
}
}
Or, cleaner in my opinion, by adding two actions
add_action( 'woocommerce_email_before_order_table', 'add_order_email_instructions', 10, 2 );
add_action( 'woocommerce_thankyou', 'bacs_order_payment_processing_order_status', 10, 1 );
function bacs_order_payment_processing_order_status( $order_id )
{
if ( ! $order_id ) {
return;
}
$order = new WC_Order( $order_id );
if ('bacs' === $order->payment_method && ('on-hold' == $order->status || 'pending' == $order->status)) {
$order->update_status('processing');
} else {
return;
}
}
function add_order_email_instructions( $order, $sent_to_admin ) {
if ( ! $sent_to_admin && 'bacs' === $order->payment_method && $order->has_status( 'processing' ) ) {
$gw = new WC_Gateway_BACS();
$reflector = new ReflectionObject($gw);
$method = $reflector->getMethod('bank_details');
$method->setAccessible(true);
$result = $method->invoke($gw, $order->id);
}
}
The second solution has the least maintenance requirements in the long run.