Apply a fee to cart in Woocommerce - php

I am trying to programatically add a fee to the woocommerce cart from a script which is executed on a form submission. As far as I am aware, I don't think I am able to use a hook as I need to apply the custom fee when the form on the page has been submitted (Custom API integration).
I have tried doing the following within the script:
add_action( 'woocommerce_cart_calculate_fees', 'woo_add_cart_fee' );
function woo_add_cart_fee( $cart ){
$valid = false;
if ( ! $_POST || ( is_admin() && ! is_ajax() ) ) {
return;
}
if (isset($_POST['coupon_code'])) {
$code = $_POST['coupon_code'];
$coupon = new WC_Coupon($code);
if($coupon->get_amount() != null){
$valid == true;
}
//if not then lets check to see if its a giftcard.
if($valid == false){
$api_login="xxxxxx";
$api_password="xxxxxx";
$url = "https://xxxxxx.com/xxxxx/xxxxx.svc";
$client = new SoapClient( $url . "?singleWsdl",
array(
"location" => $url,
"login" => $api_login,
"password" => $api_password,
"trace" => 1
)
);
$request = new StdClass();
$request->bonId = $code;
$request->bonType = 'GiftCard';
// call the correct database
$request->context = new StdClass();
$request->context->DatabaseId = 'xxxxx';
try {
$resu = $client->GetBonAvailableAmount($request);
if (isset($resu->GetBonAvailableAmountResult->Amount)) {
$amount = $resu->GetBonAvailableAmountResult->Amount;
$cart->add_fee('xxxxxx Gift Card ', floatval('-'.$amount * 0.83333), false, '' );
} else {
$response['status'] = 'error';
$response['message'] = 'Gift card not recognized.';
}
} catch (Exception $e) {
}
}
}
}
and I can see that when I echo the cart object there is a fee object which contains all the correct data.
It seems that the cart or totals are not updating, if i refresh the page, they still do not reflect the values i am expected.
I have trawled pretty much all Stack Overflow posts and cant seem to find anything which solves the issue.
Is there anything that I am missing here?

You need to use a custom function hooked in woocommerce_cart_calculate_fees action hook:
add_action( 'woocommerce_cart_calculate_fees', 'add_a_custom_fee', 10, 1 );
function add_a_custom_fee( $cart ) {
$amount = 20;
$cart->add_fee( __('Custom fee'), $amount );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Related

Custom API endpoint for WooCommerce Add to cart

I have been trying to build a custom api endpoint to add to cart. My local is working fine and adding the product to the cart easily. I made the code live and getting an error like:
Call to a member function generate_cart_id() on null
Here is the code I have written.
add_action('rest_api_init', function () {
register_rest_route( 'product/v1', 'add_to_cart',array(
'methods' => 'POST',
'callback' => 'wplms_add_to_cart'
));
});
function wplms_add_to_cart($request) {
$product_id = $request['product_id'];
$product_cart_id = WC()->cart->generate_cart_id($product_id);
if( ! WC()->cart->find_product_in_cart( $product_cart_id ) ){
// The product ID is NOT in the cart, let's add it then!
$added = $cart->add_to_cart( $product_id);
}
$response = new WP_REST_Response(var_dump($added));
$response->set_status(200);
return $response;
}
The way I resolved it.
if ( defined( 'WC_ABSPATH' ) ) {
// WC 3.6+ - Cart and other frontend functions are not included for REST requests.
include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
include_once WC_ABSPATH . 'includes/wc-notice-functions.php';
include_once WC_ABSPATH . 'includes/wc-template-hooks.php';
}
if ( null === WC()->session ) {
$session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );
WC()->session = new $session_class();
WC()->session->init();
}
if ( null === WC()->customer ) {
WC()->customer = new WC_Customer( get_current_user_id(), true );
}
if ( null === WC()->cart ) {
WC()->cart = new WC_Cart();
// We need to force a refresh of the cart contents from session here (cart contents are normally refreshed on wp_loaded, which has already happened by this point).
$cart = WC()->cart->get_cart();
$product_cart_id = WC()->cart->generate_cart_id($request['product_id']);
$cart_item_key = WC()->cart->find_product_in_cart($product_cart_id);
if (!$cart_item_key) {
$add = WC()->cart->add_to_cart( $product_id = $request['product_id'], $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() );
} else {
$add = false;
}
}

Display ACF order custom field in WooCommerce orders

Need to add a field to the WooCommerce order details page where I can add a tracking number in the order page to be displayed for the customer.
I have added ACF to the order-details.php template as below, but it does not display. When I inspect page element all I can see is <h2></h2>. This is my current ACF code:
<h2><?php the_field('tracking_number'); ?></h2>
You can use the following to dis
// Display tracking information as a row on order totals everywhere
add_filter( 'woocommerce_get_order_item_totals', 'order_item_totals_tracking_row', 1000, 3 );
function order_item_totals_tracking_row( $total_rows, $order, $tax_display ){
if( $tracking_number = get_field('tracking_number', $order->get_id()) ) {
$new_total_rows = []; // Initializing
$has_tracking_row = false; // Initializing
$tracking_row = array( // tracking information array
'label' => __("Tracking number", "woocommerce"),
'value' => $tracking_number
);
// Loop through order total rows
foreach( $total_rows as $key => $values ) {
$new_total_rows[$key] = $values;
// Inserting tracking information array
if( $key === 'shipping' ) {
$new_total_rows['tracking'] = $tracking_row;
$has_tracking_row = true;
} elseif( $key === 'payment_method' && ! $has_tracking_row ) {
$new_total_rows['tracking'] = $tracking_row;
}
}
return $new_total_rows;
}
return $total_rows;
}
Code goes in functions.php file of the active child theme (or active theme). It should works.

Display custom product image on WooCommerce email notifications

I am trying to modify the product images in the auto generated WooCommerce order e-mails. Note, I am trying to do this with hooks, rather than creating a modified e-mail template.
To start, I have a hidden input on the single product page. I have some JS that sets the ID of the image, which is based on the product's colour (a custom attribute I made with ACF).
<input class="js-col-img-id" type="hidden" name="product-col-img-id" value="">
For reference, here's what I've done to get this to work on the cart page:
// Add custom attributes to cart item data
add_action('woocommerce_add_cart_item_data', 'jwd_add_custom_attr_to_cart', 10, 3);
function jwd_add_custom_attr_to_cart($cart_item_data, $product_id, $variation_id) {
$attrs = array(...,'product-col-img-id');
foreach ($attrs as $attr) {
$san_attr = filter_input( INPUT_POST, $attr );
if ( !empty( $san_attr ) ) {
$cart_item_data[$attr] = $san_attr;
}
}
return $cart_item_data;
}
// This function sets the image ID in the cart, and it displays the image properly
add_action( 'woocommerce_before_calculate_totals', 'bwa_new_price' );
function bwa_new_price($cart) {
// ...
$cart_items = $cart->get_cart();
foreach($cart_items as $item) {
$data = $item['data'];
// product-col-img-id is sent through the hidden input when the add to cart form is submitted
$colour_img_ID = $item['product-col-img-id'] ?? false;
if ($colour_img_ID) {
$data->set_image_id($colour_img_ID);
}
}
}
// Add custom attributes to order
add_action( 'woocommerce_checkout_create_order_line_item', 'jwd_add_attr_to_order_items', 10, 4 );
function jwd_add_attr_to_order_items( $item, $cart_item_key, $values, $order ) {
if ( !empty( $values['product-colour'] ) ) {
$item->add_meta_data( 'Colour', $values['product-colour'] );
}
// I tried replicating this for the img ID. It does display it in the var dump mentioned below, and it does display it as an actual label in the e-mail (which I'd want to get rid of). The difficulty then becomes reading the ID through code and setting the product to display that image
if ( !empty( $values['product-col-img-id'] ) ) {
$item->add_meta_data( 'col_img_id', $values['product-col-img-id'] );
}
}
That works fine, but I'm having difficulty replicating this in the order e-mail. So far I've come across the following. This shows images in the e-mail, but it's the product's featured image. Of note is the "show_image" key being set to true.
// Add prod IMG to e-mails
function jwd_add_images_woocommerce_emails( $output, $order ) {
// set a flag so we don't recursively call this filter
static $run = 0;
// if we've already run this filter, bail out
if ( $run ) {
return $output;
}
$args = array(
'show_image' => true,
//'image_size' => array( 300, 300 ),
'image_size' => 'full',
);
// increment our flag so we don't run again
$run++;
// if first run, give WooComm our updated table
return wc_get_email_order_items($order, $args);
}
add_filter( 'woocommerce_email_order_items_table', 'jwd_add_images_woocommerce_emails', 10, 2 );
To modify the image, I found this filter, but I'm just having a difficult time figuring out what I need to do to replicate what I've done on the cart page. The closest I've got is having $item_data work, and I do see the product-col-img-id key in the var dump, though it's in a protected object, which I'm not sure how to access. Even if I could access, I'm not sure how I could even set the image from here.
add_filter('woocommerce_order_item_thumbnail', 'jwd_add_colour_img', 10, 2);
function jwd_add_colour_img($image, $item) {
$item_id = $item->get_id();
//var_dump($item->get_current_data());
$product = $item->get_product();
$product_id = $item->get_product_id();
$item_data = $item->get_data();
$col_img_id = false;
$item->set_image_id(1);
foreach ($item_data as $key => $item) {
//current_data
var_dump($item);
//echo "<br><br>";
$col_img_id = $item['product-col-img-id'] ?? false;
if ($col_img_id) {
break;
}
}
//var_dump($col_img_id);
//$item_id = $item->get_product_id();
//var_dump(wc_get_order_item_meta( $item_id, 'col_img_id', true ) );
return $image;
}
To get WooCommerce custom meta data, you can use WC_Data method get_meta().
Try the following instead to display your custom image on email notifications:
// Save custom image ID as order item meta
add_action( 'woocommerce_checkout_create_order_line_item', 'save_custom_image_id_to_order_item', 10, 4 );
function save_custom_image_id_to_order_item( $item, $cart_item_key, $values, $order ) {
if ( isset($values['product-colour']) && ! empty($values['product-colour']) ) {
$item->add_meta_data('Colour', $values['product-colour'] );
}
if ( isset($values['product-col-img-id']) && ! empty($values['product-col-img-id']) ) {
$item->add_meta_data('_col_img_id', $values['product-col-img-id'] );
}
}
// (optional) Force display item image on emails
add_filter( 'woocommerce_email_order_items_args', 'show_image_on_email_notifications' );
function show_image_on_email_notifications( $args ) {
$args['show_image'] = true;
return $args;
}
// Display custom image on emails
add_filter( 'woocommerce_order_item_thumbnail', 'display_email_order_item_custom_image', 10, 2 );
function display_email_order_item_custom_image( $image, $item ) {
// Only on email notifications
if( is_wc_endpoint_url() )
return $image;
$image_id = $item->get_meta('_col_img_id');
if( $image_id ) {
$image = wp_get_attachment_image( $image_id, array( 32, 32 ), false, array() );
}
return $image;
}
Code goes in functions.php file of the active child theme (or active theme). It should work.

Woocommerce Custom Email

I need help with a custom email hook for woocommerce.
I am trying to send a different email depending on product ID whenever a product is completed.
My code, which is not working, is as follows:
/**************
DIFFERENT MESSAGES FOR DIFFERENT PRODUCTS
****************/
//hook our function to the new order email
add_action('woocommerce_email_order_details', 'uiwc_email_order_details_products', 1, 4);
function uiwc_email_order_details_products($order, $admin, $plain, $email) {
$status = $order->get_status();
// checking if it's the order status we want
if ( $status == "completed" ) {
$items = $order->get_items();
if ( $item['product_id'] == "3181") {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>To get started, please follow this link to complete the Policies form.<br><br>This is a really important first step, and only takes about 5 minutes. After completeing the Policies form, you will receive additional instructions on next steps.<br><br>Congratulations! Let your journey begin.<br><br>', 'uiwc' );
}
elseif ( $item['product_id'] == "3223") {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>Differnet product so differenct email....<br><br>', 'uiwc' );
}
}
}
Any suggestions is greatly appreciated
There is some mistakes in your code, instead try the following
//hook our function to the new order email
add_action( 'woocommerce_email_order_details', 'custom_email_order_details', 4, 4 );
function custom_email_order_details( $order, $admin, $plain, $email ) {
$domain = 'woocommerce';
// checking if it's the order status we want
if ( $order->has_status('completed') ) {
foreach( $order->get_items() as $item ){
if ( $item->get_product_id() == '3181' ) {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>To get started, please follow this link to complete the Policies form.<br><br>This is a really important first step, and only takes about 5 minutes. After completeing the Policies form, you will receive additional instructions on next steps.<br><br>Congratulations! Let your journey begin.<br><br>', $domain );
break;
}
elseif ( $item->get_product_id() == '3223' ) {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>Differnet product so differenct email....<br><br>', $domain );
break;
}
}
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

WooCommerce - get_order() doesn't work and it returns zero

I am creating an online shop with WooCommerce and I'm adding a function which will update the bonus point to my database into absract-wc-payment-gateway.php.
Here is what I am doing:
Firstly, on the checkout page, the users will click the place order button and then the method will get the users bonus points and minus the bonus points with the get-total(), and then update to the database and go to the thank you page.
Then, the thank you page will get the user's bonus points from the database. And I set the bonus points value to 2000. So in this case, the bonus points should be minus by total Points($50.00)
Here is my code. It will be ran when the user clicks the place order button:
global $woocommerce;
$order = new WC_Order($order_id);
$total = $order->get_total();
$bonusPoint -= (int)$total; //minus total price and calculate the latest bonus point
$updateSql = "UPDATE userdata02 SET bonusPoint ='" .$bonusPoint. "' WHERE userID = 2147483647";
mysqli_query($link, $updateSql);// update to an int column
if(mysqli_query($link, $updateSql)) {
echo "Record updated successfully";
} else {
echo "Error update record: <>" . mysqli_error($link);
}
Call the method when the user clicks place button:
public function get_return_url( $order = null ) {
if ( $order ) {
//$message = "wrong answer";
//echo "<script type='text/javascript'>alert('$message');</script>";
$return_url = $order->get_checkout_order_received_url();
} else {
$return_url = wc_get_endpoint_url( 'order-received', '', wc_get_page_permalink( 'checkout' ) );
}
if ( is_ssl() || get_option('woocommerce_force_ssl_checkout') == 'yes' ) {
$return_url = str_replace( 'http:', 'https:', $return_url );
}
self::reducePoints(); //Call reducePoints();
return apply_filters( 'woocommerce_get_return_url', $return_url, $order );
}
The source code: reducePoints() lines 89 from abstract-WC-Payment-Gateway.php
The get_total() doesn't work and it returns zero.
What I am doing wrong?
You need to create an object for $order to use it with get_total(). Try this:
global $woocommerce;
$order = new WC_Order($order_id);
$total = $order->get_total(); //Get the total price of the order.
$bonusPoints -= (int)$total; //calculate the new bonusPoints
Update1: This is just solving the internal data error. We need to get the $order_id to get it work…
Note: You can remove global $woocommerce;before $order = new WC_Order($order_id); because is already included in public function reducePoints( ){
Update2 - The good track:
Remove my code:
global $woocommerce;
$order = new WC_Order($order_id);
Then at line 89 of your code just add $order in:
public function reducePoints( $order ){
global $woocommerce;
// ...
Really happy that this works… It was a long search...

Categories