PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.2
WooCommerce v10.9.2
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / class-wc-cart-totals.php
woocommerce / includes Last commit date
abstracts 1 month ago admin 1 week ago blocks 11 months ago cli 1 month ago customizer 4 months ago data-stores 1 month ago emails 1 month ago export 1 year ago gateways 1 month ago import 3 months ago integrations 2 months ago interfaces 4 months ago legacy 4 months ago libraries 1 month ago log-handlers 1 year ago payment-tokens 5 years ago product-usage 1 year ago queue 4 months ago react-admin 1 month ago rest-api 1 month ago shipping 3 months ago shortcodes 1 month ago theme-support 2 years ago tracks 1 month ago traits 5 years ago walkers 5 years ago wccom-site 1 month ago widgets 2 months ago class-wc-ajax.php 1 month ago class-wc-auth.php 1 year ago class-wc-autoloader.php 9 months ago class-wc-background-emailer.php 2 months ago class-wc-background-updater.php 5 years ago class-wc-brands-brand-settings-manager.php 1 year ago class-wc-brands-coupons.php 1 year ago class-wc-brands.php 1 month ago class-wc-breadcrumb.php 4 months ago class-wc-cache-helper.php 1 month ago class-wc-cart-fees.php 2 years ago class-wc-cart-session.php 3 months ago class-wc-cart-totals.php 11 months ago class-wc-cart.php 1 month ago class-wc-checkout.php 1 month ago class-wc-cli.php 10 months ago class-wc-comments.php 4 months ago class-wc-countries.php 2 months ago class-wc-coupon.php 2 months ago class-wc-customer-download-log.php 5 years ago class-wc-customer-download.php 1 year ago class-wc-customer.php 1 month ago class-wc-data-exception.php 8 years ago class-wc-data-store.php 3 years ago class-wc-datetime.php 4 years ago class-wc-deprecated-action-hooks.php 2 years ago class-wc-deprecated-filter-hooks.php 3 months ago class-wc-discounts.php 11 months ago class-wc-download-handler.php 1 month ago class-wc-emails.php 1 month ago class-wc-embed.php 1 year ago class-wc-form-handler.php 4 months ago class-wc-frontend-scripts.php 2 months ago class-wc-geo-ip.php 9 months ago class-wc-geolite-integration.php 6 years ago class-wc-geolocation.php 2 months ago class-wc-https.php 3 years ago class-wc-install.php 1 week ago class-wc-integrations.php 5 years ago class-wc-log-levels.php 2 years ago class-wc-logger.php 4 months ago class-wc-meta-data.php 1 month ago class-wc-order-factory.php 2 months ago class-wc-order-item-coupon.php 4 years ago class-wc-order-item-fee.php 5 months ago class-wc-order-item-meta.php 4 years ago class-wc-order-item-product.php 1 month ago class-wc-order-item-shipping.php 5 months ago class-wc-order-item-tax.php 1 month ago class-wc-order-item.php 1 month ago class-wc-order-query.php 1 month ago class-wc-order-refund.php 1 year ago class-wc-order.php 1 month ago class-wc-payment-gateways.php 1 month ago class-wc-payment-tokens.php 3 years ago class-wc-post-data.php 2 months ago class-wc-post-types.php 1 month ago class-wc-privacy-background-process.php 1 year ago class-wc-privacy-erasers.php 10 months ago class-wc-privacy-exporters.php 4 years ago class-wc-privacy.php 1 year ago class-wc-product-attribute.php 4 months ago class-wc-product-download.php 4 months ago class-wc-product-external.php 1 year ago class-wc-product-factory.php 3 months ago class-wc-product-grouped.php 3 months ago class-wc-product-query.php 4 months ago class-wc-product-simple.php 11 months ago class-wc-product-variable.php 1 month ago class-wc-product-variation.php 1 month ago class-wc-query.php 1 month ago class-wc-rate-limiter.php 4 years ago class-wc-regenerate-images-request.php 3 years ago class-wc-regenerate-images.php 2 years ago class-wc-register-wp-admin-settings.php 5 years ago class-wc-rest-authentication.php 2 years ago class-wc-rest-exception.php 5 years ago class-wc-session-handler.php 3 months ago class-wc-shipping-rate.php 1 year ago class-wc-shipping-zone.php 5 years ago class-wc-shipping-zones.php 7 months ago class-wc-shipping.php 2 months ago class-wc-shortcodes.php 1 year ago class-wc-structured-data.php 1 month ago class-wc-tax.php 2 months ago class-wc-template-loader.php 8 months ago class-wc-tracker.php 1 month ago class-wc-validation.php 2 years ago class-wc-webhook.php 1 month ago class-woocommerce.php 1 week ago wc-account-functions.php 1 month ago wc-attribute-functions.php 1 month ago wc-brands-functions.php 1 year ago wc-cart-functions.php 1 month ago wc-conditional-functions.php 11 months ago wc-core-functions.php 2 months ago wc-coupon-functions.php 5 months ago wc-deprecated-functions.php 1 month ago wc-formatting-functions.php 1 month ago wc-interactivity-api-functions.php 2 months ago wc-notice-functions.php 5 months ago wc-order-functions.php 2 months ago wc-order-item-functions.php 3 years ago wc-order-step-logger-functions.php 4 months ago wc-page-functions.php 2 months ago wc-product-functions.php 2 months ago wc-rest-functions.php 7 months ago wc-stock-functions.php 7 months ago wc-template-functions.php 1 month ago wc-template-hooks.php 10 months ago wc-term-functions.php 2 months ago wc-update-functions.php 1 week ago wc-user-functions.php 1 month ago wc-webhook-functions.php 1 month ago wc-widget-functions.php 5 years ago
class-wc-cart-totals.php
886 lines
1 <?php
2 /**
3 * Cart totals calculation class.
4 *
5 * Methods are protected and class is final to keep this as an internal API.
6 * May be opened in the future once structure is stable.
7 *
8 * Rounding guide:
9 * - if something is being stored e.g. item total, store unrounded. This is so taxes can be recalculated later accurately.
10 * - if calculating a total, round (if settings allow).
11 *
12 * @package WooCommerce\Classes
13 * @version 3.2.0
14 */
15
16 use Automattic\WooCommerce\Enums\ProductTaxStatus;
17 use Automattic\WooCommerce\Utilities\NumberUtil;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * WC_Cart_Totals class.
25 *
26 * @since 3.2.0
27 */
28 final class WC_Cart_Totals {
29 use WC_Item_Totals;
30
31 /**
32 * Reference to cart object.
33 *
34 * @since 3.2.0
35 * @var WC_Cart
36 */
37 protected $cart;
38
39 /**
40 * Line items to calculate.
41 *
42 * @since 3.2.0
43 * @var array
44 */
45 protected $items = array();
46
47 /**
48 * Fees to calculate.
49 *
50 * @since 3.2.0
51 * @var array
52 */
53 protected $fees = array();
54
55 /**
56 * Shipping costs.
57 *
58 * @since 3.2.0
59 * @var array
60 */
61 protected $shipping = array();
62
63 /**
64 * Applied coupon objects.
65 *
66 * @since 3.2.0
67 * @var array
68 */
69 protected $coupons = array();
70
71 /**
72 * Item/coupon discount totals.
73 *
74 * @since 3.2.0
75 * @var array
76 */
77 protected $coupon_discount_totals = array();
78
79 /**
80 * Item/coupon discount tax totals.
81 *
82 * @since 3.2.0
83 * @var array
84 */
85 protected $coupon_discount_tax_totals = array();
86
87 /**
88 * Should taxes be calculated?
89 *
90 * @var boolean
91 */
92 protected $calculate_tax = true;
93
94 /**
95 * Stores totals.
96 *
97 * @since 3.2.0
98 * @var array
99 */
100 protected $totals = array(
101 'fees_total' => 0,
102 'fees_total_tax' => 0,
103 'items_subtotal' => 0,
104 'items_subtotal_tax' => 0,
105 'items_total' => 0,
106 'items_total_tax' => 0,
107 'total' => 0,
108 'shipping_total' => 0,
109 'shipping_tax_total' => 0,
110 'discounts_total' => 0,
111 );
112
113 /**
114 * Cache of tax rates for a given tax class.
115 *
116 * @var array
117 */
118 protected $item_tax_rates;
119
120 /**
121 * Sets up the items provided, and calculate totals.
122 *
123 * @since 3.2.0
124 * @throws Exception If missing WC_Cart object.
125 * @param WC_Cart $cart Cart object to calculate totals for.
126 */
127 public function __construct( &$cart = null ) {
128 if ( ! is_a( $cart, 'WC_Cart' ) ) {
129 throw new Exception( 'A valid WC_Cart object is required' );
130 }
131
132 // Check if customer is VAT exempt, if customer is defined.
133 $customer = $cart->get_customer();
134 $is_customer_vat_exempt = $customer && $customer->get_is_vat_exempt();
135
136 $this->cart = $cart;
137 $this->calculate_tax = wc_tax_enabled() && ! $is_customer_vat_exempt;
138 $this->calculate();
139 }
140
141 /**
142 * Run all calculation methods on the given items in sequence.
143 *
144 * @since 3.2.0
145 */
146 protected function calculate() {
147 $this->calculate_item_totals();
148 $this->calculate_shipping_totals();
149 $this->calculate_fee_totals();
150 $this->calculate_totals();
151 }
152
153 /**
154 * Get default blank set of props used per item.
155 *
156 * @since 3.2.0
157 * @return array
158 */
159 protected function get_default_item_props() {
160 return (object) array(
161 'object' => null,
162 'tax_class' => '',
163 'taxable' => false,
164 'quantity' => 0,
165 'product' => false,
166 'price_includes_tax' => false,
167 'subtotal' => 0,
168 'subtotal_tax' => 0,
169 'subtotal_taxes' => array(),
170 'total' => 0,
171 'total_tax' => 0,
172 'taxes' => array(),
173 );
174 }
175
176 /**
177 * Get default blank set of props used per fee.
178 *
179 * @since 3.2.0
180 * @return array
181 */
182 protected function get_default_fee_props() {
183 return (object) array(
184 'object' => null,
185 'tax_class' => '',
186 'taxable' => false,
187 'total_tax' => 0,
188 'taxes' => array(),
189 );
190 }
191
192 /**
193 * Get default blank set of props used per shipping row.
194 *
195 * @since 3.2.0
196 * @return array
197 */
198 protected function get_default_shipping_props() {
199 return (object) array(
200 'object' => null,
201 'tax_class' => '',
202 'taxable' => false,
203 'total' => 0,
204 'total_tax' => 0,
205 'taxes' => array(),
206 );
207 }
208
209 /**
210 * Handles a cart or order object passed in for calculation. Normalises data
211 * into the same format for use by this class.
212 *
213 * Each item is made up of the following props, in addition to those returned by get_default_item_props() for totals.
214 * - key: An identifier for the item (cart item key or line item ID).
215 * - cart_item: For carts, the cart item from the cart which may include custom data.
216 * - quantity: The qty for this line.
217 * - price: The line price in cents.
218 * - product: The product object this cart item is for.
219 *
220 * @since 3.2.0
221 */
222 protected function get_items_from_cart() {
223 $this->items = array();
224
225 foreach ( $this->cart->get_cart() as $cart_item_key => $cart_item ) {
226 $item = $this->get_default_item_props();
227 $item->key = $cart_item_key;
228 $item->object = $cart_item;
229 $item->tax_class = $cart_item['data']->get_tax_class();
230 $item->taxable = ProductTaxStatus::TAXABLE === $cart_item['data']->get_tax_status();
231 $item->price_includes_tax = wc_prices_include_tax();
232 $item->quantity = $cart_item['quantity'];
233 $item->price = wc_add_number_precision_deep( (float) $cart_item['data']->get_price() * (float) $cart_item['quantity'] );
234 $item->product = $cart_item['data'];
235 $item->tax_rates = $this->get_item_tax_rates( $item );
236 $this->items[ $cart_item_key ] = $item;
237 }
238 }
239
240 /**
241 * Get item costs grouped by tax class.
242 *
243 * @since 3.2.0
244 * @return array
245 */
246 protected function get_tax_class_costs() {
247 $item_tax_classes = wp_list_pluck( $this->items, 'tax_class' );
248 $shipping_tax_classes = wp_list_pluck( $this->shipping, 'tax_class' );
249 $fee_tax_classes = wp_list_pluck( $this->fees, 'tax_class' );
250 $costs = array_fill_keys( $item_tax_classes + $shipping_tax_classes + $fee_tax_classes, 0 );
251 $costs['non-taxable'] = 0;
252
253 foreach ( $this->items + $this->fees + $this->shipping as $item ) {
254 if ( 0 > $item->total ) {
255 continue;
256 }
257 if ( ! $item->taxable ) {
258 $costs['non-taxable'] += $item->total;
259 } elseif ( 'inherit' === $item->tax_class ) {
260 $costs[ reset( $item_tax_classes ) ] += $item->total;
261 } else {
262 $costs[ $item->tax_class ] += $item->total;
263 }
264 }
265 return array_filter( $costs );
266 }
267
268 /**
269 * Get fee objects from the cart. Normalises data
270 * into the same format for use by this class.
271 *
272 * @since 3.2.0
273 */
274 protected function get_fees_from_cart() {
275 $this->fees = array();
276 $this->cart->calculate_fees();
277
278 $fee_running_total = 0;
279
280 foreach ( $this->cart->get_fees() as $fee_key => $fee_object ) {
281 $fee = $this->get_default_fee_props();
282 $fee->object = $fee_object;
283 $fee->tax_class = $fee->object->tax_class;
284 $fee->taxable = $fee->object->taxable;
285 $fee->total = wc_add_number_precision_deep( $fee->object->amount );
286
287 // Negative fees should not make the order total go negative.
288 if ( 0 > $fee->total ) {
289 $max_discount = NumberUtil::round( $this->get_total( 'items_total', true ) + $fee_running_total + $this->get_total( 'shipping_total', true ) ) * -1;
290
291 if ( $fee->total < $max_discount ) {
292 $fee->total = $max_discount;
293 }
294 }
295
296 $fee_running_total += $fee->total;
297
298 if ( $this->calculate_tax ) {
299 if ( 0 > $fee->total ) {
300 // Negative fees should have the taxes split between all items so it works as a true discount.
301 $tax_class_costs = $this->get_tax_class_costs();
302 $total_cost = array_sum( $tax_class_costs );
303
304 if ( $total_cost ) {
305 foreach ( $tax_class_costs as $tax_class => $tax_class_cost ) {
306 if ( 'non-taxable' === $tax_class ) {
307 continue;
308 }
309 $proportion = $tax_class_cost / $total_cost;
310 $cart_discount_proportion = $fee->total * $proportion;
311 $fee->taxes = wc_array_merge_recursive_numeric( $fee->taxes, WC_Tax::calc_tax( $fee->total * $proportion, WC_Tax::get_rates( $tax_class ) ) );
312 }
313 }
314 } elseif ( $fee->object->taxable ) {
315 $fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->tax_class, $this->cart->get_customer() ), false );
316 }
317 }
318
319 $fee->taxes = apply_filters( 'woocommerce_cart_totals_get_fees_from_cart_taxes', $fee->taxes, $fee, $this );
320 $fee->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $fee->taxes ) );
321
322 // Set totals within object.
323 $fee->object->total = wc_remove_number_precision_deep( $fee->total );
324 $fee->object->tax_data = wc_remove_number_precision_deep( $fee->taxes );
325 $fee->object->tax = wc_remove_number_precision_deep( $fee->total_tax );
326
327 $this->fees[ $fee_key ] = $fee;
328 }
329 }
330
331 /**
332 * Get shipping methods from the cart and normalise.
333 *
334 * @since 3.2.0
335 */
336 protected function get_shipping_from_cart() {
337 $default_shipping_props = $this->get_default_shipping_props();
338 $this->shipping = array_map(
339 function ( $shipping_object ) use ( $default_shipping_props ) {
340 $shipping_line = clone $default_shipping_props;
341 $shipping_line->object = $shipping_object;
342 $shipping_line->tax_class = get_option( 'woocommerce_shipping_tax_class', 'inherit' );
343 $shipping_line->taxable = true;
344 $shipping_line->total = wc_add_number_precision_deep( $shipping_object->cost );
345 $shipping_line->taxes = array_map(
346 array( $this, 'round_item_subtotal' ),
347 wc_add_number_precision_deep( $shipping_object->taxes, false )
348 );
349 $shipping_line->total_tax = array_sum( $shipping_line->taxes );
350 return $shipping_line;
351 },
352 $this->cart->calculate_shipping()
353 );
354 }
355
356 /**
357 * Return array of coupon objects from the cart. Normalises data
358 * into the same format for use by this class.
359 *
360 * @since 3.2.0
361 */
362 protected function get_coupons_from_cart() {
363 $this->coupons = $this->cart->get_coupons();
364
365 foreach ( $this->coupons as $coupon ) {
366 switch ( $coupon->get_discount_type() ) {
367 case 'fixed_product':
368 $coupon->sort = 1;
369 break;
370 case 'percent':
371 $coupon->sort = 2;
372 break;
373 case 'fixed_cart':
374 $coupon->sort = 3;
375 break;
376 default:
377 $coupon->sort = 0;
378 break;
379 }
380
381 // Allow plugins to override the default order.
382 $coupon->sort = apply_filters( 'woocommerce_coupon_sort', $coupon->sort, $coupon );
383 }
384
385 uasort( $this->coupons, array( $this, 'sort_coupons_callback' ) );
386 }
387
388 /**
389 * Sort coupons so discounts apply consistently across installs.
390 *
391 * In order of priority;
392 * - sort param
393 * - usage restriction
394 * - coupon value
395 * - ID
396 *
397 * @param WC_Coupon $a Coupon object.
398 * @param WC_Coupon $b Coupon object.
399 * @return int
400 */
401 protected function sort_coupons_callback( $a, $b ) {
402 if ( $a->sort === $b->sort ) {
403 if ( $a->get_limit_usage_to_x_items() === $b->get_limit_usage_to_x_items() ) {
404 if ( $a->get_amount() === $b->get_amount() ) {
405 return $b->get_id() - $a->get_id();
406 }
407 return ( $a->get_amount() < $b->get_amount() ) ? -1 : 1;
408 }
409 return ( $a->get_limit_usage_to_x_items() < $b->get_limit_usage_to_x_items() ) ? -1 : 1;
410 }
411 return ( $a->sort < $b->sort ) ? -1 : 1;
412 }
413
414 /**
415 * Ran to remove all base taxes from an item. Used when prices include tax, and the customer is tax exempt.
416 *
417 * @since 3.2.2
418 * @param object $item Item to adjust the prices of.
419 * @return object
420 */
421 protected function remove_item_base_taxes( $item ) {
422 if ( $item->price_includes_tax && $item->taxable ) {
423 if ( apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
424 $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'unfiltered' ) );
425 } else {
426 /**
427 * If we want all customers to pay the same price on this store, we should not remove base taxes from a VAT exempt user's price,
428 * but just the relevant tax rate. See issue #20911.
429 */
430 $base_tax_rates = $item->tax_rates;
431 }
432
433 // Work out a new base price without the shop's base tax.
434 $taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true );
435
436 // Now we have a new item price (excluding TAX).
437 $item->price = NumberUtil::round( $item->price - array_sum( $taxes ) );
438 $item->price_includes_tax = false;
439 }
440 return $item;
441 }
442
443 /**
444 * Only ran if woocommerce_adjust_non_base_location_prices is true.
445 *
446 * If the customer is outside of the base location, this removes the base
447 * taxes. This is off by default unless the filter is used.
448 *
449 * Uses edit context so unfiltered tax class is returned.
450 *
451 * @since 3.2.0
452 * @param object $item Item to adjust the prices of.
453 * @return object
454 */
455 protected function adjust_non_base_location_price( $item ) {
456 if ( $item->price_includes_tax && $item->taxable ) {
457 $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'unfiltered' ) );
458
459 if ( $item->tax_rates !== $base_tax_rates ) {
460 // Work out a new base price without the shop's base tax.
461 $taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true );
462 $new_taxes = WC_Tax::calc_tax( $item->price - array_sum( $taxes ), $item->tax_rates, false );
463
464 // Now we have a new item price.
465 $item->price = $item->price - array_sum( $taxes ) + array_sum( $new_taxes );
466 }
467 }
468 return $item;
469 }
470
471 /**
472 * Get discounted price of an item with precision (in cents).
473 *
474 * @since 3.2.0
475 * @param object $item_key Item to get the price of.
476 * @return int
477 */
478 protected function get_discounted_price_in_cents( $item_key ) {
479 $item = $this->items[ $item_key ];
480 $price = isset( $this->coupon_discount_totals[ $item_key ] ) ? $item->price - $this->coupon_discount_totals[ $item_key ] : $item->price;
481 return $price;
482 }
483
484 /**
485 * Get tax rates for an item. Caches rates in class to avoid multiple look ups.
486 *
487 * @param object $item Item to get tax rates for.
488 * @return array of taxes
489 */
490 protected function get_item_tax_rates( $item ) {
491 if ( ! wc_tax_enabled() ) {
492 return array();
493 }
494 $tax_class = $item->product->get_tax_class();
495 $item_tax_rates = 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->cart->get_customer() );
496
497 // Allow plugins to filter item tax rates.
498 return apply_filters( 'woocommerce_cart_totals_get_item_tax_rates', $item_tax_rates, $item, $this->cart );
499 }
500
501 /**
502 * Get item costs grouped by tax class.
503 *
504 * @since 3.2.0
505 * @return array
506 */
507 protected function get_item_costs_by_tax_class() {
508 $tax_classes = array(
509 'non-taxable' => 0,
510 );
511
512 foreach ( $this->items + $this->fees + $this->shipping as $item ) {
513 if ( ! isset( $tax_classes[ $item->tax_class ] ) ) {
514 $tax_classes[ $item->tax_class ] = 0;
515 }
516
517 if ( $item->taxable ) {
518 $tax_classes[ $item->tax_class ] += $item->total;
519 } else {
520 $tax_classes['non-taxable'] += $item->total;
521 }
522 }
523
524 return $tax_classes;
525 }
526
527 /**
528 * Get a single total with or without precision (in cents).
529 *
530 * @since 3.2.0
531 * @param string $key Total to get.
532 * @param bool $in_cents Should the totals be returned in cents, or without precision.
533 * @return int|float
534 */
535 public function get_total( $key = 'total', $in_cents = false ) {
536 $totals = $this->get_totals( $in_cents );
537 return isset( $totals[ $key ] ) ? $totals[ $key ] : 0;
538 }
539
540 /**
541 * Set a single total.
542 *
543 * @since 3.2.0
544 * @param string $key Total name you want to set.
545 * @param int $total Total to set.
546 */
547 protected function set_total( $key, $total ) {
548 $this->totals[ $key ] = $total;
549 }
550
551 /**
552 * Get all totals with or without precision (in cents).
553 *
554 * @since 3.2.0
555 * @param bool $in_cents Should the totals be returned in cents, or without precision.
556 * @return array.
557 */
558 public function get_totals( $in_cents = false ) {
559 return $in_cents ? $this->totals : wc_remove_number_precision_deep( $this->totals );
560 }
561
562 /**
563 * Returns array of values for totals calculation.
564 *
565 * @param string $field Field name. Will probably be `total` or `subtotal`.
566 * @return array Items object
567 */
568 protected function get_values_for_total( $field ) {
569 return array_values( wp_list_pluck( $this->items, $field ) );
570 }
571
572 /**
573 * Get taxes merged by type.
574 *
575 * @since 3.2.0
576 * @param bool $in_cents If returned value should be in cents.
577 * @param array|string $types Types to merge and return. Defaults to all.
578 * @return array
579 */
580 protected function get_merged_taxes( $in_cents = false, $types = array( 'items', 'fees', 'shipping' ) ) {
581 $items = array();
582 $taxes = array();
583
584 if ( is_string( $types ) ) {
585 $types = array( $types );
586 }
587
588 foreach ( $types as $type ) {
589 if ( isset( $this->$type ) ) {
590 $items = array_merge( $items, $this->$type );
591 }
592 }
593
594 foreach ( $items as $item ) {
595 foreach ( $item->taxes as $rate_id => $rate ) {
596 if ( ! isset( $taxes[ $rate_id ] ) ) {
597 $taxes[ $rate_id ] = 0;
598 }
599 $taxes[ $rate_id ] += $this->round_line_tax( $rate );
600 }
601 }
602
603 return $in_cents ? $taxes : wc_remove_number_precision_deep( $taxes );
604 }
605
606 /**
607 * Round merged taxes.
608 *
609 * @deprecated 3.9.0 `calculate_item_subtotals` should already appropriately round the tax values.
610 * @since 3.5.4
611 * @param array $taxes Taxes to round.
612 * @return array
613 */
614 protected function round_merged_taxes( $taxes ) {
615 foreach ( $taxes as $rate_id => $tax ) {
616 $taxes[ $rate_id ] = $this->round_line_tax( $tax );
617 }
618
619 return $taxes;
620 }
621
622 /**
623 * Combine item taxes into a single array, preserving keys.
624 *
625 * @since 3.2.0
626 * @param array $item_taxes Taxes to combine.
627 * @return array
628 */
629 protected function combine_item_taxes( $item_taxes ) {
630 $merged_taxes = array();
631 foreach ( $item_taxes as $taxes ) {
632 foreach ( $taxes as $tax_id => $tax_amount ) {
633 if ( ! isset( $merged_taxes[ $tax_id ] ) ) {
634 $merged_taxes[ $tax_id ] = 0;
635 }
636 $merged_taxes[ $tax_id ] += $tax_amount;
637 }
638 }
639 return $merged_taxes;
640 }
641
642 /*
643 |--------------------------------------------------------------------------
644 | Calculation methods.
645 |--------------------------------------------------------------------------
646 */
647
648 /**
649 * Calculate item totals.
650 *
651 * @since 3.2.0
652 */
653 protected function calculate_item_totals() {
654 $this->get_items_from_cart();
655 $this->calculate_item_subtotals();
656 $this->calculate_discounts();
657
658 foreach ( $this->items as $item_key => $item ) {
659 $item->total = $this->get_discounted_price_in_cents( $item_key );
660 $item->total_tax = 0;
661
662 if ( has_filter( 'woocommerce_get_discounted_price' ) ) {
663 $item->total = wc_add_number_precision(
664 /**
665 * Allow plugins to filter this price like in the legacy cart class.
666 *
667 * This is legacy and should probably be deprecated in the future.
668 * $item->object is the cart item object.
669 * $this->cart is the cart object.
670 *
671 * @since 3.2.0
672 */
673 (float) apply_filters( 'woocommerce_get_discounted_price', wc_remove_number_precision( $item->total ), $item->object, $this->cart )
674 );
675 }
676
677 if ( $this->calculate_tax && $item->product->is_taxable() ) {
678 $total_taxes = apply_filters( 'woocommerce_calculate_item_totals_taxes', WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax ), $item, $this );
679 $item->taxes = $total_taxes;
680 $item->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $item->taxes ) );
681
682 if ( $item->price_includes_tax ) {
683 // Use unrounded taxes so we can re-calculate from the orders screen accurately later.
684 $item->total = $item->total - array_sum( $item->taxes );
685 }
686 }
687
688 $this->cart->cart_contents[ $item_key ]['line_tax_data']['total'] = wc_remove_number_precision_deep( $item->taxes );
689 $this->cart->cart_contents[ $item_key ]['line_total'] = wc_remove_number_precision( $item->total );
690 $this->cart->cart_contents[ $item_key ]['line_tax'] = wc_remove_number_precision( $item->total_tax );
691 }
692
693 $items_total = $this->get_rounded_items_total( $this->get_values_for_total( 'total' ) );
694
695 $this->set_total( 'items_total', $items_total );
696 $this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
697
698 $this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
699 $this->cart->set_cart_contents_tax( array_sum( $this->get_merged_taxes( false, 'items' ) ) );
700 $this->cart->set_cart_contents_taxes( $this->get_merged_taxes( false, 'items' ) );
701 }
702
703 /**
704 * Subtotals are costs before discounts.
705 *
706 * To prevent rounding issues we need to work with the inclusive price where possible
707 * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would
708 * be 8.325 leading to totals being 1p off.
709 *
710 * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated
711 * afterwards.
712 *
713 * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
714 *
715 * @since 3.2.0
716 */
717 protected function calculate_item_subtotals() {
718 $merged_subtotal_taxes = array(); // Taxes indexed by tax rate ID for storage later.
719
720 $adjust_non_base_location_prices = apply_filters( 'woocommerce_adjust_non_base_location_prices', true );
721 $customer = $this->cart->get_customer();
722 $is_customer_vat_exempt = $customer && $customer->get_is_vat_exempt();
723
724 foreach ( $this->items as $item_key => $item ) {
725 if ( $item->price_includes_tax ) {
726 if ( $is_customer_vat_exempt ) {
727 $item = $this->remove_item_base_taxes( $item );
728 } elseif ( $adjust_non_base_location_prices ) {
729 $item = $this->adjust_non_base_location_price( $item );
730 }
731 }
732
733 $item->subtotal = $item->price;
734
735 if ( $this->calculate_tax && $item->product->is_taxable() ) {
736 $item->subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax );
737 $item->subtotal_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $item->subtotal_taxes ) );
738
739 if ( $item->price_includes_tax ) {
740 // Use unrounded taxes so we can re-calculate from the orders screen accurately later.
741 $item->subtotal = $item->subtotal - array_sum( $item->subtotal_taxes );
742 }
743
744 foreach ( $item->subtotal_taxes as $rate_id => $rate ) {
745 if ( ! isset( $merged_subtotal_taxes[ $rate_id ] ) ) {
746 $merged_subtotal_taxes[ $rate_id ] = 0;
747 }
748 $merged_subtotal_taxes[ $rate_id ] += $this->round_line_tax( $rate );
749 }
750 }
751
752 $this->cart->cart_contents[ $item_key ]['line_tax_data'] = array( 'subtotal' => wc_remove_number_precision_deep( $item->subtotal_taxes ) );
753 $this->cart->cart_contents[ $item_key ]['line_subtotal'] = wc_remove_number_precision( $item->subtotal );
754 $this->cart->cart_contents[ $item_key ]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
755 }
756
757 $items_subtotal = $this->get_rounded_items_total( $this->get_values_for_total( 'subtotal' ) );
758
759 // Prices are not rounded here because they should already be rounded based on settings in `get_rounded_items_total` and in `round_line_tax` method calls.
760 $this->set_total( 'items_subtotal', $items_subtotal );
761 $this->set_total( 'items_subtotal_tax', array_sum( $merged_subtotal_taxes ) );
762
763 $this->cart->set_subtotal( $this->get_total( 'items_subtotal' ) );
764 $this->cart->set_subtotal_tax( $this->get_total( 'items_subtotal_tax' ) );
765 }
766
767 /**
768 * Calculate COUPON based discounts which change item prices.
769 *
770 * @since 3.2.0
771 * @uses WC_Discounts class.
772 */
773 protected function calculate_discounts() {
774 $this->get_coupons_from_cart();
775
776 $discounts = new WC_Discounts( $this->cart );
777
778 // Set items directly so the discounts class can see any tax adjustments made thus far using subtotals.
779 $discounts->set_items( $this->items );
780
781 foreach ( $this->coupons as $coupon ) {
782 $discounts->apply_coupon( $coupon );
783 }
784
785 $coupon_discount_amounts = $discounts->get_discounts_by_coupon( true );
786 $coupon_discount_tax_amounts = array();
787
788 // See how much tax was 'discounted' per item and per coupon.
789 if ( $this->calculate_tax ) {
790 foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) {
791 $coupon_discount_tax_amounts[ $coupon_code ] = 0;
792
793 foreach ( $coupon_discounts as $item_key => $coupon_discount ) {
794 $item = $this->items[ $item_key ];
795
796 if ( $item->product->is_taxable() ) {
797 // Item subtotals were sent, so set 3rd param.
798 $item_tax = array_sum( WC_Tax::calc_tax( $coupon_discount, $item->tax_rates, $item->price_includes_tax ) );
799
800 // Sum total tax.
801 $coupon_discount_tax_amounts[ $coupon_code ] += $item_tax;
802
803 // Remove tax from discount total.
804 if ( $item->price_includes_tax ) {
805 $coupon_discount_amounts[ $coupon_code ] -= $item_tax;
806 }
807 }
808 }
809 }
810 }
811
812 $this->coupon_discount_totals = (array) $discounts->get_discounts_by_item( true );
813 $this->coupon_discount_tax_totals = $coupon_discount_tax_amounts;
814
815 if ( wc_prices_include_tax() ) {
816 $this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) - array_sum( $this->coupon_discount_tax_totals ) );
817 $this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
818 } else {
819 $this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) );
820 $this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
821 }
822
823 $this->cart->set_coupon_discount_totals( wc_remove_number_precision_deep( $coupon_discount_amounts ) );
824 $this->cart->set_coupon_discount_tax_totals( wc_remove_number_precision_deep( $coupon_discount_tax_amounts ) );
825
826 // Add totals to cart object. Note: Discount total for cart is excl tax.
827 $this->cart->set_discount_total( $this->get_total( 'discounts_total' ) );
828 $this->cart->set_discount_tax( $this->get_total( 'discounts_tax_total' ) );
829 }
830
831 /**
832 * Triggers the cart fees API, grabs the list of fees, and calculates taxes.
833 *
834 * 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.
835 *
836 * @since 3.2.0
837 */
838 protected function calculate_fee_totals() {
839 $this->get_fees_from_cart();
840
841 $this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
842 $this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
843
844 $this->cart->fees_api()->set_fees( wp_list_pluck( $this->fees, 'object' ) );
845 $this->cart->set_fee_total( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) ) );
846 $this->cart->set_fee_tax( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) ) );
847 $this->cart->set_fee_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->fees, 'taxes' ) ) ) );
848 }
849
850 /**
851 * Calculate any shipping taxes.
852 *
853 * @since 3.2.0
854 */
855 protected function calculate_shipping_totals() {
856 $this->get_shipping_from_cart();
857 $this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) );
858 $this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) );
859
860 $this->cart->set_shipping_total( $this->get_total( 'shipping_total' ) );
861 $this->cart->set_shipping_tax( $this->get_total( 'shipping_tax_total' ) );
862 $this->cart->set_shipping_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->shipping, 'taxes' ) ) ) );
863 }
864
865 /**
866 * Main cart totals.
867 *
868 * @since 3.2.0
869 */
870 protected function calculate_totals() {
871 $this->set_total( 'total', NumberUtil::round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ), 0 ) );
872 $items_tax = array_sum( $this->get_merged_taxes( false, array( 'items' ) ) );
873 // Shipping and fee taxes are rounded separately because they were entered excluding taxes (as opposed to item prices, which may or may not be including taxes depending upon settings).
874 $shipping_and_fee_taxes = NumberUtil::round( array_sum( $this->get_merged_taxes( false, array( 'fees', 'shipping' ) ) ), wc_get_price_decimals() );
875 $this->cart->set_total_tax( $items_tax + $shipping_and_fee_taxes );
876
877 // Allow plugins to hook and alter totals before final total is calculated.
878 if ( has_action( 'woocommerce_calculate_totals' ) ) {
879 do_action( 'woocommerce_calculate_totals', $this->cart );
880 }
881
882 // Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
883 $this->cart->set_total( max( 0, apply_filters( 'woocommerce_calculated_total', $this->get_total( 'total' ), $this->cart ) ) );
884 }
885 }
886