Pass custom posted variable from a page to Woocommerce order data - php

I can add a custom variable to a WooCommerce order, using code like this - but only if the code is on the product page:
<form action="<?php echo esc_url( wc_get_checkout_url() ); ?>" method="post">
<?php
$value = isset( $_POST['dogname'] ) ? sanitize_text_field( $_POST['dogname'] ) : '';
echo '<div><label>Name of Dog</label><p><input name="dogname" value="' . $value . '"></p></div>';
?> <button type="submit">Checkout</button>
</form>
But how do I add the data to the order it if I am not on the product page? I redirect the page after add to cart to a custom page. On this custom page the cart is already populated with the product . But submitting this form on the custom page - goes to checkout but does not update or add the variable to the order. How would I update the order with my custom variable from my custom page?
Maybe I need some extra code for the button to update the order on click?
What code would I use for a button - that on click would post the form values to my order, and direct to another page?

Here below is a way to get posted data available in checkout page and anywhere else without loosing this posted data. For that we set the posted data to a WC session variable, so this posted data is available at any moment when needed.
The form on your page (example with multiple fields):
?><form action="<?php echo esc_url( wc_get_checkout_url() ); ?>" method="post">
<?php
$dogname = isset( $_POST['dogname'] ) ? sanitize_text_field( $_POST['dogname'] ) : '';
$dogcolor = isset( $_POST['dogcolor'] ) ? sanitize_text_field( $_POST['dogcolor'] ) : '';
?>
<div><label><?php _e("Name of Dog"); ?></label><p><input name="dogname" value="<?php echo $dogname; ?>"></p></div>
<div><label><?php _e("Color of Dog"); ?></label><p><input name="dogcolor" value="<?php echo $dogcolor; ?>"></p></div>
<button class="button" type="submit" name="dog_form" value="submited"><?php _e("Checkout"); ?></button>
</form><?php
The code that set the posted data to a WC_Session variable:
// Early enable customer WC_Session
add_action( 'init', 'wc_session_enabler' );
function wc_session_enabler() {
if ( is_user_logged_in() || is_admin() )
return;
if ( isset(WC()->session) && ! WC()->session->has_session() ) {
WC()->session->set_customer_session_cookie( true );
}
}
// Set posted data in a WC session variable
add_action( 'template_redirect', 'set_custom_posted_data_to_wc_session' );
function set_custom_posted_data_to_wc_session() {
if ( is_checkout() && ! is_wc_endpoint_url() ) {
if ( isset($_POST['dog_form']) ) {
$values = array(); // Initializing
if ( isset($_POST['dogname']) && ! empty($_POST['dogname']) ) {
$values['dogname'] = sanitize_text_field($_POST['dogname']);
}
if ( isset($_POST['dogcolor']) && ! empty($_POST['dogcolor']) ) {
$values['dogcolor'] = sanitize_text_field($_POST['dogcolor']);
}
// Set data to a WC_Session variable
if ( ! empty($values) ) {
WC()->session->set('custom_data', $values);
}
}
}
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Then you can get that data on any function or template with:
$values = WC()->session->get('custom_data');
$dogname = isset($values['dogname']) ? $values['dogname'] : '';
$dogcolor = isset($values['dogcolor']) ? $values['dogcolor'] : '';
Save that data to order details:
// Save WC session data as custom order meta data
add_action( 'woocommerce_checkout_create_order', 'action_checkout_add_custom_order_meta', 10, 2 );
function action_checkout_add_custom_order_meta( $order, $data ) {
$values = WC()->session->get('custom_data'); // Get data from WC Session variable
if( ! empty($values) ) {
if ( isset($values['dogname']) ) {
$order->update_meta_data( '_dogname', $values['dogname'] ); // Set dog name to order details
}
if ( isset($values['dogcolor']) ) {
$order->update_meta_data( '_dogcolor', $values['dogcolor'] ); // Set dog color to order details
}
// Remove the WC_Session variable (as we don't need it anymore)
WC()->session->__unset('custom_data');
}
}
Code goes in functions.php file of the active child theme (or active theme).
Then you can get that data from The WC_Order object order using:
$order = wc_get_order( $order_id ); // If needed, get the WC_Order object from order Id
$dogname = $order->get_meta('_dogname');
$dogcolor = $order->get_meta('_dogcolor');
Like in this hooked function that will display data on "Order received" page:
add_action( 'woocommerce_thankyou', 'thankyou_display_dog_data' );
function thankyou_display_dog_data( $order_id ) {
$order = wc_get_order( $order_id ); // Get an instance of the WC_Order object
$dog_name = $order->get_meta('_dogname');
$dog_color = $order->get_meta('_dogcolor');
echo ! empty($dog_name) ? '<p>' . $dog_name .'<p>' : '';
echo ! empty($dog_color) ? '<p>' . $dog_color .'<p>' : '';
}
Tested and works on last WooCommerce version.

To add some more functionality to LoicTheAztec answer using his thankyou_display_dog_data function. His included action shows the function on thank you after checkout form
add_action( 'woocommerce_admin_order_data_after_shipping_address', 'thankyou_display_dog_data' ); //add to admin order data under shipping
add_action( 'woocommerce_admin_order_data_after_billing_address', 'thankyou_display_dog_data' ); //add to admin under billing - pick one or duplicate of shipping
add_action( 'woocommerce_order_details_after_order_table', 'thankyou_display_dog_data' ); //add to frontend customers my account
add_action( 'woocommerce_email_after_order_table', 'thankyou_display_dog_data', 15, 2 ); // add to emails

Related

Automatic order currency change and recalculate [duplicate]

In WooCommerce, when creating and saving a manual order via Admin, I am trying to replace the order currency value with a custom meta data value (which meta key is _wcj_order_currency)
Here are 2 screenshots of the related meta data (key/value pairs):
The order currency:
The custom currency (from Booster plugin):
So I would like to replace the order currency EUR to the custom currency USD from _order_currency meta key on save.
References I used:
How can I populate meta fields of a Woocommerce manual order
WooCommerce admin order edit save post
My code attempt:
// Saving (Updating) or doing an action when submitting
add_action( 'save_post', 'update_order_custom_field_value' );
function update_order_custom_field_value( $post_id ){
// Only for shop order
// if ( 'shop_order' != $_POST[ 'post_type' ] )
if ( 'shop_order')
return $post_id;
// Checking that is not an autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// Check the user’s permissions (for 'shop_manager' and 'administrator' user roles)
if ( ! current_user_can( 'edit_shop_order', $post_id ) )
return $post_id;
//Up to above is fine for Admin order
// Updating custom field data
if( isset( $_POST['_wcj_order_currency'] ) ) {
$order = wc_get_order( $post_id );
// Replacing and updating the value
update_post_meta( $post_id, '_order_currency', $_POST['_wcj_order_currency'] );
}}
// Testing output in order edit pages (below billing address):
//This displays the existing values well
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_order_custom_field_value' );
function display_order_custom_field_value( $order ){
echo '<p><strong>'.__('Order Currency').':</strong> <br/>' . get_post_meta( $order->get_id(), '_order_currency', true ) . '</p>';
echo '<p><strong>'.__('Booster Order Currency').':</strong> <br/>' . get_post_meta( $order->get_id(), '_wcj_order_currency', true ) . '</p>';
}
Testing output in order edit pages (below billing address). The code works well.
But I am unable to make it work and update the order currency on order creation.
Any help on this is welcome.
To make your script work on new order creation only, try the following revisited code (commented):
// Save order data
add_action( 'save_post_shop_order', 'update_order_currency_on_creation', 1000 );
function update_order_currency_on_creation( $order_id ){
// Ensure that this is a manual new order
if( $created = get_post_meta( $order_id, '_created_via', true ) ) {
return $order_id;
}
// Checking that is not an autosave (not sure that this is really needed on Woocommerce orders)
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $order_id;
}
// Check the user’s permissions (for 'shop_manager' and 'administrator' user roles)
if ( ! current_user_can( 'edit_shop_order', $order_id ) ) {
return $order_id;
}
## ---- Updating order currency ---- ##
// Get the WC_Order object
$order = wc_get_order($order_id);
// HERE below the Booster meta key for Order currency
$meta_key = '_wcj_order_currency';
// If Booster currency is already in database (in case of, to be sure)
if ( $value = $order->get_meta($meta_key) ) {
$order->set_currency( esc_attr($value) );
$order->save(); // Save order data
}
// If not, we get the posted Booster currency value (else)
elseif ( isset($_POST[$meta_key]) && ( $value = esc_attr($_POST[$meta_key]) ) ) {
$order->set_currency( esc_attr($_POST[$meta_key]) );
$order->save(); // Save order data
}
}
// Testing output in order edit pages (below billing address): This displays the existing values as well.
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_specific_order_details' );
function display_specific_order_details( $order ){
echo '<div><p><strong>'.__('Order Currency').':</strong> ' . $order->get_currency() . '</p>
<p><strong>'.__('Booster Order Currency').':</strong> ' . $order->get_meta( '_wcj_order_currency' ) . '</p></div>';
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Custom hidden field on WooCommerce checkout not saving to user meta issue

I cannot figure out what i am missing here.
I am creating a hidden field on the checkout page, that contains a value after a customer's choice.
This part is working, as i can see in the inspector on the checkout page.
The hidden field should be saved to the logged-in user, as i need it on another place in the website.
I have the following:
//This part is working!!
add_action( 'woocommerce_after_checkout_billing_form', function() {
global $woocommerce;
$items = $woocommerce->cart->get_cart();
foreach($items as $item => $values) {
if( isset($values['programmakeuze']) ){
echo '<input type="hidden" name="programchoice" id="programchoice" class="input-hidden" value="'.$values['programmakeuze'].'">';
}
}
});
//Save hidden field to user
function elearning_checkout_update_user_meta( $customer_id, $posted ) {
if (!empty($_POST['programchoice'])) {
$program = intval($_POST['programchoice'] );
update_user_meta( $customer_id, 'programchoice', $program);
}
}
add_action( 'woocommerce_checkout_update_user_meta', 'elearning_checkout_update_user_meta', 10, 2 );
function testing(){
$id = get_current_user_id();
$value = get_user_meta($id,'programchoice',true);
if ( !empty($value)) {
var_dump ($value);
}
}
add_action('wp_head','testing');
The $value returns nothing. What am i missing here?
I've partly rewritten your code. Including the use of woocommerce_checkout_update_customer action hook.
Also note the use of break in the for loop, as this is about a specific ID, and therefore about 1 unique field
However, I wouldn't use the wp_head action hook for debugging. See How to debug in WooCommerce instead.
But this should suffice, to answer your question:
// Display a custom hidden field after checkout billing form
function action_woocommerce_after_checkout_billing_form( $checkout ) {
// Loop through cart items
foreach( WC()->cart->get_cart() as $cart_item ) {
if ( isset( $cart_item['programmakeuze'] ) ) {
echo '<input type="hidden" name="programchoice" id="programchoice" class="input-hidden" value="' . $cart_item['programmakeuze'] . '">';
break;
}
}
}
add_action( 'woocommerce_after_checkout_billing_form', 'action_woocommerce_after_checkout_billing_form', 10, 1 );
// Save/update user data from custom field value
function action_woocommerce_checkout_update_customer( $customer, $data ) {
// Isset
if ( isset( $_POST['programchoice'] ) ) {
$customer->update_meta_data( '_programchoice', sanitize_text_field( $_POST['programchoice'] ) );
}
}
add_action( 'woocommerce_checkout_update_customer', 'action_woocommerce_checkout_update_customer', 10, 2 );
// Debugging purposes
function action_wp_head(){
// Get user id
$user_id = get_current_user_id();
// Get user meta
$value = get_user_meta( $user_id, '_programchoice', true );
// NOT empty
if ( ! empty( $value ) ) {
var_dump ( $value );
}
}
add_action( 'wp_head', 'action_wp_head' );

Display custom order item metadata only on WooCommerce admin orders

To display custom data , I use this hook 'woocommerce_checkout_create_order_line_item'. He works good. But it displays data in three places - in the admin panel (in order), in the order details and in the personal account. I need the data to be displayed only in the admin panel. how to do it?
My code
add_action( 'woocommerce_checkout_create_order_line_item', 'wdm_add_custom_order_line_item_meta', 10, 4 );
function wdm_add_custom_order_line_item_meta( $item, $cart_item_key, $values, $order )
{
if ( array_key_exists( 'file', $values ) ) {
$product_id = $item->get_product_id();
$permfile = $values['file'];
$basePath = plugin_base_url();
$fileid = $permfile;
....
$item->add_meta_data('File','<button > <a href="'.$fileid.'" download>' . Download. '</a></button>');
}
}
Use the following to display a custom download button on admin order items only (code is commented):
// Save custom order item meta
add_action( 'woocommerce_checkout_create_order_line_item', 'save_custom_order_item_meta', 10, 4 );
function save_custom_order_item_meta( $item, $cart_item_key, $values, $order ) {
if ( isset($values['file']) && ! empty($values['file']) ) {
// Save it in an array to hide meta data from admin order items
$item->add_meta_data('file', array( $values['file'] ) );
}
}
// Get custom order item meta and display a linked download button
add_action( 'woocommerce_after_order_itemmeta', 'display_admin_order_item_custom_button', 10, 3 );
function display_admin_order_item_custom_button( $item_id, $item, $product ){
// Only "line" items and backend order pages
if( ! ( is_admin() && $item->is_type('line_item') ) )
return;
$file_url = $item->get_meta('file'); // Get custom item meta data (array)
if( ! empty($file_url) ) {
// Display a custom download button using custom meta for the link
echo '<a href="' . reset($file_url) . '" class="button download" download>' . __("Download", "woocommerce") . '</a>';
}
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
The custom download button is only displayed in admin order items.

Replace order currency when creating a manual order in Woocommerce admin

In WooCommerce, when creating and saving a manual order via Admin, I am trying to replace the order currency value with a custom meta data value (which meta key is _wcj_order_currency)
Here are 2 screenshots of the related meta data (key/value pairs):
The order currency:
The custom currency (from Booster plugin):
So I would like to replace the order currency EUR to the custom currency USD from _order_currency meta key on save.
References I used:
How can I populate meta fields of a Woocommerce manual order
WooCommerce admin order edit save post
My code attempt:
// Saving (Updating) or doing an action when submitting
add_action( 'save_post', 'update_order_custom_field_value' );
function update_order_custom_field_value( $post_id ){
// Only for shop order
// if ( 'shop_order' != $_POST[ 'post_type' ] )
if ( 'shop_order')
return $post_id;
// Checking that is not an autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// Check the user’s permissions (for 'shop_manager' and 'administrator' user roles)
if ( ! current_user_can( 'edit_shop_order', $post_id ) )
return $post_id;
//Up to above is fine for Admin order
// Updating custom field data
if( isset( $_POST['_wcj_order_currency'] ) ) {
$order = wc_get_order( $post_id );
// Replacing and updating the value
update_post_meta( $post_id, '_order_currency', $_POST['_wcj_order_currency'] );
}}
// Testing output in order edit pages (below billing address):
//This displays the existing values well
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_order_custom_field_value' );
function display_order_custom_field_value( $order ){
echo '<p><strong>'.__('Order Currency').':</strong> <br/>' . get_post_meta( $order->get_id(), '_order_currency', true ) . '</p>';
echo '<p><strong>'.__('Booster Order Currency').':</strong> <br/>' . get_post_meta( $order->get_id(), '_wcj_order_currency', true ) . '</p>';
}
Testing output in order edit pages (below billing address). The code works well.
But I am unable to make it work and update the order currency on order creation.
Any help on this is welcome.
To make your script work on new order creation only, try the following revisited code (commented):
// Save order data
add_action( 'save_post_shop_order', 'update_order_currency_on_creation', 1000 );
function update_order_currency_on_creation( $order_id ){
// Ensure that this is a manual new order
if( $created = get_post_meta( $order_id, '_created_via', true ) ) {
return $order_id;
}
// Checking that is not an autosave (not sure that this is really needed on Woocommerce orders)
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $order_id;
}
// Check the user’s permissions (for 'shop_manager' and 'administrator' user roles)
if ( ! current_user_can( 'edit_shop_order', $order_id ) ) {
return $order_id;
}
## ---- Updating order currency ---- ##
// Get the WC_Order object
$order = wc_get_order($order_id);
// HERE below the Booster meta key for Order currency
$meta_key = '_wcj_order_currency';
// If Booster currency is already in database (in case of, to be sure)
if ( $value = $order->get_meta($meta_key) ) {
$order->set_currency( esc_attr($value) );
$order->save(); // Save order data
}
// If not, we get the posted Booster currency value (else)
elseif ( isset($_POST[$meta_key]) && ( $value = esc_attr($_POST[$meta_key]) ) ) {
$order->set_currency( esc_attr($_POST[$meta_key]) );
$order->save(); // Save order data
}
}
// Testing output in order edit pages (below billing address): This displays the existing values as well.
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_specific_order_details' );
function display_specific_order_details( $order ){
echo '<div><p><strong>'.__('Order Currency').':</strong> ' . $order->get_currency() . '</p>
<p><strong>'.__('Booster Order Currency').':</strong> ' . $order->get_meta( '_wcj_order_currency' ) . '</p></div>';
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Hook woocommerce price in backend order edition

I need to change item product prices in Woocommerce Backend Order. I tried tu use the following hook, but I have a problem trying to obtain the order id. Any suggestion? Thanks in advance!
function my_custom_prices ($price, $product)
{
if ( !is_admin() || ( is_admin() && is_post_type_archive() ) ) return $price;
global $post, $woocommerce;
$order = new WC_Order( $post_id );
$user_id = $order->user_id;
$user = get_user_by('id', $user_id);
if ( in_array( 'my_role', (array) $user->roles ) ) {
return $price * 2;
}
else {
return $price;
}
}
add_filter('woocommerce_get_price', 'my_custom_prices ', 10, 2);
Complete problem:
The complete problem is as follows. I am using a plugin that adds a field to the product called wholesale price. If the customer has the wholesale customer role, the order uses those prices. The plugin works fine, but it does not take the price in the backend. I talked to the author and it's something they do not plan to change yet. My client needs to modify the orders. But when it enters the backend, it takes the common price, not the wholesaler. I need to do something in the backend that allows me to detect if the order is from a client with a wholesale customer role. If yes, take the correct price when adding products. There is more information on the discussion with the author here. https://wordpress.org/support/topic/wholesale-prices-in-backend-editing-orders/ Thank you very much for the help you can give me.
Options:
woocommerce_get_price: does not work because I cannot obtain the customer id
woocommerce_ajax_add_order_item_meta: nice option, but I could not find a sample
Button: nice option, but I do not know how can I change the price. I tryed the follow:
add_action( 'woocommerce_order_item_add_action_buttons', 'action_aplicar_mayoristas', 10, 1);
function action_aplicar_mayoristas( $order )
{
echo '<button type="button" onclick="document.post.submit();" class="button button-primary generate-items">Aplicar precios mayoristas</button>';
echo '<input type="hidden" value="1" name="aplicar_mayoristas" />';
};
add_action('save_post', 'aplicar_mayoristas', 10, 3);
function aplicar_mayoristas($post_id, $post, $update){
$slug = 'shop_order';
if(is_admin()){
if ( $slug != $post->post_type ) {
return;
}
if(isset($_POST['aplicar_mayoristas']) && $_POST['aplicar_mayoristas']){
$order = wc_get_order( $post_id);
//$order_id = $order->get_user_id();
// Iterating through each "line" items in the order
foreach ($order->get_items() as $item_id => $item_data) {
//$item_data->set_subtotal("798");
$item_data->set_price("798");
//->set_price($custom_price);
}
}
}
}
Updated
The hook that you are using is not for orders, but only for products, and is made to change the displayed prices only. So you will not get the order ID with it.
You could change the price display in many hooks, but if you want to change order item prices for real (not only the displayed formatted prices), you should trigger this prices changes when order is updated for example.
In this case you can use a custom function hooked in save_post action hook:
add_action( 'save_post', 'change_order_item_prices', 11, 1 );
function change_order_item_prices( $post_id ) {
// If this is an autosave (has not been submitted).
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// Check the user's permissions.
if ( 'shop_order' == $_POST[ 'post_type' ] ){
if ( ! current_user_can( 'edit_shop_order', $post_id ) )
return $post_id;
} else {
if ( ! current_user_can( 'edit_post', $post_id ) )
return $post_id;
}
## ------------------- Changing prices Start code ------------------- ##
## ===> HERE define the targeted user role
$user_role = 'my_role';
## ===> HERE define the rate multiplier (for price calculations)
$multiplier = 2;
// If this Order items prices have already been updated, we exit
$items_prices_updated = get_post_meta( $post_id, 'line_item_updated', true );
if( ! empty( $items_prices_updated ) ) return $post_id; // Exit
$order = new WC_Order( $post_id ); // Get the order object
$user_id = $order->get_user_id(); // Get the user ID
$user_data = get_userdata( $user_id ); // Get the user data
// Check the user role
if ( ! in_array( $user_role, $user_data->roles ) ) return;
// Loop through order items
foreach( $order->get_items() as $item_id => $item ){
$item_data = $item->get_data(); // The item data
$taxes = array();
foreach( $item_data['taxes'] as $key_tax => $values ){
if( ! empty( $values ) ){
foreach( $values as $key => $tax_price ){
$taxes[$key_tax][$key] = floatval($tax_price) * $multiplier;
}
}
}
$new_line_subtotal = floatval( $item_data['subtotal'] ) * $multiplier;
$new_line_subt_tax = floatval( $item_data['subtotal_tax'] ) * $multiplier;
$new_line_total = floatval( $item_data['total'] ) * $multiplier;
$new_line_total_tax = floatval( $item_data['total_tax'] ) * $multiplier;
// Update Order item prices
$item->set_subtotal($new_line_subtotal);
$item->set_subtotal_tax($new_line_subt_tax);
$item->set_total($new_line_total);
$item->set_total_tax($new_line_total_tax);
$item->set_taxes($taxes);
// Save the updated data
$item->save();
}
// Udpate order totals and cache
$order->calculate_totals();
// We mark the order as updated (to avoid repetition)
update_post_meta( $post_id, 'line_item_updated', true );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and and finally works.
I have added a security to avoid the order items to be updated twice.
The method $order->calculate_totals(); slow down the process a little bit… It's normal as it will calculate totals, update data and refresh caches.
Your code needs to be debugged.
$post_id - there is not such variable or parameter inside your function. So, use $post->ID instead.
do
var_dump( (array) $user->roles);
before the
if (in_array( 'my_role', (array) $user->roles ) )
line. And make sure that my_role exists in that array.
Temporary comment this line for debugging purpose:
// if (is_admin() && is_post_type_archive())
Then you will see the reason and able to be fix it.

Categories