How to send "Customer Invoice" emails with Pay Later (pending status) gateway - woocommerce

I have created a new payment gateway plugin that allows customers to pay later. I will be using it with a woocommerce POS system.
I cannot get the 'customer invoice' email to send automatically - I believe it has something to do with the "pending" status of the order (which it must have in order to allow the customer to pay later). I have analysed many other answers but none have worked for me.
I have gently modified the code from this link (only - changed names and set up as plugin): https://github.com/creativelittledots/woocommerce-pay-later.
It uses a series of functions to trigger the email that I don't fully understand - but it doesn't seem to work: The default status is set to "on- hold" to allow the emails to trigger then the order is set to "pending" to allow the customer to pay for the order.
Here are the relevant snippets:
add_filter( 'woocommerce_default_order_status', array($this, 'default_order_status') );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_filter( 'woocommerce_email_format_string_find', array($this, 'order_status_format_string_find') );
add_filter( 'woocommerce_email_format_string_replace', array($this, 'order_status_format_string_replace'), 10, 2 );
add_action( 'woocommerce_order_status_pending', array($this, 'send_pending_order_emails') );
add_filter( 'woocommerce_valid_order_statuses_for_payment', array($this, 'valid_order_statuses_for_payment' ), 10, 2 );
add_action( 'wp', array($this, 'change_order_to_pending_on_order_received'), 8 );`
public function valid_order_statuses_for_payment($statuses, $order) {
if( $order->is_pay_later ) {
$statuses[] = 'on-hold';
}
return $statuses;
}
/**
* Change the default order status to on-hold so that pending order emails can be triggered
*/
public function default_order_status($default) {
if( ! is_admin() && WC()->session->set( 'chosen_payment_method') == $this->id ) {
$default = 'on-hold';
}
return $default;
}
/**
* Allow Order status to be accessible from emails
*/
public function order_status_format_string_find( $find ) {
$find['order-status'] = '{order_status}';
return $find;
}
/**
* Replace Order status in emails
*/
public function order_status_format_string_replace( $replace, $email ) {
if( $email->object ) {
$replace['order-status'] = wc_get_order_status_name( $email->object->get_status() );
}
return $replace;
}
/**
* Trigger pending order emails and invoice email
*/
public function send_pending_order_emails( $order_id ) {
$emails = new WC_Emails();
$order = wc_get_order( $order_id );
$emails->customer_invoice( $order_id );
$emails->emails['WC_Email_New_Order']->trigger( $order_id );
$order->set_payment_method( $this );
}
/**
* WC Shop As Customer support on Order Received, because the default status is on hold we need to change these orders to pending
*/
public function change_order_to_pending_on_order_received() {
if( class_exists('WC_Shop_As_Customer') && ! empty( $_GET['order_on_behalf'] ) && ! empty( $_GET['key'] ) && ! empty( $_GET['send_invoice'] ) ) {
global $wp;
if ( ! isset( $wp->query_vars['order-received'] ) )
return;
// Bail if we're not shopping-as - don't display the special interface.
if ( ! WC_Shop_As_Customer::get_original_user() )
return;
$order_id = $wp->query_vars['order-received'];
if ( ! empty( $order_id ) ) {
$order = new WC_Order( absint( $order_id) );
$order->update_status( 'pending' );
}
unset( $_GET['send_invoice'] );
}
}
I also read that woocommerce_order_status_pending may not trigger emails so I have tried adding this code (no successes):
add_filter( 'woocommerce_email_actions', 'filter_woocommerce_email_actions' );
function filter_woocommerce_email_actions( $actions ){
$actions[] = 'woocommerce_order_status_pending';
return $actions;
}
I suspect either:
The code to put the order 'on hold' then trigger then back to 'pending' is not set up correctly
woocommerce_order_status_pending hook does not trigger emails
I have a little work around to trigger from the "woocommerce_thankyou" hook in functions.php but I believe that this should be done in the gateway (plus I don't want to rely on woocommerce_thankyou - this email should be based on order creation).
Thanks in advance!!!!!!

Related

Change WooCommerce Status Based On Payment Method Interac (etransfer)

I want when a customer chooses to pay through Email Transfer (Interac) to go ahead and set the order status to Pending etransfer.
– My custom gateway name is custom_b9599316cc4fd28.
– My custom status slug is pending-etransfer.
add_action( 'woocommerce_order_status_changed', 'woocommerce_payment_complete_order_status', 10, 3 );
function woocommerce_payment_complete_order_status( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
if ( $order->data['status'] == 'processing' ) {
$payment_method = $order->get_payment_method();
if ( $payment_method != 'custom_b9599316cc4fd28' ) {
$order->update_status( 'pending-etransfer' );
}
}
}
Any help is greatly appreciated.
The code as you see it.

Hide item meta data in certain WooCommerce email notifications

I've got this code snippet in my functions.php file:
add_action( 'woocommerce_checkout_create_order_line_item', 'add_custom_field_to_order_item_meta', 10, 4 );
function add_custom_field_to_order_item_meta( $item, $cart_item_key, $values, $order ) {
$custom_field_value = get_post_meta( $item->get_product_id(), 'supplier_sku', true );
if ( ! empty($custom_field_value) ){
$item->update_meta_data( __('Supplier SKU', 'woocommerce'), $custom_field_value );
}
}
It pulls in the custom field on products, called Supplier SKU and then adds it to the WooCommerce email notifications. Which is fine, but I want to exclude it from the customer email notification and only have it display in the admin email notification.
How can I achieve this?
You could use the woocommerce_display_item_meta hook and return an empty string
function filter_woocommerce_display_item_meta ( $html, $item, $args ) {
$html = '';
return $html;
}
add_filter( 'woocommerce_display_item_meta', 'filter_woocommerce_display_item_meta', 10, 3 );
While the above would work, there would be some issues, namely:
The hook doesn't run just for email notifications, so it wouldn't show up anywhere
Even if this hook would only be executed for email notifications, we would still need to specify that this should only be the case for certain email notifications. However, this hook does not offer a solution for it by default to make this distinction
So a workaround will be needed, this can be done by creating a global variable through another hook that applies only to email notifications
Step 1) creating and adding a global variable
// Setting global variable
function action_woocommerce_email_before_order_table( $order, $sent_to_admin, $plain_text, $email ) {
$GLOBALS['email_id'] = $email->id;
}
add_action( 'woocommerce_email_before_order_table', 'action_woocommerce_email_before_order_table', 1, 4 );
Step 2) In the hook woocommerce_display_item_meta, add and check for specific conditions
Only for email notifications
Only for specific meta data
Only for admin 'new order' email
function filter_woocommerce_display_item_meta ( $html, $item, $args ) {
// For email notifications and specific meta
if ( ! is_wc_endpoint_url() && $item->is_type( 'line_item' ) && $item->get_meta( 'Supplier SKU' ) ) {
// Getting the email ID global variable
$ref_name_globals_var = isset( $GLOBALS ) ? $GLOBALS : '';
$email_id = isset( $ref_name_globals_var['email_id'] ) ? $ref_name_globals_var['email_id'] : '';
// NOT empty and targeting specific email. Multiple statuses can be added, separated by a comma
if ( ! empty ( $email_id ) && ! in_array( $email_id, array( 'new_order' ) ) ) {
$html = '';
}
}
return $html;
}
add_filter( 'woocommerce_display_item_meta', 'filter_woocommerce_display_item_meta', 10, 3 );

Add extra customer note on order creation for specific user role in WooCommerce

For a specific user role I want to add a specific customer note to the order.
This is my code:
add_action( 'woocommerce_new_order', 'add_customer_note_user_role' );
function add_customer_note_user_role( $order_id ) {
$user_info = get_userdata(get_current_user_id());
if ( $user_info->roles[0]=="administrator" ) {
$order = wc_get_order( $order_id );
// The text for the note
$note = 'This is the message';
// Add the note
$order->add_order_note( $note );
// Save the data
$order->save();
}
}
But this puts the message in the wrong place. When you check an order in the backend it's been displayed in the purple message boxes.
I want the message to be displayed as a customer note which is displayed under the shipping address. Because my API is picking up the notes from that place and puts them in our ERP.
I tried to change
$order->add_order_note( $note );
to
$order->add_order_note( $note, 'is_customer_note', true );
But without the desired result, any advice?
To display the message as a customer note displayed below the shipping address, you can use set_customer_note() instead.
So you get:
function action_woocommerce_new_order( $order_id ) {
// Get the WC_Order Object
$order = wc_get_order( $order_id );
// Get the WP_User Object
$user = $order->get_user();
// Check for "administrator" user roles only
if ( is_a( $user, 'WP_User' ) && in_array( 'administrator', (array) $user->roles ) ) {
// The text for the note
$note = 'This is the message';
// Set note
$order->set_customer_note( $note );
// Save
$order->save();
}
}
add_action( 'woocommerce_new_order', 'action_woocommerce_new_order', 10, 1 );
To keep the original message (when NOT empty) use this instead:
function action_woocommerce_new_order( $order_id ) {
// Get the WC_Order Object
$order = wc_get_order( $order_id );
// Get the WP_User Object
$user = $order->get_user();
// Check for "administrator" user roles only
if ( is_a( $user, 'WP_User' ) && in_array( 'administrator', (array) $user->roles ) ) {
// The text for the note
$note = 'This is the message';
// Get customer note
$customer_note = $order->get_customer_note();
// NOT empty
if ( ! empty ( $customer_note ) ) {
$note = $customer_note . ' | ' . $note;
}
// Set note
$order->set_customer_note( $note );
// Save
$order->save();
}
}
add_action( 'woocommerce_new_order', 'action_woocommerce_new_order', 10, 1 );

Woocommerce action hook to execute function on subscription renewal

I want to know if there is a action hook that can check if the subscription is successfully renewed in woocommerce ? BTW I am using woocommerce subscription plugin. I have created a functionality that records the date of the subscription order and add it to a CSV file, the function is working perfectly for the first purchase I mean when the user purchase a subscription it is recorded successfully in the CSV because I am firing up the function on woocommerce_thankyou action hook, The only issue I am facing is that I can't seem to find a hook which can execute this function on successful subscription renewal. I tried to use woocommerce_subscription_renewal_payment_complete action hook but it didn't worked below is the function that I have created.
/**
* Add subscriptions to csv.
*/
add_action( 'woocommerce_subscription_renewal_payment_complete', 'add_subs_to_csv' );
add_action( 'woocommerce_thankyou', 'add_subs_to_csv' );
function add_subs_to_csv( $order_id ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $key => $value ) {
$meta_values = $value->get_data();
foreach ( $meta_values as $meta_key => $meta_value ) {
if ( $meta_key == 'product_id' && $meta_value == 875 ) {
$paid_date = explode( " ", get_post_meta( $order_id, '_paid_date', true ) );
$subs_paid_date = date( 'd F, Y', strtotime( $paid_date[0] ) );
wc_add_order_item_meta( $key, 'Delivery Date', $subs_paid_date );
}
}
}
}
Could the wcs_renewal_order_created hook be what you're looking for? The docs say:
WooCommerce Subscriptions stores all details of each subscription
renewal in a standard WooCommerce order, only with a special meta flag
linking it to a subscription.
These orders are always created through the wcs_create_renewal_order()
function, regardless of whether they are created for a scheduled
renewal event, manually via the WooCommerce > Edit Subscription
administration screen, or via the Subscriptions endpoints for the
WooCommerce REST API. Because of this, it’s possible to add, remove or
update the value of anything on that renewal order using this filter.
For example, this can be used to add a discount to specific renewal
orders, like the 12th order each year. It could also be used to add
one-time fee for a certain renewal order, like a special annual extra
fee on a monthly subscription.
So the above hook should trigger after payment, you'd probably just need to check if it was completed status which you could also do in your current hooks:
/**
* After WooCommerce Subscriptions Creates Renewal Order
*
* #param WC_Order Object $order
* #param Integer|WC_Subscription Object $subscription
*
* #return WC_Order $order
*/
function add_subs_to_csv( $order, $subscription ) {
if( 'completed' === $order->get_status() ) {
$items = $order->get_items();
foreach ( $items as $key => $value ) {
$meta_values = $value->get_data();
foreach ( $meta_values as $meta_key => $meta_value ) {
if ( $meta_key == 'product_id' && $meta_value == 875 ) {
$paid_date = explode( " ", get_post_meta( $order_id, '_paid_date', true ) );
$subs_paid_date = date( 'd F, Y', strtotime( $paid_date[0] ) );
wc_add_order_item_meta( $key, 'Delivery Date', $subs_paid_date );
}
}
}
}
return $order
}
add_filter( 'wcs_renewal_order_created', 'add_subs_to_csv', 10, 2 );
I had a issue at Subscription renewal and I got it fixed with below code:
/*
* FIXED : Membership got PAUSED everytime at automatic subscription renewal
*/
function change_membership_status_active( $subscription , $order ) {
global $wpdb;
if( 'completed' === $order->get_status() ) {
$membership = $wpdb->get_row( "SELECT * FROM wp_postmeta WHERE meta_key = '_subscription_id' AND meta_value = $subscription->ID" );
$mem_id = $membership->post_id;
$status = 'wcm-active';
$update_args = array( 'ID' => $mem_id, 'post_status' => $status );
wp_update_post($update_args);
}
}
add_action( 'woocommerce_subscription_renewal_payment_complete', 'change_membership_status_active', 10, 2 );

WooCommerce: how to add multiple products to cart at once?

I need a "get products A, B and C for $xxx" special offer, products A, B and C must be available on their own, and the bundle is a special offer accessible through a coupon code.
On a marketing page hosting outside my site, I would like a button leading to my site that carries a query string like ?add-to-cart=244,249,200 so that once on my site, all bundle products are already added to the cart (instead of adding them one by one which sounds unacceptably tedious).
If not possible, then at least I'd like a landing page on my site with a single button adding all bundle products to cart at once.
I couldn't find working solutions googling around (here's one example). Any suggestion?
After some research I found that DsgnWrks wrote a hook that does exactly this. For your convenience, and in case the blog goes offline, I bluntly copied his code to this answer:
function woocommerce_maybe_add_multiple_products_to_cart( $url = false ) {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $id_and_quantity ) {
// Check for quantities defined in curie notation (<product_id>:<product_quantity>)
// https://dsgnwrks.pro/snippets/woocommerce-allow-adding-multiple-products-to-the-cart-via-the-add-to-cart-query-string/#comment-12236
$id_and_quantity = explode( ':', $id_and_quantity );
$product_id = $id_and_quantity[0];
$_REQUEST['quantity'] = ! empty( $id_and_quantity[1] ) ? absint( $id_and_quantity[1] ) : 1;
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action( $url );
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling
if ( 'variable' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_variable', $product_id );
// Grouped Products
} elseif ( 'grouped' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_grouped', $product_id );
// Custom Handler
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ){
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url );
// Simple Products
} else {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_simple', $product_id );
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 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()', __LINE__ );
}
$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( $class_name ), $args );
return call_user_func_array( array( $method, 'invoke' ), $args );
}
It works just like you'd expect, by providing a comma separated list of products. It even works with quantities using ?add-to-cart=63833:2,221916:4
I was, and am still looking for a 'pure' solution that allows to add multiple products to the cart without having to install a plugin or add custom actions. But for many, the above might be an appropriate solution

Resources