14

In woocommerce, we can add a discount to any order using Coupons feature (fixed amount, percent amount…).

Is it possible to add discount amount to any order programmatically where the discount amount can be any amount?

Any help will be appreciated.

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
Pankaj Verma
  • 415
  • 1
  • 4
  • 15
  • What exactly kind of discount do you want to add? Do you want to set the discount manually for each product, or to apply it programmatically? Should it apply for each user or only if some conditions are met? – pgndck Oct 04 '18 at 12:39
  • Possible duplicate of [Dynamically discounting Woocommerce Products](https://stackoverflow.com/questions/50583862/dynamically-discounting-woocommerce-products) – pgndck Oct 04 '18 at 12:41
  • I am creating order manually and data is being fetched from third party application. The third party application can provide any amount in discount and I have to add that discount in an order, make sense? EDIT: The given link doesn't add discount, just return dynamic price. – Pankaj Verma Oct 04 '18 at 12:45
  • Both coupon and negative fee solutions have some drawbacks. Sometimes it's helpful to imitate the behavior needed for the discount directly. Say you are basing the discount on the products in the cart, then you can change the price of the cart items themselves. In that case, do this: https://stackoverflow.com/questions/44974645/change-price-of-product-in-woocommerce-cart-and-checkout – CubicInfinity Apr 26 '22 at 01:24

2 Answers2

14

The only available feature to make a discount programmatically for an Order, is tricking the Fee API. For info, this trick is not recommended by woocommerce, but used by many people as there is not a discount feature in Woocommerce outside Coupons.

The following function will allow you to make a fixed discount of any amount or a percentage discount. The order need to exist (to be saved before).

The function code (For Woocommerce versions 3.2+):

/**
 * Add a discount to an Orders programmatically
 * (Using the FEE API - A negative fee)
 *
 * @since  3.2.0
 * @param  int     $order_id  The order ID. Required.
 * @param  string  $title  The label name for the discount. Required.
 * @param  mixed   $amount  Fixed amount (float) or percentage based on the subtotal. Required.
 * @param  string  $tax_class  The tax Class. '' by default. Optional.
 */
function wc_order_add_discount( $order_id, $title, $amount, $tax_class = '' ) {
    $order    = wc_get_order($order_id);
    $subtotal = $order->get_subtotal();
    $item     = new WC_Order_Item_Fee();

    if ( strpos($amount, '%') !== false ) {
        $percentage = (float) str_replace( array('%', ' '), array('', ''), $amount );
        $percentage = $percentage > 100 ? -100 : -$percentage;
        $discount   = $percentage * $subtotal / 100;
    } else {
        $discount = (float) str_replace( ' ', '', $amount );
        $discount = $discount > $subtotal ? -$subtotal : -$discount;
    }

    $item->set_tax_class( $tax_class );
    $item->set_name( $title );
    $item->set_amount( $discount );
    $item->set_total( $discount );

    if ( '0' !== $item->get_tax_class() && 'taxable' === $item->get_tax_status() && wc_tax_enabled() ) {
        $tax_for   = array(
            'country'   => $order->get_shipping_country(),
            'state'     => $order->get_shipping_state(),
            'postcode'  => $order->get_shipping_postcode(),
            'city'      => $order->get_shipping_city(),
            'tax_class' => $item->get_tax_class(),
        );
        $tax_rates = WC_Tax::find_rates( $tax_for );
        $taxes     = WC_Tax::calc_tax( $item->get_total(), $tax_rates, false );
        print_pr($taxes);

        if ( method_exists( $item, 'get_subtotal' ) ) {
            $subtotal_taxes = WC_Tax::calc_tax( $item->get_subtotal(), $tax_rates, false );
            $item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
            $item->set_total_tax( array_sum($taxes) );
        } else {
            $item->set_taxes( array( 'total' => $taxes ) );
            $item->set_total_tax( array_sum($taxes) );
        }
        $has_taxes = true;
    } else {
        $item->set_taxes( false );
        $has_taxes = false;
    }
    $item->save();

    $order->add_item( $item );
    $order->calculate_totals( $has_taxes );
    $order->save();
}

Code goes in function.php file of your active child theme (active theme). Tested and works.


USAGE Examples:

1) Fixed discount of $12 (with a dynamic $order_id):

wc_order_add_discount( $order_id, __("Fixed discount"), 12 );

enter image description here

2) Percentage discount of 5% (with a dynamic $order_id):

wc_order_add_discount( $order_id, __("Discount (5%)"), '5%' );

enter image description here

The amount (or the percentage) can be also a dynamic variable…

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • 1
    I did some changes as per my requirement. It is working like a charm! I think this is the best way to add a discount without a coupon code in woocommerce. Thanks!! – Pankaj Verma Oct 05 '18 at 06:01
  • 1
    This seems to work for Subscriptions too which is great. Thanks for sharing! – Justin Feb 28 '19 at 04:16
  • 1
    Won't it affect the default flow of WooCommerce Fees ?? – dipak_pusti Dec 14 '20 at 10:33
  • 3
    This isn't a good way to apply discounts as it will break any reports/usage of WooCommerce fees. It would be better to create a coupon / apply that or apply the discount another way, such as https://stackoverflow.com/questions/15744689/apply-a-coupon-programmatically-in-woocommerce. – bryceadams Sep 08 '21 at 11:17
7

Actually you can just make hook before calculate tax etc, get subtotal, apply discount, done :) It automatically apply for Order message etc, it is visible also in backend in proper way. Even after remove hook, info about discount stay in order information.

// Hook before calculate fees
add_action('woocommerce_cart_calculate_fees' , 'add_user_discounts');
/**
 * Add custom fee if more than three article
 * @param WC_Cart $cart
 */
function add_user_discounts( WC_Cart $cart ){
    //any of your rules
    // Calculate the amount to reduce
    $discount = $cart->get_subtotal() * 0.5;

    $cart->add_fee( 'Test discount 50%', -$discount);
}
Isu
  • 342
  • 2
  • 10
  • 1
    This is not a valid solution, see the WC comment: `@param float $amount Fee amount (do not enter negative amounts).` here: https://docs.woocommerce.com/wc-apidocs/source-class-WC_Cart.html#1703-1723 – Bjorn Mar 28 '19 at 12:59
  • As mentioned by @Bjorn, not a good solution. And causes issues when Tax is applied. – Pramod Jodhani Dec 20 '19 at 12:27
  • You saved me from a minor headache – vanlooverenkoen Sep 22 '21 at 18:51
  • yap, im currently sitting on the problem, the solution works when u have no vat, with vat this solution makes alot of headache – StefanBD Apr 09 '23 at 18:51