woocommerce_checkout_order_processed hook executing function twice - php

I have attached a function to the woocommerce_checkout_order_processed hook:
//check if woocommerce is acive
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
add_action('woocommerce_checkout_order_processed', 'wc_on_place_order');
}
The wc_on_place_order function is to be executed after the user clicks on the PLACE ORDER button. However, it's so odd that the function is executed twice.
My wc_on_place_order function calls an external api written in C#:
function wc_on_place_order( $order_id ) {
global $wpdb;
// get order object and order details
$order = new WC_Order( $order_id );
// get product details
$items = $order->get_items();
//return $items;
$products = array();
foreach ($items as $item) {
array_push($products,
array('userid' => $order->user_id, 'descr' => $item['name'], 'amt' => (float)$item['line_total'])
);
}
//passing $products to external api using `curl_exec`
. . . .
//on successful call, the page should be showing an `alert`, however, it does not
// the handle response
if (strpos($response,'ERROR') !== false) {
print_r($response);
} else {
echo "<script type='text/javascript'>alert($response)</script>";
}
}
After debugging on C# API, I noticed that it the service is being called twice, thus, the checkout is being saved twice to the API database.
Is there something wrong with the wc_on_place_order function or is woocommerce_checkout_order_processed called twice when clicking the PLACE ORDER?
Interestingly, adding return $items after $items = $order->get_items() somehow, the C# api was only called once:
// get product details
$items = $order->get_items();
return $items; //this line
Why is that so?
One more question I would like to ask, is woocommerce_checkout_order_processed the right hook I should use? I have been searching the web for the correct hook to use and it seems that woocommerce_checkout_order_processed is used in the most post. I can't use the woocommerce_thankyou hook as it is also calling the API if I refresh the page.
Any idea will be really appreciated.
EDIT:
I used woocommerce_after_checkout_validation hook which fires after pre-validations on checkout. I can't remember though why woocommerce_checkout_order_processed is being fired twice but I just changed some kind of settings in WooCommerce options page. I can't remember which.
Useful Links from the Comments:
Visual Representation of the WooCommerce hooks
WordPress Action References

I always use the hook woocommerce_payment_complete This will fire as the name suggests after the order has been paid.
function order_payment_complete( $order_id ){
$order = wc_get_order( $order_id );
/* Insert your code */
}
add_action( 'woocommerce_payment_complete', 'order_payment_complete' );

Related

WooCommerce New Order Not Returning Billing E-Mail

i'm having a odd issue with WooCommerce.
I have a plugin I developed that sends data from WooCommerce orders to HubSpot. The plugin needs to fire whenever a new order is placed OR an existing order is updated . I've been using the following truncated code to try to achieve this:
add_action('save_post', 'printout', 10, 3);
function printout($post_ID, $post, $update)
{
if ("shop_order" == $post->post_type) {
$msg = $post_ID;
delegator($msg);
}
}
function delegator($order_id){
$order = get_woocommerce_order($order_id);
// assigns the actual data of the order to the $data variable
$data = get_woocommerce_order_data($order);
// assign User e-mail for search
$email = $order->get_billing_email();
//$email = get_post_meta( $order_id, '_billing_email', true );
}
The problem i'm having is that upon new Orders being made, WordPress can't get the order details, specifically a e-mail (it returns null). The hook does return an order number, so I don't think it's the hook doing this but something else.
The odd part is if after the order is placed and I go into the Order page and just hit update on the order, it works!
So I'm wondering what I'm missing / what's wrong here. Does the flow of the hook fire before WooCommerce can make database calls?
Thanks for any help. Been driving me mad!
For orders added/updated through admin, use the code below
add_action('save_post_shop_order', 'backend_delegator', 10, 3);
function backend_delegator($post_id, $post, $update) {
// Orders in backend only
if (!is_admin())
return;
// Get an instance of the WC_Order
$order = new WC_Order($post_id);
//Fired when create a new order
if (!$update) {
$email = $order->get_billing_email();
}
if ($update) {
$email = $order->get_billing_email();
}
}
For orders placed from checkout, use the code below.
add_action('woocommerce_new_order', 'frondend_delegator', 10, 2);
function frondend_delegator($order_id, $order) {
$email = $order->get_billing_email();
}

Conditionally making WooCommerce product non purchasable

I've been struggling with a snippet for WooCommerce that I'd like to add to my child's theme functions but I am reaching the limits of my PHP knowledge.
I have set a custom attribute called pdt_volume, that I will fill everytime I add a product. I would like to remove the Add_to_cart button when the sum of this attribute for all the items already in the cart have reach a max_volume limit.
For example, the Max_volume is 100, I have 4 products of pdt_volume = 20, therefore, the add_to_cart button for a product with a pdt_volume of 25 should be removed so that I cannot add it to the cart.
So I have come up with this snippet, with bits of code found here and there.
But the functions.php won't save it, and others variations of this code has succeeded to register in the functions.php but the website would then give a 500 error...
Please, does someone has any idea how to achieve this?
What am I doing wrong?
Thanks.
EDIT 1 : Alright, so I got this code to actually be registered in the functions.php by the editor without breaking, but on the site, I still get the internal server error. Like something is not computing or something.
EDIT 2 (29/01/2017)
I used the ACF plugin I had purchased a long time ago but didn't find any suitable purpose... so far.
Here is the working code I was able to come up with. This is not a code masterpiece and I won't get any award for this, but it seems to be working so far. At least, it allows me to get a TRUE/FALSE statement that I can use in a if condition to change the add_to_cart button to a Your_box_is_full button.
Indeed, I didn't need any global $woocommerce or $product !
function get_cart_volume() {
$cart_volume = 0;
foreach( WC()->cart->get_cart() as $cart_item ) {
$item_id = $cart_item['product_id'];
$item_volume = get_field('product_volume', $item_id);
$item_qty = $cart_item['quantity'];
$vol_by_qty = $item_volume * $item_qty;
$cart_volume += $vol_by_qty;
}
return $cart_volume;
}
function check_if_full($candidate) {
$max_volume = 100;
$candidate = $product->id;
$a_volume = get_field('product_volume', $candidate);
$b_volume = get_cart_volume();
$check_volume = $a_volume + $b_volume;
if ($check_volume > $max_volume)
return true;
else
return false;
}
//Just a function to see if it's working on the cart page fur debugging purpose
add_action( 'woocommerce_check_cart_items', 'current_volume');
function current_volume() {
if (is_cart() || is_checkout()) {
global $woocommerce;
$current_volume = get_cart_volume();
wc_add_notice( sprintf( '<strong>Volume is %s.</strong>', $current_volume ),
'error' );
}
}
As Helgatheviking says, you get already, with woocommerce_is_purchasable hook in your hooked function, the $product object as 2nd argument. So you don't need and you have to remove global $woocommerce, $product; to make it work without throwing an error.
Your code is going to be:
add_filter('woocommerce_is_purchasable', 'if_room_is_purchasable', 10, 2);
function if_room_is_purchasable ($is_purchasable, $product){
$cart_volume = 0;
$max_volume = 100;
foreach( WC()->cart->get_cart() as $cart_item ){
$item_id = $cart_item['product_id'];
$terms = get_the_terms( $item_id, 'pa_pdt_volume');
foreach($terms as $key => $term){
$cart_volume += $term->name; // May be better with $term->slug;
}
}
$candidate_volume = $cart_volume + $product->get_attribute( 'pa_pdt_volume' );
if ( $candidate_volume > $max_volume )
return false;
else
return true;
}
This should work now without error.

Hide Payment Method WooCommerce after Failure of first try

I am trying to hide the payment method once user click place order using payment method lets say 'B' which returns in failure of payment.
public function process_payment( $order_id ) {
global $woocommerce;
$order = new WC_Order( $order_id );
if($this->api->somemethod()){
// Payment complete
$order->payment_complete();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
}
else {
wc_add_notice( pll__('Payment error: You cannot use this payment method, Choose another method '), 'error' );
return;
}
}
I have used following filter
add_filter( 'woocommerce_payment_gateways', 'add_gateway_cb' );
But it only works on page reload. I want something which works with Ajax process payment or at least triggers the payment methods to reload upon failure.
Alternatively:
I can use custom JS on place order click.

Woocommerce order delete hook callback is not being called

I'm trying to hook a function to the order deletion (when permanently delete is clicked from admin page) but for some reason the function isn't called. My code looks something like
add_action('woocommerce_before_delete_order_item', function($id) {
$order = new WC_Order($id);
//do some stuff with order meta data
}, 10, 1);
I tried to throw and exception inside the function but no error occurred. Can somebody help?
The woocommerce_before_delete_order_item action runs every time an item is deleted from an order, not every time an order is deleted. You need to use the before_delete_post hook:
add_action('before_delete_post', function($id) {
$post_type = get_post_type($id);
if ($post_type !== 'shop_order') {
return;
}
$order = new WC_Order($id);
//do some stuff with order meta data
}, 10, 1);
Depending on what it is you want to do, you may want to hook on to the action at an earlier stage by changing the priority argument from 10 to something smaller.
if you intend to use the Move to thrash from the order details page on admin dashboard, you can use the wp_trash_post. The before_delete_post didn't work for me.
function onOrderDelete($orderId){
$post_type = get_post_type($orderId);
if($post_type !== 'shop_order') {
return;
}
$order = wc_get_order($orderId);
// do whatever with $order
}
add_action('wp_trash_post', 'onOrderDelete', 10, 1);
function restrict_post_deletion($post_ID){
$type = get_post_type($post_ID);
if($type == 'shop_order'){
$order = new WC_Order($post_ID);
//do some stuff with order data
}
}
add_action('wp_trash_post', 'restrict_post_deletion', 10, 1);
add_action('before_delete_post', 'restrict_post_deletion', 10, 1);
Try this code snippet

how to get woocommerce product information outside loop in custom plugin

how to get a product by id outside the loop
this is my function:
function rakhsh_product_info($id){
$result = get_product( $id );
die($result);
}
get Call to undefined function get_product() error
Try this:
if ( function_exists( 'get_product' ) ) {
$result = get_product( $id );
} else {
$result = new WC_Product( $id );
}
Hope that helps.
All of these errors indicate that your plugin is loaded before Woocommerce. Place the calls to woocommerce functions at least in plugins_loaded action or later. Example from wordpress.org:
<?php
add_action( 'plugins_loaded', 'my_plugin_override' );
function my_plugin_override() {
// your code here
}
?>
Check here for list of available action hooks and action execution order.

Categories