2017-07-26 11:44:06 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Cart totals calculation class.
|
|
|
|
*
|
|
|
|
* Methods are protected and class is final to keep this as an internal API.
|
|
|
|
* May be opened in the future once structure is stable.
|
|
|
|
*
|
|
|
|
* @author Automattic
|
|
|
|
* @package WooCommerce/Classes
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WC_Cart_Totals class.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
2017-07-27 09:49:47 +00:00
|
|
|
final class WC_Cart_Totals {
|
|
|
|
|
|
|
|
/**
|
2017-07-27 12:48:58 +00:00
|
|
|
* Reference to cart object.
|
2017-07-27 09:49:47 +00:00
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $object;
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
/**
|
|
|
|
* Reference to customer object.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $customer;
|
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
/**
|
|
|
|
* Line items to calculate.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $items = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fees to calculate.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $fees = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shipping costs.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $shipping = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applied coupon objects.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $coupons = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Discount amounts in cents after calculation for the cart.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $discount_totals = array();
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
/**
|
|
|
|
* Should taxes be calculated?
|
|
|
|
*
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
protected $calculate_tax = true;
|
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
/**
|
|
|
|
* Stores totals.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $totals = array(
|
|
|
|
'fees_total' => 0,
|
|
|
|
'fees_total_tax' => 0,
|
|
|
|
'items_subtotal' => 0,
|
|
|
|
'items_subtotal_tax' => 0,
|
|
|
|
'items_total' => 0,
|
|
|
|
'items_total_tax' => 0,
|
|
|
|
'total' => 0,
|
|
|
|
'taxes' => array(),
|
|
|
|
'tax_total' => 0,
|
|
|
|
'shipping_total' => 0,
|
|
|
|
'shipping_tax_total' => 0,
|
|
|
|
'discounts_total' => 0,
|
|
|
|
'discounts_tax_total' => 0,
|
|
|
|
);
|
2017-07-26 11:44:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets up the items provided, and calculate totals.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
2017-07-27 09:49:47 +00:00
|
|
|
* @param object $cart Cart object to calculate totals for.
|
2017-07-26 11:44:06 +00:00
|
|
|
*/
|
2017-07-28 12:02:39 +00:00
|
|
|
public function __construct( &$cart = null ) {
|
2017-07-27 09:49:47 +00:00
|
|
|
if ( is_a( $cart, 'WC_Cart' ) ) {
|
2017-07-28 16:43:05 +00:00
|
|
|
$this->object = $cart;
|
|
|
|
$this->calculate_tax = wc_tax_enabled() && ! $cart->get_customer()->get_is_vat_exempt();
|
2017-07-26 11:44:06 +00:00
|
|
|
$this->calculate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
/**
|
2017-07-27 12:48:58 +00:00
|
|
|
* Run all calculations methods on the given items in sequence.
|
2017-07-27 09:49:47 +00:00
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function calculate() {
|
|
|
|
$this->calculate_item_totals();
|
|
|
|
$this->calculate_fee_totals();
|
|
|
|
$this->calculate_shipping_totals();
|
|
|
|
$this->calculate_totals();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get default blank set of props used per item.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function get_default_item_props() {
|
|
|
|
return (object) array(
|
|
|
|
'object' => null,
|
|
|
|
'quantity' => 0,
|
|
|
|
'product' => false,
|
|
|
|
'price_includes_tax' => false,
|
|
|
|
'subtotal' => 0,
|
|
|
|
'subtotal_tax' => 0,
|
|
|
|
'total' => 0,
|
|
|
|
'total_tax' => 0,
|
|
|
|
'taxes' => array(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get default blank set of props used per fee.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function get_default_fee_props() {
|
|
|
|
return (object) array(
|
|
|
|
'total_tax' => 0,
|
|
|
|
'taxes' => array(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get default blank set of props used per shipping row.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function get_default_shipping_props() {
|
|
|
|
return (object) array(
|
|
|
|
'total' => 0,
|
|
|
|
'total_tax' => 0,
|
|
|
|
'taxes' => array(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
/**
|
|
|
|
* Should we round at subtotal level only?
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function round_at_subtotal() {
|
|
|
|
return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
|
|
|
|
}
|
|
|
|
|
2017-07-26 11:44:06 +00:00
|
|
|
/**
|
|
|
|
* Handles a cart or order object passed in for calculation. Normalises data
|
|
|
|
* into the same format for use by this class.
|
|
|
|
*
|
|
|
|
* Each item is made up of the following props, in addition to those returned by get_default_item_props() for totals.
|
|
|
|
* - key: An identifier for the item (cart item key or line item ID).
|
|
|
|
* - cart_item: For carts, the cart item from the cart which may include custom data.
|
|
|
|
* - quantity: The qty for this line.
|
|
|
|
* - price: The line price in cents.
|
|
|
|
* - product: The product object this cart item is for.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function set_items() {
|
2017-07-26 13:32:43 +00:00
|
|
|
$this->items = array();
|
|
|
|
|
2017-07-26 11:44:06 +00:00
|
|
|
foreach ( $this->object->get_cart() as $cart_item_key => $cart_item ) {
|
|
|
|
$item = $this->get_default_item_props();
|
2017-07-26 13:32:43 +00:00
|
|
|
$item->object = $cart_item;
|
2017-07-26 14:47:30 +00:00
|
|
|
$item->price_includes_tax = wc_prices_include_tax();
|
2017-07-26 11:44:06 +00:00
|
|
|
$item->quantity = $cart_item['quantity'];
|
2017-07-27 09:51:08 +00:00
|
|
|
$item->subtotal = wc_add_number_precision_deep( $cart_item['data']->get_price() ) * $cart_item['quantity'];
|
2017-07-26 11:44:06 +00:00
|
|
|
$item->product = $cart_item['data'];
|
2017-07-27 12:48:58 +00:00
|
|
|
$item->tax_rates = $this->get_item_tax_rates( $item );
|
2017-07-26 11:44:06 +00:00
|
|
|
$this->items[ $cart_item_key ] = $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get fee objects from the cart. Normalises data
|
|
|
|
* into the same format for use by this class.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function set_fees() {
|
|
|
|
$this->fees = array();
|
|
|
|
$this->object->calculate_fees();
|
|
|
|
|
|
|
|
foreach ( $this->object->get_fees() as $fee_key => $fee_object ) {
|
|
|
|
$fee = $this->get_default_fee_props();
|
|
|
|
$fee->object = $fee_object;
|
2017-07-27 09:51:08 +00:00
|
|
|
$fee->total = wc_add_number_precision_deep( $fee->object->amount );
|
2017-07-26 11:44:06 +00:00
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( $this->calculate_tax && $fee->object->taxable ) {
|
2017-07-28 12:02:39 +00:00
|
|
|
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class, $this->object->get_customer() ), false );
|
2017-07-26 11:44:06 +00:00
|
|
|
$fee->total_tax = array_sum( $fee->taxes );
|
2017-07-27 12:48:58 +00:00
|
|
|
|
|
|
|
if ( ! $this->round_at_subtotal() ) {
|
|
|
|
$fee->total_tax = wc_round_tax_total( $fee->total_tax, 0 );
|
|
|
|
}
|
2017-07-26 11:44:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->fees[ $fee_key ] = $fee;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get shipping methods from the cart and normalise.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function set_shipping() {
|
|
|
|
$this->shipping = array();
|
|
|
|
|
|
|
|
foreach ( $this->object->calculate_shipping() as $key => $shipping_object ) {
|
|
|
|
$shipping_line = $this->get_default_shipping_props();
|
2017-07-26 13:32:43 +00:00
|
|
|
$shipping_line->object = $shipping_object;
|
2017-07-27 09:51:08 +00:00
|
|
|
$shipping_line->total = wc_add_number_precision_deep( $shipping_object->cost );
|
|
|
|
$shipping_line->taxes = wc_add_number_precision_deep( $shipping_object->taxes );
|
|
|
|
$shipping_line->total_tax = wc_add_number_precision_deep( array_sum( $shipping_object->taxes ) );
|
2017-07-27 12:48:58 +00:00
|
|
|
|
|
|
|
if ( ! $this->round_at_subtotal() ) {
|
|
|
|
$shipping_line->total_tax = wc_round_tax_total( $shipping_line->total_tax, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->shipping[ $key ] = $shipping_line;
|
2017-07-26 11:44:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return array of coupon objects from the cart. Normalises data
|
|
|
|
* into the same format for use by this class.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function set_coupons() {
|
|
|
|
$this->coupons = $this->object->get_coupons();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-07-27 09:49:47 +00:00
|
|
|
* Only ran if woocommerce_adjust_non_base_location_prices is true.
|
|
|
|
*
|
|
|
|
* If the customer is outside of the base location, this removes the base
|
|
|
|
* taxes. This is off by default unless the filter is used.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @param object $item Item to adjust the prices of.
|
|
|
|
* @return object
|
|
|
|
*/
|
|
|
|
protected function adjust_non_base_location_price( $item ) {
|
|
|
|
$base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->tax_class );
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( $item->tax_rates !== $base_tax_rates ) {
|
2017-07-27 09:49:47 +00:00
|
|
|
// Work out a new base price without the shop's base tax.
|
|
|
|
$taxes = WC_Tax::calc_tax( $item->subtotal, $base_tax_rates, true, true );
|
|
|
|
|
|
|
|
// Now we have a new item price (excluding TAX).
|
|
|
|
$item->subtotal = $item->subtotal - array_sum( $taxes );
|
|
|
|
$item->price_includes_tax = false;
|
|
|
|
}
|
|
|
|
return $item;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get discounted price of an item with precision (in cents).
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @param object $item_key Item to get the price of.
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
protected function get_discounted_price_in_cents( $item_key ) {
|
2017-08-02 18:07:33 +00:00
|
|
|
$item = $this->items[ $item_key ];
|
|
|
|
$price = $item->subtotal - $this->discount_totals[ $item_key ];
|
|
|
|
if ( $item->price_includes_tax ) {
|
|
|
|
$price += $item->subtotal_tax;
|
|
|
|
}
|
|
|
|
return $price;
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get tax rates for an item. Caches rates in class to avoid multiple look ups.
|
|
|
|
*
|
|
|
|
* @param object $item Item to get tax rates for.
|
|
|
|
* @return array of taxes
|
|
|
|
*/
|
|
|
|
protected function get_item_tax_rates( $item ) {
|
|
|
|
$tax_class = $item->product->get_tax_class();
|
2017-07-28 12:02:39 +00:00
|
|
|
return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class(), $this->object->get_customer() );
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a single total with or without precision (in cents).
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @param string $key Total to get.
|
|
|
|
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
|
|
|
* @return int|float
|
|
|
|
*/
|
|
|
|
public function get_total( $key = 'total', $in_cents = false ) {
|
|
|
|
$totals = $this->get_totals( $in_cents );
|
|
|
|
return isset( $totals[ $key ] ) ? $totals[ $key ] : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a single total.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @param string $key Total name you want to set.
|
|
|
|
* @param int $total Total to set.
|
|
|
|
*/
|
|
|
|
protected function set_total( $key = 'total', $total ) {
|
|
|
|
$this->totals[ $key ] = $total;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all totals with or without precision (in cents).
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
|
|
|
* @return array.
|
|
|
|
*/
|
|
|
|
public function get_totals( $in_cents = false ) {
|
2017-07-27 09:51:08 +00:00
|
|
|
return $in_cents ? $this->totals : wc_remove_number_precision_deep( $this->totals );
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all tax rows from items (including shipping and product line items).
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function get_merged_taxes() {
|
|
|
|
$taxes = array();
|
|
|
|
|
|
|
|
foreach ( array_merge( $this->items, $this->fees, $this->shipping ) as $item ) {
|
|
|
|
foreach ( $item->taxes as $rate_id => $rate ) {
|
|
|
|
$taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $this->items + $this->fees as $item ) {
|
|
|
|
foreach ( $item->taxes as $rate_id => $rate ) {
|
|
|
|
$taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $this->shipping as $item ) {
|
|
|
|
foreach ( $item->taxes as $rate_id => $rate ) {
|
|
|
|
$taxes[ $rate_id ]['shipping_tax_total'] = $taxes[ $rate_id ]['shipping_tax_total'] + $rate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $taxes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
| Calculation methods.
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate item totals.
|
2017-07-26 11:44:06 +00:00
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function calculate_item_totals() {
|
2017-07-27 09:49:47 +00:00
|
|
|
$this->set_items();
|
|
|
|
$this->calculate_item_subtotals();
|
|
|
|
$this->calculate_discounts();
|
|
|
|
|
|
|
|
foreach ( $this->items as $item_key => $item ) {
|
|
|
|
$item->total = $this->get_discounted_price_in_cents( $item_key );
|
|
|
|
$item->total_tax = 0;
|
|
|
|
|
2017-07-27 14:31:10 +00:00
|
|
|
if ( has_filter( 'woocommerce_get_discounted_price' ) ) {
|
|
|
|
/**
|
|
|
|
* Allow plugins to filter this price like in the legacy cart class.
|
|
|
|
*
|
|
|
|
* This is legacy and should probably be deprecated in the future.
|
|
|
|
* $item->object is the cart item object.
|
|
|
|
* $this->object is the cart object.
|
|
|
|
*/
|
|
|
|
$item->total = wc_add_number_precision(
|
|
|
|
apply_filters( 'woocommerce_get_discounted_price', wc_remove_number_precision( $item->total ), $item->object, $this->object )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( $this->calculate_tax && $item->product->is_taxable() ) {
|
|
|
|
$item->taxes = WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax );
|
2017-07-27 09:49:47 +00:00
|
|
|
$item->total_tax = array_sum( $item->taxes );
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( ! $this->round_at_subtotal() ) {
|
|
|
|
$item->total_tax = wc_round_tax_total( $item->total_tax, 0 );
|
|
|
|
}
|
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
if ( $item->price_includes_tax ) {
|
|
|
|
$item->total = $item->total - $item->total_tax;
|
|
|
|
} else {
|
|
|
|
$item->total = $item->total;
|
|
|
|
}
|
|
|
|
}
|
2017-07-31 18:48:34 +00:00
|
|
|
|
|
|
|
$this->object->cart_contents[ $item_key ]['line_total'] = wc_remove_number_precision( $item->total );
|
2017-07-31 20:18:39 +00:00
|
|
|
$this->object->cart_contents[ $item_key ]['line_tax'] = wc_remove_number_precision( $item->total_tax );
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
2017-07-26 11:44:06 +00:00
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
$this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
|
|
|
|
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
|
2017-07-27 10:01:48 +00:00
|
|
|
|
2017-07-26 11:44:06 +00:00
|
|
|
$this->object->subtotal = $this->get_total( 'items_total' ) + $this->get_total( 'items_total_tax' );
|
|
|
|
$this->object->subtotal_ex_tax = $this->get_total( 'items_total' );
|
|
|
|
}
|
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
/**
|
|
|
|
* Subtotals are costs before discounts.
|
|
|
|
*
|
|
|
|
* To prevent rounding issues we need to work with the inclusive price where possible.
|
|
|
|
* otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would.
|
|
|
|
* be 8.325 leading to totals being 1p off.
|
|
|
|
*
|
|
|
|
* Pre tax coupons come off the price the customer thinks they are paying - tax is calculated.
|
|
|
|
* afterwards.
|
|
|
|
*
|
|
|
|
* e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function calculate_item_subtotals() {
|
2017-07-31 18:48:34 +00:00
|
|
|
foreach ( $this->items as $item_key => $item ) {
|
2017-07-27 09:49:47 +00:00
|
|
|
if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
|
|
|
|
$item = $this->adjust_non_base_location_price( $item );
|
|
|
|
}
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( $this->calculate_tax && $item->product->is_taxable() ) {
|
|
|
|
$subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax );
|
2017-07-27 09:49:47 +00:00
|
|
|
$item->subtotal_tax = array_sum( $subtotal_taxes );
|
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( ! $this->round_at_subtotal() ) {
|
|
|
|
$item->subtotal_tax = wc_round_tax_total( $item->subtotal_tax, 0 );
|
|
|
|
}
|
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
if ( $item->price_includes_tax ) {
|
|
|
|
$item->subtotal = $item->subtotal - $item->subtotal_tax;
|
|
|
|
}
|
|
|
|
}
|
2017-07-31 18:48:34 +00:00
|
|
|
|
2017-07-31 20:18:39 +00:00
|
|
|
$this->object->cart_contents[ $item_key ]['line_subtotal'] = wc_remove_number_precision( $item->subtotal );
|
2017-07-31 18:48:34 +00:00
|
|
|
$this->object->cart_contents[ $item_key ]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
|
|
|
$this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) );
|
|
|
|
$this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) );
|
2017-07-27 10:01:48 +00:00
|
|
|
|
2017-07-27 09:49:47 +00:00
|
|
|
$this->object->subtotal = $this->get_total( 'items_total' ) + $this->get_total( 'items_total_tax' );
|
|
|
|
$this->object->subtotal_ex_tax = $this->get_total( 'items_total' );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate all discount and coupon amounts.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
* @uses WC_Discounts class.
|
|
|
|
*/
|
|
|
|
protected function calculate_discounts() {
|
|
|
|
$this->set_coupons();
|
|
|
|
|
2017-07-28 12:02:39 +00:00
|
|
|
$discounts = new WC_Discounts( $this->object );
|
2017-07-27 09:49:47 +00:00
|
|
|
|
|
|
|
foreach ( $this->coupons as $coupon ) {
|
2017-07-28 12:02:39 +00:00
|
|
|
$discounts->apply_discount( $coupon );
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
$this->discount_totals = $discounts->get_discounts_by_item( true );
|
2017-07-28 20:38:43 +00:00
|
|
|
$this->totals['discounts_total'] = ! empty( $this->discount_totals ) ? array_sum( $this->discount_totals ) : 0;
|
2017-07-28 14:35:41 +00:00
|
|
|
$this->object->coupon_discount_amounts = $discounts->get_discounts_by_coupon();
|
2017-07-27 09:49:47 +00:00
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
// See how much tax was 'discounted' per item and per coupon.
|
2017-07-27 12:48:58 +00:00
|
|
|
if ( $this->calculate_tax ) {
|
2017-07-28 14:35:41 +00:00
|
|
|
$coupon_discount_tax_amounts = array();
|
|
|
|
$item_taxes = 0;
|
2017-07-28 12:02:39 +00:00
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) {
|
|
|
|
$coupon_discount_tax_amounts[ $coupon_code ] = 0;
|
2017-07-28 12:02:39 +00:00
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
foreach ( $coupon_discounts as $item_key => $item_discount ) {
|
|
|
|
$item = $this->items[ $item_key ];
|
2017-07-28 12:02:39 +00:00
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
if ( $item->product->is_taxable() ) {
|
2017-07-28 15:43:51 +00:00
|
|
|
$item_tax = array_sum( WC_Tax::calc_tax( $item_discount, $item->tax_rates, false ) );
|
2017-07-28 14:35:41 +00:00
|
|
|
$item_taxes += $item_tax;
|
|
|
|
$coupon_discount_tax_amounts[ $coupon_code ] += $item_tax;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-28 12:02:39 +00:00
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
$this->totals['discounts_tax_total'] = $item_taxes;
|
|
|
|
$this->object->coupon_discount_tax_amounts = $coupon_discount_tax_amounts;
|
|
|
|
}
|
|
|
|
}
|
2017-07-28 12:02:39 +00:00
|
|
|
|
2017-07-28 14:35:41 +00:00
|
|
|
/**
|
|
|
|
* Return discounted tax amount for an item.
|
|
|
|
*
|
|
|
|
* @param object $item
|
|
|
|
* @param int $discount_amount
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
protected function get_item_discount_tax( $item, $discount_amount ) {
|
|
|
|
if ( $item->product->is_taxable() ) {
|
|
|
|
$taxes = WC_Tax::calc_tax( $discount_amount, $item->tax_rates, false );
|
|
|
|
return array_sum( $taxes );
|
|
|
|
}
|
|
|
|
return 0;
|
2017-07-27 09:49:47 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 11:44:06 +00:00
|
|
|
/**
|
|
|
|
* Triggers the cart fees API, grabs the list of fees, and calculates taxes.
|
|
|
|
*
|
|
|
|
* Note: This class sets the totals for the 'object' as they are calculated. This is so that APIs like the fees API can see these totals if needed.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function calculate_fee_totals() {
|
2017-07-27 09:49:47 +00:00
|
|
|
$this->set_fees();
|
|
|
|
$this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
|
|
|
|
$this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
|
2017-07-26 11:44:06 +00:00
|
|
|
|
|
|
|
foreach ( $this->fees as $fee_key => $fee ) {
|
2017-07-27 09:51:08 +00:00
|
|
|
$this->object->fees[ $fee_key ]->tax = wc_remove_number_precision_deep( $fee->total_tax );
|
|
|
|
$this->object->fees[ $fee_key ]->tax_data = wc_remove_number_precision_deep( $fee->taxes );
|
2017-07-26 11:44:06 +00:00
|
|
|
}
|
2017-07-27 09:51:08 +00:00
|
|
|
$this->object->fee_total = wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
|
2017-07-26 11:44:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate any shipping taxes.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function calculate_shipping_totals() {
|
2017-07-27 09:49:47 +00:00
|
|
|
$this->set_shipping();
|
|
|
|
$this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) );
|
|
|
|
$this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) );
|
2017-07-27 10:01:48 +00:00
|
|
|
|
2017-07-26 11:44:06 +00:00
|
|
|
$this->object->shipping_total = $this->get_total( 'shipping_total' );
|
|
|
|
$this->object->shipping_tax_total = $this->get_total( 'shipping_tax_total' );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main cart totals.
|
|
|
|
*
|
|
|
|
* @since 3.2.0
|
|
|
|
*/
|
|
|
|
protected function calculate_totals() {
|
2017-07-27 09:49:47 +00:00
|
|
|
$this->set_total( 'taxes', $this->get_merged_taxes() );
|
|
|
|
$this->set_total( 'tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'tax_total' ) ) );
|
|
|
|
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + $this->get_total( 'tax_total', true ) + $this->get_total( 'shipping_tax_total', true ) ) );
|
2017-07-27 10:01:48 +00:00
|
|
|
|
2017-07-27 12:48:58 +00:00
|
|
|
// Add totals to cart object.
|
2017-07-31 18:48:34 +00:00
|
|
|
$this->object->taxes = wp_list_pluck( $this->get_total( 'taxes' ), 'shipping_tax_total' );
|
|
|
|
$this->object->shipping_taxes = wp_list_pluck( $this->get_total( 'taxes' ), 'tax_total' );
|
|
|
|
$this->object->cart_contents_total = $this->get_total( 'items_total' );
|
|
|
|
$this->object->tax_total = $this->get_total( 'tax_total' );
|
|
|
|
$this->object->total = $this->get_total( 'total' );
|
|
|
|
$this->object->discount_cart = $this->get_total( 'discounts_total' );
|
|
|
|
$this->object->discount_cart_tax = $this->get_total( 'discounts_tax_total' );
|
2017-07-27 12:48:58 +00:00
|
|
|
|
|
|
|
// Allow plugins to hook and alter totals before final total is calculated.
|
|
|
|
if ( has_action( 'woocommerce_calculate_totals' ) ) {
|
|
|
|
do_action( 'woocommerce_calculate_totals', $this->object );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
|
2017-07-31 20:18:39 +00:00
|
|
|
$totals_to_sum = wc_add_number_precision_deep( array( $this->object->cart_contents_total, $this->object->tax_total, $this->object->shipping_tax_total, $this->object->shipping_total, $this->object->fee_total ) );
|
|
|
|
$this->object->total = max( 0, apply_filters( 'woocommerce_calculated_total', wc_remove_number_precision( round( array_sum( $totals_to_sum ) ) ), $this->object ) );
|
2017-07-26 11:44:06 +00:00
|
|
|
}
|
|
|
|
}
|