I'm using WooCommerce Bookings & WooCommerce Vendors plugins and I want to get the booking & vendor information in a Webhook (on order confirmation) along with the order information to send to a 3rd party.
Currently, the Order Created webhook only contains the product order details (not Booking & Vendor).
I've been searching a lot for different answers and trying to go down a few different paths (inputting into the webhook directly vs add_order_item_meta.
Here's one way I've tried:
add_action('woocommerce_add_order_item_meta', 'add_order_item_meta', 10, 2);
function add_order_item_meta($item_id, $values) {
// Get order id to get booking details
global $post;
$order = wc_get_order( $post->ID );
$order_id = $order->get_id();
$items = $order->get_items();
$booking_data = new WC_Booking_Data_Store();
$booking_ids = $booking_data->get_booking_ids_from_order_id( $order_id );
if ( is_array( $booking_ids ) && count( $booking_ids ) > 0 ) {
foreach ( $booking_ids as $booking_id ) {
$booking = get_wc_booking( $booking_id );
$booking_product = $booking->get_product()->get_title()
$booker_start_date = $booking->get_start_date();
$booker_end_date = $booking->get_end_date();
if ( ! empty( $booking_id ) ){
woocommerce_add_order_item_meta($item_id, "Booking Title", $booking_product);
}
if ( ! empty( $booking_id ) ){
woocommerce_add_order_item_meta($item_id, "Booking ID", $booking_id);
}
if ( ! empty( $booker_start_date ) ){
woocommerce_add_order_item_meta($item_id, "Booking Start Date", $booker_start_date);
}
if ( ! empty( $booker_end_date ) ){
woocommerce_add_order_item_meta($item_id, "Booking End Date", $booker_end_date);
}
}
}
The output of this is a large array of bookings that have been made under the user, so it seems the $order_id isn't passing into the $bookings_id properly.
Also, for some reason, I haven't been able to get $items = $order->get_items(); as I keep getting Uncaught Error: Call to a member function get_items() on boolean. I want to use this to get the vendor email.
I'd prefer a webhook rather than using the API because it looks like you can't filter through booking in the API (ie you have to fetch all bookings).
Happy to get any guidance on the best way to approach it.
Thanks, Andy
So I ended up creating a hook based on payment confirmed.
It might not be the best way, but it's piping the info through based on the correct trigger.
add_action('woocommerce_webhook_payload', 'my_woocommerce_webhook_payload', 1, 4);
function my_woocommerce_webhook_payload($payload, $resource, $resource_id, $id) {
// Get Order details via order id
$payload['order_id'] = $resource_id;
$order_id = $resource_id;
$order = wc_get_order( $order_id );
$order_data = $order->get_data();
// Get booking ids from Order
$booking_ids = WC_Booking_Data_Store::get_booking_ids_from_order_id( $order_id );
$payload['booking_ids'] = $booking_ids;
// Add relevant information to each booking
if ( is_array( $booking_ids ) && count( $booking_ids ) > 0 ) {
foreach ( $booking_ids as $key=>$booking_id ) {
// Fetch booking details
$booking = get_wc_booking( $booking_id );
$booking_product = $booking->get_product()->get_title();
$booker_start_date = $booking->get_start_date();
$booker_end_date = $booking->get_end_date();
$booking_details = [];
// Add booking details
$payload["bookings"][$key] = $booking_details;
}
}
return $payload;
}
Related
I am developing stock status log feature to collect data of all products stock changes. I am using "woocommerce_product_set_stock" hook to write changes to custom database table. One big issue is that I cannot find out how to get related order to stock change when someone has placed a new order. When stock change is based on order cancel/refund I can get the order id from $_SERVER info but when placed a new order via checkout, there's nothing related to order id.
Customer requires order id information on stock change log so any hints, please?
When order cancelled or pending WooCommerce called this wc_reduce_stock_levels and wc_increase_stock_levels function to handle stock quantity.
You can use woocommerce_reduce_order_stock and woocommerce_restore_order_stock to write changes to your custom database table
function update_in_custom_table( $order ){
$order_id = $order->get_id();
$order = wc_get_order( $order_id );
// We need an order, and a store with stock management to continue.
if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) ) {
return;
}
// Loop over all items.
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
$product = $item->get_product();
if ( ! $product || ! $product->managing_stock() ) {
continue;
}
$stock_quantity = $product->get_stock_quantity();
}
}
add_action( 'woocommerce_reduce_order_stock', 'update_in_custom_table', 10, 1 );
add_action( 'woocommerce_restore_order_stock', 'update_in_custom_table', 10, 1 );
I create "text field" on woocommerce customer dashboard, that field is used to redeem coupon. Let say that i have coupon code "BIRTHDAY29" that i setup on woocommerce coupon to be used.
User input "BIRTHDAY29" on my "text field", and hit button "REDEEM" so the transaction is applied programaticaly on their transaction history.
How do i know How many times a coupon "BIRTHDAY29" was used by that user? i want to check if "BIRTHDAY29" is ever used by that user or not, then i want to do "something.." if BIRTHDAY29 ever used by that user.
Hope you guys understand what i mean, thankyou!!
Here you go:
function coupon_usage_for_customer( $coupon_code, $user_id = NULL, $email = NULL )
{
$usage = 0;
// There must either be a user ID or email address
if( empty( $user_id ) && empty( $email ) )
return FALSE;
$params = [
'limit' => -1,
'type' => 'shop_order'
];
if( ! empty( $user_id ) ){
$params['customer_id'] = $user_id;
}
else if( ! empty( $email ) ){
$params['billing_email'] = $email;
}
// Get orders of customer
$orders = wc_get_orders( $params );
// Check if coupon used
foreach( $orders as $order )
{
$coupons_used = $order->get_coupon_codes();
if(
is_array( $coupons_used ) &&
in_array( $coupon_code, $coupons_used )
){
$usage++;
}
}
return $usage;
}
add_action('woocommerce_after_register_post_type', function(){
$user_id = 12345;
$email = 'oneguy#example.com';
$birthday29usage = coupon_usage_for_customer( 'BIRTHDAY29', $user_id, $email );
if( ! $birthday29usage )
echo 'The user has not birthday 29ed...';
});
I am using WooCommerce with WPML plugin. I want to implement a feature on checkout when a customer under certain conditions can have an upgrade of his product but keeping the old product price.
The products are variable with many variable attributes.
So, more specifically, what I want is if a customer has selected a specific product variation with x price on checkout (under a certain condition) I could change his cart item with another product's variation but keep the x price.
What I tried first is to change only the name of the product using woocommerce_order_item_name hook but the change doesn't follow on the order. This is important because some order data are then sent to an API.
Afterwards I used "Changing WooCommerce cart item names" answer code, which worked perfectly for my purpose until I installed WPML. For some reason the WC_Cart method set_name() doesn't work with WPML. I opened a support thread but they still can't find a solution.
Can anyone suggest any other solution?
Update
I have tried an approach where I remove the product item on cart and then I add the one I need. After I use set_price() to change the price of the newly added item. The removal/addition seems to be working but the price is not changed on one language and it is not applied on both languages after placing order.
This is the code I use:
function berrytaxiplon_change_product_name( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Get the product name (Added Woocommerce 3+ compatibility)
$product_id = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : $product->post->post_parent;
if ( ICL_LANGUAGE_CODE == 'en') {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 12) {
$new_product = wc_get_product( 82 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(82, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 82, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( $cart_item['s-fare'] );
}
}
} else {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 282) {
$new_product = wc_get_product( 303 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(303, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 303, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( $cart_item['s-fare']);
}
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'berrytaxiplon_change_product_name', 10, 1 );
Any idea why the set_price() method is not applied?
Update 2
WPMl uses 'woocommerce_before_calculate_totals' and overrides the action added on functions.php
WPML support provided a solution using 3 filters:
https://wpml.org/forums/topic/cant-use-set_name-method-for-the-product-object-on-checkout/#post-3977153
So this is a code that I am using in one of my projects to add a product variation to cart based off of some filters and the selected product:
$product = new WC_Product($product_id); //The main product whose variation has to be added
$product_name = $product->get_name(); //Name of the main product
$quantity = sanitize_text_field($cData['quantity']); //You can set this to 1
$variation_id = sanitize_text_field($cData['variation_id']); //I had the variation ID from filters
$variation = array(
'pa_duration' => sanitize_text_field($cData['duration']) //The variation slug was also available for me.
);
$cart_item_data = array('custom_price' => sanitize_text_field($custom_price));
$cart = WC()->cart->add_to_cart( (int)$product_id, (int)$quantity, (int)$variation_id, $variation, $cart_item_data ); //This will add products to cart but with the actual price of the variation being added and meta data holding the custom price.
WC()->cart->calculate_totals();
WC()->cart->set_session();
WC()->cart->maybe_set_cart_cookies();
Then you need to do a check on before cart totals are calculated and set the price to custom price like this:
function woocommerce_custom_price_to_cart_item( $cart_object ) {
if( !WC()->session->__isset( "reload_checkout" )) {
foreach ( $cart_object->cart_contents as $key => $value ) {
if( isset( $value["custom_price"] ) ) {
$value['data']->set_price($value["custom_price"]);
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'woocommerce_custom_price_to_cart_item', 99 );
The code provided from Faham is very helpful but the page-template that leads to checkout is already over-complicated so I focused to use his logic on the 'woocommerce_before_calculate_totals' hook I am trying all along.
So instead of trying to change the name I remove the item and add the new one. Then calling a new loop I set the price to be of the item that was removed.
function berrytaxiplon_change_product_name( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Get the product name (Added Woocommerce 3+ compatibility)
$product_id = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : $product->post->post_parent;
if ( ICL_LANGUAGE_CODE == 'en') {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 12) {
// SET THE NEW NAME
$new_product = wc_get_product( 82 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(82, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 82, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( get_post_meta( $cart_item['variation_id'], '_price', true ) );
}
}
} else {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 282) {
// SET THE NEW NAME
$new_product = wc_get_product( 303 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(303, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 303, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( get_post_meta( $cart_item['variation_id'], '_price', true ) );
}
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'berrytaxiplon_change_product_name', 10, 1 );
I use the function below to match the attributes taken from the question WooCommerce: Get Product Variation ID from Matching Attributes
function find_matching_product_variation_id($product_id, $attributes)
{
return (new \WC_Product_Data_Store_CPT())->find_matching_product_variation(
new \WC_Product($product_id),
$attributes
);
}
I am a little skeptical using add_to_cart() and a second foreach() inside the $cart_item. But I tested and it seems to work without errors.
Update
Actually there is an issue with this code (or with WPML again). It seems that set_price() is not applied on the secondary language. Yet if I reload the checkout page an send the data again the new price is applied.
I am getting problem when trying to call add_filter in woocommerce add_action hook. I have to add custom charge when user will register for first time and if he is already logged in and upgrade his membership then i want to remove these charges. Filter is working but it remove charges for first time registration as well upgradation. I am calling it on checkout page but it's not working. This is my code.
function my_remove_signup_fee( $subscription_sign_up_fee, $product ) {
$subscription_sign_up_fee =0;
return $subscription_sign_up_fee;
}
add_action('woocommerce_cart_totals_after_billing', 'action_cart_calculate_totals');
function action_cart_calculate_totals( $cart_object ) {
//echo "<pre>";
//print_r($cart_object);
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( !WC()->cart->is_empty() ):
foreach( WC()->cart->get_cart() as $cart_item ){
$product_id = $cart_item['product_id'];
}
$subfeeArr = get_post_meta( $product_id,'_subscription_sign_up_fee');
if(!empty($subfeeArr)){
$subfee = current($subfeeArr);
}else{
$subfee =0;
}
$current_user = wp_get_current_user();
$userEmail = $current_user->user_email;
$mydb = new wpdb('lbmls_c1lbmlsu','qprn230','lbmls_c1lbmls','mysql01.landbrokermls.com');
$query = "SELECT id FROM users WHERE email = '{$userEmail}' AND com = '1' AND `Coop-Fee` = '1'";
$rows = $mydb->get_results($query,ARRAY_A);
if(!empty($rows)){
$userId = current($rows);
if($userId!=""){
return add_filter( 'woocommerce_subscriptions_product_sign_up_fee', 'my_remove_signup_fee', 10, 2 );
}
}
endif;
}
I've been asked to configure a specific WooCommerce behaviour, and I can't escape having to do it with filters. Which I'm not really competent in.
What is supposed to happen, exactly, is that when an order consists only of a product from the "abo" category, it is automatically marked as 'Complete' and the admin mail is sent to a different service.
I've gathered a few examples of code, changing the e-mail recipient, the order status, or making generic changes according to the product category. This is my Frankenstein monster of a code. Both the e-mail change and the order status change failed.
/**
* Change email recipient for admin New Order emails when the order only has products from the 'abo' category
*
* #param string $recipient a comma-separated string of email recipients (will turn into an array after this filter!)
* #param \WC_Order $order the order object for which the email is sent
* #return string $recipient the updated list of email recipients
*/
add_filter( 'woocommerce_email_recipient_new_order', 'dada_conditional_email_recipient', 10, 2 );
function dada_conditional_email_recipient( $recipient, $order ) {
// Bail on WC settings pages since the order object isn't yet set yet
// Not sure why this is even a thing, but shikata ga nai
$page = $_GET['page'] = isset( $_GET['page'] ) ? $_GET['page'] : '';
if ( 'wc-settings' === $page ) {
return $recipient;
}
// just in case
if ( ! $order instanceof WC_Order ) {
return $recipient;
}
$items = $order->get_items();
foreach ( $items as $item ) {
$product = $order->get_product_from_item( $item );
// check if there's an "abo" in the order, then if there's anything else.
if ( is_product() && has_term( 'abo', 'product_cat' ) ) {
$abo_in_order = 'true';
}
if ( is_product() && has_term( 'livre', 'product_cat' ) || has_term( 'revue', 'product_cat' ) || has_term( 'livre', 'product_cat' ) ) {
$abo_alone_in_order = 'false';
}
else {
$abo_alone_in_order = 'true';
}
}
// if there's an 'abo' and nothing else, change the e-mail recipient to dada#sotiaf.fr
if ( ($abo_in_order == 'true')&&($abo_alone_in_order == 'true') ) $recipient = 'dada#sotiaf.fr';
return $recipient;
}
/**
* Autocomplete orders with only an 'abo' product
*/
add_filter( 'woocommerce_payment_complete_order_status', 'dada_abo_order_autocomplete', 10, 2 );
function dada_abo_order_autocomplete( $order_status, $order_id ) {
$order = wc_get_order( $order_id );
if ('processing' == $order_status && ('on-hold' == $order->status || 'pending' == $order->status || 'failed' == $order->status)) {
// just in case
if ( ! $order instanceof WC_Order ) {
return $order_status;
}
$items = $order->get_items();
foreach ( $items as $item ) {
$product = $order->get_product_from_item( $item );
// check if there's an "abo" in the order, then if there's anything else.
if ( is_product() && has_term( 'abo', 'product_cat' ) ) {
$abo_in_order = 'true';
}
if ( is_product() && has_term( 'livre', 'product_cat' ) || has_term( 'revue', 'product_cat' ) || has_term( 'livre', 'product_cat' ) ) {
$abo_alone_in_order = 'false';
}
else {
$abo_alone_in_order = 'true';
}
}
// if there's an 'abo' and nothing else, change the order status to 'completed'
if ( ($abo_in_order == 'true')&&($abo_alone_in_order == 'true') ) $order_status = 'completed';
}
return $order_status;
}
Any idea where the issue comes from?
Thank you,
Joss
You should implement "woocommerce_thankyou" hook for order status update instead of "woocommerce_payment_complete_order_status". Because, your current update order status code will trigerr when any order with completed status is placed. But your requirement is to change status to "Completed".
For adding new email recepient dynamically, you should try this hook : "woocommerce_email_recipient_new_order"
Refer to this : WooCommerce send new order email to customer