kilbot/WooCommerce-POS

View on GitHub
includes/api/legacy/orders.php

Summary

Maintainability
F
3 days
Test Coverage
<?php

/**
 * POS Orders Class
 * duck punches the WC REST API
 *
 * @package    WCPOS\API_Orders
 * @author   Paul Kilmurray <paul@kilbot.com.au>
 * @link     http://www.wcpos.com
 */

namespace WCPOS\API;

use WC_API_Server;
use WC_Emails;
use WC_Order;
use WCPOS\Admin\Settings\Checkout;
use WCPOS\Gateways\Cash;
use WP_Error;
use WC_API_Exception;

class Orders {

    /** @var string $base the route base */
    protected $base = '/orders';

    /** @var array Contains the raw order data */
    private $data = array();
    private $flag = false;


    /**
     * @param WC_API_Server $server
     */
    public function __construct( WC_API_Server $server ) {

        // order response
        add_filter( 'woocommerce_api_order_response', array( $this, 'order_response' ), 10, 4 );

        // order emails
        add_filter( 'woocommerce_email', array( $this, 'woocommerce_email' ), 99 );

        if ( substr( $server->path, 0, strlen( $this->base ) ) === $this->base ) {
            add_filter( 'woocommerce_api_create_order_data', array( $this, 'create_order_data' ) );
            add_filter( 'woocommerce_api_edit_order_data', array( $this, 'edit_order_data' ), 10, 2 );
            add_action( 'woocommerce_api_create_order', array( $this, 'create_order' ) );
            add_action( 'woocommerce_api_edit_order', array( $this, 'edit_order' ) );

            // payment
            add_action( 'woocommerce_pos_process_payment', array( $this, 'process_payment' ), 10, 2 );
            add_action( 'woocommerce_payment_complete', array( $this, 'payment_complete' ), 10, 1 );

            // dummy order
            add_filter( 'woocommerce_api_serve_request', array( $this, 'serve_request' ), 10, 3 );

            // email receipts
            add_filter( 'woocommerce_api_create_order_note_data', array( $this, 'create_order_note_data' ), 10, 2 );
        }

    }


    /**
     * Create order data
     *
     * @param array $data
     * @return array
     */
    public function create_order_data( array $data ) {

        // add filters & actions for create order
        add_filter( 'woocommerce_product_tax_class', array( $this, 'product_tax_class' ), 10, 2 );
        add_filter( 'woocommerce_get_product_from_item', array( $this, 'get_product_from_item' ), 10, 3 );
        add_filter( 'pre_option_woocommerce_tax_based_on', array( $this, 'woocommerce_tax_based_on' ) );
        add_filter( 'woocommerce_find_rates', array( $this, 'find_rates' ), 10, 2 );
        add_action( 'woocommerce_order_add_product', array( $this, 'order_add_product' ), 10, 5 );
        add_action( 'updated_order_item_meta', array( $this, 'updated_order_item_meta' ), 10, 4 );

        // WC API < 2.4 has a bug if $fee['taxable'] = false
        // set $fee['taxable'] = true and use random tax_class so not tax is calculated
        if ( version_compare( WC()->version, '2.4', '<' ) && isset( $data['fee_lines'] ) ) {
            foreach ( $data['fee_lines'] as &$fee ) {
                if ( ! isset( $fee['taxable'] ) || $fee['taxable'] == false ) {
                    $fee['taxable']   = true;
                    $fee['tax_class'] = 'upgrade-woocommerce-' . time();
                }
            }
        }

        // WC handling of shipping is FUBAR
        // if order has shipping line, we'll have to calc the tax ourselves
        // also recalc total tax for negative fee lines
        $has_fee      = isset( $data['fee_lines'] ) && ! empty( $data['fee_lines'] );
        $has_shipping = isset( $data['shipping_lines'] ) && ! empty( $data['shipping_lines'] );

        if ( $has_shipping ) {
            add_filter( 'pre_option_woocommerce_shipping_tax_class', array( $this, 'woocommerce_shipping_tax_class' ) );
            add_action( 'woocommerce_order_add_shipping', array( $this, 'order_add_shipping' ), 10, 3 );
        }

        if ( $has_fee || $has_shipping ) {
            add_filter( 'update_post_metadata', array( $this, 'update_post_metadata' ), 10, 5 );
        }

        // populate customer data
        $customer_id = isset( $data['customer_id'] ) ? $data['customer_id'] : 0;
        if ( $customer_id ) {
            $data['billing_address']  = $this->get_customer_details( $customer_id, 'billing' );
            $data['shipping_address'] = $this->get_customer_details( $customer_id, 'shipping' );
        }

        // store data
        $this->data = $data;

        return $this->data;
    }

    /**
     * Edit order data
     *
     * @param $data
     * @param $order_id
     * @return array
     */
    public function edit_order_data( array $data, $order_id ) {
//    $this->delete_order_items($order_id);
        return $this->create_order_data( $data );
    }

    /**
     * Change the product tax_class
     * @param $tax_class
     * @param $product
     * @return string
     */
    public function product_tax_class( $tax_class, $product ) {

        $id = isset( $product->id ) ? $product->id : false;
        $id = isset( $product->variation_id ) ? $product->variation_id : $id;

        if ( $id ) {
            $item = $this->get_line_item( $id );
            if ( isset( $item['tax_class'] ) ) {
                $tax_class = sanitize_text_field( $item['tax_class'] );
            }
        }

        return $tax_class;
    }

    /**
     * Set taxable
     * @param $product
     * @param $item
     * @param $WC_Product
     * @return mixed
     */
    public function get_product_from_item( $product, $item, $WC_Product ) {

        $id = isset( $product->id ) ? $product->id : false;
        $id = isset( $product->variation_id ) ? $product->variation_id : $id;

        if ( $id ) {
            $data = $this->get_line_item( $id );
            if ( isset( $data['taxable'] ) ) {
                // api has options true/false
                // tax_status has options taxable/shipping/none
                $product->tax_status = $data['taxable'] ? 'taxable' : 'none';
            }
        }

        return $product;

    }

    /**
     * Opportunity to change title and add line_item meta
     * @param $order_id
     * @param $item_id
     * @param $product
     * @param $qty
     * @param $args
     */
    public function order_add_product( $order_id, $item_id, $product, $qty, $args ) {

        $id = isset( $product->id ) ? $product->id : false;
        $id = isset( $product->variation_id ) ? $product->variation_id : $id;

        if ( $id ) {
            $data = $this->get_line_item( $id );

            // update title
            if ( isset( $data['title'] ) ) {
                wc_update_order_item( $item_id,
                    array(
                        'order_item_name' => sanitize_text_field( $data['title'] )
                    )
                );
            }

            // update meta
            if ( isset( $data['meta'] ) && ! empty( $data['meta'] ) ) {
                $this->add_product_meta( $item_id, $data['meta'] );
            }

            // add regular_price and taxable
            wc_add_order_item_meta( $item_id, '_regular_price', $data['regular_price'] );
            wc_add_order_item_meta( $item_id, '_taxable', $data['taxable'] );

        }

    }

    /**
     * Add product meta
     * @param $item_id
     * @param array $meta
     */
    private function add_product_meta( $item_id, array $meta ) {

        // line meta
        foreach ( $meta as $m ) {
            $label = isset( $m['label'] ) ? $m['label'] : '';
            $value = isset( $m['value'] ) ? $m['value'] : '';
            wc_add_order_item_meta( $item_id, $label, $value );
        }

    }

    /**
     *
     * @param $order_id
     * @param $item_id
     * @param $rate
     */
    public function order_add_shipping( $order_id, $item_id, $rate ) {
        $shipping_line = $this->get_shipping_line( $rate, $item_id );

        if ( $shipping_line && isset( $shipping_line['tax'] ) ) {
            $taxes = array();
            foreach ( $shipping_line['tax'] as $k => $tax ) {
                if ( ! empty( $tax ) ) {
                    $taxes[ $k ] = isset( $tax['total'] ) ? wc_format_decimal( $tax['total'] ) : 0;
                }
            }
            wc_update_order_item_meta( $item_id, 'taxes', $taxes );
        }
    }

    /**
     * Short circuit get_option('woocommerce_tax_based_on') as shop base
     * @return string
     */
    public function woocommerce_tax_based_on() {
        return 'base';
    }

    /**
     * Short circuit get_option('woocommerce_shipping_tax_class') as standard
     * @return string
     */
    public function woocommerce_shipping_tax_class() {
        return 'standard';
    }

    /**
     * Remove calc_shipping_tax
     * @param $matched_tax_rates
     * @param $args
     * @return array
     */
    public function find_rates( $matched_tax_rates, $args ) {
        if ( $matched_tax_rates ) {
            foreach ( $matched_tax_rates as &$rate ) {
                $rate['shipping'] = 'no';
            }
        }

        return $matched_tax_rates;
    }

    /**
     * Retrieve line_item from raw data
     * @param $product_id
     */
    private function get_line_item( $product_id ) {
        if ( ! isset( $this->data['line_items'] ) ) {
            return;
        }

        foreach ( $this->data['line_items'] as $item ) {
            if ( isset( $item['product_id'] ) && $item['product_id'] == $product_id ) {
                return $item;
            }
        }
    }

    /**
     * Match $rate properties to raw data
     * - there's no id to match so do best guess match id, title & cost
     * @param $rate
     * @param $item_id
     */
    private function get_shipping_line( $rate, $item_id ) {
        if ( ! isset( $this->data['shipping_lines'] ) ) {
            return;
        }

        $lines = $this->data['shipping_lines'];
        foreach ( $lines as $key => &$line ) {
            if (
                $line['method_id'] == $rate->method_id &&
                $line['method_title'] == $rate->label &&
                $line['total'] == $rate->cost &&
                ! isset( $line['id'] )
            ) {
                $line['id'] = $item_id;
            }

            return $line;
        }
    }

    /**
     * Fix item meta for negative product/fee values
     * @param $meta_id
     * @param $order_id
     * @param $meta_key
     * @param $meta_value
     */
    public function updated_order_item_meta( $meta_id, $order_id, $meta_key, $meta_value ) {

        if ( $meta_key == '_line_tax_data' ) {
            $line_subtotal_taxes = isset( $meta_value['subtotal'] ) ? $meta_value['subtotal'] : array();
            $line_taxes          = isset( $meta_value['total'] ) ? $meta_value['total'] : array();
            $line_subtotal_tax   = array_sum( $line_subtotal_taxes );
            $line_tax            = array_sum( $line_taxes );

            wc_update_order_item_meta( $order_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) );
            wc_update_order_item_meta( $order_id, '_line_tax', wc_format_decimal( $line_tax ) );
        }

    }

    /**
     * Apply shipping tax, fix order_tax
     * -
     * - filter has already passed $this->data['shipping_tax'] test
     * @param $null
     * @param $order_id
     * @param $meta_key
     * @param $meta_value
     * @param $prev_value
     * @return null
     */
    public function update_post_metadata( $null, $order_id, $meta_key, $meta_value, $prev_value ) {

        // we want last update to _order_shipping after $order->calculate_taxes()
        // set flag true on first pass
        if ( $meta_key == '_order_shipping_tax' ) {
            $this->flag = true;
        }

        if ( $meta_key != '_order_shipping' || ! $this->flag ) {
            return $null;
        }

        // update order meta
        $shipping_tax_total = isset( $this->data['shipping_tax'] ) ? $this->data['shipping_tax'] : 0;
        update_post_meta( $order_id, '_order_shipping_tax', wc_format_decimal( $shipping_tax_total ) );

        // check each shipping tax line
        // if order item meta already exists, update the shipping_tax_amount
        // if order item meta not present, add the new tax
        // ... nasty :(

        // first get an assoc array of $rate_id => $item_id
        $tax_items = array();
        $order_tax = 0;
        $order     = wc_get_order( $order_id );
        foreach ( $order->get_tax_totals() as $code => $tax ) {
            $tax_items[ $tax->rate_id ] = $tax->id;
            $order_tax                  += $tax->amount;
        }

        // fix total_tax calc
        update_post_meta( $order_id, '_order_tax', $order_tax );

        // now loop through the shipping_lines
        if ( isset( $shipping['shipping_lines'] ) ) :
            foreach ( $this->data['shipping_lines'] as $shipping ) :
                if ( isset( $shipping['tax'] ) ) :
                    foreach ( $shipping['tax'] as $rate_id => $tax ) :
                        if ( isset( $tax['total'] ) ) :

                            if ( array_key_exists( $rate_id, $tax_items ) ) {
                                wc_update_order_item_meta( $tax_items[ $rate_id ], 'shipping_tax_amount', wc_format_decimal( $tax['total'] ) );
                            } else {
                                $order->add_tax( $rate_id, 0, $tax['total'] );
                            }

                        endif;
                    endforeach;
                endif;
            endforeach;
        endif;

        return $null;
    }

    /**
     * Create order complete
     * @todo combine all pos meta for REST transfer
     *
     * @param $order_id
     */
    public function create_order( $order_id ) {
        // pos meta
        $current_user = wp_get_current_user();

        // pos meta
        update_post_meta( $order_id, '_pos', 1 );
        update_post_meta( $order_id, '_pos_user', $current_user->ID );
        update_post_meta( $order_id, '_pos_user_name', $current_user->user_firstname . ' ' . $current_user->user_lastname );

        // pos tax meta
        $pos_taxes = isset( $this->data['pos_taxes'] ) ? $this->data['pos_taxes'] : '';
        if ( ! empty( $pos_taxes ) ) {
            update_post_meta( $order_id, '_pos_taxes', $pos_taxes );
        }

        // check _order_tax and _order_shipping_tax for reports
        if ( ! get_post_meta( $order_id, '_order_tax', true ) ) {
            update_post_meta( $order_id, '_order_tax', 0 );
        }

        if ( ! get_post_meta( $order_id, '_order_shipping_tax', true ) ) {
            update_post_meta( $order_id, '_order_shipping_tax', 0 );
        }

        // payment
        do_action( 'woocommerce_pos_process_payment', $order_id, $this->data );
    }

    /**
     * Edit order complete
     * @param $order_id
     */
    public function edit_order( $order_id ) {

        // pos tax meta
        $pos_taxes = isset( $this->data['pos_taxes'] ) ? $this->data['pos_taxes'] : '';
        if ( ! empty( $pos_taxes ) ) {
            update_post_meta( $order_id, '_pos_taxes', $pos_taxes );
        }

        // payment
        do_action( 'woocommerce_pos_process_payment', $order_id, $this->data );
    }

    /**
     * Process payment
     * @param $order_id
     * @param $data
     */
    public function process_payment( $order_id, $data ) {

        if ( ! isset( $data['payment_details'] ) ) {
            return;
        }

        // some gateways check if a user is signed in, so let's switch to customer
        $logged_in_user = get_current_user_id();
        $customer_id    = isset( $data['customer_id'] ) ? $data['customer_id'] : 0;
        wp_set_current_user( $customer_id );

        // load the gateways & process payment
        $gateway_id = $data['payment_details']['method_id'];
        add_filter( 'option_woocommerce_' . $gateway_id . '_settings', array( $this, 'force_enable_gateway' ) );
        $settings = Checkout::get_instance();
        $gateways = $settings->load_enabled_gateways();

        if ( isset( $gateways[ $gateway_id ] ) ) {
            do_action( 'woocommerce_pos_process_' . strtolower( $gateway_id ) . '_payment', $order_id, $data );
            $response = $gateways[ $gateway_id ]->process_payment( $order_id );
            if ( isset( $response['result'] ) && $response['result'] == 'success' ) {
                $this->payment_success( $gateway_id, $order_id, $response );
            } else {
                $this->payment_failure( $gateway_id, $order_id, $response );
            }

        } else {
            $this->payment_failure( $gateway_id, $order_id, '' );
        }

        // switch back to logged in user
        wp_set_current_user( $logged_in_user );

        // clear any payment gateway messages
        wc_clear_notices();
    }

    /**
     * Some gateways will check if enabled
     * @param $data
     * @return mixed
     */
    public function force_enable_gateway( $data ) {
        if ( isset( $data['enabled'] ) ) {
            $data['enabled'] = 'yes';
        }

        return $data;
    }

    /**
     * @param $gateway_id
     * @param $order_id
     * @param $response
     */
    private function payment_success( $gateway_id, $order_id, $response ) {

        // capture any instructions
        ob_start();
        do_action( 'woocommerce_thankyou_' . $gateway_id, $order_id );
        $response['messages'] = ob_get_contents();
        ob_end_clean();

        // redirect
        if ( isset( $response['redirect'] ) ) {
            $response['messages'] = $this->payment_redirect( $gateway_id, $order_id, $response );
        }

        update_post_meta( $order_id, '_pos_payment_result', 'success' );
        update_post_meta( $order_id, '_pos_payment_message', $response['messages'] );
    }

    /**
     * @param $gateway_id
     * @param $order_id
     * @param $response
     */
    private function payment_failure( $gateway_id, $order_id, $response ) {
        $message = isset( $response['messages'] ) ? $response['messages'] : wc_get_notices( 'error' );

        // if messages empty give generic response
        if ( empty( $message ) ) {
            $message = __( 'There was an error processing the payment', 'woocommerce-pos' );
        }

        update_post_meta( $order_id, '_pos_payment_result', 'failure' );
        update_post_meta( $order_id, '_pos_payment_message', $message );
    }

    /**
     * @param $gateway_id
     * @param $order_id
     * @param $response
     * @return string
     */
    private function payment_redirect( $gateway_id, $order_id, $response ) {
        $message = $response['messages'];

        // compare url fragments
        $success_url = wc_get_endpoint_url( 'order-received', $order_id, get_permalink( wc_get_page_id( 'checkout' ) ) );
        $success     = wp_parse_args( parse_url( $success_url ), array( 'host' => '', 'path' => '' ) );
        $redirect    = wp_parse_args( parse_url( $response['redirect'] ), array( 'host' => '', 'path' => '' ) );

        $offsite = $success['host'] !== $redirect['host'];
        $reload  = ! $offsite && $success['path'] !== $redirect['path'] && $response['messages'] == '';

        if ( $offsite || $reload ) {
            update_post_meta( $order_id, '_pos_payment_redirect', $response['redirect'] );
            $message = __( 'You are now being redirected offsite to complete the payment. ', 'woocommerce-pos' );
            $message .= sprintf( __( '<a href="%s">Click here</a> if you are not redirected automatically. ', 'woocommerce-pos' ), $response['redirect'] );
        }

        return $message;
    }

    /**
     * @param $order_id
     */
    public function payment_complete( $order_id ) {

        // update order status
        $order = new WC_Order( $order_id );
        if ( $order->status == 'processing' ) {
            $message = __( 'POS Transaction completed.', 'woocommerce-pos' );
            $order->update_status( wcpos_get_option( 'checkout', 'order_status' ), $message );
        }

    }

    /**
     * Add any payment messages to API response
     * Also add subtotal_tax to receipt which is not included for some reason
     * @param array $order_data
     * @param $order
     * @param $fields
     * @param $server
     * @return array
     */
    public function order_response( $order_data, $order, $fields, $server ) {

        // add cashier data
        $order_data['cashier'] = $this->add_cashier_details( $order );

        // add pos payment info
        $payment_details               = isset( $order_data['payment_details'] ) ? $order_data['payment_details'] : array();
        $order_data['payment_details'] = $this->add_payment_details( $order, $payment_details );

        // addresses
//    $order_data['billing_address'] = $this->filter_address($order_data['billing_address'], $order);
//    $order_data['shipping_address'] = $this->filter_address($order_data['shipping_address'], $order, 'shipping');

        // allow decimal quantity
        // fixed in WC 2.4+
        if ( version_compare( WC()->version, '2.4', '<' ) && wcpos_get_option( 'products', 'decimal_qty' ) ) {
            $order_data['line_items'] = $this->filter_qty( $order_data['line_items'] );
        }

        // add subtotal_tax
        // add line_item regular_price and taxable
        $subtotal_tax = 0;
        foreach ( $order_data['line_items'] as &$item ) {
            if ( isset( $item['subtotal_tax'] ) ) {
                $subtotal_tax += wc_format_decimal( $item['subtotal_tax'] );
            }

            $item['regular_price'] = wc_get_order_item_meta( $item['id'], '_regular_price' );
            if ( ! $item['regular_price'] ) {
                // get regular price from subtotal
                $item['regular_price'] = wc_format_decimal( $item['subtotal'] / $item['quantity'] );
            }

            $taxable = wc_get_order_item_meta( $item['id'], '_taxable' );
            if ( $taxable ) {
                $item['taxable'] = $taxable == '1';
            } else {
                // take a guess
                $item['taxable'] = floatval( $item['total_tax'] ) > 0;
            }
        }
        $order_data['subtotal_tax'] = wc_format_decimal( $subtotal_tax );

        // add shipping tax
        foreach ( $order_data['shipping_lines'] as &$item ) {
            if ( isset( $item['id'] ) ) {
                $taxes             = wc_get_order_item_meta( $item['id'], 'taxes' );
                $item['total_tax'] = is_array( $taxes ) ? wc_format_decimal( array_sum( $taxes ) ) : 0;
            }
        }

        // add cart discount tax
        $order_data['cart_discount_tax'] = get_post_meta( $order->id, '_cart_discount_tax', true );

        // add pos tax meta
        $pos_taxes = get_post_meta( $order->id, '_pos_taxes', true );
        if ( $pos_taxes ) {
            $order_data['pos_taxes'] = $pos_taxes;
        }

        return $order_data;
    }

    /**
     * @param $order
     * @return array
     */
    private function add_cashier_details( $order ) {

        if ( ! $cashier_id = get_post_meta( $order->id, '_pos_user', true ) ) {
            return;
        }

        $first_name = get_post_meta( $order->id, '_pos_user_first_name', true );
        $last_name  = get_post_meta( $order->id, '_pos_user_last_name', true );
        if ( ! $first_name && ! $last_name && $user_info = get_userdata( $cashier_id ) ) {
            $first_name = $user_info->first_name;
            $last_name  = $user_info->last_name;
        }

        $cashier = array(
            'id'         => $cashier_id,
            'first_name' => $first_name,
            'last_name'  => $last_name
        );

        return apply_filters( 'woocommerce_pos_order_response_cashier', $cashier, $order );

    }

    /**
     * @param $order
     * @param array $payment_details
     * @return array
     */
    private function add_payment_details( $order, array $payment_details ) {

        $payment_details['result']   = get_post_meta( $order->id, '_pos_payment_result', true );
        $payment_details['message']  = get_post_meta( $order->id, '_pos_payment_message', true );
        $payment_details['redirect'] = get_post_meta( $order->id, '_pos_payment_redirect', true );

        if ( isset( $payment_details['method_id'] ) && $payment_details['method_id'] == 'pos_cash' ) {
            $payment_details['method_pos_cash'] = Cash::payment_details( $order );
        }

        return apply_filters( 'woocommerce_pos_order_response_payment_details', $payment_details, $order );

    }

    /**
     * Adds support for custom address fields
     * @param $address
     * @param $order
     * @param string $type
     * @return array
     */
    private function filter_address( $address, $order, $type = 'billing' ) {
        $fields = apply_filters( 'woocommerce_admin_' . $type . '_fields', false );
        if ( $fields ) {
            $address = array();
            foreach ( $fields as $key => $value ) {
                $address[ $key ] = $order->{$type . '_' . $key};
            }
        }

        return $address;
    }

    /**
     * Get customer details
     * - mirrors woocommerce/includes/class-wc-ajax.php->get_customer_details()
     * @param $user_id
     * @param $type_to_load
     * @return mixed|void
     */
    private function get_customer_details( $user_id, $type_to_load ) {
        $customer_data = array(
            $type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
            $type_to_load . '_last_name'  => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
            $type_to_load . '_company'    => get_user_meta( $user_id, $type_to_load . '_company', true ),
            $type_to_load . '_address_1'  => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
            $type_to_load . '_address_2'  => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
            $type_to_load . '_city'       => get_user_meta( $user_id, $type_to_load . '_city', true ),
            $type_to_load . '_postcode'   => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
            $type_to_load . '_country'    => get_user_meta( $user_id, $type_to_load . '_country', true ),
            $type_to_load . '_state'      => get_user_meta( $user_id, $type_to_load . '_state', true ),
            $type_to_load . '_email'      => get_user_meta( $user_id, $type_to_load . '_email', true ),
            $type_to_load . '_phone'      => get_user_meta( $user_id, $type_to_load . '_phone', true ),
        );
        $customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );

        // remove billing_ or shipping_ prefix for WC REST API
        $data = array();
        foreach ( $customer_data as $key => $value ): if ( $value ):
            $key          = str_replace( $type_to_load . '_', '', $key );
            $data[ $key ] = $value;
        endif; endforeach;

        return $data;
    }

    /**
     * @param $line_items
     * @return mixed
     */
    private function filter_qty( $line_items ) {
        foreach ( $line_items as &$item ) {
            $qty              = wc_get_order_item_meta( $item['id'], '_qty' );
            $item['quantity'] = wc_stock_amount( $qty );
        }

        return $line_items;
    }


    /**
     * Delete all order items
     * @param $order_id
     */
    private function delete_order_items( $order_id ) {
        global $wpdb;
        $order_item_ids = $wpdb->get_col( $wpdb->prepare( "
      SELECT      order_item_id
      FROM        {$wpdb->prefix}woocommerce_order_items
      WHERE       order_id = %d
    ", $order_id ) );

        foreach ( $order_item_ids as $item_id ) {
            wc_delete_order_item( $item_id );
        }
    }

    /**
     * WC email notifications
     * @param WC_Emails $wc_emails
     */
    public function woocommerce_email( WC_Emails $wc_emails ) {

        if ( ! wcpos_get_option( 'checkout', 'customer_emails' ) ) {
            $this->remove_customer_emails( $wc_emails );
        }

        if ( ! wcpos_get_option( 'checkout', 'admin_emails' ) ) {
            $this->remove_admin_emails( $wc_emails );
        }
    }

    /**
     * @param WC_Emails $wc_emails
     */
    private function remove_customer_emails( WC_Emails $wc_emails ) {
        remove_action(
            'woocommerce_order_status_pending_to_processing_notification',
            array(
                $wc_emails->emails['WC_Email_Customer_Processing_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_pending_to_on-hold_notification',
            array(
                $wc_emails->emails['WC_Email_Customer_Processing_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_completed_notification',
            array(
                $wc_emails->emails['WC_Email_Customer_Completed_Order'],
                'trigger'
            )
        );
    }

    /**
     * @param WC_Emails $wc_emails
     */
    private function remove_admin_emails( WC_Emails $wc_emails ) {
        // send 'woocommerce_low_stock_notification'
        // send 'woocommerce_no_stock_notification'
        // send 'woocommerce_product_on_backorder_notification'
        remove_action(
            'woocommerce_order_status_pending_to_processing_notification',
            array(
                $wc_emails->emails['WC_Email_New_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_pending_to_completed_notification',
            array(
                $wc_emails->emails['WC_Email_New_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_pending_to_on-hold_notification',
            array(
                $wc_emails->emails['WC_Email_New_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_failed_to_processing_notification',
            array(
                $wc_emails->emails['WC_Email_New_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_failed_to_completed_notification',
            array(
                $wc_emails->emails['WC_Email_New_Order'],
                'trigger'
            )
        );
        remove_action(
            'woocommerce_order_status_failed_to_on-hold_notification',
            array(
                $wc_emails->emails['WC_Email_New_Order'],
                'trigger'
            )
        );

    }

    /**
     * Send email receipt
     * @param $data
     * @param $order_id
     * @return mixed
     * @throws WC_API_Exception
     */
    static public function create_order_note_data( $data, $order_id ) {
        if ( ! isset( $data['email'] ) ) {
            return $data;
        }

        $note           = '';
        $order          = wc_get_order( $order_id );
        $original_email = $order->billing_email;

        if ( ! empty( $data['email'] ) ) {
            $order->billing_email = $data['email'];
        }

        if ( ! is_email( $order->billing_email ) ) {
            throw new WC_API_Exception( 'woocommerce_pos_email_error', __( 'A valid email address is required', 'woocommerce-pos' ), 400 );
        }

        // WC Mailer returns nothing, use PHPMailer exceptions instead
        try {
            WC()->mailer()->customer_invoice( $order );
        } catch ( phpmailerException $e ) {
            throw new WC_API_Exception( 'woocommerce_pos_email_error', __( 'There was an error sending the email', 'woocommerce-pos' ), 400 );
        }

        // save email to order billing_email
        if ( isset( $data['save'] ) && $data['save'] ) {
            update_post_meta( $order_id, '_billing_email', $order->billing_email );

            // bump modified date
            $post_modified     = current_time( 'mysql' );
            $post_modified_gmt = current_time( 'mysql', 1 );
            wp_update_post( array(
                'ID'                => $order_id,
                'post_modified'     => $post_modified,
                'post_modified_gmt' => $post_modified_gmt
            ) );

            if ( $original_email != '' && $original_email != $order->billing_email ) {
                $note .= sprintf( __( 'Billing email changed from %s to %s.', 'woocommerce-pos' ), $original_email, $order->billing_email );
                $note .= ' ';
            }
        }

        // hook for third party plugins
        do_action( 'woocommerce_pos_email_receipt', $order->billing_email, $order_id, $order );

        $note         .= sprintf( __( 'Customer receipt manually sent from POS to %s.', 'woocommerce-pos' ), $order->billing_email );
        $data['note'] = $note;

        return $data;
    }

    /**
     *
     */
    public function serve_request( $served, $result, $server ) {

        // check if dummy order is required, eg: for print preview
        $dummy = isset( $server->params['GET']['dummy'] )
                 && $server->params['GET']['dummy'] == 1
                 && isset( $result['orders'] )
                 && empty( $result['orders'] );

        if ( $dummy ) {
            if ( 'HEAD' === $server->method ) {
                return;
            }

            // dummy data
            $result = array(
                'orders' => json_decode( file_get_contents( \WCPOS\PLUGIN_PATH . 'includes/dummy-data/legacy/v3/order.json' ) )
            );

            echo $server->handler->generate_response( $result );
            $served = true;
        }

        return $served;
    }

}