I am using the code below. I want to add an extra user role instead of the default 'customer' role right after an order is placed by a customer.
Unfortunately, the user role is not added by this code after an order is placed. Where did I miss?
add_action( 'woocommerce_order_status_completed', 'change_role_on_purchase' );
function change_role_on_purchase( $order_id ) {
$order = new WC_Order( $order_id );
$items = $order->get_items();
$product_id = 73; // that's my product ID
foreach ( $items as $item ) {
if( $product_id == $item['product_id'] && $order->user_id ) {
$user = new WP_User( $order->user_id );
// Remove role
$user->remove_role( 'customer' );
// Add role
$user->add_role( 'premium' );
}
}
}
With your current code, or rather using the woocommerce_order_status_completed hook the user role will only be modified when an order contains the status 'complete'. However, this is rarely the case immediately right after an order is placed by a customer, the order status will be much more likely to be 'pending' or 'on-hold'
If you want to add a user role for existing users, immediately after an order is placed, you can do this via the woocommerce_thankyou hook
So you get:
function action_woocommerce_thankyou( $order_id ) {
// Get $order object
$order = wc_get_order( $order_id );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// Get user
$user = $order->get_user();
if ( is_a( $user, 'WP_User' ) ) {
// Add role
$user->add_role( 'premium' );
}
}
}
add_action( 'woocommerce_thankyou', 'action_woocommerce_thankyou', 10, 1 );
OR
function action_woocommerce_thankyou( $order_id ) {
// Get $order object
$order = wc_get_order( $order_id );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// Get the WP_User Object
$user = $order->get_user();
// Check for "customer" user roles only
if ( is_a( $user, 'WP_User' ) && in_array( 'customer', (array) $user->roles ) ) {
// Remove WooCommerce "customer" role (Optional)
$user->remove_role( 'customer' );
// Add role
$user->add_role( 'premium' );
}
}
}
add_action( 'woocommerce_thankyou', 'action_woocommerce_thankyou', 10, 1 );
Where you don't apply the code for every user, but only for a user with a certain user role
We use the wc_order_is_editable hook to disable the editing of the order items on the backend for some order statuses.
add_filter( 'wc_order_is_editable', 'wc_make_orders_editable', 10, 2 );
function wc_make_orders_editable( $is_editable, $order ) {
if ( $order->get_status() == 'completed' ) {
$is_editable = false;
}
return $is_editable;
}
But i wanted to disable the ability to change the shipping details (Name, Address, etc.) as well.
The logic is that if an order isn't sent already i let our staff change the order items and the shipping info but once the order is sent i want to disable it.
There is not immediately a filter to adjust this, so you could use some jQuery, to hide the edit icon.
Only on order edit page
Checks for user role, administrator
Based on one or more order statuses
Important: Because no direct distinction is made between "billing details" and "shipping details" contains the H3 selector a part of the title
$( "h3:contains('Shipping') .edit_address" );
Where 'shipping' may need to be replaced by the title in the language that you use.
:contains() Selector
So you get:
function action_admin_footer () {
global $pagenow;
// Only on order edit page
if ( $pagenow != 'post.php' || get_post_type( $_GET['post'] ) != 'shop_order' ) return;
// Get current user
$user = wp_get_current_user();
// Safe usage
if ( ! ( $user instanceof WP_User ) ) {
return;
}
// In array, administrator role
if ( in_array( 'administrator', $user->roles ) ) {
// Get an instance of the WC_Order object
$order = wc_get_order( get_the_id() );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// Get order status
$order_status = $order->get_status();
// Status in array
if ( in_array( $order_status, array( 'pending', 'on-hold', 'processing' ) ) ) {
?>
<script>
jQuery( document ).ready( function( $ ) {
// IMPORTANT: edit H3 tag contains 'Shipping' if necessary
$( "h3:contains('Shipping') .edit_address" ).hide();
});
</script>
<?php
}
}
}
}
add_action( 'admin_footer', 'action_admin_footer', 10, 0 );
I would like to add an action in woocommerce to remove the tax from orders when created in the backend and for special users as customers.
This code is working for the normal order process on the website, but not on the backend
add_action( 'woocommerce_checkout_update_order_review', 'remove_tax_from_user' );
add_action( 'woocommerce_before_cart_contents', 'remove_tax_from_user' );
function remove_tax_from_user( $post_data ) {
global $woocommerce;
$username = $woocommerce->customer->get_username();
$user = get_user_by('login',$username);
if($user)
{
if( get_field('steuer_befreit', "user_{$user->ID}") ):
$woocommerce->customer->set_is_vat_exempt( true );
endif;
}
}
In backend customer needs to be "exempt of Vat" before clicking on "Add order" .
You could try to use the hook save_post_shop_order that is triggered before order data is saved in backend, this way:
add_action( 'save_post_shop_order', 'backend_remove_tax_from_user', 50, 3 );
function backend_remove_tax_from_user( $post_id, $post, $update ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id; // Exit if it's an autosave
if ( $post->post_status != 'publish' )
return $post_id; // Exit if not 'publish' post status
if ( ! current_user_can( 'edit_order', $post_id ) )
return $post_id; // Exit if user is not allowed
if( ! isset($_POST['customer_user']) ) return $post_id; // Exit
if( $_POST['customer_user'] > 0 ){
$customer_id = intval($_POST['customer_user']);
if( get_field('steuer_befreit', "user_{$customer_id}") ){
$wc_customer = new WC_Customer( $customer_id );
$wc_customer->set_is_vat_exempt( true );
}
}
}
Code goes in function.php file of your active child theme (or theme).
But this will not work and you will not be able with any exiting hook to make that work. The only way Is to make first customer exempt of vat, then you can Add an order for this customer.
How can I get the payment details from Paypal like PaymentID, PaymentFirstName/LastName, and other details?
The code PayPal Standard integration uses valid-paypal-standard-ipn-request action to process the valid IPN response. You can use the same action to hook into the IPN and get/store any information you want.
To save additional information:
// Hook before the code has processed the order
add_action( 'valid-paypal-standard-ipn-request', 'prefix_process_valid_ipn_response', 9 );
function prefix_process_valid_ipn_response( $posted ) {
if ( ! empty( $posted['custom'] ) && ( $order = prefix_get_paypal_order( $posted['custom'] ) ) ) {
// Lowercase returned variables.
$posted['payment_status'] = strtolower( $posted['payment_status'] );
// Any status can be checked here
if ( 'completed' == $posted['payment_status'] ) {
// Save additional information you want
}
}
}
/**
* From the Abstract "WC_Gateway_Paypal_Response" class
*
* #param $raw_custom
*
* #return bool|WC_Order|WC_Refund
*/
function prefix_get_paypal_order( $raw_custom ) {
// We have the data in the correct format, so get the order.
if ( ( $custom = json_decode( $raw_custom ) ) && is_object( $custom ) ) {
$order_id = $custom->order_id;
$order_key = $custom->order_key;
// Nothing was found.
} else {
return false;
}
if ( ! $order = wc_get_order( $order_id ) ) {
// We have an invalid $order_id, probably because invoice_prefix has changed.
$order_id = wc_get_order_id_by_order_key( $order_key );
$order = wc_get_order( $order_id );
}
if ( ! $order || $order->get_order_key() !== $order_key ) {
return false;
}
return $order;
}
You can find the PayPal variables here: https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNIntro/#id08CKFJ00JYK
The WC core also saves to the order a lot of the IPN data already. All data is saved to the order meta, so you can access it using get_post_meta or $order->get_meta('meta_key').
List by meta_key:
'Payer PayPal address' - The payer address
'Payer first name' - Payer first name
'Payer last name' - Payer last name
'Payment type' - Payment Type
'_paypal_status' - PayPal payment status
please help! I'm trying to define Payment Gateway by changing order details from admin.
As a default option I want to use 'bacs' payment gateway. Customer make order and then I want to change order and turn payment method to custom 'payment2' gateway.
For this, I've made metabox with checkbox which should turn on/off 'payment2' method and unset default 'bacs'. Checkbox working properly.
But, I can't get it to work. First of all, I can't get post meta with checkbox value. Check code below please:
function show_payment2_payment_gateway( $available_gateways ) {
$use_payment2 = get_post_meta( $post->ID, 'use_payment2', true );
if($use_payment2 == "yes") {
unset( $available_gateways['bacs'] );
}
else {
unset( $available_gateways['payment2'] );
}
return $available_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'show_payment2_payment_gateway', 10, 1 );
UPD
This is my code for backend checkbox. As I said it's working well and save meta value as 'yes'
//
//Adding Meta container admin shop_order pages
//
add_action( 'add_meta_boxes', 'mv_add_meta_boxes' );
if ( ! function_exists( 'mv_add_meta_boxes' ) )
{
function mv_add_meta_boxes()
{
global $woocommerce, $order, $post;
add_meta_box( 'mv_other_fields', __('PAYMENT2','woocommerce'), 'mv_add_other_fields_for_packaging', 'shop_order', 'side', 'core' );
}
}
//
//adding Meta field in the meta container admin shop_order pages
//
if ( ! function_exists( 'mv_save_wc_order_other_fields' ) )
{
function mv_add_other_fields_for_packaging()
{
global $woocommerce, $order, $post;
$meta_field_data = get_post_meta( $post->ID, 'use_payment2', true );
$meta_field_data_checked = $meta_field_data["use_payment2"][0];
if($meta_field_data == "yes") $meta_field_data_checked = 'checked="checked"';
echo '
<label for="use_epay">TURN PAYMENT2 ON?</label>
<input type="hidden" name="mv_other_meta_field_nonce" value="' . wp_create_nonce() . '">
<input type="checkbox" name="use_payment2" value="yes" '.$meta_field_data_checked.'>';
}
}
//
//Save the data of the Meta field
//
add_action( 'save_post', 'mv_save_wc_order_other_fields', 10, 1 );
if ( ! function_exists( 'mv_save_wc_order_other_fields' ) )
{
function mv_save_wc_order_other_fields( $post_id ) {
// We need to verify this with the proper authorization (security stuff).
// Check if our nonce is set.
if ( ! isset( $_POST[ 'mv_other_meta_field_nonce' ] ) ) {
return $post_id;
}
$nonce = $_REQUEST[ 'mv_other_meta_field_nonce' ];
//Verify that the nonce is valid.
if ( ! wp_verify_nonce( $nonce ) ) {
return $post_id;
}
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Check the user's permissions.
if ( 'page' == $_POST[ 'post_type' ] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
// --- Its safe for us to save the data ! --- //
// Sanitize user input and update the meta field in the database.
update_post_meta( $post_id, 'use_payment2', $_POST[ 'use_payment2' ] );
}
}
UPD
This is working code for Back-End (custom checkbox metabox). It save checkbox value and change payment method in order details:
//
//Adding Meta container admin shop_order pages
//
add_action( 'add_meta_boxes', 'mv_add_meta_boxes' );
if ( ! function_exists( 'mv_add_meta_boxes' ) )
{
function mv_add_meta_boxes()
{
global $woocommerce, $order, $post;
add_meta_box( 'mv_other_fields', __('PAYMENT2','woocommerce'), 'mv_add_other_fields_for_packaging', 'shop_order', 'side', 'core' );
}
}
//
//adding Meta field in the meta container admin shop_order pages
//
if ( ! function_exists( 'mv_save_wc_order_other_fields' ) )
{
function mv_add_other_fields_for_packaging()
{
global $woocommerce, $order, $post;
$meta_field_data = get_post_meta( $post->ID, 'use_payment2', true );
echo '<label for="use_payment2">USE PAYMENT2?</label>
<input type="hidden" name="mv_other_meta_field_nonce" value="' . wp_create_nonce() . '">';
if($meta_field_data == "yes") {
$meta_field_data_checked = 'checked="checked"';
echo'<input type="checkbox" name="use_payment2" value="yes" '.$meta_field_data_checked.'>';
}
else {
echo'<input type="checkbox" name="use_payment2" value="yes">';
}
}
}
//Save the data of the Meta field
add_action( 'save_post', 'mv_save_wc_order_other_fields', 10, 1 );
if ( ! function_exists( 'mv_save_wc_order_other_fields' ) )
{
function mv_save_wc_order_other_fields( $post_id ) {
// We need to verify this with the proper authorization (security stuff).
// Check if our nonce is set.
if ( ! isset( $_POST[ 'mv_other_meta_field_nonce' ] ) ) {
return $post_id;
}
$nonce = $_REQUEST[ 'mv_other_meta_field_nonce' ];
//Verify that the nonce is valid.
if ( ! wp_verify_nonce( $nonce ) ) {
return $post_id;
}
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Check the user's permissions.
if ( 'page' == $_POST[ 'post_type' ] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
// --- Its safe for us to save the data ! --- //
// Sanitize user input and update the meta field in the database.
$use_payment2 = sanitize_text_field($_POST[ 'use_payment2' ]);
update_post_meta( $post_id, 'use_payment2', $use_payment2 );
if($_POST[ 'use_payment2' ] == 'yes') {
update_post_meta( $post_id, '_payment_method', 'payment2' );
}
elseif (get_post_meta( $post_id, '_payment_method', true ) != 'bacs') {
update_post_meta( $post_id, '_payment_method', 'bacs' );
}
}
}
But, how I can use checkbox state on my front-end? I still can't get checkbox value using this code:
function show_payment2_payment_gateway( $available_gateways ) {
global $woocommerce, $order, $post;
$payment_method = get_post_meta( $post_id, 'use_payment2', true );
if(isset($payment_method) == 'yes') {
unset( $available_gateways['bacs'] );
}
else {
unset( $available_gateways['payment2'] );
}
return $available_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'show_payment2_payment_gateway', 10, 1 );
Now, it's always showing Payment2 option even if checkbox is checked or unchecked.
Update 2 related to your comments (and your question update)
The hook your are using is a front end hook (not admin), so it will not work.
To achieve what want, you need to replace some code inside the function that is going to save your custom checkbox value when you update the order in backend (Admin) edit order pages.
So your code will be now like this:
add_action( 'save_post', 'mv_save_wc_order_other_fields', 10, 1 );
if ( ! function_exists( 'mv_save_wc_order_other_fields' ) )
{
function mv_save_wc_order_other_fields( $post_id ) {
// We need to verify this with the proper authorization (security stuff).
// Check if our nonce is set.
if ( ! isset( $_POST[ 'mv_other_meta_field_nonce' ] ) )
return $post_id;
// Passing the value to a variable
$nonce = $_REQUEST[ 'mv_other_meta_field_nonce' ];
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// Check the user's permissions.
if ( 'page' == $_POST[ 'post_type' ] ) {
if ( ! current_user_can( 'edit_page', $post_id ) )
return $post_id;
} else {
if ( ! current_user_can( 'edit_post', $post_id ) )
return $post_id;
}
// --- Its safe for us to save the data ! --- //
// Sanitize user input and update the meta field in the database.
$use_payment2 = sanitize_text_field($_POST[ 'use_payment2' ]);
update_post_meta( $post_id, 'use_payment2', $use_payment2 );
// Updating securely the data with your conditions
if($use_payment2 == 'yes')
update_post_meta( $post_id, '_payment_method', 'payment2' );
else
update_post_meta( $post_id, '_payment_method', 'bacs' );
}
}
This should work as you expect now…
Code goes in function.php file of your active child theme (or theme). Or also in any plugin php files.
As this code comme from one of my answers, you are not obliged to keep the same functions beginning names with "mv_" that was related to the username of the question. You can change it to "dan_" for example…
Reference: WooCommerce : Add custom Metabox to admin order page
The function that lists default payment gateways in WooCommerce is core_gateways(). This function is hooked to a filter called woocommerce_payment_gateways. So, the first step is to remove that filter and add our own. I will work only in the functions.php file within the theme folder (remember? Never modify core files). To do so, we’ll use the remove_filter() and the add_filter() functions:
remove_filter( 'woocommerce_payment_gateways', 'core_gateways' );
add_filter( 'woocommerce_payment_gateways', 'my_core_gateways' );
Now that we have removed the filter, you can see that in the add_filter() function we have a callback named my_core_gateways. This callback is the name of a function that will replace the default core_gateways() function. This function is the one that list WooCommerce default payment gateways. I will change the content of that function and replace the call to the WC_Gateway_BACS class. This class is the bank transfer default class. Here is the code of that new function:
/**
* core_gateways function modified.
*
* #access public
* #param mixed $methods
* #return void
*/
function my_core_gateways( $methods ) {
$methods[] = 'WC_Gateway_BACS_custom';
$methods[] = 'WC_Gateway_Cheque';
$methods[] = 'WC_Gateway_COD';
$methods[] = 'WC_Gateway_Mijireh';
$methods[] = 'WC_Gateway_Paypal';
return $methods;
}
As you can see the only change I made is that I replaced WC_Gateway_BACS by WC_Gateway_BACS_custom.
Are you still with me huh? Well, to summarize, I need to remove the filter that calls the default payment gateways, and use a custom function. In this custom function, I replace the call to the BACS class, and now i need to create this new BACS class. To do so, use that code:
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 (that's what we want to change!)
$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' => add_query_arg('key', $order->order_key, add_query_arg('order', $order->id, get_permalink(woocommerce_get_page_id('thanks'))))
);
}
}
In this snippet, I only changed the default status from “on-hold” to “processing”…. and boom the magic appears! Now each order paid using the BACS payment gateway will be marked as processing, not as on hold.
After few days of headache I found easy way how to show defined payment gateway only when I send link to customer.
Now customer can make order with default 'bacs' method, and Admin can check it before payment. Then admin change order status to Waiting for payment and link sends to customer. When customer opens link, my custom payment gateway becomes active.
I decided to use woocommerce endpoints to check if it 'order-pay' page. I used code below:
function show_payment2_payment_gateway( $available_gateways ) {
global $woocommerce, $order, $post;
if (is_wc_endpoint_url( 'order-pay' )) {
unset( $available_gateways['bacs'] );
}
else {
unset( $available_gateways['payment2'] );
}
return $available_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'show_payment2_payment_gateway', 10, 1 );
Now it works exactly as I wanted before. I hope this will be useful. Thanks to #LoicTheAztec for help!