/** * Get rates. */ public function test_get_base_tax_rates() { global $wpdb; $wpdb->query("DELETE FROM {$wpdb->prefix}woocommerce_tax_rates"); $wpdb->query("DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations"); $tax_rate = array('tax_rate_country' => 'GB', '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); $tax_rates = WC_Tax::get_base_tax_rates(); $this->assertEquals($tax_rates, array($tax_rate_id => array('rate' => '20.0000', 'label' => 'VAT', 'shipping' => 'yes', 'compound' => 'no'))); WC_Tax::_delete_tax_rate($tax_rate_id); }
/** * Returns base tax rates for all tax classes * @return array */ public static function tax_rates() { $rates = array(); foreach (self::tax_classes() as $class => $label) { if ($rate = WC_Tax::get_base_tax_rates($class)) { // WC_Tax returns a assoc array with int as keys = world of pain in js // possibly change $key to $rate['id'] $rates[$class] = $rate; } } return $rates; }
/** * Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting. * Uses store base tax rates. Can work for a specific $qty for more accurate taxes. * * @param string $price to calculate, left blank to just use get_price() * @return string */ public function get_price_excluding_tax($qty = 1, $price = '') { if ($price === '') { $price = $this->get_price(); } if ($this->is_taxable() && get_option('woocommerce_prices_include_tax') === 'yes') { $tax_rates = WC_Tax::get_base_tax_rates($this->tax_class); $taxes = WC_Tax::calc_tax($price * $qty, $tax_rates, true); $price = WC_Tax::round($price * $qty - array_sum($taxes)); } else { $price = $price * $qty; } return apply_filters('woocommerce_get_price_excluding_tax', $price, $qty, $this); }
/** * Calculate totals for the items in the cart. */ public function calculate_totals() { $this->reset(); $this->coupons = $this->get_coupons(); do_action('woocommerce_before_calculate_totals', $this); if ($this->is_empty()) { $this->set_session(); return; } $tax_rates = array(); $shop_tax_rates = array(); $cart = $this->get_cart(); /** * Calculate subtotals for items. This is done first so that discount logic can use the values. */ foreach ($cart as $cart_item_key => $values) { $_product = $values['data']; $line_price = $_product->get_price() * $values['quantity']; $line_subtotal = 0; $line_subtotal_tax = 0; /** * No tax to calculate. */ if (!$_product->is_taxable()) { // Subtotal is the undiscounted price $this->subtotal += $line_price; $this->subtotal_ex_tax += $line_price; /** * Prices include tax. * * 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. */ } elseif ($this->prices_include_tax) { // Get base tax rates if (empty($shop_tax_rates[$_product->tax_class])) { $shop_tax_rates[$_product->tax_class] = WC_Tax::get_base_tax_rates($_product->tax_class); } // Get item tax rates if (empty($tax_rates[$_product->get_tax_class()])) { $tax_rates[$_product->get_tax_class()] = WC_Tax::get_rates($_product->get_tax_class()); } $base_tax_rates = $shop_tax_rates[$_product->tax_class]; $item_tax_rates = $tax_rates[$_product->get_tax_class()]; /** * ADJUST TAX - Calculations when base tax is not equal to the item tax. * * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. */ if ($item_tax_rates !== $base_tax_rates && apply_filters('woocommerce_adjust_non_base_location_prices', true)) { // Work out a new base price without the shop's base tax $taxes = WC_Tax::calc_tax($line_price, $base_tax_rates, true, true); // Now we have a new item price (excluding TAX) $line_subtotal = $line_price - array_sum($taxes); // Now add modified taxes $tax_result = WC_Tax::calc_tax($line_subtotal, $item_tax_rates); $line_subtotal_tax = array_sum($tax_result); /** * Regular tax calculation (customer inside base and the tax class is unmodified. */ } else { // Calc tax normally $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates, true); $line_subtotal_tax = array_sum($taxes); $line_subtotal = $line_price - array_sum($taxes); } /** * Prices exclude tax. * * This calculation is simpler - work with the base, untaxed price. */ } else { // Get item tax rates if (empty($tax_rates[$_product->get_tax_class()])) { $tax_rates[$_product->get_tax_class()] = WC_Tax::get_rates($_product->get_tax_class()); } $item_tax_rates = $tax_rates[$_product->get_tax_class()]; // Base tax for line before discount - we will store this in the order data $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates); $line_subtotal_tax = array_sum($taxes); $line_subtotal = $line_price; } // Add to main subtotal $this->subtotal += $line_subtotal + $line_subtotal_tax; $this->subtotal_ex_tax += $line_subtotal; } // Order cart items by price so coupon logic is 'fair' for customers and not based on order added to cart. uasort($cart, array($this, 'sort_by_subtotal')); /** * Calculate totals for items. */ foreach ($cart as $cart_item_key => $values) { $_product = $values['data']; // Prices $base_price = $_product->get_price(); $line_price = $_product->get_price() * $values['quantity']; // Tax data $taxes = array(); $discounted_taxes = array(); /** * No tax to calculate. */ if (!$_product->is_taxable()) { // Discounted Price (price with any pre-tax discounts applied) $discounted_price = $this->get_discounted_price($values, $base_price, true); $line_subtotal_tax = 0; $line_subtotal = $line_price; $line_tax = 0; $line_total = round($discounted_price * $values['quantity'], wc_get_rounding_precision()); /** * Prices include tax. */ } elseif ($this->prices_include_tax) { $base_tax_rates = $shop_tax_rates[$_product->tax_class]; $item_tax_rates = $tax_rates[$_product->get_tax_class()]; /** * ADJUST TAX - Calculations when base tax is not equal to the item tax. * * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. */ if ($item_tax_rates !== $base_tax_rates && apply_filters('woocommerce_adjust_non_base_location_prices', true)) { // Work out a new base price without the shop's base tax $taxes = WC_Tax::calc_tax($line_price, $base_tax_rates, true, true); // Now we have a new item price (excluding TAX) $line_subtotal = round($line_price - array_sum($taxes), wc_get_rounding_precision()); $taxes = WC_Tax::calc_tax($line_subtotal, $item_tax_rates); $line_subtotal_tax = array_sum($taxes); // Adjusted price (this is the price including the new tax rate) $adjusted_price = ($line_subtotal + $line_subtotal_tax) / $values['quantity']; // Apply discounts and get the discounted price FOR A SINGLE ITEM $discounted_price = $this->get_discounted_price($values, $adjusted_price, true); // Convert back to line price $discounted_line_price = $discounted_price * $values['quantity']; // Now use rounded line price to get taxes. $discounted_taxes = WC_Tax::calc_tax($discounted_line_price, $item_tax_rates, true); $line_tax = array_sum($discounted_taxes); $line_total = $discounted_line_price - $line_tax; /** * Regular tax calculation (customer inside base and the tax class is unmodified. */ } else { // Work out a new base price without the item tax $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates, true); // Now we have a new item price (excluding TAX) $line_subtotal = $line_price - array_sum($taxes); $line_subtotal_tax = array_sum($taxes); // Calc prices and tax (discounted) $discounted_price = $this->get_discounted_price($values, $base_price, true); // Convert back to line price $discounted_line_price = $discounted_price * $values['quantity']; // Now use rounded line price to get taxes. $discounted_taxes = WC_Tax::calc_tax($discounted_line_price, $item_tax_rates, true); $line_tax = array_sum($discounted_taxes); $line_total = $discounted_line_price - $line_tax; } // Tax rows - merge the totals we just got foreach (array_keys($this->taxes + $discounted_taxes) as $key) { $this->taxes[$key] = (isset($discounted_taxes[$key]) ? $discounted_taxes[$key] : 0) + (isset($this->taxes[$key]) ? $this->taxes[$key] : 0); } /** * Prices exclude tax. */ } else { $item_tax_rates = $tax_rates[$_product->get_tax_class()]; // Work out a new base price without the shop's base tax $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates); // Now we have the item price (excluding TAX) $line_subtotal = $line_price; $line_subtotal_tax = array_sum($taxes); // Now calc product rates $discounted_price = $this->get_discounted_price($values, $base_price, true); $discounted_taxes = WC_Tax::calc_tax($discounted_price * $values['quantity'], $item_tax_rates); $discounted_tax_amount = array_sum($discounted_taxes); $line_tax = $discounted_tax_amount; $line_total = $discounted_price * $values['quantity']; // Tax rows - merge the totals we just got foreach (array_keys($this->taxes + $discounted_taxes) as $key) { $this->taxes[$key] = (isset($discounted_taxes[$key]) ? $discounted_taxes[$key] : 0) + (isset($this->taxes[$key]) ? $this->taxes[$key] : 0); } } // Cart contents total is based on discounted prices and is used for the final total calculation $this->cart_contents_total += $line_total; /** * Store costs + taxes for lines. For tax inclusive prices, we do some extra rounding logic so the stored * values "add up" when viewing the order in admin. This does have the disadvatage of not being able to * recalculate the tax total/subtotal accurately in the future, but it does ensure the data looks correct. * * Tax exclusive prices are not affected. */ if (!$_product->is_taxable() || $this->prices_include_tax) { $this->cart_contents[$cart_item_key]['line_total'] = round($line_total + $line_tax - wc_round_tax_total($line_tax), $this->dp); $this->cart_contents[$cart_item_key]['line_subtotal'] = round($line_subtotal + $line_subtotal_tax - wc_round_tax_total($line_subtotal_tax), $this->dp); $this->cart_contents[$cart_item_key]['line_tax'] = wc_round_tax_total($line_tax); $this->cart_contents[$cart_item_key]['line_subtotal_tax'] = wc_round_tax_total($line_subtotal_tax); $this->cart_contents[$cart_item_key]['line_tax_data'] = array('total' => array_map('wc_round_tax_total', $discounted_taxes), 'subtotal' => array_map('wc_round_tax_total', $taxes)); } else { $this->cart_contents[$cart_item_key]['line_total'] = $line_total; $this->cart_contents[$cart_item_key]['line_subtotal'] = $line_subtotal; $this->cart_contents[$cart_item_key]['line_tax'] = $line_tax; $this->cart_contents[$cart_item_key]['line_subtotal_tax'] = $line_subtotal_tax; $this->cart_contents[$cart_item_key]['line_tax_data'] = array('total' => $discounted_taxes, 'subtotal' => $taxes); } } // Only calculate the grand total + shipping if on the cart/checkout if (is_checkout() || is_cart() || defined('WOOCOMMERCE_CHECKOUT') || defined('WOOCOMMERCE_CART')) { // Calculate the Shipping $this->calculate_shipping(); // Trigger the fees API where developers can add fees to the cart $this->calculate_fees(); // Total up/round taxes and shipping taxes if ($this->round_at_subtotal) { $this->tax_total = WC_Tax::get_tax_total($this->taxes); $this->shipping_tax_total = WC_Tax::get_tax_total($this->shipping_taxes); $this->taxes = array_map(array('WC_Tax', 'round'), $this->taxes); $this->shipping_taxes = array_map(array('WC_Tax', 'round'), $this->shipping_taxes); } else { $this->tax_total = array_sum($this->taxes); $this->shipping_tax_total = array_sum($this->shipping_taxes); } // VAT exemption done at this point - so all totals are correct before exemption if (WC()->customer->get_is_vat_exempt()) { $this->remove_taxes(); } // Allow plugins to hook and alter totals before final total is calculated do_action('woocommerce_calculate_totals', $this); // Grand Total - Discounted product prices, discounted tax, shipping cost + tax $this->total = max(0, apply_filters('woocommerce_calculated_total', round($this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total + $this->fee_total, $this->dp), $this)); } else { // Set tax total to sum of all tax rows $this->tax_total = WC_Tax::get_tax_total($this->taxes); // VAT exemption done at this point - so all totals are correct before exemption if (WC()->customer->get_is_vat_exempt()) { $this->remove_taxes(); } } do_action('woocommerce_after_calculate_totals', $this); $this->set_session(); }
/** * Calculate totals for the items in the cart. */ public function calculate_totals() { $this->reset(); $this->coupons = $this->get_coupons(); do_action('woocommerce_before_calculate_totals', $this); if ($this->is_empty()) { $this->set_session(); return; } $tax_rates = array(); $shop_tax_rates = array(); /** * Calculate subtotals for items. This is done first so that discount logic can use the values. */ foreach ($this->get_cart() as $cart_item_key => $values) { $_product = $values['data']; // Count items + weight $this->cart_contents_weight += $_product->get_weight() * $values['quantity']; $this->cart_contents_count += $values['quantity']; // Prices $line_price = $_product->get_price() * $values['quantity']; $line_subtotal = 0; $line_subtotal_tax = 0; /** * No tax to calculate */ if (!$_product->is_taxable()) { // Subtotal is the undiscounted price $this->subtotal += $line_price; $this->subtotal_ex_tax += $line_price; /** * Prices include tax * * 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 */ } elseif ($this->prices_include_tax) { // Get base tax rates if (empty($shop_tax_rates[$_product->tax_class])) { $shop_tax_rates[$_product->tax_class] = WC_Tax::get_base_tax_rates($_product->tax_class); } // Get item tax rates if (empty($tax_rates[$_product->get_tax_class()])) { $tax_rates[$_product->get_tax_class()] = WC_Tax::get_rates($_product->get_tax_class()); } $base_tax_rates = $shop_tax_rates[$_product->tax_class]; $item_tax_rates = $tax_rates[$_product->get_tax_class()]; /** * ADJUST TAX - Calculations when base tax is not equal to the item tax */ if ($item_tax_rates !== $base_tax_rates) { // Work out a new base price without the shop's base tax $taxes = WC_Tax::calc_tax($line_price, $base_tax_rates, true, true); // Now we have a new item price (excluding TAX) $line_subtotal = $line_price - array_sum($taxes); // Now add modified taxes $tax_result = WC_Tax::calc_tax($line_subtotal, $item_tax_rates); $line_subtotal_tax = array_sum($tax_result); /** * Regular tax calculation (customer inside base and the tax class is unmodified */ } else { // Calc tax normally $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates, true); $line_subtotal_tax = array_sum($taxes); $line_subtotal = $line_price - array_sum($taxes); } /** * Prices exclude tax * * This calculation is simpler - work with the base, untaxed price. */ } else { // Get item tax rates if (empty($tax_rates[$_product->get_tax_class()])) { $tax_rates[$_product->get_tax_class()] = WC_Tax::get_rates($_product->get_tax_class()); } $item_tax_rates = $tax_rates[$_product->get_tax_class()]; // Base tax for line before discount - we will store this in the order data $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates); $line_subtotal_tax = array_sum($taxes); $line_subtotal = $line_price; } // Add to main subtotal $this->subtotal += $line_subtotal + $line_subtotal_tax; $this->subtotal_ex_tax += $line_subtotal; } /** * Calculate totals for items */ foreach ($this->get_cart() as $cart_item_key => $values) { $_product = $values['data']; // Prices $base_price = $_product->get_price(); $line_price = $_product->get_price() * $values['quantity']; // Tax data $taxes = array(); $discounted_taxes = array(); /** * No tax to calculate */ if (!$_product->is_taxable()) { // Discounted Price (price with any pre-tax discounts applied) $discounted_price = $this->get_discounted_price($values, $base_price, true); $line_subtotal_tax = 0; $line_subtotal = $line_price; $line_tax = 0; $line_total = WC_Tax::round($discounted_price * $values['quantity']); /** * Prices include tax */ } elseif ($this->prices_include_tax) { $base_tax_rates = $shop_tax_rates[$_product->tax_class]; $item_tax_rates = $tax_rates[$_product->get_tax_class()]; /** * ADJUST TAX - Calculations when base tax is not equal to the item tax */ if ($item_tax_rates !== $base_tax_rates) { // Work out a new base price without the shop's base tax $taxes = WC_Tax::calc_tax($line_price, $base_tax_rates, true, true); // Now we have a new item price (excluding TAX) $line_subtotal = round($line_price - array_sum($taxes), WC_ROUNDING_PRECISION); $taxes = WC_Tax::calc_tax($line_subtotal, $item_tax_rates); $line_subtotal_tax = array_sum($taxes); // Adjusted price (this is the price including the new tax rate) $adjusted_price = ($line_subtotal + $line_subtotal_tax) / $values['quantity']; // Apply discounts $discounted_price = $this->get_discounted_price($values, $adjusted_price, true); $discounted_taxes = WC_Tax::calc_tax($discounted_price * $values['quantity'], $item_tax_rates, true); $line_tax = array_sum($discounted_taxes); $line_total = $discounted_price * $values['quantity'] - $line_tax; /** * Regular tax calculation (customer inside base and the tax class is unmodified */ } else { // Work out a new base price without the item tax $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates, true); // Now we have a new item price (excluding TAX) $line_subtotal = $line_price - array_sum($taxes); $line_subtotal_tax = array_sum($taxes); // Calc prices and tax (discounted) $discounted_price = $this->get_discounted_price($values, $base_price, true); $discounted_taxes = WC_Tax::calc_tax($discounted_price * $values['quantity'], $item_tax_rates, true); $line_tax = array_sum($discounted_taxes); $line_total = $discounted_price * $values['quantity'] - $line_tax; } // Tax rows - merge the totals we just got foreach (array_keys($this->taxes + $discounted_taxes) as $key) { $this->taxes[$key] = (isset($discounted_taxes[$key]) ? $discounted_taxes[$key] : 0) + (isset($this->taxes[$key]) ? $this->taxes[$key] : 0); } /** * Prices exclude tax */ } else { $item_tax_rates = $tax_rates[$_product->get_tax_class()]; // Work out a new base price without the shop's base tax $taxes = WC_Tax::calc_tax($line_price, $item_tax_rates); // Now we have the item price (excluding TAX) $line_subtotal = $line_price; $line_subtotal_tax = array_sum($taxes); // Now calc product rates $discounted_price = $this->get_discounted_price($values, $base_price, true); $discounted_taxes = WC_Tax::calc_tax($discounted_price * $values['quantity'], $item_tax_rates); $discounted_tax_amount = array_sum($discounted_taxes); $line_tax = $discounted_tax_amount; $line_total = $discounted_price * $values['quantity']; // Tax rows - merge the totals we just got foreach (array_keys($this->taxes + $discounted_taxes) as $key) { $this->taxes[$key] = (isset($discounted_taxes[$key]) ? $discounted_taxes[$key] : 0) + (isset($this->taxes[$key]) ? $this->taxes[$key] : 0); } } // Cart contents total is based on discounted prices and is used for the final total calculation $this->cart_contents_total += $line_total; // Store costs + taxes for lines $this->cart_contents[$cart_item_key]['line_total'] = $line_total; $this->cart_contents[$cart_item_key]['line_tax'] = $line_tax; $this->cart_contents[$cart_item_key]['line_subtotal'] = $line_subtotal; $this->cart_contents[$cart_item_key]['line_subtotal_tax'] = $line_subtotal_tax; // Store rates ID and costs - Since 2.2 $this->cart_contents[$cart_item_key]['line_tax_data'] = array('total' => $discounted_taxes, 'subtotal' => $taxes); } // Round cart contents $this->cart_contents_total = round($this->cart_contents_total, $this->dp); // Only calculate the grand total + shipping if on the cart/checkout if (is_checkout() || is_cart() || defined('WOOCOMMERCE_CHECKOUT') || defined('WOOCOMMERCE_CART')) { // Calculate the Shipping $this->calculate_shipping(); // Trigger the fees API where developers can add fees to the cart $this->calculate_fees(); // Total up/round taxes and shipping taxes if ($this->round_at_subtotal) { $this->tax_total = WC_Tax::get_tax_total($this->taxes); $this->shipping_tax_total = WC_Tax::get_tax_total($this->shipping_taxes); $this->taxes = array_map(array('WC_Tax', 'round'), $this->taxes); $this->shipping_taxes = array_map(array('WC_Tax', 'round'), $this->shipping_taxes); } else { $this->tax_total = array_sum($this->taxes); $this->shipping_tax_total = array_sum($this->shipping_taxes); } // VAT exemption done at this point - so all totals are correct before exemption if (WC()->customer->is_vat_exempt()) { $this->remove_taxes(); } // Allow plugins to hook and alter totals before final total is calculated do_action('woocommerce_calculate_totals', $this); // Grand Total - Discounted product prices, discounted tax, shipping cost + tax $this->total = max(0, apply_filters('woocommerce_calculated_total', round($this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total + $this->fee_total, $this->dp), $this)); } else { // Set tax total to sum of all tax rows $this->tax_total = WC_Tax::get_tax_total($this->taxes); // VAT exemption done at this point - so all totals are correct before exemption if (WC()->customer->is_vat_exempt()) { $this->remove_taxes(); } } do_action('woocommerce_after_calculate_totals', $this); $this->set_session(); }
/** * For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings. * @since 2.7.0 * @param WC_Product $product * @param array $args * @return float */ function wc_get_price_excluding_tax($product, $args = array()) { $args = wp_parse_args($args, array('qty' => '', 'price' => '')); $price = $args['price'] ? $args['price'] : $product->get_price(); $qty = $args['qty'] ? $args['qty'] : 1; if ($product->is_taxable() && wc_prices_include_tax()) { $tax_rates = WC_Tax::get_base_tax_rates($product->get_tax_class(true)); $taxes = WC_Tax::calc_tax($price * $qty, $tax_rates, true); $price = WC_Tax::round($price * $qty - array_sum($taxes)); } else { $price = $price * $qty; } return apply_filters('woocommerce_get_price_excluding_tax', $price, $qty, $product); }
/** * Output the form */ public function output() { $this->errors = array(); $step = 1; try { if (!empty($_POST) && !check_admin_referer('create_booking_notification')) { throw new Exception(__('Error - please try again', 'woocommerce-bookings')); } if (!empty($_POST['create_booking'])) { $customer_id = absint($_POST['customer_id']); $bookable_product_id = absint($_POST['bookable_product_id']); $booking_order = wc_clean($_POST['booking_order']); if (!$bookable_product_id) { throw new Exception(__('Please choose a bookable product', 'woocommerce-bookings')); } if ($booking_order === 'existing') { $order_id = absint($_POST['booking_order_id']); $booking_order = $order_id; if (!$booking_order || get_post_type($booking_order) !== 'shop_order') { throw new Exception(__('Invalid order ID provided', 'woocommerce-bookings')); } } $step++; $product = get_product($bookable_product_id); $booking_form = new WC_Booking_Form($product); } elseif (!empty($_POST['create_booking_2'])) { $customer_id = absint($_POST['customer_id']); $bookable_product_id = absint($_POST['bookable_product_id']); $booking_order = wc_clean($_POST['booking_order']); $product = get_product($bookable_product_id); $booking_form = new WC_Booking_Form($product); $booking_data = $booking_form->get_posted_data($_POST); $booking_cost = ($cost = $booking_form->calculate_booking_cost($_POST)) && !is_wp_error($cost) ? number_format($cost, 2, '.', '') : 0; $create_order = false; if ('yes' === get_option('woocommerce_prices_include_tax')) { if (version_compare(WOOCOMMERCE_VERSION, '2.3', '<')) { $base_tax_rates = WC_Tax::get_shop_base_rate($product->tax_class); } else { $base_tax_rates = WC_Tax::get_base_tax_rates($product->tax_class); } $base_taxes = WC_Tax::calc_tax($booking_cost, $base_tax_rates, true); $booking_cost = round($booking_cost - array_sum($base_taxes), absint(get_option('woocommerce_price_num_decimals'))); } // Data to go into the booking $new_booking_data = array('user_id' => $customer_id, 'product_id' => $product->id, 'resource_id' => isset($booking_data['_resource_id']) ? $booking_data['_resource_id'] : '', 'persons' => $booking_data['_persons'], 'cost' => $booking_cost, 'start_date' => $booking_data['_start_date'], 'end_date' => $booking_data['_end_date'], 'all_day' => $booking_data['_all_day'] ? 1 : 0); // Create order if ($booking_order === 'new') { $create_order = true; $order_id = $this->create_order($booking_cost, $customer_id); if (!$order_id) { throw new Exception(__('Error: Could not create order', 'woocommerce-bookings')); } } elseif ($booking_order > 0) { $order_id = absint($booking_order); if (!$order_id || get_post_type($order_id) !== 'shop_order') { throw new Exception(__('Invalid order ID provided', 'woocommerce-bookings')); } $order = new WC_Order($order_id); update_post_meta($order_id, '_order_total', $order->get_total() + $booking_cost); update_post_meta($order_id, '_booking_order', '1'); } else { $order_id = 0; } if ($order_id) { $item_id = woocommerce_add_order_item($order_id, array('order_item_name' => $product->get_title(), 'order_item_type' => 'line_item')); if (!$item_id) { throw new Exception(__('Error: Could not create item', 'woocommerce-bookings')); } // Add line item meta woocommerce_add_order_item_meta($item_id, '_qty', 1); woocommerce_add_order_item_meta($item_id, '_tax_class', $product->get_tax_class()); woocommerce_add_order_item_meta($item_id, '_product_id', $product->id); woocommerce_add_order_item_meta($item_id, '_variation_id', ''); woocommerce_add_order_item_meta($item_id, '_line_subtotal', $booking_cost); woocommerce_add_order_item_meta($item_id, '_line_total', $booking_cost); woocommerce_add_order_item_meta($item_id, '_line_tax', 0); woocommerce_add_order_item_meta($item_id, '_line_subtotal_tax', 0); // We have an item id $new_booking_data['order_item_id'] = $item_id; // Add line item data foreach ($booking_data as $key => $value) { if (strpos($key, '_') !== 0) { woocommerce_add_order_item_meta($item_id, get_wc_booking_data_label($key, $product), $value); } } } // Create the booking itself $new_booking = get_wc_booking($new_booking_data); $new_booking->create($create_order ? 'unpaid' : 'pending-confirmation'); wp_safe_redirect(admin_url('post.php?post=' . ($create_order ? $order_id : $new_booking->id) . '&action=edit')); exit; } } catch (Exception $e) { $this->errors[] = $e->getMessage(); } switch ($step) { case 1: include 'views/html-create-booking-page.php'; break; case 2: include 'views/html-create-booking-page-2.php'; break; } }
/** * Restore renewal flag when cart is reset and modify Product object with renewal order related info * * @since 2.0 */ public function get_cart_item_from_session($cart_item_session_data, $cart_item, $key) { if (isset($cart_item[$this->cart_item_key]['subscription_id'])) { $cart_item_session_data[$this->cart_item_key] = $cart_item[$this->cart_item_key]; $_product = $cart_item_session_data['data']; // Need to get the original subscription price, not the current price $subscription = wcs_get_subscription($cart_item[$this->cart_item_key]['subscription_id']); if ($subscription) { $subscription_items = $subscription->get_items(); $item_to_renew = $subscription_items[$cart_item_session_data[$this->cart_item_key]['subscription_line_item_id']]; $price = $item_to_renew['line_subtotal']; if (wc_prices_include_tax()) { $base_tax_rates = WC_Tax::get_base_tax_rates($_product->tax_class); $base_taxes_on_item = WC_Tax::calc_tax($price, $base_tax_rates, false, false); $price += array_sum($base_taxes_on_item); } $_product->price = $price / $item_to_renew['qty']; // Don't carry over any sign up fee $_product->subscription_sign_up_fee = 0; $_product->post->post_title = apply_filters('woocommerce_subscriptions_renewal_product_title', $_product->get_title(), $_product); // Make sure the same quantity is renewed $cart_item_session_data['quantity'] = $item_to_renew['qty']; } } return $cart_item_session_data; }
/** * Build the transaction lines * * @param array $items * @param String $country * * @return array */ private function build_transaction_lines($items, $country) { // Transaction lines $transaction_lines = array(); // Lo0p if (count($items) > 0) { // Country manager $country_manager = new WC_TA_Country_Manager(); foreach ($items as $item_key => $item) { if (isset($item['data'])) { // The transaction line $transaction_line = array(); $product = $item['data']; // Set the product type $type = 'default'; // Check if this is a virtual product if ($product->is_virtual()) { $type = 'e-service'; } else { $transaction_line['informative'] = true; } // Check if this is an e-book $is_ebook = get_post_meta($item['id'], '_ebook', true); if ('yes' === $is_ebook) { $type = 'e-book'; } // Check if Taxamo supports taxes for this country if (false === $country_manager->is_tax_supported_for_country($country)) { // Taxes aren't supported, set the line to informative $transaction_line['informative'] = true; } // Set the product type $transaction_line['product_type'] = $type; // Custom ID $transaction_line['custom_id'] = "" . $item_key; // Quantity $transaction_line['quantity'] = $item['quantity']; // Price if ($product->is_taxable()) { // Always without tax $transaction_line['amount'] = $item['line_total']; // Get the base tax rates if (method_exists('WC_Tax', 'get_base_tax_rates')) { $base_tax_rates = WC_Tax::get_base_tax_rates($product->get_tax_class()); } else { $base_tax_rates = WC_Tax::get_shop_base_rate($product->get_tax_class()); } $base_tax_rate = array_shift($base_tax_rates); // Set the tax rate $transaction_line['tax_rate'] = $base_tax_rate['rate']; } else { // Set Price $transaction_line['amount'] = $product->get_price(); // Set 0 tax rate $transaction_line['tax_rate'] = 0; // Force non taxable $transaction_line['informative'] = true; } // What do we want? Floats! When do we want them? Now! // Check if amount is set if (isset($transaction_line['amount'])) { $transaction_line['amount'] = floatval($transaction_line['amount']); } // Check if total_amount is set if (isset($transaction_line['total_amount'])) { $transaction_line['total_amount'] = floatval($transaction_line['total_amount']); } // Float the tax rate $transaction_line['tax_rate'] = floatval($transaction_line['tax_rate']); // Add transaction line to transaction lines $transaction_lines[] = $transaction_line; } } } return $transaction_lines; }