Remove order subclass
This commit is contained in:
parent
cfb04f0ead
commit
306db69eaf
|
@ -1001,22 +1001,20 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
|
|||
}
|
||||
|
||||
/**
|
||||
* Calculate taxes for all line items and shipping, and store the totals and tax rows.
|
||||
* Get tax location for this order.
|
||||
*
|
||||
* If by default the taxes are based on the shipping address and the current order doesn't
|
||||
* have any, it would use the billing address rather than using the Shopping base location.
|
||||
*
|
||||
* Will use the base country unless customer addresses are set.
|
||||
* @param $args array Added in 3.0.0 to pass things like location.
|
||||
* @since 3.2.0
|
||||
* @param $args array Override the location.
|
||||
* @return array
|
||||
*/
|
||||
public function calculate_taxes( $args = array() ) {
|
||||
protected function get_tax_location( $args = array() ) {
|
||||
$tax_based_on = get_option( 'woocommerce_tax_based_on' );
|
||||
|
||||
if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
|
||||
$tax_based_on = 'billing';
|
||||
}
|
||||
|
||||
$args = wp_parse_args( $args, array(
|
||||
$args = wp_parse_args( $args, array(
|
||||
'country' => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
|
||||
'state' => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
|
||||
'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
|
||||
|
@ -1032,75 +1030,38 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
|
|||
$args['city'] = '';
|
||||
}
|
||||
|
||||
// Calc taxes for line items
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate taxes for all line items and shipping, and store the totals and tax rows.
|
||||
*
|
||||
* If by default the taxes are based on the shipping address and the current order doesn't
|
||||
* have any, it would use the billing address rather than using the Shopping base location.
|
||||
*
|
||||
* Will use the base country unless customer addresses are set.
|
||||
*
|
||||
* @param array $args Added in 3.0.0 to pass things like location.
|
||||
*/
|
||||
public function calculate_taxes( $args = array() ) {
|
||||
$calculate_tax_for = $this->get_tax_location( $args );
|
||||
$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
|
||||
|
||||
if ( 'inherit' === $shipping_tax_class ) {
|
||||
$shipping_tax_class = current( array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() ) );
|
||||
}
|
||||
|
||||
// Trigger tax recalculation for all items.
|
||||
foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
|
||||
$tax_class = $item->get_tax_class();
|
||||
$tax_status = $item->get_tax_status();
|
||||
|
||||
if ( '0' !== $tax_class && 'taxable' === $tax_status && wc_tax_enabled() ) {
|
||||
$tax_rates = WC_Tax::find_rates( array(
|
||||
'country' => $args['country'],
|
||||
'state' => $args['state'],
|
||||
'postcode' => $args['postcode'],
|
||||
'city' => $args['city'],
|
||||
'tax_class' => $tax_class,
|
||||
) );
|
||||
|
||||
$total = $item->get_total();
|
||||
$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
|
||||
|
||||
if ( $item->is_type( 'line_item' ) ) {
|
||||
$subtotal = $item->get_subtotal();
|
||||
$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
|
||||
$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
|
||||
} else {
|
||||
$item->set_taxes( array( 'total' => $taxes ) );
|
||||
}
|
||||
} else {
|
||||
$item->set_taxes( false );
|
||||
}
|
||||
$item->calculate_taxes( $calculate_tax_for );
|
||||
$item->save();
|
||||
}
|
||||
|
||||
// Calc taxes for shipping
|
||||
foreach ( $this->get_shipping_methods() as $item_id => $item ) {
|
||||
if ( wc_tax_enabled() ) {
|
||||
$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
|
||||
|
||||
// Inherit tax class from items
|
||||
if ( 'inherit' === $shipping_tax_class ) {
|
||||
$tax_rates = array();
|
||||
$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_class_slugs() );
|
||||
$found_tax_classes = $this->get_items_tax_classes();
|
||||
|
||||
foreach ( $tax_classes as $tax_class ) {
|
||||
if ( in_array( $tax_class, $found_tax_classes ) ) {
|
||||
$tax_rates = WC_Tax::find_shipping_rates( array(
|
||||
'country' => $args['country'],
|
||||
'state' => $args['state'],
|
||||
'postcode' => $args['postcode'],
|
||||
'city' => $args['city'],
|
||||
'tax_class' => $tax_class,
|
||||
) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$tax_rates = WC_Tax::find_shipping_rates( array(
|
||||
'country' => $args['country'],
|
||||
'state' => $args['state'],
|
||||
'postcode' => $args['postcode'],
|
||||
'city' => $args['city'],
|
||||
'tax_class' => $shipping_tax_class,
|
||||
) );
|
||||
}
|
||||
|
||||
$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
|
||||
} else {
|
||||
$item->set_taxes( false );
|
||||
}
|
||||
$item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
|
||||
$item->save();
|
||||
}
|
||||
|
||||
$this->update_taxes();
|
||||
}
|
||||
|
||||
|
@ -1150,7 +1111,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
|
|||
$this->add_item( $item );
|
||||
}
|
||||
|
||||
// Save tax totals
|
||||
// Save tax totals.
|
||||
$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
|
||||
$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
|
||||
$this->save();
|
||||
|
|
|
@ -1,511 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Order/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_Totals class.
|
||||
*
|
||||
* @todo woocommerce_tax_round_at_subtotal option - how should we handle this with precision?
|
||||
* @todo Manual discounts.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
abstract class WC_Totals {
|
||||
|
||||
/**
|
||||
* Reference to cart or order object.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected $object;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Precision so we can work in cents.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var int
|
||||
*/
|
||||
protected $precision = 1;
|
||||
|
||||
/**
|
||||
* Whether to calculate taxes when calculating totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var bool
|
||||
*/
|
||||
protected $calculate_taxes = true;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets up the items provided, and calculate totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param object $object Cart or order object to calculate totals for.
|
||||
*/
|
||||
public function __construct( &$object = null ) {
|
||||
$this->precision = pow( 10, wc_get_price_decimals() );
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all calculations methods on the given items in sequence. @todo More documentation, and add other calculation methods for taxes and totals only?
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param bool $calculate_taxes Whether to calculate taxes (optional).
|
||||
*/
|
||||
public function calculate( $calculate_taxes = true ) {
|
||||
$this->calculate_taxes = (bool) $calculate_taxes;
|
||||
|
||||
$this->calculate_item_totals();
|
||||
$this->calculate_fee_totals();
|
||||
$this->calculate_shipping_totals();
|
||||
$this->calculate_totals();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a cart or order object passed in for calculation. Normalises data
|
||||
* into the same format for use by this class.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function set_items() {
|
||||
$this->items = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and normalise fees.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function set_fees() {
|
||||
$this->fees = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping methods and normalise.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function set_shipping() {
|
||||
$this->shipping = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set array of coupon objects from the cart or an order.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function set_coupons() {
|
||||
$this->coupons = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add precision (deep) to a price.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param int|array $value Value to remove precision from.
|
||||
* @return float
|
||||
*/
|
||||
protected function add_precision( $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $subvalue ) {
|
||||
$value[ $key ] = $this->add_precision( $subvalue );
|
||||
}
|
||||
} else {
|
||||
$value = $value * $this->precision;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove precision (deep) from a price.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param int|array $value Value to remove precision from.
|
||||
* @return float
|
||||
*/
|
||||
protected function remove_precision( $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $subvalue ) {
|
||||
$value[ $key ] = $this->remove_precision( $subvalue );
|
||||
}
|
||||
} else {
|
||||
$value = wc_format_decimal( $value / $this->precision, wc_get_price_decimals() );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
$item_tax_rates = $this->get_item_tax_rates( $item );
|
||||
|
||||
if ( $item_tax_rates !== $base_tax_rates ) {
|
||||
// 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 ) {
|
||||
return $this->items[ $item_key ]->subtotal - $this->discount_totals[ $item_key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
return $in_cents ? $this->totals : array_map( array( $this, 'remove_precision' ), $this->totals );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_item_totals() {
|
||||
$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;
|
||||
|
||||
if ( $this->calculate_taxes && wc_tax_enabled() && $item->product->is_taxable() ) {
|
||||
$item->taxes = WC_Tax::calc_tax( $item->total, $this->get_item_tax_rates( $item ), $item->price_includes_tax );
|
||||
$item->total_tax = array_sum( $item->taxes );
|
||||
|
||||
if ( $item->price_includes_tax ) {
|
||||
$item->total = $item->total - $item->total_tax;
|
||||
} else {
|
||||
$item->total = $item->total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
|
||||
if ( $this->calculate_taxes ) {
|
||||
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
foreach ( $this->items as $item ) {
|
||||
if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
|
||||
$item = $this->adjust_non_base_location_price( $item );
|
||||
}
|
||||
if ( $this->calculate_taxes && wc_tax_enabled() && $item->product->is_taxable() ) {
|
||||
$subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $this->get_item_tax_rates( $item ), $item->price_includes_tax );
|
||||
$item->subtotal_tax = array_sum( $subtotal_taxes );
|
||||
|
||||
if ( $item->price_includes_tax ) {
|
||||
$item->subtotal = $item->subtotal - $item->subtotal_tax;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) );
|
||||
if ( $this->calculate_taxes ) {
|
||||
$this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate all discount and coupon amounts.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @uses WC_Discounts class.
|
||||
*/
|
||||
protected function calculate_discounts() {
|
||||
$this->set_coupons();
|
||||
|
||||
$discounts = new WC_Discounts( $this->items );
|
||||
|
||||
foreach ( $this->coupons as $coupon ) {
|
||||
$discounts->apply_coupon( $coupon );
|
||||
}
|
||||
|
||||
$this->discount_totals = $discounts->get_discounts( true );
|
||||
$this->totals['discounts_total'] = array_sum( $this->discount_totals );
|
||||
|
||||
// See how much tax was 'discounted'.
|
||||
if ( $this->calculate_taxes && wc_tax_enabled() ) {
|
||||
foreach ( $this->discount_totals as $cart_item_key => $discount ) {
|
||||
$item = $this->items[ $cart_item_key ];
|
||||
if ( $item->product->is_taxable() ) {
|
||||
$tax_rates = $this->get_item_tax_rates( $item );
|
||||
$taxes = WC_Tax::calc_tax( $discount, $tax_rates, false );
|
||||
$this->totals['discounts_tax_total'] += array_sum( $taxes );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
$this->set_fees();
|
||||
$this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
|
||||
if ( $this->calculate_taxes ) {
|
||||
$this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate any shipping taxes.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_shipping_totals() {
|
||||
$this->set_shipping();
|
||||
$this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) );
|
||||
if ( $this->calculate_taxes ) {
|
||||
$this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main cart totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_totals() {
|
||||
if ( $this->calculate_taxes ) {
|
||||
$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 ) ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,25 +19,156 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* @todo woocommerce_calculate_totals action for carts.
|
||||
* @todo woocommerce_calculated_total filter for carts.
|
||||
* @todo record coupon totals and counts for cart.
|
||||
* @todo woocommerce_tax_round_at_subtotal option - how should we handle this with precision?
|
||||
* @todo Manual discounts.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
final class WC_Cart_Totals extends WC_Totals {
|
||||
final class WC_Cart_Totals {
|
||||
|
||||
/**
|
||||
* Reference to cart or order object.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected $object;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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,
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets up the items provided, and calculate totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param object $object Cart or order object to calculate totals for.
|
||||
* @param object $cart Cart object to calculate totals for.
|
||||
*/
|
||||
public function __construct( &$object = null ) {
|
||||
parent::__construct( $object );
|
||||
public function __construct( &$cart = null ) {
|
||||
$this->object = $object;
|
||||
|
||||
if ( is_a( $object, 'WC_Cart' ) ) {
|
||||
if ( is_a( $cart, 'WC_Cart' ) ) {
|
||||
$this->calculate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all calculations methods on the given items in sequence. @todo More documentation, and add other calculation methods for taxes and totals only?
|
||||
*
|
||||
* @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(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a cart or order object passed in for calculation. Normalises data
|
||||
* into the same format for use by this class.
|
||||
|
@ -118,17 +249,219 @@ final class WC_Cart_Totals extends WC_Totals {
|
|||
}
|
||||
|
||||
/**
|
||||
* Totals are costs after discounts.
|
||||
* 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 );
|
||||
$item_tax_rates = $this->get_item_tax_rates( $item );
|
||||
|
||||
if ( $item_tax_rates !== $base_tax_rates ) {
|
||||
// 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 ) {
|
||||
return $this->items[ $item_key ]->subtotal - $this->discount_totals[ $item_key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
return $in_cents ? $this->totals : array_map( array( $this, 'remove_precision' ), $this->totals );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_item_totals() {
|
||||
parent::calculate_item_totals();
|
||||
$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;
|
||||
|
||||
if ( wc_tax_enabled() && $item->product->is_taxable() ) {
|
||||
$item->taxes = WC_Tax::calc_tax( $item->total, $this->get_item_tax_rates( $item ), $item->price_includes_tax );
|
||||
$item->total_tax = array_sum( $item->taxes );
|
||||
|
||||
if ( $item->price_includes_tax ) {
|
||||
$item->total = $item->total - $item->total_tax;
|
||||
} else {
|
||||
$item->total = $item->total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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' ) ) ) );
|
||||
$this->object->subtotal = $this->get_total( 'items_total' ) + $this->get_total( 'items_total_tax' );
|
||||
$this->object->subtotal_ex_tax = $this->get_total( 'items_total' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
foreach ( $this->items as $item ) {
|
||||
if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
|
||||
$item = $this->adjust_non_base_location_price( $item );
|
||||
}
|
||||
if ( wc_tax_enabled() && $item->product->is_taxable() ) {
|
||||
$subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $this->get_item_tax_rates( $item ), $item->price_includes_tax );
|
||||
$item->subtotal_tax = array_sum( $subtotal_taxes );
|
||||
|
||||
if ( $item->price_includes_tax ) {
|
||||
$item->subtotal = $item->subtotal - $item->subtotal_tax;
|
||||
}
|
||||
}
|
||||
}
|
||||
$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' ) ) ) );
|
||||
$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();
|
||||
|
||||
$discounts = new WC_Discounts( $this->items );
|
||||
|
||||
foreach ( $this->coupons as $coupon ) {
|
||||
$discounts->apply_coupon( $coupon );
|
||||
}
|
||||
|
||||
$this->discount_totals = $discounts->get_discounts( true );
|
||||
$this->totals['discounts_total'] = array_sum( $this->discount_totals );
|
||||
|
||||
// See how much tax was 'discounted'.
|
||||
if ( wc_tax_enabled() ) {
|
||||
foreach ( $this->discount_totals as $cart_item_key => $discount ) {
|
||||
$item = $this->items[ $cart_item_key ];
|
||||
if ( $item->product->is_taxable() ) {
|
||||
$tax_rates = $this->get_item_tax_rates( $item );
|
||||
$taxes = WC_Tax::calc_tax( $discount, $tax_rates, false );
|
||||
$this->totals['discounts_tax_total'] += array_sum( $taxes );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the cart fees API, grabs the list of fees, and calculates taxes.
|
||||
*
|
||||
|
@ -137,7 +470,9 @@ final class WC_Cart_Totals extends WC_Totals {
|
|||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_fee_totals() {
|
||||
parent::calculate_fee_totals();
|
||||
$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' ) ) );
|
||||
|
||||
foreach ( $this->fees as $fee_key => $fee ) {
|
||||
$this->object->fees[ $fee_key ]->tax = $this->remove_precision( $fee->total_tax );
|
||||
|
@ -152,8 +487,9 @@ final class WC_Cart_Totals extends WC_Totals {
|
|||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_shipping_totals() {
|
||||
parent::calculate_shipping_totals();
|
||||
|
||||
$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' ) ) );
|
||||
$this->object->shipping_total = $this->get_total( 'shipping_total' );
|
||||
$this->object->shipping_tax_total = $this->get_total( 'shipping_tax_total' );
|
||||
}
|
||||
|
@ -164,9 +500,8 @@ final class WC_Cart_Totals extends WC_Totals {
|
|||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_totals() {
|
||||
parent::calculate_totals();
|
||||
|
||||
$this->object->tax_total = $this->get_total( 'tax_total' );
|
||||
$this->object->total = $this->get_total( 'total' );
|
||||
$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 ) ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,27 @@ class WC_Order_Item_Shipping extends WC_Order_Item {
|
|||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculate item taxes.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param array $calculate_tax_for Location data to get taxes for. Required.
|
||||
* @return bool True if taxes were calculated.
|
||||
*/
|
||||
public function calculate_taxes( $calculate_tax_for = array() ) {
|
||||
if ( ! isset( $calculate_tax_for['country'], $calculate_tax_for['state'], $calculate_tax_for['postcode'], $calculate_tax_for['city'], $calculate_tax_for['tax_class'] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( wc_tax_enabled() ) {
|
||||
$tax_rates = WC_Tax::find_shipping_rates( $calculate_tax_for );
|
||||
$taxes = WC_Tax::calc_tax( $this->get_total(), $tax_rates, false );
|
||||
$this->set_taxes( array( 'total' => $taxes ) );
|
||||
} else {
|
||||
$this->set_taxes( false );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Setters
|
||||
|
|
|
@ -152,7 +152,8 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
|
|||
*/
|
||||
|
||||
/**
|
||||
* Type checking
|
||||
* Type checking.
|
||||
*
|
||||
* @param string|array $type
|
||||
* @return boolean
|
||||
*/
|
||||
|
@ -160,6 +161,34 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
|
|||
return is_array( $type ) ? in_array( $this->get_type(), $type ) : $type === $this->get_type();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate item taxes.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param array $calculate_tax_for Location data to get taxes for. Required.
|
||||
* @return bool True if taxes were calculated.
|
||||
*/
|
||||
public function calculate_taxes( $calculate_tax_for = array() ) {
|
||||
if ( ! isset( $calculate_tax_for['country'], $calculate_tax_for['state'], $calculate_tax_for['postcode'], $calculate_tax_for['city'] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( '0' !== $this->get_tax_class() && 'taxable' === $this->get_tax_status() && wc_tax_enabled() ) {
|
||||
$calculate_tax_for['tax_class'] = $this->get_tax_class();
|
||||
$tax_rates = WC_Tax::find_rates( $calculate_tax_for );
|
||||
$taxes = WC_Tax::calc_tax( $this->get_total(), $tax_rates, false );
|
||||
|
||||
if ( method_exists( $this, 'get_subtotal' ) ) {
|
||||
$subtotal_taxes = WC_Tax::calc_tax( $this->get_subtotal(), $tax_rates, false );
|
||||
$this->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
|
||||
} else {
|
||||
$this->set_taxes( array( 'total' => $taxes ) );
|
||||
}
|
||||
} else {
|
||||
$this->set_taxes( false );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Meta Data Handling
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Order 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_Order_Totals class.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
final class WC_Order_Totals extends WC_Totals {
|
||||
|
||||
/**
|
||||
* Sets up the items provided, and calculate totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param object $object Cart or order object to calculate totals for.
|
||||
*/
|
||||
public function __construct( &$object = null ) {
|
||||
parent::__construct( $object );
|
||||
|
||||
if ( is_a( $object, 'WC_Order' ) ) {
|
||||
// Get items from the order. @todo call calculate or make it manual?
|
||||
$this->set_items();
|
||||
$this->set_fees();
|
||||
$this->set_shipping();
|
||||
$this->set_coupons();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an order object passed in for calculation. Normalises data into the same format for use by this class.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function set_items() {
|
||||
$this->items = array();
|
||||
|
||||
foreach ( $this->object->get_items() as $item_key => $item_object ) {
|
||||
$item = $this->get_default_item_props();
|
||||
$item_taxes = $item_object->get_taxes();
|
||||
$item->object = $item_object;
|
||||
$item->product = $item_object->get_product();
|
||||
$item->quantity = $item_object->get_quantity();
|
||||
$item->subtotal = $this->add_precision( $item_object->get_subtotal() );
|
||||
$item->subtotal_tax = $this->add_precision( $item_object->get_subtotal_tax() );
|
||||
$item->total = $this->add_precision( $item_object->get_total() );
|
||||
$item->total_tax = $this->add_precision( $item_object->get_total_tax() );
|
||||
$item->taxes = $this->add_precision( $item_taxes['total'] );
|
||||
$this->items[ $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();
|
||||
|
||||
foreach ( $this->object->get_fees() as $fee_key => $fee_object ) {
|
||||
$fee = $this->get_default_fee_props();
|
||||
$fee->object = $fee_object;
|
||||
$fee->total = $this->add_precision( $fee_object->get_total() );
|
||||
$fee->taxes = $this->add_precision( $fee_object->get_taxes() );
|
||||
$fee->total_tax = $this->add_precision( $fee_object->get_total_tax() );
|
||||
$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->get_shipping_methods() as $key => $shipping_object ) {
|
||||
$shipping_line = $this->get_default_shipping_props();
|
||||
$shipping_line_taxes = $shipping_object->get_taxes();
|
||||
$shipping_line->object = $shipping_object;
|
||||
$shipping_line->total = $this->add_precision( $shipping_object->get_total() );
|
||||
$shipping_line->taxes = $this->add_precision( $shipping_line_taxes['total'] );
|
||||
$shipping_line->total_tax = $this->add_precision( $shipping_object->get_total_tax() );
|
||||
$this->shipping[ $key ] = $shipping_line;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(); @todo get_used_coupons = codes
|
||||
}
|
||||
|
||||
/**
|
||||
* Main cart totals. Set all order totals here after calculation.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_totals() {
|
||||
parent::calculate_totals();
|
||||
|
||||
$this->object->set_discount_total( $this->get_total( 'discounts_total' ) );
|
||||
$this->object->set_discount_tax( $this->get_total( 'discounts_tax_total' ) );
|
||||
$this->object->set_shipping_total( $this->get_total( 'shipping_total' ) );
|
||||
$this->object->set_shipping_tax( $this->get_total( 'shipping_tax_total' ) );
|
||||
$this->object->set_cart_tax( $this->get_total( 'tax_total' ) );
|
||||
$this->object->set_total( $this->get_total( 'total' ) );
|
||||
}
|
||||
}
|
|
@ -303,7 +303,6 @@ final class WooCommerce {
|
|||
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-log-handler.php' );
|
||||
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-deprecated-hooks.php' );
|
||||
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-session.php' );
|
||||
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-totals.php' );
|
||||
|
||||
/**
|
||||
* Core classes.
|
||||
|
@ -336,7 +335,6 @@ final class WooCommerce {
|
|||
include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' );
|
||||
include_once( WC_ABSPATH . 'includes/class-wc-discounts.php' );
|
||||
include_once( WC_ABSPATH . 'includes/class-wc-cart-totals.php' );
|
||||
include_once( WC_ABSPATH . 'includes/class-wc-order-totals.php' );
|
||||
|
||||
/**
|
||||
* Data stores - used to store and retrieve CRUD object data from the database.
|
||||
|
|
|
@ -1443,6 +1443,66 @@ function wc_get_rounding_precision() {
|
|||
return $precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add precision to a number and return an int.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param float $value Number to add precision to.
|
||||
* @return int
|
||||
*/
|
||||
function wc_add_number_precision( $value ) {
|
||||
$precision = pow( 10, wc_get_price_decimals() );
|
||||
return $value * $precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove precision from a number and return a float.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param float $value Number to add precision to.
|
||||
* @return float
|
||||
*/
|
||||
function wc_remove_number_precision( $value ) {
|
||||
$precision = pow( 10, wc_get_price_decimals() );
|
||||
return wc_format_decimal( $value / $precision, wc_get_price_decimals() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add precision to an array of number and return an array of int.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param array $value Number to add precision to.
|
||||
* @return int
|
||||
*/
|
||||
function wc_add_number_precision_deep( $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $subvalue ) {
|
||||
$value[ $key ] = wc_add_number_precision_deep( $subvalue );
|
||||
}
|
||||
} else {
|
||||
$value = wc_add_number_precision( $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove precision from an array of number and return an array of int.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param array $value Number to add precision to.
|
||||
* @return int
|
||||
*/
|
||||
function wc_remove_number_precision_deep( $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $subvalue ) {
|
||||
$value[ $key ] = wc_remove_number_precision_deep( $subvalue );
|
||||
}
|
||||
} else {
|
||||
$value = wc_remove_number_precision( $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shared logger instance.
|
||||
*
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for the totals class.
|
||||
*
|
||||
* @package WooCommerce\Tests\Discounts
|
||||
*/
|
||||
|
||||
/**
|
||||
* WC_Tests_Order_Totals
|
||||
*/
|
||||
class WC_Tests_Order_Totals extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Totals class for getter tests.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $totals;
|
||||
|
||||
/**
|
||||
* ID tracking for cleanup.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ids = array();
|
||||
|
||||
/**
|
||||
* Order being tested.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $order;
|
||||
|
||||
/**
|
||||
* Setup the cart for totals calculation.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->ids = array();
|
||||
|
||||
$tax_rate = array(
|
||||
'tax_rate_country' => '',
|
||||
'tax_rate_state' => '',
|
||||
'tax_rate' => '20.0000',
|
||||
'tax_rate_name' => 'VAT',
|
||||
'tax_rate_priority' => '1',
|
||||
'tax_rate_compound' => '0',
|
||||
'tax_rate_shipping' => '1',
|
||||
'tax_rate_order' => '1',
|
||||
'tax_rate_class' => '',
|
||||
);
|
||||
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
||||
update_option( 'woocommerce_calc_taxes', 'yes' );
|
||||
|
||||
$product = WC_Helper_Product::create_simple_product();
|
||||
$product2 = WC_Helper_Product::create_simple_product();
|
||||
|
||||
WC_Helper_Shipping::create_simple_flat_rate();
|
||||
$shipping_taxes = WC_Tax::calc_shipping_tax( '10', WC_Tax::get_shipping_tax_rates() );
|
||||
$rate = new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', $shipping_taxes, 'flat_rate' );
|
||||
$shipping_item = new WC_Order_Item_Shipping();
|
||||
$shipping_item->set_props( array(
|
||||
'method_title' => $rate->label,
|
||||
'method_id' => $rate->id,
|
||||
'total' => wc_format_decimal( $rate->cost ),
|
||||
'taxes' => $rate->taxes,
|
||||
) );
|
||||
foreach ( $rate->get_meta_data() as $key => $value ) {
|
||||
$shipping_item->add_meta_data( $key, $value, true );
|
||||
}
|
||||
|
||||
$coupon = new WC_Coupon;
|
||||
$coupon->set_code( 'test-coupon-10' );
|
||||
$coupon->set_amount( 10 );
|
||||
$coupon->set_discount_type( 'percent' );
|
||||
$coupon->save();
|
||||
|
||||
$this->ids['tax_rate_ids'][] = $tax_rate_id;
|
||||
$this->ids['products'][] = $product;
|
||||
$this->ids['products'][] = $product2;
|
||||
$this->ids['coupons'][] = $coupon;
|
||||
|
||||
$this->order = new WC_Order();
|
||||
$this->order->add_product( $product, 1 );
|
||||
$this->order->add_product( $product2, 2 );
|
||||
$this->order->add_item( $shipping_item );
|
||||
|
||||
// @todo add coupon
|
||||
// @todo add fee
|
||||
|
||||
$this->order->save();
|
||||
|
||||
$this->totals = new WC_Order_Totals( $this->order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after test.
|
||||
*/
|
||||
public function tearDown() {
|
||||
$this->order->delete();
|
||||
WC_Helper_Shipping::delete_simple_flat_rate();
|
||||
update_option( 'woocommerce_calc_taxes', 'no' );
|
||||
remove_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) );
|
||||
|
||||
foreach ( $this->ids['products'] as $product ) {
|
||||
$product->delete( true );
|
||||
}
|
||||
|
||||
foreach ( $this->ids['coupons'] as $coupon ) {
|
||||
$coupon->delete( true );
|
||||
wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'coupons' ) . 'coupon_id_from_code_' . $coupon->get_code(), 'coupons' );
|
||||
}
|
||||
|
||||
foreach ( $this->ids['tax_rate_ids'] as $tax_rate_id ) {
|
||||
WC_Tax::_delete_tax_rate( $tax_rate_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that order totals get updated.
|
||||
*/
|
||||
public function test_order_totals() {
|
||||
$this->totals->calculate();
|
||||
|
||||
// $10 item + ($10*2) item + $10 shipping + (.2*$40) tax = $48.00
|
||||
// These tests will need to be updated when coupons + fees get added.
|
||||
|
||||
$this->assertEquals( 48.00, $this->order->get_total() );
|
||||
$this->assertEquals( 10.00, $this->order->get_shipping_total() );
|
||||
$this->assertEquals( 2.00, $this->order->get_shipping_tax() );
|
||||
$this->assertEquals( 6.00, $this->order->get_cart_tax() );
|
||||
$this->assertEquals( 8.00, $this->order->get_total_tax() );
|
||||
$this->assertEquals( 0, $this->order->get_discount_total() );
|
||||
|
||||
// Calculating ex tax shouldn't modify existing tax fields.
|
||||
$this->totals->calculate( false );
|
||||
|
||||
$this->assertEquals( 48.00, $this->order->get_total() );
|
||||
$this->assertEquals( 10.00, $this->order->get_shipping_total() );
|
||||
$this->assertEquals( 2.00, $this->order->get_shipping_tax() );
|
||||
$this->assertEquals( 6.00, $this->order->get_cart_tax() );
|
||||
$this->assertEquals( 8.00, $this->order->get_total_tax() );
|
||||
$this->assertEquals( 0, $this->order->get_discount_total() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that non-tax order fields get updated in ex tax calculate.
|
||||
*/
|
||||
public function test_order_totals_ex_taxes() {
|
||||
$this->totals->calculate( false );
|
||||
|
||||
$this->assertEquals( 10.00, $this->order->get_shipping_total() );
|
||||
$this->assertEquals( 40.00, $this->order->get_subtotal() );
|
||||
$this->assertEquals( 0, $this->order->get_discount_total() );
|
||||
|
||||
// These don't get calculated when not calculating taxes.
|
||||
$this->assertEquals( 0, $this->order->get_shipping_tax() );
|
||||
$this->assertEquals( 0, $this->order->get_cart_tax() );
|
||||
$this->assertEquals( 0, $this->order->get_total_tax() );
|
||||
$this->assertEquals( 0, $this->order->get_total() );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue