<?php

use Automattic\WooCommerce\Utilities\NumberUtil;
defined('ABSPATH') || exit;
require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php';
abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order
{
    use WC_Item_Totals;
    protected $data = array('parent_id' => 0, 'status' => '', 'currency' => '', 'version' => '', 'prices_include_tax' => false, 'date_created' => null, 'date_modified' => null, 'discount_total' => 0, 'discount_tax' => 0, 'shipping_total' => 0, 'shipping_tax' => 0, 'cart_tax' => 0, 'total' => 0, 'total_tax' => 0);
    protected $items = array();
    protected $items_to_delete = array();
    protected $cache_group = 'orders';
    protected $data_store_name = 'order';
    protected $object_type = 'order';
    public function __construct($order = 0)
    {
        parent::__construct($order);
        if (is_numeric($order) && $order > 0) {
            $this->set_id($order);
        } elseif ($order instanceof self) {
            $this->set_id($order->get_id());
        } elseif (!empty($order->ID)) {
            $this->set_id($order->ID);
        } else {
            $this->set_object_read(true);
        }
        $this->data_store = WC_Data_Store::load($this->data_store_name);
        if ($this->get_id() > 0) {
            $this->data_store->read($this);
        }
    }
    public function get_type()
    {
        return 'shop_order';
    }
    public function get_data()
    {
        return array_merge(array('id' => $this->get_id()), $this->data, array('meta_data' => $this->get_meta_data(), 'line_items' => $this->get_items('line_item'), 'tax_lines' => $this->get_items('tax'), 'shipping_lines' => $this->get_items('shipping'), 'fee_lines' => $this->get_items('fee'), 'coupon_lines' => $this->get_items('coupon')));
    }
    public function save()
    {
        if (!$this->data_store) {
            return $this->get_id();
        }
        try {
            do_action('woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store);
            if ($this->get_id()) {
                $this->data_store->update($this);
            } else {
                $this->data_store->create($this);
            }
            $this->save_items();
            do_action('woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store);
        } catch (Exception $e) {
            $this->handle_exception($e, __('Error saving order.', 'woocommerce'));
        }
        return $this->get_id();
    }
    protected function handle_exception($e, $message = 'Error')
    {
        wc_get_logger()->error($message, array('order' => $this, 'error' => $e));
    }
    protected function save_items()
    {
        $items_changed = false;
        foreach ($this->items_to_delete as $item) {
            $item->delete();
            $items_changed = true;
        }
        $this->items_to_delete = array();
        foreach ($this->items as $item_group => $items) {
            if (is_array($items)) {
                $items = array_filter($items);
                foreach ($items as $item_key => $item) {
                    $item->set_order_id($this->get_id());
                    $item_id = $item->save();
                    if ($item_id !== $item_key) {
                        $this->items[$item_group][$item_id] = $item;
                        unset($this->items[$item_group][$item_key]);
                        $items_changed = true;
                    }
                }
            }
        }
        if ($items_changed) {
            delete_transient('wc_order_' . $this->get_id() . '_needs_processing');
        }
    }
    public function get_parent_id($context = 'view')
    {
        return $this->get_prop('parent_id', $context);
    }
    public function get_currency($context = 'view')
    {
        return $this->get_prop('currency', $context);
    }
    public function get_version($context = 'view')
    {
        return $this->get_prop('version', $context);
    }
    public function get_prices_include_tax($context = 'view')
    {
        return $this->get_prop('prices_include_tax', $context);
    }
    public function get_date_created($context = 'view')
    {
        return $this->get_prop('date_created', $context);
    }
    public function get_date_modified($context = 'view')
    {
        return $this->get_prop('date_modified', $context);
    }
    public function get_status($context = 'view')
    {
        $status = $this->get_prop('status', $context);
        if (empty($status) && 'view' === $context) {
            $status = apply_filters('woocommerce_default_order_status', 'pending');
        }
        return $status;
    }
    public function get_discount_total($context = 'view')
    {
        return $this->get_prop('discount_total', $context);
    }
    public function get_discount_tax($context = 'view')
    {
        return $this->get_prop('discount_tax', $context);
    }
    public function get_shipping_total($context = 'view')
    {
        return $this->get_prop('shipping_total', $context);
    }
    public function get_shipping_tax($context = 'view')
    {
        return $this->get_prop('shipping_tax', $context);
    }
    public function get_cart_tax($context = 'view')
    {
        return $this->get_prop('cart_tax', $context);
    }
    public function get_total($context = 'view')
    {
        return $this->get_prop('total', $context);
    }
    public function get_total_tax($context = 'view')
    {
        return $this->get_prop('total_tax', $context);
    }
    public function get_total_discount($ex_tax = true)
    {
        if ($ex_tax) {
            $total_discount = $this->get_discount_total();
        } else {
            $total_discount = $this->get_discount_total() + $this->get_discount_tax();
        }
        return apply_filters('woocommerce_order_get_total_discount', NumberUtil::round($total_discount, WC_ROUNDING_PRECISION), $this);
    }
    public function get_subtotal()
    {
        $subtotal = NumberUtil::round($this->get_cart_subtotal_for_order(), wc_get_price_decimals());
        return apply_filters('woocommerce_order_get_subtotal', (float) $subtotal, $this);
    }
    public function get_tax_totals()
    {
        $tax_totals = array();
        foreach ($this->get_items('tax') as $key => $tax) {
            $code = $tax->get_rate_code();
            if (!isset($tax_totals[$code])) {
                $tax_totals[$code] = new stdClass();
                $tax_totals[$code]->amount = 0;
            }
            $tax_totals[$code]->id = $key;
            $tax_totals[$code]->rate_id = $tax->get_rate_id();
            $tax_totals[$code]->is_compound = $tax->is_compound();
            $tax_totals[$code]->label = $tax->get_label();
            $tax_totals[$code]->amount += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total();
            $tax_totals[$code]->formatted_amount = wc_price($tax_totals[$code]->amount, array('currency' => $this->get_currency()));
        }
        if (apply_filters('woocommerce_order_hide_zero_taxes', true)) {
            $amounts = array_filter(wp_list_pluck($tax_totals, 'amount'));
            $tax_totals = array_intersect_key($tax_totals, $amounts);
        }
        return apply_filters('woocommerce_order_get_tax_totals', $tax_totals, $this);
    }
    protected function get_valid_statuses()
    {
        return array_keys(wc_get_order_statuses());
    }
    public function get_user_id($context = 'view')
    {
        return 0;
    }
    public function get_user()
    {
        return false;
    }
    public function set_parent_id($value)
    {
        if ($value && ($value === $this->get_id() || !wc_get_order($value))) {
            $this->error('order_invalid_parent_id', __('Invalid parent ID', 'woocommerce'));
        }
        $this->set_prop('parent_id', absint($value));
    }
    public function set_status($new_status)
    {
        $old_status = $this->get_status();
        $new_status = 'wc-' === substr($new_status, 0, 3) ? substr($new_status, 3) : $new_status;
        if (true === $this->object_read) {
            if (!in_array('wc-' . $new_status, $this->get_valid_statuses(), true) && 'trash' !== $new_status) {
                $new_status = 'pending';
            }
            if ($old_status && !in_array('wc-' . $old_status, $this->get_valid_statuses(), true) && 'trash' !== $old_status) {
                $old_status = 'pending';
            }
        }
        $this->set_prop('status', $new_status);
        return array('from' => $old_status, 'to' => $new_status);
    }
    public function set_version($value)
    {
        $this->set_prop('version', $value);
    }
    public function set_currency($value)
    {
        if ($value && !in_array($value, array_keys(get_woocommerce_currencies()), true)) {
            $this->error('order_invalid_currency', __('Invalid currency code', 'woocommerce'));
        }
        $this->set_prop('currency', $value ? $value : get_woocommerce_currency());
    }
    public function set_prices_include_tax($value)
    {
        $this->set_prop('prices_include_tax', (bool) $value);
    }
    public function set_date_created($date = null)
    {
        $this->set_date_prop('date_created', $date);
    }
    public function set_date_modified($date = null)
    {
        $this->set_date_prop('date_modified', $date);
    }
    public function set_discount_total($value)
    {
        $this->set_prop('discount_total', wc_format_decimal($value));
    }
    public function set_discount_tax($value)
    {
        $this->set_prop('discount_tax', wc_format_decimal($value));
    }
    public function set_shipping_total($value)
    {
        $this->set_prop('shipping_total', wc_format_decimal($value));
    }
    public function set_shipping_tax($value)
    {
        $this->set_prop('shipping_tax', wc_format_decimal($value));
        $this->set_total_tax((float) $this->get_cart_tax() + (float) $this->get_shipping_tax());
    }
    public function set_cart_tax($value)
    {
        $this->set_prop('cart_tax', wc_format_decimal($value));
        $this->set_total_tax((float) $this->get_cart_tax() + (float) $this->get_shipping_tax());
    }
    protected function set_total_tax($value)
    {
        $this->set_prop('total_tax', wc_format_decimal(NumberUtil::round($value, wc_get_price_decimals())));
    }
    public function set_total($value, $deprecated = '')
    {
        if ($deprecated) {
            wc_deprecated_argument('total_type', '3.0', 'Use dedicated total setter methods instead.');
            return $this->legacy_set_total($value, $deprecated);
        }
        $this->set_prop('total', wc_format_decimal($value, wc_get_price_decimals()));
    }
    public function remove_order_items($type = null)
    {
        if (!empty($type)) {
            $this->data_store->delete_items($this, $type);
            $group = $this->type_to_group($type);
            if ($group) {
                unset($this->items[$group]);
            }
        } else {
            $this->data_store->delete_items($this);
            $this->items = array();
        }
    }
    protected function type_to_group($type)
    {
        $type_to_group = apply_filters('woocommerce_order_type_to_group', array('line_item' => 'line_items', 'tax' => 'tax_lines', 'shipping' => 'shipping_lines', 'fee' => 'fee_lines', 'coupon' => 'coupon_lines'));
        return isset($type_to_group[$type]) ? $type_to_group[$type] : '';
    }
    public function get_items($types = 'line_item')
    {
        $items = array();
        $types = array_filter((array) $types);
        foreach ($types as $type) {
            $group = $this->type_to_group($type);
            if ($group) {
                if (!isset($this->items[$group])) {
                    $this->items[$group] = array_filter($this->data_store->read_items($this, $type));
                }
                $items = $items + $this->items[$group];
            }
        }
        return apply_filters('woocommerce_order_get_items', $items, $this, $types);
    }
    protected function get_values_for_total($field)
    {
        $items = array_map(function ($item) use($field) {
            return wc_add_number_precision($item[$field], false);
        }, array_values($this->get_items()));
        return $items;
    }
    public function get_coupons()
    {
        return $this->get_items('coupon');
    }
    public function get_fees()
    {
        return $this->get_items('fee');
    }
    public function get_taxes()
    {
        return $this->get_items('tax');
    }
    public function get_shipping_methods()
    {
        return $this->get_items('shipping');
    }
    public function get_shipping_method()
    {
        $names = array();
        foreach ($this->get_shipping_methods() as $shipping_method) {
            $names[] = $shipping_method->get_name();
        }
        return apply_filters('woocommerce_order_shipping_method', implode(', ', $names), $this);
    }
    public function get_coupon_codes()
    {
        $coupon_codes = array();
        $coupons = $this->get_items('coupon');
        if ($coupons) {
            foreach ($coupons as $coupon) {
                $coupon_codes[] = $coupon->get_code();
            }
        }
        return $coupon_codes;
    }
    public function get_item_count($item_type = '')
    {
        $items = $this->get_items(empty($item_type) ? 'line_item' : $item_type);
        $count = 0;
        foreach ($items as $item) {
            $count += $item->get_quantity();
        }
        return apply_filters('woocommerce_get_item_count', $count, $item_type, $this);
    }
    public function get_item($item_id, $load_from_db = true)
    {
        if ($load_from_db) {
            return WC_Order_Factory::get_order_item($item_id);
        }
        if ($this->items) {
            foreach ($this->items as $group => $items) {
                if (isset($items[$item_id])) {
                    return $items[$item_id];
                }
            }
        }
        $type = $this->data_store->get_order_item_type($this, $item_id);
        if (!$type) {
            return false;
        }
        $items = $this->get_items($type);
        return !empty($items[$item_id]) ? $items[$item_id] : false;
    }
    protected function get_items_key($item)
    {
        if (is_a($item, 'WC_Order_Item_Product')) {
            return 'line_items';
        } elseif (is_a($item, 'WC_Order_Item_Fee')) {
            return 'fee_lines';
        } elseif (is_a($item, 'WC_Order_Item_Shipping')) {
            return 'shipping_lines';
        } elseif (is_a($item, 'WC_Order_Item_Tax')) {
            return 'tax_lines';
        } elseif (is_a($item, 'WC_Order_Item_Coupon')) {
            return 'coupon_lines';
        }
        return apply_filters('woocommerce_get_items_key', '', $item);
    }
    public function remove_item($item_id)
    {
        $item = $this->get_item($item_id, false);
        $items_key = $item ? $this->get_items_key($item) : false;
        if (!$items_key) {
            return false;
        }
        $this->items_to_delete[] = $item;
        unset($this->items[$items_key][$item->get_id()]);
    }
    public function add_item($item)
    {
        $items_key = $this->get_items_key($item);
        if (!$items_key) {
            return false;
        }
        if (!isset($this->items[$items_key])) {
            $this->items[$items_key] = $this->get_items($item->get_type());
        }
        $item->set_order_id($this->get_id());
        $item_id = $item->get_id();
        if ($item_id) {
            $this->items[$items_key][$item_id] = $item;
        } else {
            $this->items[$items_key]['new:' . $items_key . count($this->items[$items_key])] = $item;
        }
    }
    public function hold_applied_coupons($billing_email)
    {
        $held_keys = array();
        $held_keys_for_user = array();
        $error = null;
        try {
            foreach (WC()->cart->get_applied_coupons() as $code) {
                $coupon = new WC_Coupon($code);
                if (!$coupon->get_data_store()) {
                    continue;
                }
                if (0 < $coupon->get_usage_limit()) {
                    $held_key = $this->hold_coupon($coupon);
                    if ($held_key) {
                        $held_keys[$coupon->get_id()] = $held_key;
                    }
                }
                if (0 < $coupon->get_usage_limit_per_user()) {
                    if (!isset($user_ids_and_emails)) {
                        $user_alias = get_current_user_id() ? wp_get_current_user()->ID : sanitize_email($billing_email);
                        $user_ids_and_emails = $this->get_billing_and_current_user_aliases($billing_email);
                    }
                    $held_key_for_user = $this->hold_coupon_for_users($coupon, $user_ids_and_emails, $user_alias);
                    if ($held_key_for_user) {
                        $held_keys_for_user[$coupon->get_id()] = $held_key_for_user;
                    }
                }
            }
        } catch (Exception $e) {
            $error = $e;
        } finally {
            if (0 < count($held_keys_for_user) || 0 < count($held_keys)) {
                $this->get_data_store()->set_coupon_held_keys($this, $held_keys, $held_keys_for_user);
            }
            if ($error instanceof Exception) {
                throw $error;
            }
        }
    }
    private function hold_coupon($coupon)
    {
        $result = $coupon->get_data_store()->check_and_hold_coupon($coupon);
        if (false === $result) {
            throw new Exception(sprintf(__('An unexpected error happened while applying the Coupon %s.', 'woocommerce'), esc_html($coupon->get_code())));
        } elseif (0 === $result) {
            throw new Exception(sprintf(__('Coupon %s was used in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce'), esc_html($coupon->get_code())));
        }
        return $result;
    }
    private function hold_coupon_for_users($coupon, $user_ids_and_emails, $user_alias)
    {
        $result = $coupon->get_data_store()->check_and_hold_coupon_for_user($coupon, $user_ids_and_emails, $user_alias);
        if (false === $result) {
            throw new Exception(sprintf(__('An unexpected error happened while applying the Coupon %s.', 'woocommerce'), esc_html($coupon->get_code())));
        } elseif (0 === $result) {
            throw new Exception(sprintf(__('You have used this coupon %s in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce'), esc_html($coupon->get_code())));
        }
        return $result;
    }
    private function get_billing_and_current_user_aliases($billing_email)
    {
        $emails = array($billing_email);
        if (get_current_user_id()) {
            $emails[] = wp_get_current_user()->user_email;
        }
        $emails = array_unique(array_map('strtolower', array_map('sanitize_email', $emails)));
        $customer_data_store = WC_Data_Store::load('customer');
        $user_ids = $customer_data_store->get_user_ids_for_billing_email($emails);
        return array_merge($user_ids, $emails);
    }
    public function apply_coupon($raw_coupon)
    {
        if (is_a($raw_coupon, 'WC_Coupon')) {
            $coupon = $raw_coupon;
        } elseif (is_string($raw_coupon)) {
            $code = wc_format_coupon_code($raw_coupon);
            $coupon = new WC_Coupon($code);
            if ($coupon->get_code() !== $code) {
                return new WP_Error('invalid_coupon', __('Invalid coupon code', 'woocommerce'));
            }
        } else {
            return new WP_Error('invalid_coupon', __('Invalid coupon', 'woocommerce'));
        }
        $applied_coupons = $this->get_items('coupon');
        foreach ($applied_coupons as $applied_coupon) {
            if ($applied_coupon->get_code() === $coupon->get_code()) {
                return new WP_Error('invalid_coupon', __('Coupon code already applied!', 'woocommerce'));
            }
        }
        $discounts = new WC_Discounts($this);
        $applied = $discounts->apply_coupon($coupon);
        if (is_wp_error($applied)) {
            return $applied;
        }
        $data_store = $coupon->get_data_store();
        if ($data_store && 0 === $this->get_customer_id()) {
            $usage_count = $data_store->get_usage_by_email($coupon, $this->get_billing_email());
            if (0 < $coupon->get_usage_limit_per_user() && $usage_count >= $coupon->get_usage_limit_per_user()) {
                return new WP_Error('invalid_coupon', $coupon->get_coupon_error(106), array('status' => 400));
            }
        }
        $this->set_coupon_discount_amounts($discounts);
        $this->save();
        $this->recalculate_coupons();
        $used_by = $this->get_user_id();
        if (!$used_by) {
            $used_by = $this->get_billing_email();
        }
        $coupon->increase_usage_count($used_by);
        return true;
    }
    public function remove_coupon($code)
    {
        $coupons = $this->get_items('coupon');
        foreach ($coupons as $item_id => $coupon) {
            if ($coupon->get_code() === $code) {
                $this->remove_item($item_id);
                $coupon_object = new WC_Coupon($code);
                $coupon_object->decrease_usage_count($this->get_user_id());
                $this->recalculate_coupons();
                break;
            }
        }
    }
    public function recalculate_coupons()
    {
        foreach ($this->get_items() as $item) {
            $item->set_total($item->get_subtotal());
            $item->set_total_tax($item->get_subtotal_tax());
        }
        $discounts = new WC_Discounts($this);
        foreach ($this->get_items('coupon') as $coupon_item) {
            $coupon_code = $coupon_item->get_code();
            $coupon_id = wc_get_coupon_id_by_code($coupon_code);
            if ($coupon_id) {
                $coupon_object = new WC_Coupon($coupon_id);
            } else {
                $coupon_object = new WC_Coupon();
                $coupon_object->set_props((array) $coupon_item->get_meta('coupon_data', true));
                $coupon_object->set_code($coupon_code);
                $coupon_object->set_virtual(true);
                if (!$coupon_object->get_amount()) {
                    if ($this->get_prices_include_tax()) {
                        $coupon_object->set_amount($coupon_item->get_discount() + $coupon_item->get_discount_tax());
                    } else {
                        $coupon_object->set_amount($coupon_item->get_discount());
                    }
                    $coupon_object->set_discount_type('fixed_cart');
                }
            }
            $coupon_object = apply_filters('woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this);
            if ($coupon_object) {
                $discounts->apply_coupon($coupon_object, false);
            }
        }
        $this->set_coupon_discount_amounts($discounts);
        $this->set_item_discount_amounts($discounts);
        $this->calculate_totals(true);
    }
    protected function set_item_discount_amounts($discounts)
    {
        $item_discounts = $discounts->get_discounts_by_item();
        $tax_location = $this->get_tax_location();
        $tax_location = array($tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city']);
        if ($item_discounts) {
            foreach ($item_discounts as $item_id => $amount) {
                $item = $this->get_item($item_id, false);
                if ($this->get_prices_include_tax() && wc_tax_enabled() && 'taxable' === $item->get_tax_status()) {
                    $taxes = WC_Tax::calc_tax($amount, $this->get_tax_rates($item->get_tax_class(), $tax_location), true);
                    $amount = $amount - array_sum($taxes);
                }
                $item->set_total(max(0, $item->get_total() - $amount));
            }
        }
    }
    protected function set_coupon_discount_amounts($discounts)
    {
        $coupons = $this->get_items('coupon');
        $coupon_code_to_id = wc_list_pluck($coupons, 'get_id', 'get_code');
        $all_discounts = $discounts->get_discounts();
        $coupon_discounts = $discounts->get_discounts_by_coupon();
        $tax_location = $this->get_tax_location();
        $tax_location = array($tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city']);
        if ($coupon_discounts) {
            foreach ($coupon_discounts as $coupon_code => $amount) {
                $item_id = isset($coupon_code_to_id[$coupon_code]) ? $coupon_code_to_id[$coupon_code] : 0;
                if (!$item_id) {
                    $coupon_item = new WC_Order_Item_Coupon();
                    $coupon_item->set_code($coupon_code);
                } else {
                    $coupon_item = $this->get_item($item_id, false);
                }
                $discount_tax = 0;
                foreach ($all_discounts[$coupon_code] as $item_id => $item_discount_amount) {
                    $item = $this->get_item($item_id, false);
                    if ('taxable' !== $item->get_tax_status() || !wc_tax_enabled()) {
                        continue;
                    }
                    $taxes = array_sum(WC_Tax::calc_tax($item_discount_amount, $this->get_tax_rates($item->get_tax_class(), $tax_location), $this->get_prices_include_tax()));
                    if ('yes' !== get_option('woocommerce_tax_round_at_subtotal')) {
                        $taxes = wc_round_tax_total($taxes);
                    }
                    $discount_tax += $taxes;
                    if ($this->get_prices_include_tax()) {
                        $amount = $amount - $taxes;
                    }
                }
                $coupon_item->set_discount($amount);
                $coupon_item->set_discount_tax($discount_tax);
                $this->add_item($coupon_item);
            }
        }
    }
    public function add_product($product, $qty = 1, $args = array())
    {
        if ($product) {
            $default_args = array('name' => $product->get_name(), 'tax_class' => $product->get_tax_class(), 'product_id' => $product->is_type('variation') ? $product->get_parent_id() : $product->get_id(), 'variation_id' => $product->is_type('variation') ? $product->get_id() : 0, 'variation' => $product->is_type('variation') ? $product->get_attributes() : array(), 'subtotal' => wc_get_price_excluding_tax($product, array('qty' => $qty)), 'total' => wc_get_price_excluding_tax($product, array('qty' => $qty)), 'quantity' => $qty);
        } else {
            $default_args = array('quantity' => $qty);
        }
        $args = wp_parse_args($args, $default_args);
        if (isset($args['totals'])) {
            foreach ($args['totals'] as $key => $value) {
                if ('tax' === $key) {
                    $args['total_tax'] = $value;
                } elseif ('tax_data' === $key) {
                    $args['taxes'] = $value;
                } else {
                    $args[$key] = $value;
                }
            }
        }
        $item = new WC_Order_Item_Product();
        $item->set_props($args);
        $item->set_backorder_meta();
        $item->set_order_id($this->get_id());
        $item->save();
        $this->add_item($item);
        wc_do_deprecated_action('woocommerce_order_add_product', array($this->get_id(), $item->get_id(), $product, $qty, $args), '3.0', 'woocommerce_new_order_item action instead');
        delete_transient('wc_order_' . $this->get_id() . '_needs_processing');
        return $item->get_id();
    }
    public function add_payment_token($token)
    {
        if (empty($token) || !$token instanceof WC_Payment_Token) {
            return false;
        }
        $token_ids = $this->data_store->get_payment_token_ids($this);
        $token_ids[] = $token->get_id();
        $this->data_store->update_payment_token_ids($this, $token_ids);
        do_action('woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids);
        return $token->get_id();
    }
    public function get_payment_tokens()
    {
        return $this->data_store->get_payment_token_ids($this);
    }
    public function calculate_shipping()
    {
        $shipping_total = 0;
        foreach ($this->get_shipping_methods() as $shipping) {
            $shipping_total += $shipping->get_total();
        }
        $this->set_shipping_total($shipping_total);
        $this->save();
        return $this->get_shipping_total();
    }
    public function get_items_tax_classes()
    {
        $found_tax_classes = array();
        foreach ($this->get_items() as $item) {
            if (is_callable(array($item, 'get_tax_status')) && in_array($item->get_tax_status(), array('taxable', 'shipping'), true)) {
                $found_tax_classes[] = $item->get_tax_class();
            }
        }
        return array_unique($found_tax_classes);
    }
    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('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(), 'city' => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city()));
        if ('base' === $tax_based_on || empty($args['country'])) {
            $args['country'] = WC()->countries->get_base_country();
            $args['state'] = WC()->countries->get_base_state();
            $args['postcode'] = WC()->countries->get_base_postcode();
            $args['city'] = WC()->countries->get_base_city();
        }
        return apply_filters('woocommerce_order_get_tax_location', $args, $this);
    }
    protected function get_tax_rates($tax_class, $location_args = array(), $customer = null)
    {
        $tax_location = $this->get_tax_location($location_args);
        $tax_location = array($tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city']);
        return WC_Tax::get_rates_from_location($tax_class, $tax_location, $customer);
    }
    public function calculate_taxes($args = array())
    {
        do_action('woocommerce_order_before_calculate_taxes', $args, $this);
        $calculate_tax_for = $this->get_tax_location($args);
        $shipping_tax_class = get_option('woocommerce_shipping_tax_class');
        if ('inherit' === $shipping_tax_class) {
            $found_classes = array_intersect(array_merge(array(''), WC_Tax::get_tax_class_slugs()), $this->get_items_tax_classes());
            $shipping_tax_class = count($found_classes) ? current($found_classes) : false;
        }
        $is_vat_exempt = apply_filters('woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta('is_vat_exempt'), $this);
        foreach ($this->get_items(array('line_item', 'fee')) as $item_id => $item) {
            if (!$is_vat_exempt) {
                $item->calculate_taxes($calculate_tax_for);
            } else {
                $item->set_taxes(false);
            }
        }
        foreach ($this->get_shipping_methods() as $item_id => $item) {
            if (false !== $shipping_tax_class && !$is_vat_exempt) {
                $item->calculate_taxes(array_merge($calculate_tax_for, array('tax_class' => $shipping_tax_class)));
            } else {
                $item->set_taxes(false);
            }
        }
        $this->update_taxes();
    }
    public function get_total_fees()
    {
        return array_reduce($this->get_fees(), function ($carry, $item) {
            return $carry + $item->get_total();
        });
    }
    public function update_taxes()
    {
        $cart_taxes = array();
        $shipping_taxes = array();
        $existing_taxes = $this->get_taxes();
        $saved_rate_ids = array();
        foreach ($this->get_items(array('line_item', 'fee')) as $item_id => $item) {
            $taxes = $item->get_taxes();
            foreach ($taxes['total'] as $tax_rate_id => $tax) {
                $tax_amount = (float) $this->round_line_tax($tax, false);
                $cart_taxes[$tax_rate_id] = isset($cart_taxes[$tax_rate_id]) ? (float) $cart_taxes[$tax_rate_id] + $tax_amount : $tax_amount;
            }
        }
        foreach ($this->get_shipping_methods() as $item_id => $item) {
            $taxes = $item->get_taxes();
            foreach ($taxes['total'] as $tax_rate_id => $tax) {
                $tax_amount = (float) $tax;
                if ('yes' !== get_option('woocommerce_tax_round_at_subtotal')) {
                    $tax_amount = wc_round_tax_total($tax_amount);
                }
                $shipping_taxes[$tax_rate_id] = isset($shipping_taxes[$tax_rate_id]) ? $shipping_taxes[$tax_rate_id] + $tax_amount : $tax_amount;
            }
        }
        foreach ($existing_taxes as $tax) {
            if (!array_key_exists($tax->get_rate_id(), $cart_taxes) && !array_key_exists($tax->get_rate_id(), $shipping_taxes) || in_array($tax->get_rate_id(), $saved_rate_ids, true)) {
                $this->remove_item($tax->get_id());
                continue;
            }
            $saved_rate_ids[] = $tax->get_rate_id();
            $tax->set_rate($tax->get_rate_id());
            $tax->set_tax_total(isset($cart_taxes[$tax->get_rate_id()]) ? $cart_taxes[$tax->get_rate_id()] : 0);
            $tax->set_label(WC_Tax::get_rate_label($tax->get_rate_id()));
            $tax->set_shipping_tax_total(!empty($shipping_taxes[$tax->get_rate_id()]) ? $shipping_taxes[$tax->get_rate_id()] : 0);
            $tax->save();
        }
        $new_rate_ids = wp_parse_id_list(array_diff(array_keys($cart_taxes + $shipping_taxes), $saved_rate_ids));
        foreach ($new_rate_ids as $tax_rate_id) {
            $item = new WC_Order_Item_Tax();
            $item->set_rate($tax_rate_id);
            $item->set_tax_total(isset($cart_taxes[$tax_rate_id]) ? $cart_taxes[$tax_rate_id] : 0);
            $item->set_shipping_tax_total(!empty($shipping_taxes[$tax_rate_id]) ? $shipping_taxes[$tax_rate_id] : 0);
            $this->add_item($item);
        }
        $this->set_shipping_tax(array_sum($shipping_taxes));
        $this->set_cart_tax(array_sum($cart_taxes));
        $this->save();
    }
    protected function get_cart_subtotal_for_order()
    {
        return wc_remove_number_precision($this->get_rounded_items_total($this->get_values_for_total('subtotal')));
    }
    protected function get_cart_total_for_order()
    {
        return wc_remove_number_precision($this->get_rounded_items_total($this->get_values_for_total('total')));
    }
    public function calculate_totals($and_taxes = true)
    {
        do_action('woocommerce_order_before_calculate_totals', $and_taxes, $this);
        $fees_total = 0;
        $shipping_total = 0;
        $cart_subtotal_tax = 0;
        $cart_total_tax = 0;
        $cart_subtotal = $this->get_cart_subtotal_for_order();
        $cart_total = $this->get_cart_total_for_order();
        foreach ($this->get_shipping_methods() as $shipping) {
            $shipping_total += NumberUtil::round($shipping->get_total(), wc_get_price_decimals());
        }
        $this->set_shipping_total($shipping_total);
        foreach ($this->get_fees() as $item) {
            $fee_total = $item->get_total();
            if (0 > $fee_total) {
                $max_discount = NumberUtil::round($cart_total + $fees_total + $shipping_total, wc_get_price_decimals()) * -1;
                if ($fee_total < $max_discount && 0 > $max_discount) {
                    $item->set_total($max_discount);
                }
            }
            $fees_total += $item->get_total();
        }
        if ($and_taxes) {
            $this->calculate_taxes();
        }
        foreach ($this->get_items() as $item) {
            $taxes = $item->get_taxes();
            foreach ($taxes['total'] as $tax_rate_id => $tax) {
                $cart_total_tax += (float) $tax;
            }
            foreach ($taxes['subtotal'] as $tax_rate_id => $tax) {
                $cart_subtotal_tax += (float) $tax;
            }
        }
        $this->set_discount_total(NumberUtil::round($cart_subtotal - $cart_total, wc_get_price_decimals()));
        $this->set_discount_tax(wc_round_tax_total($cart_subtotal_tax - $cart_total_tax));
        $this->set_total(NumberUtil::round($cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals()));
        do_action('woocommerce_order_after_calculate_totals', $and_taxes, $this);
        $this->save();
        return $this->get_total();
    }
    public function get_item_subtotal($item, $inc_tax = false, $round = true)
    {
        $subtotal = 0;
        if (is_callable(array($item, 'get_subtotal')) && $item->get_quantity()) {
            if ($inc_tax) {
                $subtotal = ($item->get_subtotal() + $item->get_subtotal_tax()) / $item->get_quantity();
            } else {
                $subtotal = floatval($item->get_subtotal()) / $item->get_quantity();
            }
            $subtotal = $round ? number_format((float) $subtotal, wc_get_price_decimals(), '.', '') : $subtotal;
        }
        return apply_filters('woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round);
    }
    public function get_line_subtotal($item, $inc_tax = false, $round = true)
    {
        $subtotal = 0;
        if (is_callable(array($item, 'get_subtotal'))) {
            if ($inc_tax) {
                $subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
            } else {
                $subtotal = $item->get_subtotal();
            }
            $subtotal = $round ? NumberUtil::round($subtotal, wc_get_price_decimals()) : $subtotal;
        }
        return apply_filters('woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round);
    }
    public function get_item_total($item, $inc_tax = false, $round = true)
    {
        $total = 0;
        if (is_callable(array($item, 'get_total')) && $item->get_quantity()) {
            if ($inc_tax) {
                $total = ($item->get_total() + $item->get_total_tax()) / $item->get_quantity();
            } else {
                $total = floatval($item->get_total()) / $item->get_quantity();
            }
            $total = $round ? NumberUtil::round($total, wc_get_price_decimals()) : $total;
        }
        return apply_filters('woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round);
    }
    public function get_line_total($item, $inc_tax = false, $round = true)
    {
        $total = 0;
        if (is_callable(array($item, 'get_total'))) {
            $total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
            $total = $round ? NumberUtil::round($total, wc_get_price_decimals()) : $total;
        }
        return apply_filters('woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round);
    }
    public function get_item_tax($item, $round = true)
    {
        $tax = 0;
        if (is_callable(array($item, 'get_total_tax')) && $item->get_quantity()) {
            $tax = $item->get_total_tax() / $item->get_quantity();
            $tax = $round ? wc_round_tax_total($tax) : $tax;
        }
        return apply_filters('woocommerce_order_amount_item_tax', $tax, $item, $round, $this);
    }
    public function get_line_tax($item)
    {
        return apply_filters('woocommerce_order_amount_line_tax', is_callable(array($item, 'get_total_tax')) ? wc_round_tax_total($item->get_total_tax()) : 0, $item, $this);
    }
    public function get_formatted_line_subtotal($item, $tax_display = '')
    {
        $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart');
        if ('excl' === $tax_display) {
            $ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
            $subtotal = wc_price($this->get_line_subtotal($item), array('ex_tax_label' => $ex_tax_label, 'currency' => $this->get_currency()));
        } else {
            $subtotal = wc_price($this->get_line_subtotal($item, true), array('currency' => $this->get_currency()));
        }
        return apply_filters('woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this);
    }
    public function get_formatted_order_total()
    {
        $formatted_total = wc_price($this->get_total(), array('currency' => $this->get_currency()));
        return apply_filters('woocommerce_get_formatted_order_total', $formatted_total, $this);
    }
    public function get_subtotal_to_display($compound = false, $tax_display = '')
    {
        $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart');
        $subtotal = $this->get_cart_subtotal_for_order();
        if (!$compound) {
            if ('incl' === $tax_display) {
                $subtotal_taxes = 0;
                foreach ($this->get_items() as $item) {
                    $subtotal_taxes += self::round_line_tax($item->get_subtotal_tax(), false);
                }
                $subtotal += wc_round_tax_total($subtotal_taxes);
            }
            $subtotal = wc_price($subtotal, array('currency' => $this->get_currency()));
            if ('excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled()) {
                $subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
            }
        } else {
            if ('incl' === $tax_display) {
                return '';
            }
            $subtotal += $this->get_shipping_total();
            foreach ($this->get_taxes() as $tax) {
                if ($tax->is_compound()) {
                    continue;
                }
                $subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
            }
            $subtotal = $subtotal - $this->get_total_discount();
            $subtotal = wc_price($subtotal, array('currency' => $this->get_currency()));
        }
        return apply_filters('woocommerce_order_subtotal_to_display', $subtotal, $compound, $this);
    }
    public function get_shipping_to_display($tax_display = '')
    {
        $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart');
        if (0 < abs((float) $this->get_shipping_total())) {
            if ('excl' === $tax_display) {
                $shipping = wc_price($this->get_shipping_total(), array('currency' => $this->get_currency()));
                if ((float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax()) {
                    $shipping .= apply_filters('woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display);
                }
            } else {
                $shipping = wc_price($this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()));
                if ((float) $this->get_shipping_tax() > 0 && !$this->get_prices_include_tax()) {
                    $shipping .= apply_filters('woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display);
                }
            }
            $shipping .= apply_filters('woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf(__('via %s', 'woocommerce'), $this->get_shipping_method()) . '</small>', $this);
        } elseif ($this->get_shipping_method()) {
            $shipping = $this->get_shipping_method();
        } else {
            $shipping = __('Free!', 'woocommerce');
        }
        return apply_filters('woocommerce_order_shipping_to_display', $shipping, $this, $tax_display);
    }
    public function get_discount_to_display($tax_display = '')
    {
        $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart');
        return apply_filters('woocommerce_order_discount_to_display', wc_price($this->get_total_discount('excl' === $tax_display && 'excl' === get_option('woocommerce_tax_display_cart')), array('currency' => $this->get_currency())), $this);
    }
    protected function add_order_item_totals_subtotal_row(&$total_rows, $tax_display)
    {
        $subtotal = $this->get_subtotal_to_display(false, $tax_display);
        if ($subtotal) {
            $total_rows['cart_subtotal'] = array('label' => __('Subtotal:', 'woocommerce'), 'value' => $subtotal);
        }
    }
    protected function add_order_item_totals_discount_row(&$total_rows, $tax_display)
    {
        if ($this->get_total_discount() > 0) {
            $total_rows['discount'] = array('label' => __('Discount:', 'woocommerce'), 'value' => '-' . $this->get_discount_to_display($tax_display));
        }
    }
    protected function add_order_item_totals_shipping_row(&$total_rows, $tax_display)
    {
        if ($this->get_shipping_method()) {
            $total_rows['shipping'] = array('label' => __('Shipping:', 'woocommerce'), 'value' => $this->get_shipping_to_display($tax_display));
        }
    }
    protected function add_order_item_totals_fee_rows(&$total_rows, $tax_display)
    {
        $fees = $this->get_fees();
        if ($fees) {
            foreach ($fees as $id => $fee) {
                if (apply_filters('woocommerce_get_order_item_totals_excl_free_fees', empty($fee['line_total']) && empty($fee['line_tax']), $id)) {
                    continue;
                }
                $total_rows['fee_' . $fee->get_id()] = array('label' => $fee->get_name() . ':', 'value' => wc_price('excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency())));
            }
        }
    }
    protected function add_order_item_totals_tax_rows(&$total_rows, $tax_display)
    {
        if ('excl' === $tax_display && wc_tax_enabled()) {
            if ('itemized' === get_option('woocommerce_tax_total_display')) {
                foreach ($this->get_tax_totals() as $code => $tax) {
                    $total_rows[sanitize_title($code)] = array('label' => $tax->label . ':', 'value' => $tax->formatted_amount);
                }
            } else {
                $total_rows['tax'] = array('label' => WC()->countries->tax_or_vat() . ':', 'value' => wc_price($this->get_total_tax(), array('currency' => $this->get_currency())));
            }
        }
    }
    protected function add_order_item_totals_total_row(&$total_rows, $tax_display)
    {
        $total_rows['order_total'] = array('label' => __('Total:', 'woocommerce'), 'value' => $this->get_formatted_order_total($tax_display));
    }
    public function get_order_item_totals($tax_display = '')
    {
        $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart');
        $total_rows = array();
        $this->add_order_item_totals_subtotal_row($total_rows, $tax_display);
        $this->add_order_item_totals_discount_row($total_rows, $tax_display);
        $this->add_order_item_totals_shipping_row($total_rows, $tax_display);
        $this->add_order_item_totals_fee_rows($total_rows, $tax_display);
        $this->add_order_item_totals_tax_rows($total_rows, $tax_display);
        $this->add_order_item_totals_total_row($total_rows, $tax_display);
        return apply_filters('woocommerce_get_order_item_totals', $total_rows, $this, $tax_display);
    }
    public function has_status($status)
    {
        return apply_filters('woocommerce_order_has_status', is_array($status) && in_array($this->get_status(), $status, true) || $this->get_status() === $status, $this, $status);
    }
    public function has_shipping_method($method_id)
    {
        foreach ($this->get_shipping_methods() as $shipping_method) {
            if (strpos($shipping_method->get_method_id(), $method_id) === 0) {
                return true;
            }
        }
        return false;
    }
    public function has_free_item()
    {
        foreach ($this->get_items() as $item) {
            if (!$item->get_total()) {
                return true;
            }
        }
        return false;
    }
}