Plugin Directory

Changeset 2440303


Ignore:
Timestamp:
12/16/2020 02:11:40 AM (5 years ago)
Author:
taxjar
Message:

Preparing for 3.2.4 release

Location:
taxjar-simplified-taxes-for-woocommerce/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • taxjar-simplified-taxes-for-woocommerce/trunk/CHANGELOG.md

    r2389903 r2440303  
     1# 3.2.4 (2020-12-15)
     2* Fix occasional missing PTC from subscription orders
     3* Fix issues that prevented exempt customers from syncing to TaxJar
     4* WooCommerce 4.8.0 and WordPress 5.6 support
     5
    16# 3.2.3 (2020-09-28)
    27* Add filter to nexus check
  • taxjar-simplified-taxes-for-woocommerce/trunk/includes/class-taxjar-customer-record.php

    r2147335 r2440303  
    1515        } else {
    1616            try {
    17                 $customer =  new WC_Customer( $this->get_record_id() );
     17                $customer = new WC_Customer( $this->get_record_id() );
    1818                if ( $customer instanceof WC_Customer ) {
    1919                    $this->object = $customer;
     
    5050
    5151        $data = $this->get_data();
    52         if ( empty( $data[ 'customer_id' ] ) ) {
     52        if ( empty( $data['customer_id'] ) ) {
    5353            $this->add_error( __( 'Customer failed validation, customer missing required field: customer_id.', 'wc-taxjar' ) );
    5454            return false;
    5555        }
    5656
    57         if ( empty( $data[ 'exemption_type' ] ) ) {
     57        if ( empty( $data['exemption_type'] ) ) {
    5858            $this->add_error( __( 'Customer failed validation, customer missing required field: exemption_type.', 'wc-taxjar' ) );
    5959            return false;
    6060        }
    6161
    62         if ( empty( $data[ 'name' ] ) ) {
     62        if ( empty( $data['name'] ) ) {
    6363            $this->add_error( __( 'Customer failed validation, customer missing required field: name.', 'wc-taxjar' ) );
    6464            return false;
     
    8989    public function create_in_taxjar() {
    9090        $data = $this->get_data();
    91         $url = self::API_URI . 'customers';
     91        $url  = self::API_URI . 'customers';
    9292        $body = wp_json_encode( $data );
    9393
    94         $response = wp_remote_post( $url, array(
    95             'headers' => array(
    96                 'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
    97                 'Content-Type' => 'application/json',
    98             ),
    99             'user-agent' => $this->taxjar_integration->ua,
    100             'body' => $body,
    101         ) );
     94        $response = wp_remote_post(
     95            $url,
     96            array(
     97                'headers'    => array(
     98                    'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
     99                    'Content-Type'  => 'application/json',
     100                ),
     101                'user-agent' => $this->taxjar_integration->ua,
     102                'body'       => $body,
     103            )
     104        );
    102105
    103106        $this->set_last_request( $body );
     
    109112     * @return array|WP_Error - API response or WP_Error if request fails
    110113     */
    111     public function update_in_taxjar(){
     114    public function update_in_taxjar() {
    112115        $customer_id = $this->get_customer_id();
    113         $data = $this->get_data();
    114 
    115         $url = self::API_URI . 'customers/' . $customer_id;
     116        $data        = $this->get_data();
     117
     118        $url  = self::API_URI . 'customers/' . $customer_id;
    116119        $body = wp_json_encode( $data );
    117120
    118         $response = wp_remote_request( $url, array(
    119             'method' => 'PUT',
    120             'headers' => array(
    121                 'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
    122                 'Content-Type' => 'application/json',
    123             ),
    124             'user-agent' => $this->taxjar_integration->ua,
    125             'body' => $body,
    126         ) );
     121        $response = wp_remote_request(
     122            $url,
     123            array(
     124                'method'     => 'PUT',
     125                'headers'    => array(
     126                    'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
     127                    'Content-Type'  => 'application/json',
     128                ),
     129                'user-agent' => $this->taxjar_integration->ua,
     130                'body'       => $body,
     131            )
     132        );
    127133
    128134        $this->set_last_request( $body );
     
    134140     * @return array|WP_Error - API response or WP_Error if request fails
    135141     */
    136     public function delete_in_taxjar(){
     142    public function delete_in_taxjar() {
    137143        $customer_id = $this->get_customer_id();
    138         $url = self::API_URI . 'customers/' . $customer_id;
    139         $data = array(
     144        $url         = self::API_URI . 'customers/' . $customer_id;
     145        $data        = array(
    140146            'customer_id' => $customer_id,
    141147        );
    142         $body = wp_json_encode( $data );
    143 
    144         $response = wp_remote_request( $url, array(
    145             'method' => 'DELETE',
    146             'headers' => array(
    147                 'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
    148                 'Content-Type' => 'application/json',
    149             ),
    150             'user-agent' => $this->taxjar_integration->ua,
    151             'body' => $body,
    152         ) );
     148        $body        = wp_json_encode( $data );
     149
     150        $response = wp_remote_request(
     151            $url,
     152            array(
     153                'method'     => 'DELETE',
     154                'headers'    => array(
     155                    'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
     156                    'Content-Type'  => 'application/json',
     157                ),
     158                'user-agent' => $this->taxjar_integration->ua,
     159                'body'       => $body,
     160            )
     161        );
    153162
    154163        $this->set_last_request( $body );
     
    162171    public function get_from_taxjar() {
    163172        $customer_id = $this->get_customer_id();
    164         $url = self::API_URI . 'customers/' . $customer_id;
    165 
    166         $response = wp_remote_request( $url, array(
    167             'method' => 'GET',
    168             'headers' => array(
    169                 'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
    170                 'Content-Type' => 'application/json',
    171             ),
    172             'user-agent' => $this->taxjar_integration->ua,
    173         ) );
     173        $url         = self::API_URI . 'customers/' . $customer_id;
     174
     175        $response = wp_remote_request(
     176            $url,
     177            array(
     178                'method'     => 'GET',
     179                'headers'    => array(
     180                    'Authorization' => 'Token token="' . $this->taxjar_integration->settings['api_token'] . '"',
     181                    'Content-Type'  => 'application/json',
     182                ),
     183                'user-agent' => $this->taxjar_integration->ua,
     184            )
     185        );
    174186
    175187        $this->set_last_request( $customer_id );
     
    184196        $customer_data = array();
    185197
    186         $customer_data[ 'customer_id' ] = $this->get_customer_id();
    187         $customer_data[ 'exemption_type' ] = $this->get_exemption_type();
    188 
    189         $first_name = $this->object->get_shipping_first_name();
    190         $last_name = $this->object->get_shipping_last_name();
    191 
    192         if ( empty( $first_name ) ) {
    193             $first_name = $this->object->get_billing_first_name();
    194             if ( empty( $first_name ) ) {
    195                 $first_name = $this->object->get_first_name();
    196             }
    197         }
    198 
    199         if ( empty( $last_name ) ) {
    200             $last_name = $this->object->get_billing_last_name();
    201             if ( empty( $last_name ) ) {
    202                 $last_name = $this->object->get_last_name();
    203             }
    204         }
    205 
    206         if ( empty( $first_name ) && empty( $last_name ) ) {
    207             $name = '';
    208         } else if ( empty( $first_name ) ) {
    209             $name = $last_name;
    210         } else if ( empty( $last_name ) ) {
    211             $name = $first_name;
    212         } else {
    213             $name = $first_name . ' ' . $last_name;
    214         }
    215         $customer_data[ 'name' ] = $name;
    216 
    217         $customer_data[ 'exempt_regions' ] = $this->get_exempt_regions();
     198        $customer_data['customer_id']    = $this->get_customer_id();
     199        $customer_data['name']           = $this->get_customer_name();
     200        $customer_data['exemption_type'] = $this->get_exemption_type();
     201        $customer_data['exempt_regions'] = $this->get_exempt_regions();
    218202
    219203        $country = $this->object->get_shipping_country();
     
    222206        }
    223207        if ( ! empty( $country ) ) {
    224             $customer_data[ 'country' ] = $country;
     208            $customer_data['country'] = $country;
    225209        }
    226210
     
    230214        }
    231215        if ( ! empty( $state ) ) {
    232             $customer_data[ 'state' ] = $state;
     216            $customer_data['state'] = $state;
    233217        }
    234218
     
    238222        }
    239223        if ( ! empty( $postcode ) ) {
    240             $customer_data[ 'zip' ] = $postcode;
     224            $customer_data['zip'] = $postcode;
    241225        }
    242226
     
    246230        }
    247231        if ( ! empty( $city ) ) {
    248             $customer_data[ 'city' ] = $city;
     232            $customer_data['city'] = $city;
    249233        }
    250234
     
    254238        }
    255239        if ( ! empty( $address ) ) {
    256             $customer_data[ 'street' ] = $address;
     240            $customer_data['street'] = $address;
    257241        }
    258242
    259243        $customer_data = apply_filters( 'taxjar_customer_sync_data', $customer_data, $this->object );
    260         $this->data = $customer_data;
     244        $this->data    = $customer_data;
    261245        return $customer_data;
    262246    }
    263247
    264248    /**
     249     * Retrieves the name of the customer.
     250     * Falls back to username if no shipping, billing or account names are available.
     251     *
     252     * @return string - Customer's name
     253     */
     254    public function get_customer_name() {
     255        $name = $this->object->get_shipping_first_name() . ' ' . $this->object->get_shipping_last_name();
     256
     257        if ( ! empty( trim( $name ) ) ) {
     258            return $name;
     259        }
     260
     261        $name = $this->object->get_billing_first_name() . ' ' . $this->object->get_billing_last_name();
     262
     263        if ( ! empty( trim( $name ) ) ) {
     264            return $name;
     265        }
     266
     267        $name = $this->object->get_first_name() . ' ' . $this->object->get_last_name();
     268
     269        if ( ! empty( trim( $name ) ) ) {
     270            return $name;
     271        }
     272
     273        return $this->object->get_username();
     274    }
     275
     276    /**
     277     * Gets the user ID of the record (customer)
     278     *
    265279     * @return int|string
    266280     */
     
    269283    }
    270284
     285    /**
     286     * Gets the exemption type saved on the user
     287     *
     288     * @return mixed|string - exemption type
     289     */
    271290    public function get_exemption_type() {
    272         $valid_types = array( 'wholesale', 'government', 'other', 'non_exempt' );
     291        $valid_types    = array( 'wholesale', 'government', 'other', 'non_exempt' );
    273292        $exemption_type = get_user_meta( $this->object->get_id(), 'tax_exemption_type', true );
    274         if ( ! in_array( $exemption_type, $valid_types ) ) {
     293        if ( ! in_array( $exemption_type, $valid_types, true ) ) {
    275294            $exemption_type = 'non_exempt';
    276295        }
     
    278297    }
    279298
     299    /**
     300     * Get the exempt regions saved on the user
     301     *
     302     * @return array - array of exemption regions
     303     */
    280304    public function get_exempt_regions() {
    281         $states = WC_Taxjar_Customer_Sync::get_all_exempt_regions();
     305        $states               = WC_Taxjar_Customer_Sync::get_all_exempt_regions();
    282306        $valid_exempt_regions = array_keys( $states );
    283         $exempt_meta = get_user_meta( $this->object->get_id(), 'tax_exempt_regions', true );
    284         $saved_regions = explode( ',', $exempt_meta );
    285         $intersect = array_intersect( $valid_exempt_regions, $saved_regions );
    286         $exempt_regions = array();
     307        $exempt_meta          = get_user_meta( $this->object->get_id(), 'tax_exempt_regions', true );
     308        $saved_regions        = explode( ',', $exempt_meta );
     309        $intersect            = array_intersect( $valid_exempt_regions, $saved_regions );
     310        $exempt_regions       = array();
    287311
    288312        if ( ! empty( $intersect ) ) {
    289             foreach( $intersect as $region ) {
     313            foreach ( $intersect as $region ) {
    290314                $exempt_regions[] = array(
    291315                    'country' => 'US',
    292                     'state'   => $region
     316                    'state'   => $region,
    293317                );
    294318            }
  • taxjar-simplified-taxes-for-woocommerce/trunk/includes/class-wc-taxjar-customer-sync.php

    r2147335 r2440303  
    2626     */
    2727    public function init() {
    28         if ( apply_filters( 'taxjar_enabled', isset( $this->taxjar_integration->settings['enabled'] ) && 'yes' == $this->taxjar_integration->settings['enabled'] ) ) {
    29             add_action( 'show_user_profile', array( $this, 'add_customer_meta_fields' ) );
    30             add_action( 'edit_user_profile', array( $this, 'add_customer_meta_fields' ) );
    31 
    32             add_action( 'personal_options_update', array( $this, 'save_customer_meta_fields' ) );
    33             add_action( 'edit_user_profile_update', array( $this, 'save_customer_meta_fields' ) );
    34 
    35             add_action( 'taxjar_customer_exemption_settings_updated', array( $this, 'maybe_sync_customer_on_update' ) );
    36 
    37             add_action( 'delete_user', array( $this, 'maybe_delete_customer' ) );
    38         }
     28        if ( apply_filters( 'taxjar_enabled', isset( $this->taxjar_integration->settings['enabled'] ) && 'yes' === $this->taxjar_integration->settings['enabled'] ) ) {
     29            add_action( 'show_user_profile', array( $this, 'add_customer_meta_fields' ) );
     30            add_action( 'edit_user_profile', array( $this, 'add_customer_meta_fields' ) );
     31
     32            add_action( 'personal_options_update', array( $this, 'save_customer_meta_fields' ) );
     33            add_action( 'edit_user_profile_update', array( $this, 'save_customer_meta_fields' ) );
     34
     35            add_action( 'taxjar_customer_exemption_settings_updated', array( $this, 'maybe_sync_customer_on_update' ) );
     36
     37            add_action( 'delete_user', array( $this, 'maybe_delete_customer' ) );
     38        }
    3939    }
    4040
     
    6565    public function get_customer_meta_fields() {
    6666        $show_fields = apply_filters(
    67             'taxjar_customer_meta_fields', array(
    68                 'exemptions'  => array(
     67            'taxjar_customer_meta_fields',
     68            array(
     69                'exemptions' => array(
    6970                    'title'  => __( 'TaxJar Sales Tax Exemptions', 'wc-taxjar' ),
    7071                    'fields' => array(
    71                         'tax_exemption_type'  => array(
     72                        'tax_exemption_type' => array(
    7273                            'label'       => __( 'Exemption Type', 'wc-taxjar' ),
    7374                            'description' => __( 'All customers are presumed non-exempt unless otherwise selected.', 'wc-taxjar' ),
     
    9091    }
    9192
     93    /**
     94     * Adds customer exemption fields to edit user page
     95     *
     96     * @param $user
     97     */
    9298    public function add_customer_meta_fields( $user ) {
    9399        if ( ! apply_filters( 'taxjar_current_user_can_edit_customer_meta_fields', current_user_can( 'manage_woocommerce' ), $user->ID ) ) {
     
    121127                                $saved_value = esc_attr( get_user_meta( $user->ID, $key, true ) );
    122128                                if ( ! empty( $saved_value ) ) {
    123                                     $saved_value = explode( ',', $saved_value );
    124                                 }
     129                                    $saved_value = explode( ',', $saved_value );
     130                                }
    125131                                foreach ( $field['options'] as $option_key => $option_value ) :
    126                                     if ( ! empty( $saved_value ) && in_array( $option_key, $saved_value ) ) {
    127                                         $selected = $option_key;
    128                                     } else {
    129                                         $selected = false;
    130                                     }
     132                                    if ( ! empty( $saved_value ) && in_array( $option_key, $saved_value, true ) ) {
     133                                        $selected = $option_key;
     134                                    } else {
     135                                        $selected = false;
     136                                    }
    131137                                    ?>
    132138                                    <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $selected, $option_key, true ); ?>><?php echo esc_attr( $option_value ); ?></option>
     
    143149                <?php endforeach; ?>
    144150            </table>
    145         <?php
     151            <?php
    146152        endforeach;
    147153    }
    148154
     155    /**
     156     * Saves tax exemption user meta if necessary
     157     *
     158     * @param $user_id - Id of user to update
     159     */
    149160    public function save_customer_meta_fields( $user_id ) {
    150         if ( ! apply_filters( 'taxjar_current_user_can_edit_customer_meta_fields', current_user_can( 'manage_woocommerce' ), $user_id ) ) {
     161        $has_permission = current_user_can( 'manage_woocommerce' ) || current_user_can( 'edit_users' );
     162        if ( ! apply_filters( 'taxjar_current_user_can_edit_customer_meta_fields', $has_permission, $user_id ) ) {
    151163            return;
    152164        }
    153165
    154         $save_fields = $this->get_customer_meta_fields();
    155         $change = false;
    156         foreach ( $save_fields as $fieldset ) {
    157             foreach ( $fieldset['fields'] as $key => $field ) {
    158                 if ( isset( $field['type'] ) && 'multi-select' === $field['type'] ) {
    159                     $prev_value = get_user_meta( $user_id, $key, true );
    160                     if ( empty( $_POST[ $key ] ) ) {
    161                         $exempt_regions = '';
    162                     } else {
    163                         $exempt_regions = array_map( 'wc_clean',  $_POST[ $key ] );
    164                         $exempt_regions = implode( ',', $exempt_regions );
    165                     }
    166 
    167                     if ( $exempt_regions != $prev_value ) {
    168                         update_user_meta( $user_id, $key, $exempt_regions );
    169                         $change = true;
    170                     }
    171                 } elseif ( isset( $_POST[ $key ] ) ) {
    172                     $prev_value = get_user_meta( $user_id, $key, true );
    173                     $new_value = wc_clean( $_POST[ $key ] );
    174                     if ( $prev_value != $new_value ) {
    175                         update_user_meta( $user_id, $key, $new_value );
    176                         $change = true;
    177                     }
    178                 }
    179             }
    180         }
    181 
    182         if ( $change ) {
    183             do_action( 'taxjar_customer_exemption_settings_updated', $user_id );
    184         }
    185     }
    186 
     166        update_user_meta( $user_id, 'tax_exemption_type', $this->get_posted_exemption_type() );
     167        update_user_meta( $user_id, 'tax_exempt_regions', $this->get_posted_exempt_regions() );
     168
     169        do_action( 'taxjar_customer_exemption_settings_updated', $user_id );
     170    }
     171
     172    /**
     173     * Checks if exemption type has changed when saving a user
     174     *
     175     * @param $user_id - Id of customer
     176     *
     177     * @return bool - Whether or not the exemption type has been changed
     178     */
     179    public function has_exemption_type_changed( $user_id ) {
     180        $saved_exemption_type = self::get_saved_exemption_type( $user_id );
     181        $new_exemption_type   = $this->get_posted_exemption_type();
     182        if ( $new_exemption_type !== $saved_exemption_type ) {
     183            return true;
     184        }
     185
     186        return false;
     187    }
     188
     189    /**
     190     * Gets the submitted tax exemption type during user save
     191     *
     192     * @return array|string - value to save
     193     */
     194    public function get_posted_exemption_type() {
     195        return wc_clean( $_POST['tax_exemption_type'] );
     196    }
     197
     198    /**
     199     * Checks if exempt regions have changed for a user
     200     *
     201     * @param $user_id - Id of user to check
     202     *
     203     * @return bool - Whether or not the exempt regions have changed
     204     */
     205    public function have_exempt_regions_changed( $user_id ) {
     206        $saved_exempt_regions = self::get_saved_exempt_regions( $user_id );
     207        $new_exempt_regions   = $this->get_posted_exempt_regions();
     208        if ( $new_exempt_regions !== $saved_exempt_regions ) {
     209            return true;
     210        }
     211
     212        return false;
     213    }
     214
     215    /**
     216     * Gets the submitted exempt regions value during user save
     217     *
     218     * @return string - Concatenated string containing the exempt regions
     219     */
     220    public function get_posted_exempt_regions() {
     221        if ( empty( $_POST['tax_exempt_regions'] ) ) {
     222            return '';
     223        } else {
     224            return implode( ',', wc_clean( $_POST['tax_exempt_regions'] ) );
     225        }
     226    }
     227
     228    /**
     229     * Gets the exemption type user meta
     230     *
     231     * @param $user_id - ID of user
     232     *
     233     * @return mixed
     234     */
     235    public static function get_saved_exemption_type( $user_id ) {
     236        return get_user_meta( $user_id, 'tax_exemption_type', true );
     237    }
     238
     239    /**
     240     * Gets the exempt regions user metadata
     241     *
     242     * @param $user_id - ID of user
     243     *
     244     * @return mixed
     245     */
     246    public static function get_saved_exempt_regions( $user_id ) {
     247        return get_user_meta( $user_id, 'tax_exempt_regions', true );
     248    }
     249
     250    /**
     251     * Syncs customer record to TaxJar if all validation passes
     252     *
     253     * @param $user_id
     254     */
    187255    public function maybe_sync_customer_on_update( $user_id ) {
    188256        $record = TaxJar_Customer_Record::find_active_in_queue( $user_id );
     
    199267
    200268        $result = $record->sync();
    201     }
    202 
     269    }
     270
     271    /**
     272     * Creates array of valid option for customer exemption type dropdown
     273     *
     274     * @return array - customer exemption type options
     275     */
    203276    public static function get_customer_exemption_types() {
    204277        return array(
    205             'wholesale' => __( 'Wholesale / Resale', 'wc-taxjar' ),
     278            'wholesale'  => __( 'Wholesale / Resale', 'wc-taxjar' ),
    206279            'government' => __( 'Government', 'wc-taxjar' ),
    207             'other' => __( 'Other', 'wc-taxjar' ),
     280            'other'      => __( 'Other', 'wc-taxjar' ),
    208281        );
    209282    }
    210283
     284    /**
     285     * Creates array of all valid exempt regions (states)
     286     *
     287     * @return array - available exempt regions
     288     */
    211289    public static function get_all_exempt_regions() {
    212290        return array(
     
    265343
    266344    /**
    267      * Deletes customer from TaxJar when synced customer is deleted in WordPress
     345     * Deletes customer from TaxJar when synced customer is deleted in WordPress
     346     *
    268347     * @param $id - user id
    269348     */
    270349    public function maybe_delete_customer( $id ) {
    271350        $last_sync = get_user_meta( $id, '_taxjar_last_sync', true );
    272         $hash = get_user_meta( $id, '_taxjar_hash', true );
     351        $hash      = get_user_meta( $id, '_taxjar_hash', true );
    273352        if ( $last_sync || $hash ) {
    274             $record = TaxJar_Customer_Record::find_active_in_queue( $id );
     353            $record = TaxJar_Customer_Record::find_active_in_queue( $id );
    275354            if ( ! $record ) {
    276355                $record = new TaxJar_Customer_Record( $id, true );
     
    279358            $record->delete_in_taxjar();
    280359            $record->delete();
    281         }
    282     }
     360        }
     361    }
    283362
    284363}
  • taxjar-simplified-taxes-for-woocommerce/trunk/includes/class-wc-taxjar-integration.php

    r2374160 r2440303  
    1010if ( ! class_exists( 'WC_Taxjar_Integration' ) ) :
    1111
    12 class WC_Taxjar_Integration extends WC_Settings_API {
    13 
    14     protected static $_instance = null;
    15 
    16     /**
    17      * Main TaxJar Integration Instance.
    18      * Ensures only one instance of TaxJar Integration is loaded or can be loaded.
    19      *
    20      * @return WC_Taxjar_Integration - Main instance.
    21      */
    22     public static function instance() {
    23         if ( is_null( self::$_instance ) ) {
    24             self::$_instance = new self();
    25         }
    26         return self::$_instance;
    27     }
    28 
    29     /**
    30      * Init and hook in the integration.
    31      */
    32     public function __construct() {
    33         $this->id                 = 'taxjar-integration';
    34         $this->method_title       = __( 'TaxJar', 'wc-taxjar' );
    35         $this->method_description = apply_filters( 'taxjar_method_description', __( 'TaxJar is the easiest to use sales tax calculation and reporting engine for WooCommerce. Connect your TaxJar account and enter the city and zip code from which your store ships. Enable TaxJar calculations to automatically collect sales tax at checkout. You may also enable sales tax reporting to begin importing transactions from this store into your TaxJar account, all in one click!<br><br><b>For the fastest help, please email <a href="mailto:[email protected]">[email protected]</a>. We\'ll get back to you within hours.</b>', 'wc-taxjar' ) );
    36         $this->app_uri            = 'https://app.taxjar.com/';
    37         $this->integration_uri    = $this->app_uri . 'account/apps/add/woo';
    38         $this->regions_uri        = $this->app_uri . 'account#states';
    39         $this->uri                = 'https://api.taxjar.com/v2/';
    40         $this->ua                 = self::get_ua_header();
    41         $this->debug              = filter_var( $this->get_option( 'debug' ), FILTER_VALIDATE_BOOLEAN );
    42         $this->download_orders    = new WC_Taxjar_Download_Orders( $this );
    43         $this->transaction_sync   = new WC_Taxjar_Transaction_Sync( $this );
    44         $this->customer_sync      = new WC_Taxjar_Customer_Sync( $this );
    45         $this->api_calculation    = new WC_Taxjar_API_Calculation( $this );
    46 
    47         // Load the settings.
    48         $this->init_settings();
    49 
    50         // Cache rates for 1 hour.
    51         $this->cache_time = HOUR_IN_SECONDS;
    52 
    53         if ( $this->on_settings_page() ) {
    54             add_action( 'admin_enqueue_scripts', array( $this, 'load_taxjar_admin_assets' ) );
    55         }
    56 
    57         // TaxJar Config Tab
    58         add_action( 'admin_menu', array( $this, 'taxjar_admin_menu' ),  15 );
    59         add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 50 );
    60         add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
    61         add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
    62         add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
    63 
    64         add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_taxjar-integration_settings', array( $this, 'sanitize_settings' ), 10, 2 );
    65 
    66         if ( apply_filters( 'taxjar_enabled', isset( $this->settings['enabled'] ) && 'yes' == $this->settings['enabled'] ) ) {
    67             // Calculate Taxes at Cart / Checkout
    68             if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
    69                 add_action( 'woocommerce_after_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     12    class WC_Taxjar_Integration extends WC_Settings_API {
     13
     14        protected static $_instance = null;
     15
     16        /**
     17         * Main TaxJar Integration Instance.
     18         * Ensures only one instance of TaxJar Integration is loaded or can be loaded.
     19         *
     20         * @return WC_Taxjar_Integration - Main instance.
     21         */
     22        public static function instance() {
     23            if ( is_null( self::$_instance ) ) {
     24                self::$_instance = new self();
     25            }
     26            return self::$_instance;
     27        }
     28
     29        /**
     30         * Init and hook in the integration.
     31         */
     32        public function __construct() {
     33            $this->id                 = 'taxjar-integration';
     34            $this->method_title       = __( 'TaxJar', 'wc-taxjar' );
     35            $this->method_description = apply_filters( 'taxjar_method_description', __( 'TaxJar is the easiest to use sales tax calculation and reporting engine for WooCommerce. Connect your TaxJar account and enter the city and zip code from which your store ships. Enable TaxJar calculations to automatically collect sales tax at checkout. You may also enable sales tax reporting to begin importing transactions from this store into your TaxJar account, all in one click!<br><br><b>For the fastest help, please email <a href="mailto:[email protected]">[email protected]</a>. We\'ll get back to you within hours.</b>', 'wc-taxjar' ) );
     36            $this->app_uri            = 'https://app.taxjar.com/';
     37            $this->integration_uri    = $this->app_uri . 'account/apps/add/woo';
     38            $this->regions_uri        = $this->app_uri . 'account#states';
     39            $this->uri                = 'https://api.taxjar.com/v2/';
     40            $this->ua                 = self::get_ua_header();
     41            $this->debug              = filter_var( $this->get_option( 'debug' ), FILTER_VALIDATE_BOOLEAN );
     42            $this->download_orders    = new WC_Taxjar_Download_Orders( $this );
     43            $this->transaction_sync   = new WC_Taxjar_Transaction_Sync( $this );
     44            $this->customer_sync      = new WC_Taxjar_Customer_Sync( $this );
     45            $this->api_calculation    = new WC_Taxjar_API_Calculation( $this );
     46
     47            // Load the settings.
     48            $this->init_settings();
     49
     50            // Cache rates for 1 hour.
     51            $this->cache_time = HOUR_IN_SECONDS;
     52
     53            if ( $this->on_settings_page() ) {
     54                add_action( 'admin_enqueue_scripts', array( $this, 'load_taxjar_admin_assets' ) );
     55            }
     56
     57            // TaxJar Config Tab
     58            add_action( 'admin_menu', array( $this, 'taxjar_admin_menu' ), 15 );
     59            add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 50 );
     60            add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
     61            add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
     62            add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
     63
     64            add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_taxjar-integration_settings', array( $this, 'sanitize_settings' ), 10, 2 );
     65
     66            if ( apply_filters( 'taxjar_enabled', isset( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'] ) ) {
     67                // Calculate Taxes at Cart / Checkout
     68                if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
     69                    add_action( 'woocommerce_after_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     70                } else {
     71                    add_action( 'woocommerce_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     72                }
     73
     74                // Calculate Taxes for Backend Orders (Woo 2.6+)
     75                add_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
     76
     77                // Calculate taxes for WooCommerce Subscriptions renewal orders
     78                add_filter( 'wcs_new_order_created', array( $this, 'calculate_renewal_order_totals' ), 10, 3 );
     79
     80                // Settings Page
     81                add_action( 'woocommerce_sections_tax', array( $this, 'output_sections_before' ), 9 );
     82
     83                // Filters
     84                add_filter( 'woocommerce_calc_tax', array( $this, 'override_woocommerce_tax_rates' ), 10, 3 );
     85                add_filter( 'woocommerce_customer_taxable_address', array( $this, 'append_base_address_to_customer_taxable_address' ), 10, 1 );
     86                add_filter( 'woocommerce_matched_rates', array( $this, 'allow_street_address_for_matched_rates' ), 10, 2 );
     87
     88                // Scripts / Stylesheets
     89                add_action( 'admin_enqueue_scripts', array( $this, 'load_taxjar_admin_new_order_assets' ) );
     90
     91                // If TaxJar is enabled and user disables taxes we re-enable them
     92                update_option( 'woocommerce_calc_taxes', 'yes' );
     93
     94                // Users can set either billing or shipping address for tax rates but not shop
     95                update_option( 'woocommerce_tax_based_on', 'shipping' );
     96
     97                // Rate calculations assume tax not included
     98                update_option( 'woocommerce_prices_include_tax', 'no' );
     99
     100                // Use no special handling on shipping taxes, our API handles that
     101                update_option( 'woocommerce_shipping_tax_class', '' );
     102
     103                // API handles rounding precision
     104                update_option( 'woocommerce_tax_round_at_subtotal', 'no' );
     105
     106                // Rates are calculated in the cart assuming tax not included
     107                update_option( 'woocommerce_tax_display_shop', 'excl' );
     108
     109                // TaxJar returns one total amount, not line item amounts
     110                update_option( 'woocommerce_tax_display_cart', 'excl' );
     111
     112                // TaxJar returns one total amount, not line item amounts
     113                update_option( 'woocommerce_tax_total_display', 'single' );
     114            } // End if().
     115        }
     116
     117        public function add_settings_page( $settings_tabs ) {
     118            $settings_tabs[ $this->id ] = __( 'TaxJar', 'taxjar' );
     119            return $settings_tabs;
     120        }
     121
     122        /**
     123         * Output sections.
     124         */
     125        public function output_sections() {
     126            global $current_section;
     127
     128            $sections = $this->get_sections();
     129
     130            if ( empty( $sections ) || 1 === sizeof( $sections ) ) {
     131                return;
     132            }
     133
     134            echo '<ul class="subsubsub">';
     135
     136            $array_keys = array_keys( $sections );
     137
     138            foreach ( $sections as $id => $label ) {
     139                echo '<li><a href="' . admin_url( 'admin.php?page=wc-settings&tab=' . $this->id . '&section=' . sanitize_title( $id ) ) . '" class="' . ( $current_section === $id ? 'current' : '' ) . '">' . $label . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>';
     140            }
     141
     142            echo '</ul><br class="clear" />';
     143        }
     144
     145        /**
     146         * Output the settings.
     147         */
     148        public function output() {
     149            global $current_section;
     150
     151            if ( '' === $current_section ) {
     152                $settings = $this->get_settings();
     153                WC_Admin_Settings::output_fields( $settings );
     154            } elseif ( 'transaction_backfill' === $current_section ) {
     155                $this->output_transaction_backfill();
     156            } elseif ( 'sync_queue' === $current_section ) {
     157                $this->output_sync_queue();
     158            }
     159        }
     160
     161        /**
     162         * Output the transaction backfill settings page.
     163         */
     164        public function output_transaction_backfill() {
     165            global $hide_save_button;
     166            $hide_save_button = true;
     167
     168            if ( isset( $this->settings['taxjar_download'] ) && 'yes' === $this->settings['taxjar_download'] ) {
     169                $current_date = current_time( 'Y-m-d' );
     170                ?>
     171            <table class="form-table">
     172                <tbody>
     173                <tr valign="top">
     174                    <th scope="row" class="titledesc">
     175                        <label for="start_date">Backfill Start Date</label>
     176                    </th>
     177                    <td class="start_date_field">
     178                        <input type="text" class="taxjar-datepicker" style="" name="start_date" id="start_date" value="<?php echo $current_date; ?>" placeholder="YYYY-MM-DD" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])">
     179                    </td>
     180                </tr>
     181                <tr valign="top">
     182                    <th scope="row" class="titledesc">
     183                        <label for="end_date">Backfill End Date</label>
     184                    </th>
     185                    <td class="end_date_field">
     186                        <input type="text" class="taxjar-datepicker" style="" name="end_date" id="end_date" value="<?php echo $current_date; ?>" placeholder="YYYY-MM-DD" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])">
     187                    </td>
     188                </tr>
     189                <tr valign="top" class="">
     190                    <th scope="row" class="titledesc">Force Sync</th>
     191                    <td class="forminp forminp-checkbox">
     192                        <fieldset>
     193                            <legend class="screen-reader-text"><span>Force Sync</span></legend>
     194                            <label for="force_sync">
     195                                <input name="force_sync" id="force_sync" type="checkbox" class="" value="1"> If enabled, all orders and refunds will be added to the queue to sync to TaxJar, regardless of if they have been updated since they were lasted synced.
     196                            </label>
     197                        </fieldset>
     198                    </td>
     199                </tr>
     200                </tbody>
     201            </table>
     202            <p>
     203                <button class='button js-wc-taxjar-transaction-backfill'>Run Backfill</button>
     204            </p>
     205                <?php
    70206            } else {
    71                 add_action( 'woocommerce_calculate_totals', array( $this, 'calculate_totals' ), 20 );
    72             }
    73 
    74             // Calculate Taxes for Backend Orders (Woo 2.6+)
    75             add_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
    76 
    77             // Calculate taxes for WooCommerce Subscriptions renewal orders
    78             add_filter( 'wcs_new_order_created', array( $this, 'calculate_renewal_order_totals' ), 10, 3 );
    79 
    80             // Settings Page
    81             add_action( 'woocommerce_sections_tax',  array( $this, 'output_sections_before' ),  9 );
    82 
    83             // Filters
    84             add_filter( 'woocommerce_calc_tax', array( $this, 'override_woocommerce_tax_rates' ), 10, 3 );
    85             add_filter( 'woocommerce_customer_taxable_address', array( $this, 'append_base_address_to_customer_taxable_address' ), 10, 1 );
    86             add_filter( 'woocommerce_matched_rates', array( $this, 'allow_street_address_for_matched_rates' ), 10, 2 );
    87 
    88             // Scripts / Stylesheets
    89             add_action( 'admin_enqueue_scripts', array( $this, 'load_taxjar_admin_new_order_assets' ) );
    90 
    91             // If TaxJar is enabled and user disables taxes we re-enable them
    92             update_option( 'woocommerce_calc_taxes', 'yes' );
    93 
    94             // Users can set either billing or shipping address for tax rates but not shop
    95             update_option( 'woocommerce_tax_based_on', 'shipping' );
    96 
    97             // Rate calculations assume tax not included
    98             update_option( 'woocommerce_prices_include_tax', 'no' );
    99 
    100             // Use no special handling on shipping taxes, our API handles that
    101             update_option( 'woocommerce_shipping_tax_class', '' );
    102 
    103             // API handles rounding precision
    104             update_option( 'woocommerce_tax_round_at_subtotal', 'no' );
    105 
    106             // Rates are calculated in the cart assuming tax not included
    107             update_option( 'woocommerce_tax_display_shop', 'excl' );
    108 
    109             // TaxJar returns one total amount, not line item amounts
    110             update_option( 'woocommerce_tax_display_cart', 'excl' );
    111 
    112             // TaxJar returns one total amount, not line item amounts
    113             update_option( 'woocommerce_tax_total_display', 'single' );
    114         } // End if().
    115     }
    116 
    117     public function add_settings_page( $settings_tabs ) {
    118         $settings_tabs[ $this->id ] = __( 'TaxJar', 'taxjar' );
    119         return $settings_tabs;
    120     }
    121 
    122     /**
    123      * Output sections.
    124      */
    125     public function output_sections() {
    126         global $current_section;
    127 
    128         $sections = $this->get_sections();
    129 
    130         if ( empty( $sections ) || 1 === sizeof( $sections ) ) {
    131             return;
    132         }
    133 
    134         echo '<ul class="subsubsub">';
    135 
    136         $array_keys = array_keys( $sections );
    137 
    138         foreach ( $sections as $id => $label ) {
    139             echo '<li><a href="' . admin_url( 'admin.php?page=wc-settings&tab=' . $this->id . '&section=' . sanitize_title( $id ) ) . '" class="' . ( $current_section == $id ? 'current' : '' ) . '">' . $label . '</a> ' . ( end( $array_keys ) == $id ? '' : '|' ) . ' </li>';
    140         }
    141 
    142         echo '</ul><br class="clear" />';
    143     }
    144 
    145     /**
    146      * Output the settings.
    147      */
    148     public function output() {
    149         global $current_section;
    150 
    151         if ( $current_section == '' ) {
    152             $settings = $this->get_settings();
    153             WC_Admin_Settings::output_fields( $settings );
    154         } else if ( $current_section == 'transaction_backfill' ) {
    155             $this->output_transaction_backfill();
    156         } else if ( $current_section == 'sync_queue' ) {
    157             $this->output_sync_queue();
    158         }
    159     }
    160 
    161     /**
    162      * Output the transaction backfill settings page.
    163      */
    164     public function output_transaction_backfill() {
    165         global $hide_save_button;
    166         $hide_save_button = true;
    167 
    168         if ( isset( $this->settings['taxjar_download'] ) && 'yes' == $this->settings['taxjar_download'] ) {
    169             $current_date = current_time( 'Y-m-d' );
    170             ?>
    171             <table class="form-table">
    172                 <tbody>
    173                 <tr valign="top">
    174                     <th scope="row" class="titledesc">
    175                         <label for="start_date">Backfill Start Date</label>
    176                     </th>
    177                     <td class="start_date_field">
    178                         <input type="text" class="taxjar-datepicker" style="" name="start_date" id="start_date"
    179                                value="<?php echo $current_date; ?>" placeholder="YYYY-MM-DD"
    180                                pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])">
    181                     </td>
    182                 </tr>
    183                 <tr valign="top">
    184                     <th scope="row" class="titledesc">
    185                         <label for="end_date">Backfill End Date</label>
    186                     </th>
    187                     <td class="end_date_field">
    188                         <input type="text" class="taxjar-datepicker" style="" name="end_date" id="end_date"
    189                                value="<?php echo $current_date; ?>" placeholder="YYYY-MM-DD"
    190                                pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])">
    191                     </td>
    192                 </tr>
    193                 <tr valign="top" class="">
    194                     <th scope="row" class="titledesc">Force Sync</th>
    195                     <td class="forminp forminp-checkbox">
    196                         <fieldset>
    197                             <legend class="screen-reader-text"><span>Force Sync</span></legend>
    198                             <label for="force_sync">
    199                                 <input name="force_sync" id="force_sync" type="checkbox" class="" value="1"> If enabled,
    200                                 all orders and refunds will be added to the queue to sync to TaxJar, regardless of if
    201                                 they have been updated since they were lasted synced.
    202                             </label>
    203                         </fieldset>
    204                     </td>
    205                 </tr>
    206                 </tbody>
    207             </table>
    208             <p>
    209                 <button class='button js-wc-taxjar-transaction-backfill'>Run Backfill</button>
    210             </p>
    211             <?php
    212         } else {
    213             ?>
    214                 <p>Sales Tax Reporting must be enabled in order to use transaction back fill.</p>
    215             <?php
    216         }
    217     }
    218 
    219     /**
    220      * Output the sync queue settings page.
    221      */
    222     public function output_sync_queue() {
    223         global $hide_save_button;
    224         $hide_save_button = true;
    225 
    226         if ( isset( $this->settings['taxjar_download'] ) && 'yes' == $this->settings['taxjar_download'] ) {
    227             $report = new WC_Taxjar_Queue_List();
    228             $report->output_report();
    229         } else {
    230             ?>
    231                 <p>Enable Sales Tax Reporting in order to view the transaction queue.</p>
    232             <?php
    233         }
    234     }
    235 
    236     /**
    237      * Get settings array.
    238      *
    239      * @return array
    240      */
    241     public function get_settings( $current_section = '' ) {
    242         $settings = array();
    243 
    244         if ( '' === $current_section ) {
    245             $store_settings = $this->get_store_settings();
    246             $tj_connection  = new WC_TaxJar_Connection( $this );
    247 
    248             if ( empty( $store_settings['state'] ) ) {
    249                 $store_settings['state'] = 'N/A';
    250             }
    251 
    252             $api_token_valid = apply_filters( 'taxjar_api_token_valid', $this->post_or_setting( 'api_token' ) && $tj_connection->api_token_valid );
    253 
    254             $settings = array(
    255                 array(
    256                     'title' => __( 'TaxJar', 'wc-taxjar' ),
    257                     'type'  => 'title',
    258                     'desc'  => __( "TaxJar is the easiest to use sales tax calculation and reporting engine for WooCommerce. Connect your TaxJar account and enter the city and zip code from which your store ships. Enable TaxJar calculations to automatically collect sales tax at checkout. You may also enable sales tax reporting to begin importing transactions from this store into your TaxJar account, all in one click!", 'wc-taxjar' ),
    259                 ),
    260                 array(
    261                     'type' => 'sectionend',
    262                 ),
    263                 array(
    264                     'type' => 'title',
    265                     'desc' => '<strong>' . __( "For the fastest help, please email", 'wc-taxjar' ) . ' <a href="mailto:[email protected]">[email protected]</a>. ' . __( "We'll get back to you within hours.", 'wc-taxjar' ),
    266                 ),
    267                 array(
    268                     'type' => 'sectionend',
    269                 ),
    270                 array(
    271                     'title' => __( 'Step 1: Activate your TaxJar WooCommerce Plugin', 'wc-taxjar' ),
    272                     'type'  => 'title',
    273                     'desc'  => '',
    274                 ),
    275 
    276             );
    277 
    278             if ( $api_token_valid ) {
    279                 $connected_email = $this->post_or_setting( 'connected_email' );
    280 
    281                 if ( $connected_email ) {
    282                     array_push( $settings, array(
     207                ?>
     208                <p>Sales Tax Reporting must be enabled in order to use transaction back fill.</p>
     209                <?php
     210            }
     211        }
     212
     213        /**
     214         * Output the sync queue settings page.
     215         */
     216        public function output_sync_queue() {
     217            global $hide_save_button;
     218            $hide_save_button = true;
     219
     220            if ( isset( $this->settings['taxjar_download'] ) && 'yes' === $this->settings['taxjar_download'] ) {
     221                $report = new WC_Taxjar_Queue_List();
     222                $report->output_report();
     223            } else {
     224                ?>
     225                <p>Enable Sales Tax Reporting in order to view the transaction queue.</p>
     226                <?php
     227            }
     228        }
     229
     230        /**
     231         * Get settings array.
     232         *
     233         * @return array
     234         */
     235        public function get_settings( $current_section = '' ) {
     236            $settings = array();
     237
     238            if ( '' === $current_section ) {
     239                $store_settings = $this->get_store_settings();
     240                $tj_connection  = new WC_TaxJar_Connection( $this );
     241
     242                if ( empty( $store_settings['state'] ) ) {
     243                    $store_settings['state'] = 'N/A';
     244                }
     245
     246                $api_token_valid = apply_filters( 'taxjar_api_token_valid', $this->post_or_setting( 'api_token' ) && $tj_connection->api_token_valid );
     247
     248                $settings = array(
     249                    array(
     250                        'title' => __( 'TaxJar', 'wc-taxjar' ),
     251                        'type'  => 'title',
     252                        'desc'  => __( 'TaxJar is the easiest to use sales tax calculation and reporting engine for WooCommerce. Connect your TaxJar account and enter the city and zip code from which your store ships. Enable TaxJar calculations to automatically collect sales tax at checkout. You may also enable sales tax reporting to begin importing transactions from this store into your TaxJar account, all in one click!', 'wc-taxjar' ),
     253                    ),
     254                    array(
     255                        'type' => 'sectionend',
     256                    ),
     257                    array(
     258                        'type' => 'title',
     259                        'desc' => '<strong>' . __( 'For the fastest help, please email', 'wc-taxjar' ) . ' <a href="mailto:[email protected]">[email protected]</a>. ' . __( "We'll get back to you within hours.", 'wc-taxjar' ),
     260                    ),
     261                    array(
     262                        'type' => 'sectionend',
     263                    ),
     264                    array(
     265                        'title' => __( 'Step 1: Activate your TaxJar WooCommerce Plugin', 'wc-taxjar' ),
     266                        'type'  => 'title',
     267                        'desc'  => '',
     268                    ),
     269
     270                );
     271
     272                if ( $api_token_valid ) {
     273                    $connected_email = $this->post_or_setting( 'connected_email' );
     274
     275                    if ( $connected_email ) {
     276                        array_push(
     277                            $settings,
     278                            array(
     279                                'title' => '',
     280                                'type'  => 'title',
     281                                'desc'  => '<div class="taxjar-connected"><span class="dashicons dashicons-yes-alt"></span><p>' . $connected_email . '</p></div>',
     282                                'id'    => 'connected-to-taxjar',
     283                            )
     284                        );
     285                    } else {
     286                        array_push(
     287                            $settings,
     288                            array(
     289                                'title' => '',
     290                                'type'  => 'title',
     291                                'desc'  => '<div class="taxjar-connected"><span class="dashicons dashicons-yes-alt"></span><p>Connected To TaxJar</p></div>',
     292                                'id'    => 'connected-to-taxjar',
     293                            )
     294                        );
     295                    }
     296
     297                    array_push(
     298                        $settings,
     299                        array(
     300                            'title' => '',
     301                            'type'  => 'title',
     302                            'desc'  => '<button id="disconnect-from-taxjar" name="disconnect-from-taxjar" class="button-primary" type="submit" value="Disconnect">' . __( 'Disconnect From TaxJar', 'wc-taxjar' ) . '</button><p><a href="#" id="connect-manual-edit">' . __( 'Edit API Token', 'wc-taxjar' ) . '</a></p>',
     303                        )
     304                    );
     305                } else {
     306                    array_push(
     307                        $settings,
     308                        array(
     309                            'title' => '',
     310                            'type'  => 'title',
     311                            'desc'  => '<button id="connect-to-taxjar" name="connect-to-taxjar" class="button-primary" type="submit" value="Connect">' . __( 'Connect To TaxJar', 'wc-taxjar' ) . '</button><p>' . __( 'Already have an API Token?', 'wc-taxjar' ) . ' <a href="#" id="connect-manual-edit">' . __( 'Edit API Token.', 'wc-taxjar' ) . '</a></p>',
     312                        )
     313                    );
     314                }
     315
     316                array_push(
     317                    $settings,
     318                    array(
     319                        'title'   => 'TaxJar API Token',
     320                        'type'    => 'text',
     321                        'desc'    => '<p class="hidden tj-api-token-title"><a href="' . $this->app_uri . 'account#api-access" target="_blank">' . __( 'Get API token', 'wc-taxjar' ) . '</a></p>',
     322                        'default' => '',
     323                        'class'   => 'hidden',
     324                        'id'      => 'woocommerce_taxjar-integration_settings[api_token]',
     325                    )
     326                );
     327                array_push(
     328                    $settings,
     329                    array(
     330                        'type' => 'sectionend',
     331                    )
     332                );
     333                array_push(
     334                    $settings,
     335                    array(
    283336                        'title' => '',
    284                         'type'  => 'title',
    285                         'desc'  => '<div class="taxjar-connected"><span class="dashicons dashicons-yes-alt"></span><p>' . $connected_email . '</p></div>',
    286                         'id'    => 'connected-to-taxjar'
    287                     ) );
    288                 } else {
    289                     array_push( $settings, array(
    290                         'title' => '',
    291                         'type'  => 'title',
    292                         'desc'  => '<div class="taxjar-connected"><span class="dashicons dashicons-yes-alt"></span><p>Connected To TaxJar</p></div>',
    293                         'id'    => 'connected-to-taxjar'
    294                     ) );
    295                 }
    296 
    297                 array_push( $settings, array(
    298                     'title' => '',
    299                     'type'  => 'title',
    300                     'desc'  => __( '<button id="disconnect-from-taxjar" name="disconnect-from-taxjar" class="button-primary" type="submit" value="Disconnect">Disconnect From TaxJar</button><p><a href="#" id="connect-manual-edit">Edit API Token</a></p>', 'wc-taxjar' ),
    301                 ) );
    302             } else {
    303                 array_push( $settings, array(
    304                     'title' => '',
    305                     'type'  => 'title',
    306                     'desc'  => __( '<button id="connect-to-taxjar" name="connect-to-taxjar" class="button-primary" type="submit" value="Connect">Connect To TaxJar</button><p>Already have an API Token? <a href="#" id="connect-manual-edit">Edit API Token.</a></p>', 'wc-taxjar' ),
    307                 ) );
    308             }
    309 
    310             array_push( $settings, array(
    311                 'title'   => 'TaxJar API Token',
    312                 'type'  => 'text',
    313                 'desc'  => __( '<p class="hidden tj-api-token-title"><a href="' . $this->app_uri . 'account#api-access" target="_blank">Get API token</a></p>', 'wc-taxjar' ),
    314                 'default' => '',
    315                 'class'   => 'hidden',
    316                 'id'      => 'woocommerce_taxjar-integration_settings[api_token]'
    317             ) );
    318             array_push( $settings, array(
    319                 'type' => 'sectionend',
    320             ) );
    321             array_push( $settings, array(
    322                 'title'    => '',
    323                 'type'      => 'email',
    324                 'desc'      => '',
    325                 'class'    => 'hidden',
    326                 'id'          => 'woocommerce_taxjar-integration_settings[connected_email]'
    327             ) );
    328             array_push( $settings, array(
    329                 'type' => 'sectionend',
    330             ) );
    331 
    332             if ( ! $tj_connection->can_connect || ! $tj_connection->api_token_valid ) {
    333                 array_push( $settings, $tj_connection->get_form_settings_field() );
    334                 array_push( $settings, array(
    335                     'type' => 'sectionend',
    336                 ) );
    337             }
    338 
    339             if ( $api_token_valid ) {
    340                 array_push( $settings, array(
    341                     'title' => __( 'Step 2: Configure your sales tax settings', 'wc-taxjar' ),
    342                     'type'  => 'title',
    343                     'desc'  => ''
    344                 ) );
    345                 array_push( $settings, array(
    346                     'title'   => __( 'Sales Tax Calculation', 'wc-taxjar' ),
    347                     'type'    => 'checkbox',
    348                     'default' => 'no',
    349                     'desc'    => __( 'If enabled, TaxJar will calculate sales tax for your store.', 'wc-taxjar' ),
    350                     'id'      => 'woocommerce_taxjar-integration_settings[enabled]'
    351                 ) );
    352                 array_push( $settings, array(
    353                     'title'   => __( 'Tax Calculation on API Orders', 'wc-taxjar' ),
    354                     'type'    => 'checkbox',
    355                     'default' => 'no',
    356                     'desc'    => __( 'If enabled, TaxJar will calculate sales tax for orders created through the WooCommerce REST API.', 'wc-taxjar' ),
    357                     'id'      => 'woocommerce_taxjar-integration_settings[api_calcs_enabled]'
    358                 ) );
    359                 array_push( $settings, array(
    360                     'type' => 'sectionend',
    361                 ) );
    362 
    363                 if ( $this->post_or_setting( 'enabled' ) ) {
    364                     $tj_nexus   = new WC_Taxjar_Nexus( $this );
    365                     $settings[] = $tj_nexus->get_form_settings_field();
     337                        'type'  => 'email',
     338                        'desc'  => '',
     339                        'class' => 'hidden',
     340                        'id'    => 'woocommerce_taxjar-integration_settings[connected_email]',
     341                    )
     342                );
     343                array_push(
     344                    $settings,
     345                    array(
     346                        'type' => 'sectionend',
     347                    )
     348                );
     349
     350                if ( ! $tj_connection->can_connect || ! $tj_connection->api_token_valid ) {
     351                    array_push( $settings, $tj_connection->get_form_settings_field() );
     352                    array_push(
     353                        $settings,
     354                        array(
     355                            'type' => 'sectionend',
     356                        )
     357                    );
     358                }
     359
     360                if ( $api_token_valid ) {
     361                    array_push(
     362                        $settings,
     363                        array(
     364                            'title' => __( 'Step 2: Configure your sales tax settings', 'wc-taxjar' ),
     365                            'type'  => 'title',
     366                            'desc'  => '',
     367                        )
     368                    );
     369                    array_push(
     370                        $settings,
     371                        array(
     372                            'title'   => __( 'Sales Tax Calculation', 'wc-taxjar' ),
     373                            'type'    => 'checkbox',
     374                            'default' => 'no',
     375                            'desc'    => __( 'If enabled, TaxJar will calculate sales tax for your store.', 'wc-taxjar' ),
     376                            'id'      => 'woocommerce_taxjar-integration_settings[enabled]',
     377                        )
     378                    );
     379                    array_push(
     380                        $settings,
     381                        array(
     382                            'title'   => __( 'Tax Calculation on API Orders', 'wc-taxjar' ),
     383                            'type'    => 'checkbox',
     384                            'default' => 'no',
     385                            'desc'    => __( 'If enabled, TaxJar will calculate sales tax for orders created through the WooCommerce REST API.', 'wc-taxjar' ),
     386                            'id'      => 'woocommerce_taxjar-integration_settings[api_calcs_enabled]',
     387                        )
     388                    );
     389                    array_push(
     390                        $settings,
     391                        array(
     392                            'type' => 'sectionend',
     393                        )
     394                    );
     395
     396                    if ( $this->post_or_setting( 'enabled' ) ) {
     397                        $tj_nexus   = new WC_Taxjar_Nexus( $this );
     398                        $settings[] = $tj_nexus->get_form_settings_field();
     399                        $settings[] = array(
     400                            'type' => 'sectionend',
     401                        );
     402                    }
     403
     404                    if ( get_option( 'woocommerce_store_address' ) || get_option( 'woocommerce_store_city' ) || get_option( 'woocommerce_store_postcode' ) ) {
     405                        $settings[] = array(
     406                            'title' => __( 'Ship From Address', 'wc-taxjar' ),
     407                            'type'  => 'title',
     408                            'desc'  => __( 'We have automatically detected your ship from address:', 'wc-taxjar' ) . '<br><br>' . $store_settings['street'] . '<br>' . $store_settings['city'] . ', ' . $store_settings['state'] . ' ' . $store_settings['postcode'] . '<br>' . WC()->countries->countries[ $store_settings['country'] ] . '<br><br>' . __( 'You can change this setting at:', 'wc-taxjar' ) . '<br><a href="' . get_admin_url( null, 'admin.php?page=wc-settings' ) . '">WooCommerce -> Settings -> General -> Store Address</a>',
     409                        );
     410                        $settings[] = array(
     411                            'type' => 'sectionend',
     412                        );
     413                    } else {
     414                        $settings[] = array(
     415                            'type' => 'title',
     416                        );
     417                        $settings[] = array(
     418                            'title'    => __( 'Ship From Address', 'wc-taxjar' ),
     419                            'type'     => 'text',
     420                            'desc'     => __( 'Enter the street address where your store ships from.', 'wc-taxjar' ),
     421                            'desc_tip' => true,
     422                            'default'  => '',
     423                            'id'       => 'woocommerce_taxjar-integration_settings[store_street]',
     424                        );
     425                        $settings[] = array(
     426                            'title'       => __( 'Ship From City', 'wc-taxjar' ),
     427                            'type'        => 'text',
     428                            'description' => __( 'Enter the city where your store ships from.', 'wc-taxjar' ),
     429                            'desc_tip'    => true,
     430                            'default'     => '',
     431                            'id'          => 'woocommerce_taxjar-integration_settings[store_city]',
     432                        );
     433                        $settings[] = array(
     434                            'title'       => __( 'Ship From State', 'wc-taxjar' ),
     435                            'type'        => 'title',
     436                            'description' => __( 'We have automatically detected your ship from state as being ', 'wc-taxjar' ) . $store_settings['state'] . '.<br>' . __( 'You can change this setting at', 'wc-taxjar' ) . ' <a href="' . get_admin_url( null, 'admin.php?page=wc-settings' ) . '">WooCommerce -> Settings -> General -> Base Location</a>',
     437                            'class'       => 'input-text disabled regular-input',
     438                            'id'          => 'woocommerce_taxjar-integration_settings[store_state]',
     439                        );
     440                        $settings[] = array(
     441                            'title'       => __( 'Ship From Postcode / ZIP', 'wc-taxjar' ),
     442                            'type'        => 'text',
     443                            'description' => __( 'Enter the zip code from which your store ships products.', 'wc-taxjar' ),
     444                            'desc_tip'    => true,
     445                            'default'     => '',
     446                            'id'          => 'woocommerce_taxjar-integration_settings[store_postcode]',
     447                        );
     448                        $settings[] = array(
     449                            'title'       => __( 'Ship From Country', 'wc-taxjar' ),
     450                            'type'        => 'hidden',
     451                            'description' => __( 'We have automatically detected your ship from country as being ', 'wc-taxjar' ) . $store_settings['country'] . '.<br>' . __( 'You can change this setting at', 'wc-taxjar' ) . ' <a href="' . get_admin_url( null, 'admin.php?page=wc-settings' ) . '">WooCommerce -> Settings -> General -> Base Location</a>',
     452                            'class'       => 'input-text disabled regular-input',
     453                            'id'          => 'woocommerce_taxjar-integration_settings[store_country]',
     454                        );
     455                        $settings[] = array(
     456                            'type' => 'sectionend',
     457                        );
     458                    }
     459
    366460                    $settings[] = array(
    367                         'type' => 'sectionend',
     461                        'type' => 'title',
    368462                    );
    369                 }
    370 
    371                 if ( get_option( 'woocommerce_store_address' ) || get_option( 'woocommerce_store_city' ) || get_option( 'woocommerce_store_postcode' ) ) {
     463                    $settings[] = $this->download_orders->get_form_settings_field();
    372464                    $settings[] = array(
    373                         'title' => __( 'Ship From Address', 'wc-taxjar' ),
    374                         'type'  => 'title',
    375                         'desc'  => __( 'We have automatically detected your ship from address:<br><br>' . $store_settings['street'] . '<br>' . $store_settings['city'] . ', ' . $store_settings['state'] . ' ' . $store_settings['postcode'] . '<br>' . WC()->countries->countries[ $store_settings['country'] ] . '<br><br>You can change this setting at:<br><a href="' . get_admin_url( null, 'admin.php?page=wc-settings' ) . '">WooCommerce -> Settings -> General -> Store Address</a>', 'wc-taxjar' ),
     465                        'title'   => __( 'Debug Log', 'wc-taxjar' ),
     466                        'type'    => 'checkbox',
     467                        'default' => 'no',
     468                        'desc'    => __( 'Log events such as API requests.', 'wc-taxjar' ),
     469                        'id'      => 'woocommerce_taxjar-integration_settings[debug]',
    376470                    );
    377471                    $settings[] = array(
    378472                        'type' => 'sectionend',
    379473                    );
    380                 } else {
    381474                    $settings[] = array(
    382475                        'type' => 'title',
    383                     );
    384                     $settings[] = array(
    385                         'title'    => __( 'Ship From Address', 'wc-taxjar' ),
    386                         'type'     => 'text',
    387                         'desc'     => __( 'Enter the street address where your store ships from.', 'wc-taxjar' ),
    388                         'desc_tip' => true,
    389                         'default'  => '',
    390                         'id'       => 'woocommerce_taxjar-integration_settings[store_street]'
    391                     );
    392                     $settings[] = array(
    393                         'title'       => __( 'Ship From City', 'wc-taxjar' ),
    394                         'type'        => 'text',
    395                         'description' => __( 'Enter the city where your store ships from.', 'wc-taxjar' ),
    396                         'desc_tip'    => true,
    397                         'default'     => '',
    398                         'id'          => 'woocommerce_taxjar-integration_settings[store_city]'
    399                     );
    400                     $settings[] = array(
    401                         'title'       => __( 'Ship From State', 'wc-taxjar' ),
    402                         'type'        => 'title',
    403                         'description' => __( 'We have automatically detected your ship from state as being ' . $store_settings['state'] . '.<br>You can change this setting at <a href="' . get_admin_url( null, 'admin.php?page=wc-settings' ) . '">WooCommerce -> Settings -> General -> Base Location</a>', 'wc-taxjar' ),
    404                         'class'       => 'input-text disabled regular-input',
    405                         'id'          => 'woocommerce_taxjar-integration_settings[store_state]'
    406                     );
    407                     $settings[] = array(
    408                         'title'       => __( 'Ship From Postcode / ZIP', 'wc-taxjar' ),
    409                         'type'        => 'text',
    410                         'description' => __( 'Enter the zip code from which your store ships products.', 'wc-taxjar' ),
    411                         'desc_tip'    => true,
    412                         'default'     => '',
    413                         'id'          => 'woocommerce_taxjar-integration_settings[store_postcode]'
    414                     );
    415                     $settings[] = array(
    416                         'title'       => __( 'Ship From Country', 'wc-taxjar' ),
    417                         'type'        => 'hidden',
    418                         'description' => __( 'We have automatically detected your ship from country as being ' . $store_settings['country'] . '.<br>You can change this setting at <a href="' . get_admin_url( null, 'admin.php?page=wc-settings' ) . '">WooCommerce -> Settings -> General -> Base Location</a>', 'wc-taxjar' ),
    419                         'class'       => 'input-text disabled regular-input',
    420                         'id'          => 'woocommerce_taxjar-integration_settings[store_country]'
     476                        'desc' => __( 'If you find TaxJar for WooCommerce useful, please rate us <a href="https://wordpress.org/support/plugin/taxjar-simplified-taxes-for-woocommerce/reviews/#new-post" target="_blank">&#9733;&#9733;&#9733;&#9733;&#9733;</a>. Thank you!', 'wc-taxjar' ),
    421477                    );
    422478                    $settings[] = array(
     
    424480                    );
    425481                }
    426 
    427                 $settings[] = array(
    428                     'type' => 'title',
     482            }
     483
     484            return apply_filters( 'woocommerce_get_settings_' . $this->id, $settings );
     485        }
     486
     487        /**
     488         * Get sections.
     489         *
     490         * @return array
     491         */
     492        public function get_sections() {
     493            $sections = array(
     494                ''                     => __( 'Settings', 'woocommerce' ),
     495                'transaction_backfill' => __( 'Transaction Backfill', 'wc-taxjar' ),
     496                'sync_queue'           => __( 'Sync Queue', 'wc-taxjar' ),
     497            );
     498            return apply_filters( 'woocommerce_get_sections_' . $this->id, $sections );
     499        }
     500
     501        /**
     502         * Save settings.
     503         */
     504        public function save() {
     505            $settings = $this->get_settings();
     506            WC_Admin_Settings::save_fields( $settings );
     507        }
     508
     509        /**
     510         * Prints debug info to wp-content/uploads/wc-logs/taxjar-*.log
     511         *
     512         * @return void
     513         */
     514        public function _log( $message ) {
     515            do_action( 'taxjar_log', $message );
     516            if ( $this->debug ) {
     517                if ( ! isset( $this->log ) ) {
     518                    $this->log = new WC_Logger();
     519                }
     520                if ( is_array( $message ) || is_object( $message ) ) {
     521                    $this->log->add( 'taxjar', print_r( $message, true ) );
     522                } else {
     523                    $this->log->add( 'taxjar', $message );
     524                }
     525            }
     526        }
     527
     528        /**
     529         * Slightly altered copy and paste of private function in WC_Tax class file
     530         */
     531        public function _get_wildcard_postcodes( $postcode ) {
     532            $postcodes = array( '*', strtoupper( $postcode ) );
     533
     534            if ( version_compare( WC()->version, '2.4.0', '>=' ) ) {
     535                $postcodes = array( '*', strtoupper( $postcode ), strtoupper( $postcode ) . '*' );
     536            }
     537
     538            $postcode_length   = strlen( $postcode );
     539            $wildcard_postcode = strtoupper( $postcode );
     540
     541            for ( $i = 0; $i < $postcode_length; $i ++ ) {
     542                $wildcard_postcode = substr( $wildcard_postcode, 0, -1 );
     543                $postcodes[]       = $wildcard_postcode . '*';
     544            }
     545            return $postcodes;
     546        }
     547
     548        /**
     549         * Calculate sales tax using SmartCalcs
     550         *
     551         * @return array|boolean
     552         */
     553        public function calculate_tax( $options = array() ) {
     554            $this->_log( ':::: TaxJar Plugin requested ::::' );
     555
     556            // Process $options array and turn them into variables
     557            $options = is_array( $options ) ? $options : array();
     558
     559            $calculation_data = array_replace_recursive(
     560                array(
     561                    'to_country'      => null,
     562                    'to_state'        => null,
     563                    'to_zip'          => null,
     564                    'to_city'         => null,
     565                    'to_street'       => null,
     566                    'shipping_amount' => null, // WC()->shipping->shipping_total
     567                    'line_items'      => null,
     568                    'customer_id'     => 0,
     569                    'exemption_type'  => '',
     570                ),
     571                $options
     572            );
     573
     574            $taxes = array(
     575                'freight_taxable' => 1,
     576                'has_nexus'       => 0,
     577                'line_items'      => array(),
     578                'rate_ids'        => array(),
     579                'tax_rate'        => 0,
     580            );
     581
     582            // Strict conditions to be met before API call can be conducted
     583            if (
     584                empty( $calculation_data['to_country'] ) ||
     585                empty( $calculation_data['to_zip'] ) ||
     586                ( empty( $calculation_data['line_items'] ) && ( 0 === $calculation_data['shipping_amount'] ) )
     587                ) {
     588                return false;
     589            }
     590
     591            // validate customer exemption before sending API call
     592            if ( is_object( WC()->customer ) ) {
     593                if ( WC()->customer->is_vat_exempt() ) {
     594                    return false;
     595                }
     596            }
     597
     598            // Valid zip codes to prevent unnecessary API requests
     599            if ( ! $this->is_postal_code_valid( $calculation_data['to_country'], $calculation_data['to_state'], $calculation_data['to_zip'] ) ) {
     600                return false;
     601            }
     602
     603            $taxjar_nexus = new WC_Taxjar_Nexus( $this );
     604
     605            if ( ! $taxjar_nexus->has_nexus_check( $calculation_data['to_country'], $calculation_data['to_state'] ) ) {
     606                $this->_log( ':::: Order not shipping to nexus area ::::' );
     607                return false;
     608            }
     609
     610            $calculation_data['to_zip'] = explode( ',', $calculation_data['to_zip'] );
     611            $calculation_data['to_zip'] = array_shift( $calculation_data['to_zip'] );
     612
     613            $store_settings                      = $this->get_store_settings();
     614            $from_country                        = $store_settings['country'];
     615            $from_state                          = $store_settings['state'];
     616            $from_zip                            = $store_settings['postcode'];
     617            $from_city                           = $store_settings['city'];
     618            $from_street                         = $store_settings['street'];
     619            $calculation_data['shipping_amount'] = is_null( $calculation_data['shipping_amount'] ) ? 0.0 : $calculation_data['shipping_amount'];
     620
     621            $this->_log( ':::: TaxJar API called ::::' );
     622
     623            $url = $this->uri . 'taxes';
     624
     625            $body = array(
     626                'from_country' => $from_country,
     627                'from_state'   => $from_state,
     628                'from_zip'     => $from_zip,
     629                'from_city'    => $from_city,
     630                'from_street'  => $from_street,
     631                'to_country'   => $calculation_data['to_country'],
     632                'to_state'     => $calculation_data['to_state'],
     633                'to_zip'       => $calculation_data['to_zip'],
     634                'to_city'      => $calculation_data['to_city'],
     635                'to_street'    => $calculation_data['to_street'],
     636                'shipping'     => $calculation_data['shipping_amount'],
     637                'plugin'       => 'woo',
     638            );
     639
     640            if ( is_int( $calculation_data['customer_id'] ) ) {
     641                if ( $calculation_data['customer_id'] > 0 ) {
     642                    $body['customer_id'] = $calculation_data['customer_id'];
     643                }
     644            } else {
     645                if ( ! empty( $calculation_data['customer_id'] ) ) {
     646                    $body['customer_id'] = $calculation_data['customer_id'];
     647                }
     648            }
     649
     650            if ( ! empty( $calculation_data['exemption_type'] ) ) {
     651                if ( self::is_valid_exemption_type( $calculation_data['exemption_type'] ) ) {
     652                    $body['exemption_type'] = $calculation_data['exemption_type'];
     653                }
     654            }
     655
     656            // Either `amount` or `line_items` parameters are required to perform tax calculations.
     657            if ( empty( $calculation_data['line_items'] ) ) {
     658                $body['amount'] = 0.0;
     659            } else {
     660                $body['line_items'] = $calculation_data['line_items'];
     661            }
     662
     663            $response = $this->smartcalcs_cache_request( wp_json_encode( $body ) );
     664
     665            if ( isset( $response ) ) {
     666                // Log the response
     667                $this->_log( 'Received: ' . $response['body'] );
     668
     669                // Decode Response
     670                $taxjar_response = json_decode( $response['body'] );
     671                $taxjar_response = $taxjar_response->tax;
     672
     673                // Update Properties based on Response
     674                $taxes['freight_taxable'] = (int) $taxjar_response->freight_taxable;
     675                $taxes['has_nexus']       = (int) $taxjar_response->has_nexus;
     676                $taxes['shipping_rate']   = $taxjar_response->rate;
     677
     678                if ( ! empty( $taxjar_response->breakdown ) ) {
     679
     680                    if ( ! empty( $taxjar_response->breakdown->shipping ) ) {
     681                        $taxes['shipping_rate'] = $taxjar_response->breakdown->shipping->combined_tax_rate;
     682                    }
     683
     684                    if ( ! empty( $taxjar_response->breakdown->line_items ) ) {
     685                        $calculation_data['line_items'] = array();
     686                        foreach ( $taxjar_response->breakdown->line_items as $line_item ) {
     687                            $calculation_data['line_items'][ $line_item->id ] = $line_item;
     688                        }
     689                        $taxes['line_items'] = $calculation_data['line_items'];
     690                    }
     691                }
     692            }
     693
     694            // Remove taxes if they are set somehow and customer is exempt
     695            if ( is_object( WC()->customer ) && WC()->customer->is_vat_exempt() ) {
     696                WC()->cart->remove_taxes(); // Woo < 3.2
     697            } elseif ( $taxes['has_nexus'] ) {
     698                // Use Woo core to find matching rates for taxable address
     699                $location = array(
     700                    'to_country' => $calculation_data['to_country'],
     701                    'to_state'   => $calculation_data['to_state'],
     702                    'to_zip'     => $calculation_data['to_zip'],
     703                    'to_city'    => $calculation_data['to_city'],
    429704                );
    430                 $settings[] = $this->download_orders->get_form_settings_field();
    431                 $settings[] = array(
    432                     'title'   => __( 'Debug Log', 'wc-taxjar' ),
    433                     'type'    => 'checkbox',
    434                     'default' => 'no',
    435                     'desc'    => __( 'Log events such as API requests.', 'wc-taxjar' ),
    436                     'id'      => 'woocommerce_taxjar-integration_settings[debug]'
     705
     706                // Add line item tax rates
     707                foreach ( $taxes['line_items'] as $line_item_key => $line_item ) {
     708                    $line_item_key_chunks = explode( '-', $line_item_key );
     709                    $product_id           = $line_item_key_chunks[0];
     710                    $product              = wc_get_product( $product_id );
     711
     712                    if ( $product ) {
     713                        $tax_class = $product->get_tax_class();
     714                    } else {
     715                        if ( isset( $this->backend_tax_classes[ $product_id ] ) ) {
     716                            $tax_class = $this->backend_tax_classes[ $product_id ];
     717                        }
     718                    }
     719
     720                    if ( $line_item->combined_tax_rate ) {
     721                        $taxes['rate_ids'][ $line_item_key ] = $this->create_or_update_tax_rate(
     722                            $location,
     723                            $line_item->combined_tax_rate * 100,
     724                            $tax_class,
     725                            $taxes['freight_taxable']
     726                        );
     727                    }
     728                }
     729
     730                // Add shipping tax rate
     731                $taxes['rate_ids']['shipping'] = $this->create_or_update_tax_rate(
     732                    $location,
     733                    $taxes['shipping_rate'] * 100,
     734                    '',
     735                    $taxes['freight_taxable']
    437736                );
    438                 $settings[] = array(
    439                     'type' => 'sectionend',
     737            } // End if().
     738
     739            return $taxes;
     740        } // End calculate_tax().
     741
     742        /**
     743         * Add or update a native WooCommerce tax rate
     744         *
     745         * @return void
     746         */
     747        public function create_or_update_tax_rate( $location, $rate, $tax_class = '', $freight_taxable = 1 ) {
     748            $tax_rate = array(
     749                'tax_rate_country'  => $location['to_country'],
     750                'tax_rate_state'    => $location['to_state'],
     751                'tax_rate_name'     => sprintf( '%s Tax', $location['to_state'] ),
     752                'tax_rate_priority' => 1,
     753                'tax_rate_compound' => false,
     754                'tax_rate_shipping' => $freight_taxable,
     755                'tax_rate'          => $rate,
     756                'tax_rate_class'    => $tax_class,
     757            );
     758
     759            $rate_lookup = array(
     760                'country'   => $location['to_country'],
     761                'state'     => $location['to_state'],
     762                'postcode'  => $location['to_zip'],
     763                'city'      => $location['to_city'],
     764                'tax_class' => $tax_class,
     765            );
     766
     767            if ( version_compare( WC()->version, '3.2.0', '>=' ) ) {
     768                $rate_lookup['state'] = sanitize_key( $location['to_state'] );
     769            }
     770
     771            $wc_rate = WC_Tax::find_rates( $rate_lookup );
     772
     773            if ( ! empty( $wc_rate ) ) {
     774                $this->_log( ':: Tax Rate Found ::' );
     775                $this->_log( $wc_rate );
     776
     777                // Get the existing ID
     778                $rate_id = key( $wc_rate );
     779
     780                // Update Tax Rates with TaxJar rates ( rates might be coming from a cached taxjar rate )
     781                $this->_log( ':: Updating Tax Rate To ::' );
     782                $this->_log( $tax_rate );
     783
     784                WC_Tax::_update_tax_rate( $rate_id, $tax_rate );
     785            } else {
     786                // Insert a rate if we did not find one
     787                $this->_log( ':: Adding New Tax Rate ::' );
     788                $this->_log( $tax_rate );
     789                $rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
     790                WC_Tax::_update_tax_rate_postcodes( $rate_id, wc_clean( $location['to_zip'] ) );
     791                WC_Tax::_update_tax_rate_cities( $rate_id, wc_clean( $location['to_city'] ) );
     792            }
     793
     794            $this->_log( 'Tax Rate ID Set for ' . $rate_id );
     795            return $rate_id;
     796        }
     797
     798        public function smartcalcs_request( $json ) {
     799            $response = apply_filters( 'taxjar_smartcalcs_request', false, $json );
     800            if ( ! $response ) {
     801                $url = $this->uri . 'taxes';
     802                $this->_log( 'Requesting: ' . $this->uri . 'taxes - ' . $json );
     803
     804                $response = wp_remote_post(
     805                    $url,
     806                    array(
     807                        'headers'    => array(
     808                            'Authorization' => 'Token token="' . $this->settings['api_token'] . '"',
     809                            'Content-Type'  => 'application/json',
     810                        ),
     811                        'user-agent' => $this->ua,
     812                        'body'       => $json,
     813                    )
    440814                );
    441                 $settings[] = array(
    442                     'type' => 'title',
    443                     'desc' => __( 'If you find TaxJar for WooCommerce useful, please rate us <a href="https://wordpress.org/support/plugin/taxjar-simplified-taxes-for-woocommerce/reviews/#new-post" target="_blank">&#9733;&#9733;&#9733;&#9733;&#9733;</a>. Thank you!', 'wc-taxjar' ),
    444                 );
    445                 $settings[] = array(
    446                     'type' => 'sectionend',
    447                 );
    448             }
    449         }
    450 
    451         return apply_filters( 'woocommerce_get_settings_' . $this->id, $settings );
    452     }
    453 
    454     /**
    455      * Get sections.
    456      *
    457      * @return array
    458      */
    459     public function get_sections() {
    460         $sections = array(
    461             ''             => __( 'Settings', 'woocommerce' ),
    462             'transaction_backfill'    => __( 'Transaction Backfill', 'wc-taxjar' ),
    463             'sync_queue' => __( 'Sync Queue', 'wc-taxjar' ),
    464         );
    465         return apply_filters( 'woocommerce_get_sections_' . $this->id, $sections );
    466     }
    467 
    468     /**
    469      * Save settings.
    470      */
    471     public function save() {
    472         $settings = $this->get_settings();
    473         WC_Admin_Settings::save_fields( $settings );
    474     }
    475 
    476     /**
    477      * Prints debug info to wp-content/uploads/wc-logs/taxjar-*.log
    478      *
    479      * @return void
    480      */
    481     public function _log( $message ) {
    482         do_action( 'taxjar_log', $message );
    483         if ( $this->debug ) {
    484             if ( ! isset( $this->log ) ) {
    485                 $this->log = new WC_Logger();
    486             }
    487             if ( is_array( $message ) || is_object( $message ) ) {
    488                 $this->log->add( 'taxjar', print_r( $message, true ) );
     815            }
     816
     817            if ( is_wp_error( $response ) ) {
     818                new WP_Error( 'request', __( 'There was an error retrieving the tax rates. Please check your server configuration.' ) );
     819            } elseif ( 200 === $response['response']['code'] ) {
     820                return $response;
    489821            } else {
    490                 $this->log->add( 'taxjar', $message );
    491             }
    492         }
    493     }
    494 
    495     /**
    496      * Slightly altered copy and paste of private function in WC_Tax class file
    497      */
    498     public function _get_wildcard_postcodes( $postcode ) {
    499         $postcodes = array( '*', strtoupper( $postcode ) );
    500 
    501         if ( version_compare( WC()->version, '2.4.0', '>=' ) ) {
    502             $postcodes = array( '*', strtoupper( $postcode ), strtoupper( $postcode ) . '*' );
    503         }
    504 
    505         $postcode_length   = strlen( $postcode );
    506         $wildcard_postcode = strtoupper( $postcode );
    507 
    508         for ( $i = 0; $i < $postcode_length; $i ++ ) {
    509             $wildcard_postcode = substr( $wildcard_postcode, 0, -1 );
    510             $postcodes[] = $wildcard_postcode . '*';
    511         }
    512         return $postcodes;
    513     }
    514 
    515     /**
    516      * Calculate sales tax using SmartCalcs
    517      *
    518      * @return void
    519      */
    520     public function calculate_tax( $options = array() ) {
    521         $this->_log( ':::: TaxJar Plugin requested ::::' );
    522 
    523         // Process $options array and turn them into variables
    524         $options = is_array( $options ) ? $options : array();
    525 
    526         extract( array_replace_recursive(array(
    527             'to_country' => null,
    528             'to_state' => null,
    529             'to_zip' => null,
    530             'to_city' => null,
    531             'to_street' => null,
    532             'shipping_amount' => null, // WC()->shipping->shipping_total
    533             'line_items' => null,
    534             'customer_id' => 0,
    535             'exemption_type' => '',
    536         ), $options) );
    537 
    538         $taxes = array(
    539             'freight_taxable' => 1,
    540             'has_nexus' => 0,
    541             'line_items' => array(),
    542             'rate_ids' => array(),
    543             'tax_rate' => 0,
    544         );
    545 
    546         // Strict conditions to be met before API call can be conducted
    547         if (
    548             empty( $to_country ) ||
    549             empty( $to_zip ) ||
    550             ( empty( $line_items ) && ( 0 == $shipping_amount ) )
    551         ) {
    552             return false;
    553         }
    554 
    555         // validate customer exemption before sending API call
    556         if ( is_object( WC()->customer ) ) {
    557             if ( WC()->customer->is_vat_exempt() ) {
    558                 return false;
    559             }
    560         }
    561 
    562         // Valid zip codes to prevent unnecessary API requests
    563         if ( ! $this->is_postal_code_valid( $to_country, $to_state, $to_zip ) ) {
    564             return false;
    565         }
    566 
    567         $taxjar_nexus = new WC_Taxjar_Nexus( $this );
    568 
    569         if ( ! $taxjar_nexus->has_nexus_check( $to_country, $to_state ) ) {
    570             $this->_log( ':::: Order not shipping to nexus area ::::' );
    571             return false;
    572         }
    573 
    574         $to_zip           = explode( ',' , $to_zip );
    575         $to_zip           = array_shift( $to_zip );
    576 
    577         $store_settings   = $this->get_store_settings();
    578         $from_country     = $store_settings['country'];
    579         $from_state       = $store_settings['state'];
    580         $from_zip         = $store_settings['postcode'];
    581         $from_city        = $store_settings['city'];
    582         $from_street      = $store_settings['street'];
    583         $shipping_amount  = is_null( $shipping_amount ) ? 0.0 : $shipping_amount;
    584 
    585         $this->_log( ':::: TaxJar API called ::::' );
    586 
    587         $url = $this->uri . 'taxes';
    588 
    589         $body = array(
    590             'from_country' => $from_country,
    591             'from_state' => $from_state,
    592             'from_zip' => $from_zip,
    593             'from_city' => $from_city,
    594             'from_street' => $from_street,
    595             'to_country' => $to_country,
    596             'to_state' => $to_state,
    597             'to_zip' => $to_zip,
    598             'to_city' => $to_city,
    599             'to_street' => $to_street,
    600             'shipping' => $shipping_amount,
    601             'plugin' => 'woo',
    602         );
    603 
    604         if ( is_int ( $customer_id ) ) {
    605             if ( $customer_id > 0 ) {
    606                 $body[ 'customer_id' ] = $customer_id;
    607             }
    608         } else {
    609             if ( ! empty( $customer_id ) ) {
    610                 $body[ 'customer_id' ] = $customer_id;
    611             }
    612         }
    613 
    614         if ( ! empty( $exemption_type ) ) {
    615             if ( self::is_valid_exemption_type( $exemption_type ) ) {
    616                 $body[ 'exemption_type' ] = $exemption_type;
    617             }
    618         }
    619 
    620         // Either `amount` or `line_items` parameters are required to perform tax calculations.
    621         if ( empty( $line_items ) ) {
    622             $body['amount'] = 0.0;
    623         } else {
    624             $body['line_items'] = $line_items;
    625         }
    626 
    627         $response = $this->smartcalcs_cache_request( wp_json_encode( $body ) );
    628 
    629         if ( isset( $response ) ) {
    630             // Log the response
    631             $this->_log( 'Received: ' . $response['body'] );
    632 
    633             // Decode Response
    634             $taxjar_response          = json_decode( $response['body'] );
    635             $taxjar_response          = $taxjar_response->tax;
    636 
    637             // Update Properties based on Response
    638             $taxes['freight_taxable']    = (int) $taxjar_response->freight_taxable;
    639             $taxes['has_nexus']          = (int) $taxjar_response->has_nexus;
    640             $taxes['shipping_rate']      = $taxjar_response->rate;
    641 
    642             if ( ! empty( $taxjar_response->breakdown ) ) {
    643 
    644                 if ( ! empty( $taxjar_response->breakdown->shipping ) ) {
    645                     $taxes['shipping_rate'] = $taxjar_response->breakdown->shipping->combined_tax_rate;
    646                 }
    647 
    648                 if ( ! empty( $taxjar_response->breakdown->line_items ) ) {
    649                     $line_items = array();
    650                     foreach ( $taxjar_response->breakdown->line_items as $line_item ) {
    651                         $line_items[ $line_item->id ] = $line_item;
     822                $this->_log( 'Received (' . $response['response']['code'] . '): ' . $response['body'] );
     823            }
     824        }
     825
     826        public function smartcalcs_cache_request( $json ) {
     827            $cache_key = 'tj_tax_' . hash( 'md5', $json );
     828            $response  = get_transient( $cache_key );
     829
     830            if ( false === $response ) {
     831                $response = $this->smartcalcs_request( $json );
     832
     833                if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
     834                    set_transient( $cache_key, $response, $this->cache_time );
     835                }
     836            }
     837
     838            return $response;
     839        }
     840
     841        /**
     842         * Calculate tax / totals using TaxJar at checkout
     843         *
     844         * @return void
     845         */
     846        public function calculate_totals( $wc_cart_object ) {
     847            // If outside of cart and checkout page or within mini-cart, skip calculations
     848            if ( ( ! is_cart() && ! is_checkout() ) || ( is_cart() && is_ajax() ) ) {
     849                return;
     850            }
     851
     852            // prevent unnecessary calls to API during add to cart process
     853            if ( doing_action( 'woocommerce_add_to_cart' ) ) {
     854                return;
     855            }
     856
     857            $cart_taxes     = array();
     858            $cart_tax_total = 0;
     859
     860            foreach ( $wc_cart_object->coupons as $coupon ) {
     861                if ( method_exists( $coupon, 'get_id' ) ) { // Woo 3.0+
     862                    $limit_usage_qty = get_post_meta( $coupon->get_id(), 'limit_usage_to_x_items', true );
     863
     864                    if ( $limit_usage_qty ) {
     865                        $coupon->set_limit_usage_to_x_items( $limit_usage_qty );
    652866                    }
    653                     $taxes['line_items'] = $line_items;
    654                 }
    655             }
    656         }
    657 
    658         // Remove taxes if they are set somehow and customer is exempt
    659         if ( is_object( WC()->customer ) && WC()->customer->is_vat_exempt() ) {
    660             WC()->cart->remove_taxes(); // Woo < 3.2
    661         } elseif ( $taxes['has_nexus'] ) {
    662             // Use Woo core to find matching rates for taxable address
    663             $location = array(
    664                 'to_country' => $to_country,
    665                 'to_state' => $to_state,
    666                 'to_zip' => $to_zip,
    667                 'to_city' => $to_city,
     867                }
     868            }
     869
     870            $address        = $this->get_address( $wc_cart_object );
     871            $line_items     = $this->get_line_items( $wc_cart_object );
     872            $shipping_total = $wc_cart_object->get_shipping_total();
     873
     874            $customer_id = 0;
     875            if ( is_object( WC()->customer ) ) {
     876                $customer_id = apply_filters( 'taxjar_get_customer_id', WC()->customer->get_id(), WC()->customer );
     877            }
     878
     879            $exemption_type = apply_filters( 'taxjar_cart_exemption_type', '', $wc_cart_object );
     880
     881            $taxes = $this->calculate_tax(
     882                array(
     883                    'to_country'      => $address['to_country'],
     884                    'to_zip'          => $address['to_zip'],
     885                    'to_state'        => $address['to_state'],
     886                    'to_city'         => $address['to_city'],
     887                    'to_street'       => $address['to_street'],
     888                    'shipping_amount' => $shipping_total,
     889                    'line_items'      => $line_items,
     890                    'customer_id'     => $customer_id,
     891                    'exemption_type'  => $exemption_type,
     892                )
    668893            );
    669894
    670             // Add line item tax rates
    671             foreach ( $taxes['line_items'] as $line_item_key => $line_item ) {
    672                 $line_item_key_chunks = explode( '-', $line_item_key );
    673                 $product_id = $line_item_key_chunks[0];
    674                 $product = wc_get_product( $product_id );
    675 
    676                 if ( $product ) {
    677                     $tax_class = $product->get_tax_class();
    678                 } else {
    679                     if ( isset( $this->backend_tax_classes[$product_id] ) ) {
    680                         $tax_class = $this->backend_tax_classes[$product_id];
     895            if ( false === $taxes ) {
     896                return;
     897            }
     898
     899            $this->response_rate_ids   = $taxes['rate_ids'];
     900            $this->response_line_items = $taxes['line_items'];
     901
     902            if ( isset( $this->response_line_items ) ) {
     903                foreach ( $this->response_line_items as $response_line_item_key => $response_line_item ) {
     904                    $line_item = $this->get_line_item( $response_line_item_key, $line_items );
     905
     906                    if ( isset( $line_item ) ) {
     907                        $this->response_line_items[ $response_line_item_key ]->line_total = ( $line_item['unit_price'] * $line_item['quantity'] ) - $line_item['discount'];
    681908                    }
    682909                }
    683 
    684                 if ( $line_item->combined_tax_rate ) {
    685                     $taxes['rate_ids'][ $line_item_key ] = $this->create_or_update_tax_rate(
    686                         $location,
    687                         $line_item->combined_tax_rate * 100,
    688                         $tax_class,
    689                         $taxes['freight_taxable']
    690                     );
    691                 }
    692             }
    693 
    694             // Add shipping tax rate
    695             $taxes['rate_ids']['shipping'] = $this->create_or_update_tax_rate(
    696                 $location,
    697                 $taxes['shipping_rate'] * 100,
    698                 '',
    699                 $taxes['freight_taxable']
     910            }
     911
     912            foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
     913                $product       = $cart_item['data'];
     914                $line_item_key = $product->get_id() . '-' . $cart_item_key;
     915                if ( isset( $taxes['line_items'][ $line_item_key ] ) && ! $taxes['line_items'][ $line_item_key ]->combined_tax_rate ) {
     916                    if ( method_exists( $product, 'set_tax_status' ) ) {
     917                        $product->set_tax_status( 'none' ); // Woo 3.0+
     918                    } else {
     919                        $product->tax_status = 'none'; // Woo 2.6
     920                    }
     921                }
     922            }
     923
     924            // ensure fully exempt orders have no tax on shipping
     925            if ( ! $taxes['freight_taxable'] ) {
     926                foreach ( $wc_cart_object->get_shipping_packages() as $package_key => $package ) {
     927                    $shipping_for_package = WC()->session->get( 'shipping_for_package_' . $package_key );
     928                    if ( ! empty( $shipping_for_package['rates'] ) ) {
     929                        foreach ( $shipping_for_package['rates'] as $shipping_rate ) {
     930                            if ( method_exists( $shipping_rate, 'set_taxes' ) ) {
     931                                $shipping_rate->set_taxes( array() );
     932                            } else {
     933                                $shipping_rate->taxes = array();
     934                            }
     935                            WC()->session->set( 'shipping_for_package_' . $package_key, $shipping_for_package );
     936                        }
     937                    }
     938                }
     939            }
     940
     941            if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
     942                do_action( 'woocommerce_cart_reset', $wc_cart_object, false );
     943                do_action( 'woocommerce_before_calculate_totals', $wc_cart_object );
     944
     945                // Prevent WooCommerce Smart Coupons from removing the applied gift card amount when calculating totals the second time
     946                if ( WC()->cart->smart_coupon_credit_used ) {
     947                    WC()->cart->smart_coupon_credit_used = array();
     948                }
     949
     950                new WC_Cart_Totals( $wc_cart_object );
     951                remove_action( 'woocommerce_after_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     952                do_action( 'woocommerce_after_calculate_totals', $wc_cart_object );
     953                add_action( 'woocommerce_after_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     954            } else {
     955                remove_action( 'woocommerce_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     956                $wc_cart_object->calculate_totals();
     957                add_action( 'woocommerce_calculate_totals', array( $this, 'calculate_totals' ), 20 );
     958            }
     959        }
     960
     961        /**
     962         * Calculate tax / totals using TaxJar for backend orders
     963         *
     964         * @return void
     965         */
     966        public function calculate_backend_totals( $order_id ) {
     967            $order      = wc_get_order( $order_id );
     968            $address    = $this->get_backend_address();
     969            $line_items = $this->get_backend_line_items( $order );
     970
     971            if ( method_exists( $order, 'get_shipping_total' ) ) {
     972                $shipping = $order->get_shipping_total(); // Woo 3.0+
     973            } else {
     974                $shipping = $order->get_total_shipping(); // Woo 2.6
     975            }
     976
     977            $customer_id = apply_filters( 'taxjar_get_customer_id', isset( $_POST['customer_user'] ) ? wc_clean( $_POST['customer_user'] ) : 0 );
     978
     979            $exemption_type = apply_filters( 'taxjar_order_calculation_exemption_type', '', $order );
     980
     981            $taxes = $this->calculate_tax(
     982                array(
     983                    'to_country'      => $address['to_country'],
     984                    'to_state'        => $address['to_state'],
     985                    'to_zip'          => $address['to_zip'],
     986                    'to_city'         => $address['to_city'],
     987                    'to_street'       => $address['to_street'],
     988                    'shipping_amount' => $shipping,
     989                    'line_items'      => $line_items,
     990                    'customer_id'     => $customer_id,
     991                    'exemption_type'  => $exemption_type,
     992                )
    700993            );
    701         } // End if().
    702 
    703         return $taxes;
    704     } // End calculate_tax().
    705 
    706     /**
    707      * Add or update a native WooCommerce tax rate
    708      *
    709      * @return void
    710      */
    711     public function create_or_update_tax_rate( $location, $rate, $tax_class = '', $freight_taxable = 1 ) {
    712         $tax_rate = array(
    713             'tax_rate_country' => $location['to_country'],
    714             'tax_rate_state' => $location['to_state'],
    715             'tax_rate_name' => sprintf( "%s Tax", $location['to_state'] ),
    716             'tax_rate_priority' => 1,
    717             'tax_rate_compound' => false,
    718             'tax_rate_shipping' => $freight_taxable,
    719             'tax_rate' => $rate,
    720             'tax_rate_class' => $tax_class,
    721         );
    722 
    723         $rate_lookup = array(
    724             'country' => $location['to_country'],
    725             'state' => $location['to_state'],
    726             'postcode' => $location['to_zip'],
    727             'city' => $location['to_city'],
    728             'tax_class' => $tax_class,
    729         );
    730 
    731         if ( version_compare( WC()->version, '3.2.0', '>=' ) ) {
    732             $rate_lookup['state'] = sanitize_key( $location['to_state'] );
    733         }
    734 
    735         $wc_rate = WC_Tax::find_rates( $rate_lookup );
    736 
    737         if ( ! empty( $wc_rate ) ) {
    738             $this->_log( ':: Tax Rate Found ::' );
    739             $this->_log( $wc_rate );
    740 
    741             // Get the existing ID
    742             $rate_id = key( $wc_rate );
    743 
    744             // Update Tax Rates with TaxJar rates ( rates might be coming from a cached taxjar rate )
    745             $this->_log( ':: Updating Tax Rate To ::' );
    746             $this->_log( $tax_rate );
    747 
    748             WC_Tax::_update_tax_rate( $rate_id, $tax_rate );
    749         } else {
    750             // Insert a rate if we did not find one
    751             $this->_log( ':: Adding New Tax Rate ::' );
    752             $this->_log( $tax_rate );
    753             $rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
    754             WC_Tax::_update_tax_rate_postcodes( $rate_id, wc_clean( $location['to_zip'] ) );
    755             WC_Tax::_update_tax_rate_cities( $rate_id, wc_clean( $location['to_city'] ) );
    756         }
    757 
    758         $this->_log( 'Tax Rate ID Set for ' . $rate_id );
    759         return $rate_id;
    760     }
    761 
    762     public function smartcalcs_request( $json ) {
    763         $response = apply_filters( 'taxjar_smartcalcs_request', false, $json );
    764         if ( ! $response ) {
    765             $url = $this->uri . 'taxes';
    766             $this->_log( 'Requesting: ' . $this->uri . 'taxes - ' . $json );
    767 
    768             $response = wp_remote_post( $url, array(
    769                 'headers' => array(
    770                                 'Authorization' => 'Token token="' . $this->settings['api_token'] . '"',
    771                                 'Content-Type' => 'application/json',
    772                             ),
    773                 'user-agent' => $this->ua,
    774                 'body' => $json,
    775             ) );
    776         }
    777 
    778         if ( is_wp_error( $response ) ) {
    779             new WP_Error( 'request', __( 'There was an error retrieving the tax rates. Please check your server configuration.' ) );
    780         } elseif ( 200 == $response['response']['code'] ) {
    781             return $response;
    782         } else {
    783             $this->_log( 'Received (' . $response['response']['code'] . '): ' . $response['body'] );
    784         }
    785     }
    786 
    787     public function smartcalcs_cache_request( $json ) {
    788         $cache_key = 'tj_tax_' . hash( 'md5', $json );
    789         $response  = get_transient( $cache_key );
    790 
    791         if ( false === $response ) {
    792             $response = $this->smartcalcs_request( $json );
    793 
    794             if ( 200 == wp_remote_retrieve_response_code( $response ) ) {
    795                 set_transient( $cache_key, $response, $this->cache_time );
    796             }
    797         }
    798 
    799         return $response;
    800     }
    801 
    802     /**
    803      * Calculate tax / totals using TaxJar at checkout
    804      *
    805      * @return void
    806      */
    807     public function calculate_totals( $wc_cart_object ) {
    808         // If outside of cart and checkout page or within mini-cart, skip calculations
    809         if ( ( ! is_cart() && ! is_checkout() ) || ( is_cart() && is_ajax() ) ) {
    810             return;
    811         }
    812 
    813         // prevent unnecessary calls to API during add to cart process
    814         if ( doing_action( 'woocommerce_add_to_cart' ) ) {
    815             return;
    816         }
    817 
    818         $cart_taxes = array();
    819         $cart_tax_total = 0;
    820 
    821         foreach ( $wc_cart_object->coupons as $coupon ) {
    822             if ( method_exists( $coupon, 'get_id' ) ) { // Woo 3.0+
    823                 $limit_usage_qty = get_post_meta( $coupon->get_id(), 'limit_usage_to_x_items', true );
    824 
    825                 if ( $limit_usage_qty ) {
    826                     $coupon->set_limit_usage_to_x_items( $limit_usage_qty );
    827                 }
    828             }
    829         }
    830 
    831         $address = $this->get_address( $wc_cart_object );
    832         $line_items = $this->get_line_items( $wc_cart_object );
    833         $shipping_total = $wc_cart_object->get_shipping_total();
    834 
    835         $customer_id = 0;
    836         if ( is_object( WC()->customer ) ) {
    837             $customer_id = apply_filters( 'taxjar_get_customer_id', WC()->customer->get_id(), WC()->customer );
    838         }
    839 
    840         $exemption_type = apply_filters( 'taxjar_cart_exemption_type', '', $wc_cart_object );
    841 
    842         $taxes = $this->calculate_tax( array(
    843             'to_country' => $address['to_country'],
    844             'to_zip' => $address['to_zip'],
    845             'to_state' => $address['to_state'],
    846             'to_city' => $address['to_city'],
    847             'to_street' => $address['to_street'],
    848             'shipping_amount' => $shipping_total,
    849             'line_items' => $line_items,
    850             'customer_id' => $customer_id,
    851             'exemption_type' => $exemption_type,
    852         ) );
    853 
    854         if ( $taxes === false ) {
    855             return;
    856         }
    857 
    858         $this->response_rate_ids = $taxes['rate_ids'];
    859         $this->response_line_items = $taxes['line_items'];
    860 
    861         if ( isset( $this->response_line_items ) ) {
    862             foreach ( $this->response_line_items as $response_line_item_key => $response_line_item ) {
    863                 $line_item = $this->get_line_item( $response_line_item_key, $line_items );
    864 
    865                 if ( isset( $line_item ) ) {
    866                     $this->response_line_items[ $response_line_item_key ]->line_total = ( $line_item['unit_price'] * $line_item['quantity'] ) - $line_item['discount'];
    867                 }
    868             }
    869         }
    870 
    871         foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
    872             $product = $cart_item['data'];
    873             $line_item_key = $product->get_id() . '-' . $cart_item_key;
    874             if ( isset( $taxes['line_items'][ $line_item_key ] ) && ! $taxes['line_items'][ $line_item_key ]->combined_tax_rate ) {
    875                 if ( method_exists( $product, 'set_tax_status' ) ) {
    876                     $product->set_tax_status( 'none' ); // Woo 3.0+
    877                 } else {
    878                     $product->tax_status = 'none'; // Woo 2.6
    879                 }
    880             }
    881         }
    882 
    883         // ensure fully exempt orders have no tax on shipping
    884         if ( ! $taxes[ 'freight_taxable' ] ) {
    885             foreach ( $wc_cart_object->get_shipping_packages() as $package_key => $package ) {
    886                 $shipping_for_package = WC()->session->get( 'shipping_for_package_' . $package_key );
    887                 if ( ! empty( $shipping_for_package['rates'] ) ) {
    888                     foreach ( $shipping_for_package['rates'] as $shipping_rate ) {
    889                         if ( method_exists( $shipping_rate, 'set_taxes' ) ) {
    890                             $shipping_rate->set_taxes( array() );
    891                         } else {
    892                             $shipping_rate->taxes = array();
    893                         }
    894                         WC()->session->set( 'shipping_for_package_' . $package_key, $shipping_for_package );
     994
     995            if ( false === $taxes ) {
     996                return;
     997            }
     998
     999            if ( class_exists( 'WC_Order_Item_Tax' ) ) { // Add tax rates manually for Woo 3.0+
     1000                foreach ( $order->get_items() as $item_key => $item ) {
     1001                    $product_id    = $item->get_product_id();
     1002                    $line_item_key = $product_id . '-' . $item_key;
     1003
     1004                    if ( isset( $taxes['rate_ids'][ $line_item_key ] ) ) {
     1005                        $rate_id  = $taxes['rate_ids'][ $line_item_key ];
     1006                        $item_tax = new WC_Order_Item_Tax();
     1007                        $item_tax->set_rate( $rate_id );
     1008                        $item_tax->set_order_id( $order_id );
     1009                        $item_tax->save();
    8951010                    }
    8961011                }
    897             }
    898         }
    899 
    900         if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
    901             do_action( 'woocommerce_cart_reset', $wc_cart_object, false );
    902             do_action( 'woocommerce_before_calculate_totals', $wc_cart_object );
    903 
    904             // Prevent WooCommerce Smart Coupons from removing the applied gift card amount when calculating totals the second time
    905             if ( WC()->cart->smart_coupon_credit_used ) {
    906                 WC()->cart->smart_coupon_credit_used = array();
    907             }
    908 
    909             new WC_Cart_Totals( $wc_cart_object );
    910             remove_action( 'woocommerce_after_calculate_totals', array( $this, 'calculate_totals' ), 20 );
    911             do_action( 'woocommerce_after_calculate_totals', $wc_cart_object );
    912             add_action( 'woocommerce_after_calculate_totals', array( $this, 'calculate_totals' ), 20 );
    913         } else {
    914             remove_action( 'woocommerce_calculate_totals', array( $this, 'calculate_totals' ), 20 );
    915             $wc_cart_object->calculate_totals();
    916             add_action( 'woocommerce_calculate_totals', array( $this, 'calculate_totals' ), 20 );
    917         }
    918     }
    919 
    920     /**
    921      * Calculate tax / totals using TaxJar for backend orders
    922      *
    923      * @return void
    924      */
    925     public function calculate_backend_totals( $order_id ) {
    926         $order = wc_get_order( $order_id );
    927         $address = $this->get_backend_address();
    928         $line_items = $this->get_backend_line_items( $order );
    929 
    930         if ( method_exists( $order, 'get_shipping_total' ) ) {
    931             $shipping = $order->get_shipping_total(); // Woo 3.0+
    932         } else {
    933             $shipping = $order->get_total_shipping(); // Woo 2.6
    934         }
    935 
    936         $customer_id = apply_filters( 'taxjar_get_customer_id', isset( $_POST[ 'customer_user' ] ) ? wc_clean( $_POST[ 'customer_user' ] ) : 0 );
    937 
    938         $exemption_type = apply_filters( 'taxjar_order_calculation_exemption_type', '', $order );
    939 
    940         $taxes = $this->calculate_tax( array(
    941             'to_country' => $address['to_country'],
    942             'to_state' => $address['to_state'],
    943             'to_zip' => $address['to_zip'],
    944             'to_city' => $address['to_city'],
    945             'to_street' => $address['to_street'],
    946             'shipping_amount' => $shipping,
    947             'line_items' => $line_items,
    948             'customer_id' => $customer_id,
    949             'exemption_type' => $exemption_type,
    950         ) );
    951 
    952         if ( $taxes === false ) {
    953             return;
    954         }
    955 
    956         if ( class_exists( 'WC_Order_Item_Tax' ) ) { // Add tax rates manually for Woo 3.0+
     1012            } else { // Recalculate tax for Woo 2.6 to apply new tax rates
     1013                if ( class_exists( 'WC_AJAX' ) ) {
     1014                    remove_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
     1015                    if ( check_ajax_referer( 'calc-totals', 'security', false ) ) {
     1016                        WC_AJAX::calc_line_taxes();
     1017                    }
     1018                    add_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
     1019                }
     1020            }
     1021        }
     1022
     1023        /**
     1024         * Triggers tax calculation on both renewal order and subscription when creating a new renewal order
     1025         *
     1026         * @return WC_Order
     1027         */
     1028        public function calculate_renewal_order_totals( $order, $subscription, $type ) {
     1029
     1030            if ( ! is_object( $subscription ) ) {
     1031                $subscription = wcs_get_subscription( $subscription );
     1032            }
     1033
     1034            // Ensure payment gateway allows order totals to be changed
     1035            if ( ! $subscription->payment_method_supports( 'subscription_amount_changes' ) ) {
     1036                return $order;
     1037            }
     1038
     1039            $this->calculate_order_tax( $order );
     1040
     1041            // must calculate tax on subscription in order for my account to properly display the correct tax
     1042            $this->calculate_order_tax( $subscription );
     1043
     1044            $order->calculate_totals();
     1045            $subscription->calculate_totals();
     1046
     1047            return $order;
     1048        }
     1049
     1050        /**
     1051         * Calculate tax on an order
     1052         *
     1053         * @return null
     1054         */
     1055        public function calculate_order_tax( $order ) {
     1056            $address    = $this->get_address_from_order( $order );
     1057            $line_items = $this->get_backend_line_items( $order );
     1058            $shipping   = $order->get_shipping_total(); // Woo 3.0+
     1059
     1060            $customer_id = apply_filters( 'taxjar_get_customer_id', $order->get_customer_id() );
     1061
     1062            $exemption_type = apply_filters( 'taxjar_order_calculation_exemption_type', '', $order );
     1063
     1064            $taxes = $this->calculate_tax(
     1065                array(
     1066                    'to_country'      => $address['to_country'],
     1067                    'to_state'        => $address['to_state'],
     1068                    'to_zip'          => $address['to_zip'],
     1069                    'to_city'         => $address['to_city'],
     1070                    'to_street'       => $address['to_street'],
     1071                    'shipping_amount' => $shipping,
     1072                    'line_items'      => $line_items,
     1073                    'customer_id'     => $customer_id,
     1074                    'exemption_type'  => $exemption_type,
     1075                )
     1076            );
     1077
     1078            if ( false === $taxes ) {
     1079                return;
     1080            }
     1081
    9571082            foreach ( $order->get_items() as $item_key => $item ) {
    958                 $product_id = $item->get_product_id();
     1083                $product_id    = $item->get_product_id();
    9591084                $line_item_key = $product_id . '-' . $item_key;
    9601085
    9611086                if ( isset( $taxes['rate_ids'][ $line_item_key ] ) ) {
    962                     $rate_id = $taxes['rate_ids'][ $line_item_key ];
     1087                    $rate_id  = $taxes['rate_ids'][ $line_item_key ];
    9631088                    $item_tax = new WC_Order_Item_Tax();
    9641089                    $item_tax->set_rate( $rate_id );
    965                     $item_tax->set_order_id( $order_id );
     1090                    $item_tax->set_order_id( $order->get_id() );
    9661091                    $item_tax->save();
    9671092                }
    9681093            }
    969         } else { // Recalculate tax for Woo 2.6 to apply new tax rates
    970             if ( class_exists( 'WC_AJAX' ) ) {
    971                 remove_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
    972                 if ( check_ajax_referer( 'calc-totals', 'security', false ) ) {
    973                     WC_AJAX::calc_line_taxes();
    974                 }
    975                 add_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
    976             }
    977         }
    978     }
    979 
    980     /**
    981      * Triggers tax calculation on both renewal order and subscription when creating a new renewal order
    982      *
    983      * @return WC_Order
    984      */
    985     public function calculate_renewal_order_totals( $order, $subscription, $type ) {
    986 
    987         if ( ! is_object( $subscription ) ) {
    988             $subscription = wcs_get_subscription( $subscription );
    989         }
    990 
    991         // Ensure payment gateway allows order totals to be changed
    992         if ( ! $subscription->payment_method_supports( 'subscription_amount_changes' ) ) {
    993             return $order;
    994         }
    995 
    996         $this->calculate_order_tax( $order );
    997 
    998         // must calculate tax on subscription in order for my account to properly display the correct tax
    999         $this->calculate_order_tax( $subscription );
    1000 
    1001         $order->calculate_totals();
    1002         $subscription->calculate_totals();
    1003 
    1004         return $order;
    1005     }
    1006 
    1007     /**
    1008      * Calculate tax on an order
    1009      *
    1010      * @return null
    1011      */
    1012     public function calculate_order_tax( $order ) {
    1013         $address = $this->get_address_from_order( $order );
    1014         $line_items = $this->get_backend_line_items( $order );
    1015         $shipping = $order->get_shipping_total(); // Woo 3.0+
    1016 
    1017         $customer_id = apply_filters( 'taxjar_get_customer_id', $order->get_customer_id() );
    1018 
    1019         $exemption_type = apply_filters( 'taxjar_order_calculation_exemption_type', '', $order );
    1020 
    1021         $taxes = $this->calculate_tax( array(
    1022             'to_country' => $address[ 'to_country' ],
    1023             'to_state' => $address[ 'to_state' ],
    1024             'to_zip' => $address[ 'to_zip' ],
    1025             'to_city' => $address[ 'to_city' ],
    1026             'to_street' => $address[ 'to_street' ],
    1027             'shipping_amount' => $shipping,
    1028             'line_items' => $line_items,
    1029             'customer_id' => $customer_id,
    1030             'exemption_type' => $exemption_type,
    1031         ) );
    1032 
    1033         if ( $taxes === false ) {
    1034             return;
    1035         }
    1036 
    1037         foreach ( $order->get_items() as $item_key => $item ) {
    1038             $product_id = $item->get_product_id();
    1039             $line_item_key = $product_id . '-' . $item_key;
    1040 
    1041             if ( isset( $taxes['rate_ids'][ $line_item_key ] ) ) {
    1042                 $rate_id = $taxes['rate_ids'][ $line_item_key ];
    1043                 $item_tax = new WC_Order_Item_Tax();
    1044                 $item_tax->set_rate( $rate_id );
    1045                 $item_tax->set_order_id( $order->get_id() );
    1046                 $item_tax->save();
    1047             }
    1048         }
    1049     }
    1050 
    1051     /**
    1052      * Get address details of customer at checkout
    1053      *
    1054      * @return array
    1055      */
    1056     protected function get_address() {
    1057         $taxable_address = $this->get_taxable_address();
    1058         $taxable_address = is_array( $taxable_address ) ? $taxable_address : array();
    1059 
    1060         $to_country = isset( $taxable_address[0] ) && ! empty( $taxable_address[0] ) ? $taxable_address[0] : false;
    1061         $to_state = isset( $taxable_address[1] ) && ! empty( $taxable_address[1] ) ? $taxable_address[1] : false;
    1062         $to_zip = isset( $taxable_address[2] ) && ! empty( $taxable_address[2] ) ? $taxable_address[2] : false;
    1063         $to_city = isset( $taxable_address[3] ) && ! empty( $taxable_address[3] ) ? $taxable_address[3] : false;
    1064         $to_street = isset( $taxable_address[4] ) && ! empty( $taxable_address[4] ) ? $taxable_address[4] : false;
    1065 
    1066         return array(
    1067             'to_country' => $to_country,
    1068             'to_state' => $to_state,
    1069             'to_zip' => $to_zip,
    1070             'to_city' => $to_city,
    1071             'to_street' => $to_street,
    1072         );
    1073     }
    1074 
    1075     /**
    1076      * Get address details of customer for backend orders
    1077      *
    1078      * @return array
    1079      */
    1080     protected function get_backend_address() {
    1081         $to_country = isset( $_POST['country'] ) ? strtoupper( wc_clean( $_POST['country'] ) ) : false;
    1082         $to_state = isset( $_POST['state'] ) ? strtoupper( wc_clean( $_POST['state'] ) ) : false;
    1083         $to_zip = isset( $_POST['postcode'] ) ? strtoupper( wc_clean( $_POST['postcode'] ) ) : false;
    1084         $to_city = isset( $_POST['city'] ) ? strtoupper( wc_clean( $_POST['city'] ) ) : false;
    1085         $to_street = isset( $_POST['street'] ) ? strtoupper( wc_clean( $_POST['street'] ) ) : false;
    1086 
    1087         return array(
    1088             'to_country' => $to_country,
    1089             'to_state' => $to_state,
    1090             'to_zip' => $to_zip,
    1091             'to_city' => $to_city,
    1092             'to_street' => $to_street,
    1093         );
    1094     }
    1095 
    1096     /**
    1097      * Get ship to address from order object
    1098      *
    1099      * @return array
    1100      */
    1101     public function get_address_from_order( $order ) {
    1102         $address = $order->get_address( 'shipping' );
    1103         return array(
    1104             'to_country' => $address[ 'country' ],
    1105             'to_state' => $address[ 'state' ],
    1106             'to_zip' => $address[ 'postcode' ],
    1107             'to_city' => $address[ 'city' ],
    1108             'to_street' => $address[ 'address_1' ],
    1109         );
    1110     }
    1111 
    1112     /**
    1113      * Get line items at checkout
    1114      *
    1115      * @return array
    1116      */
    1117     protected function get_line_items( $wc_cart_object ) {
    1118         $line_items = array();
    1119 
    1120         foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
    1121             $product = $cart_item['data'];
    1122             $id = $product->get_id();
    1123             $quantity = $cart_item['quantity'];
    1124             $unit_price = wc_format_decimal( $product->get_price() );
    1125             $line_subtotal = wc_format_decimal( $cart_item['line_subtotal'] );
    1126             $discount = wc_format_decimal( $cart_item['line_subtotal'] - $cart_item['line_total'] );
    1127             $tax_code = self::get_tax_code_from_class( $product->get_tax_class() );
    1128 
    1129             if ( ! $product->is_taxable() || 'zero-rate' == sanitize_title( $product->get_tax_class() ) ) {
    1130                 $tax_code = '99999';
    1131             }
    1132 
    1133             // Get WC Subscription sign-up fees for calculations
    1134             if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
    1135                 if ( 'none' == WC_Subscriptions_Cart::get_calculation_type() ) {
    1136                     $unit_price = WC_Subscriptions_Cart::set_subscription_prices_for_calculation( $unit_price, $product );
    1137                 }
    1138             }
    1139 
    1140             if ( $unit_price && $line_subtotal ) {
    1141                 array_push($line_items, array(
    1142                     'id' => $id . '-' . $cart_item_key,
    1143                     'quantity' => $quantity,
    1144                     'product_tax_code' => $tax_code,
    1145                     'unit_price' => $unit_price,
    1146                     'discount' => $discount,
    1147                 ));
    1148             }
    1149         }
    1150 
    1151         return apply_filters( 'taxjar_cart_get_line_items', $line_items, $wc_cart_object, $this );
    1152     }
    1153 
    1154     /**
    1155      * Get line items for backend orders
    1156      *
    1157      * @return array
    1158      */
    1159     protected function get_backend_line_items( $order ) {
    1160         $line_items = array();
    1161         $this->backend_tax_classes = array();
    1162 
    1163         foreach ( $order->get_items() as $item_key => $item ) {
    1164             if ( is_object( $item ) ) { // Woo 3.0+
    1165                 $id = $item->get_product_id();
    1166                 $quantity = $item->get_quantity();
    1167                 $unit_price = wc_format_decimal( $item->get_subtotal() / $quantity );
    1168                 $discount = wc_format_decimal( $item->get_subtotal() - $item->get_total() );
    1169                 $tax_class_name = $item->get_tax_class();
    1170                 $tax_status = $item->get_tax_status();
    1171             }
    1172 
    1173             $this->backend_tax_classes[$id] = $tax_class_name;
    1174             $tax_code = self::get_tax_code_from_class( $tax_class_name );
    1175 
    1176             if ( 'taxable' !== $tax_status ) {
    1177                 $tax_code = '99999';
    1178             }
    1179 
    1180             if ( $unit_price ) {
    1181                 array_push($line_items, array(
    1182                     'id' => $id . '-' . $item_key,
    1183                     'quantity' => $quantity,
    1184                     'product_tax_code' => $tax_code,
    1185                     'unit_price' => $unit_price,
    1186                     'discount' => $discount,
    1187                 ));
    1188             }
    1189         }
    1190 
    1191         return apply_filters( 'taxjar_order_calculation_get_line_items', $line_items, $order );
    1192     }
    1193 
    1194     protected function get_line_item( $id, $line_items ) {
    1195         foreach ( $line_items as $line_item ) {
    1196             if ( $line_item['id'] === $id ) {
    1197                 return $line_item;
    1198             }
    1199         }
    1200 
    1201         return null;
    1202     }
    1203 
    1204     /**
    1205      * Override Woo's native tax rates to handle multiple line items with the same tax rate
    1206      * within the same tax class with different rates due to exemption thresholds
    1207      *
    1208      * @return array
    1209      */
    1210     public function override_woocommerce_tax_rates( $taxes, $price, $rates ) {
    1211         if ( isset( $this->response_line_items ) && array_values( $rates ) ) {
    1212             // Get tax rate ID for current item
    1213             $keys = array_keys( $taxes );
    1214             $tax_rate_id = $keys[0];
     1094        }
     1095
     1096        /**
     1097         * Get address details of customer at checkout
     1098         *
     1099         * @return array
     1100         */
     1101        protected function get_address() {
     1102            $taxable_address = $this->get_taxable_address();
     1103            $taxable_address = is_array( $taxable_address ) ? $taxable_address : array();
     1104
     1105            $to_country = isset( $taxable_address[0] ) && ! empty( $taxable_address[0] ) ? $taxable_address[0] : false;
     1106            $to_state   = isset( $taxable_address[1] ) && ! empty( $taxable_address[1] ) ? $taxable_address[1] : false;
     1107            $to_zip     = isset( $taxable_address[2] ) && ! empty( $taxable_address[2] ) ? $taxable_address[2] : false;
     1108            $to_city    = isset( $taxable_address[3] ) && ! empty( $taxable_address[3] ) ? $taxable_address[3] : false;
     1109            $to_street  = isset( $taxable_address[4] ) && ! empty( $taxable_address[4] ) ? $taxable_address[4] : false;
     1110
     1111            return array(
     1112                'to_country' => $to_country,
     1113                'to_state'   => $to_state,
     1114                'to_zip'     => $to_zip,
     1115                'to_city'    => $to_city,
     1116                'to_street'  => $to_street,
     1117            );
     1118        }
     1119
     1120        /**
     1121         * Get address details of customer for backend orders
     1122         *
     1123         * @return array
     1124         */
     1125        protected function get_backend_address() {
     1126            $to_country = isset( $_POST['country'] ) ? strtoupper( wc_clean( $_POST['country'] ) ) : false;
     1127            $to_state   = isset( $_POST['state'] ) ? strtoupper( wc_clean( $_POST['state'] ) ) : false;
     1128            $to_zip     = isset( $_POST['postcode'] ) ? strtoupper( wc_clean( $_POST['postcode'] ) ) : false;
     1129            $to_city    = isset( $_POST['city'] ) ? strtoupper( wc_clean( $_POST['city'] ) ) : false;
     1130            $to_street  = isset( $_POST['street'] ) ? strtoupper( wc_clean( $_POST['street'] ) ) : false;
     1131
     1132            return array(
     1133                'to_country' => $to_country,
     1134                'to_state'   => $to_state,
     1135                'to_zip'     => $to_zip,
     1136                'to_city'    => $to_city,
     1137                'to_street'  => $to_street,
     1138            );
     1139        }
     1140
     1141        /**
     1142         * Get ship to address from order object
     1143         *
     1144         * @return array
     1145         */
     1146        public function get_address_from_order( $order ) {
     1147            $address = $order->get_address( 'shipping' );
     1148            return array(
     1149                'to_country' => $address['country'],
     1150                'to_state'   => $address['state'],
     1151                'to_zip'     => $address['postcode'],
     1152                'to_city'    => $address['city'],
     1153                'to_street'  => $address['address_1'],
     1154            );
     1155        }
     1156
     1157        /**
     1158         * Get line items at checkout
     1159         *
     1160         * @return array
     1161         */
     1162        public function get_line_items( $wc_cart_object ) {
    12151163            $line_items = array();
    12161164
    1217             // Map line items using rate ID
    1218             foreach ( $this->response_rate_ids as $line_item_key => $rate_id ) {
    1219                 if ( $rate_id == $tax_rate_id ) {
    1220                     $line_items[] = $line_item_key;
    1221                 }
    1222             }
    1223 
    1224             // Remove number precision if Woo 3.2+
    1225             if ( function_exists( 'wc_remove_number_precision' ) ) {
    1226                 $price = wc_remove_number_precision( $price );
    1227             }
    1228 
    1229             foreach ( $this->response_line_items as $line_item_key => $line_item ) {
    1230                 // If line item belongs to rate and matches the price, manually set the tax
    1231                 if ( in_array( $line_item_key, $line_items ) && round( $price, 2 ) == round( $line_item->line_total, 2 ) ) {
    1232                     if ( function_exists( 'wc_add_number_precision' ) ) {
    1233                         $taxes[ $tax_rate_id ] = wc_add_number_precision( $line_item->tax_collectable );
     1165            foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
     1166                $id            = $cart_item['data']->get_id();
     1167                $product       = wc_get_product( $id );
     1168                $quantity      = $cart_item['quantity'];
     1169                $unit_price    = wc_format_decimal( $product->get_price() );
     1170                $line_subtotal = wc_format_decimal( $cart_item['line_subtotal'] );
     1171                $discount      = wc_format_decimal( $cart_item['line_subtotal'] - $cart_item['line_total'] );
     1172                $tax_code      = self::get_tax_code_from_class( $product->get_tax_class() );
     1173
     1174                if ( ! $product->is_taxable() || 'zero-rate' === sanitize_title( $product->get_tax_class() ) ) {
     1175                    $tax_code = '99999';
     1176                }
     1177
     1178                // Get WC Subscription sign-up fees for calculations
     1179                if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
     1180                    if ( 'none' === WC_Subscriptions_Cart::get_calculation_type() ) {
     1181                        $unit_price = WC_Subscriptions_Cart::set_subscription_prices_for_calculation( $unit_price, $product );
     1182                    }
     1183                }
     1184
     1185                if ( $unit_price && $line_subtotal ) {
     1186                    array_push(
     1187                        $line_items,
     1188                        array(
     1189                            'id'               => $id . '-' . $cart_item_key,
     1190                            'quantity'         => $quantity,
     1191                            'product_tax_code' => $tax_code,
     1192                            'unit_price'       => $unit_price,
     1193                            'discount'         => $discount,
     1194                        )
     1195                    );
     1196                }
     1197            }
     1198
     1199            return apply_filters( 'taxjar_cart_get_line_items', $line_items, $wc_cart_object, $this );
     1200        }
     1201
     1202        /**
     1203         * Get line items for backend orders
     1204         *
     1205         * @return array
     1206         */
     1207        protected function get_backend_line_items( $order ) {
     1208            $line_items                = array();
     1209            $this->backend_tax_classes = array();
     1210
     1211            foreach ( $order->get_items() as $item_key => $item ) {
     1212                if ( is_object( $item ) ) { // Woo 3.0+
     1213                    $id             = $item->get_product_id();
     1214                    $quantity       = $item->get_quantity();
     1215                    $unit_price     = wc_format_decimal( $item->get_subtotal() / $quantity );
     1216                    $discount       = wc_format_decimal( $item->get_subtotal() - $item->get_total() );
     1217                    $tax_class_name = $item->get_tax_class();
     1218                    $tax_status     = $item->get_tax_status();
     1219                }
     1220
     1221                $this->backend_tax_classes[ $id ] = $tax_class_name;
     1222                $tax_code                         = self::get_tax_code_from_class( $tax_class_name );
     1223
     1224                if ( 'taxable' !== $tax_status ) {
     1225                    $tax_code = '99999';
     1226                }
     1227
     1228                if ( $unit_price ) {
     1229                    array_push(
     1230                        $line_items,
     1231                        array(
     1232                            'id'               => $id . '-' . $item_key,
     1233                            'quantity'         => $quantity,
     1234                            'product_tax_code' => $tax_code,
     1235                            'unit_price'       => $unit_price,
     1236                            'discount'         => $discount,
     1237                        )
     1238                    );
     1239                }
     1240            }
     1241
     1242            return apply_filters( 'taxjar_order_calculation_get_line_items', $line_items, $order );
     1243        }
     1244
     1245        protected function get_line_item( $id, $line_items ) {
     1246            foreach ( $line_items as $line_item ) {
     1247                if ( $line_item['id'] === $id ) {
     1248                    return $line_item;
     1249                }
     1250            }
     1251
     1252            return null;
     1253        }
     1254
     1255        /**
     1256         * Override Woo's native tax rates to handle multiple line items with the same tax rate
     1257         * within the same tax class with different rates due to exemption thresholds
     1258         *
     1259         * @return array
     1260         */
     1261        public function override_woocommerce_tax_rates( $taxes, $price, $rates ) {
     1262            if ( isset( $this->response_line_items ) && array_values( $rates ) ) {
     1263                // Get tax rate ID for current item
     1264                $keys        = array_keys( $taxes );
     1265                $tax_rate_id = $keys[0];
     1266                $line_items  = array();
     1267
     1268                // Map line items using rate ID
     1269                foreach ( $this->response_rate_ids as $line_item_key => $rate_id ) {
     1270                    if ( $rate_id === $tax_rate_id ) {
     1271                        $line_items[] = $line_item_key;
     1272                    }
     1273                }
     1274
     1275                // Remove number precision if Woo 3.2+
     1276                if ( function_exists( 'wc_remove_number_precision' ) ) {
     1277                    $price = wc_remove_number_precision( $price );
     1278                }
     1279
     1280                foreach ( $this->response_line_items as $line_item_key => $line_item ) {
     1281                    // If line item belongs to rate and matches the price, manually set the tax
     1282                    if ( in_array( $line_item_key, $line_items, true ) && round( $price, 2 ) === round( $line_item->line_total, 2 ) ) {
     1283                        if ( function_exists( 'wc_add_number_precision' ) ) {
     1284                            $taxes[ $tax_rate_id ] = wc_add_number_precision( $line_item->tax_collectable );
     1285                        } else {
     1286                            $taxes[ $tax_rate_id ] = $line_item->tax_collectable;
     1287                        }
     1288                    }
     1289                }
     1290            }
     1291
     1292            return $taxes;
     1293        }
     1294
     1295        /**
     1296         * Set customer zip code and state to store if local shipping option set
     1297         *
     1298         * @return array
     1299         */
     1300        public function append_base_address_to_customer_taxable_address( $address ) {
     1301            $tax_based_on = '';
     1302
     1303            list( $country, $state, $postcode, $city, $street ) = array_pad( $address, 5, '' );
     1304
     1305            // See WC_Customer get_taxable_address()
     1306            // wc_get_chosen_shipping_method_ids() available since Woo 2.6.2+
     1307            if ( function_exists( 'wc_get_chosen_shipping_method_ids' ) ) {
     1308                if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( wc_get_chosen_shipping_method_ids(), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
     1309                    $tax_based_on = 'base';
     1310                }
     1311            } else {
     1312                if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( WC()->session->get( 'chosen_shipping_methods', array() ), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
     1313                    $tax_based_on = 'base';
     1314                }
     1315            }
     1316
     1317            if ( 'base' === $tax_based_on ) {
     1318                $store_settings = $this->get_store_settings();
     1319                $postcode       = $store_settings['postcode'];
     1320                $city           = strtoupper( $store_settings['city'] );
     1321                $street         = $store_settings['street'];
     1322            }
     1323
     1324            if ( '' !== $street ) {
     1325                return array( $country, $state, $postcode, $city, $street );
     1326            } else {
     1327                return array( $country, $state, $postcode, $city );
     1328            }
     1329        }
     1330
     1331        /**
     1332         * Allow street address to be passed when finding rates
     1333         *
     1334         * @param array $matched_tax_rates
     1335         * @param string $tax_class
     1336         * @return array
     1337         */
     1338        public function allow_street_address_for_matched_rates( $matched_tax_rates, $tax_class = '' ) {
     1339            $tax_class         = sanitize_title( $tax_class );
     1340            $location          = WC_Tax::get_tax_location( $tax_class );
     1341            $matched_tax_rates = array();
     1342
     1343            if ( sizeof( $location ) >= 4 ) {
     1344                list( $country, $state, $postcode, $city, $street ) = array_pad( $location, 5, '' );
     1345
     1346                $matched_tax_rates = WC_Tax::find_rates(
     1347                    array(
     1348                        'country'   => $country,
     1349                        'state'     => $state,
     1350                        'postcode'  => $postcode,
     1351                        'city'      => $city,
     1352                        'tax_class' => $tax_class,
     1353                    )
     1354                );
     1355            }
     1356
     1357            return $matched_tax_rates;
     1358        }
     1359
     1360        /**
     1361         * Get taxable address.
     1362         * @return array
     1363         */
     1364        public function get_taxable_address() {
     1365            $tax_based_on = get_option( 'woocommerce_tax_based_on' );
     1366
     1367            // Check shipping method at this point to see if we need special handling
     1368            // See WC_Customer get_taxable_address()
     1369            // wc_get_chosen_shipping_method_ids() available since Woo 2.6.2+
     1370            if ( function_exists( 'wc_get_chosen_shipping_method_ids' ) ) {
     1371                if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( wc_get_chosen_shipping_method_ids(), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
     1372                    $tax_based_on = 'base';
     1373                }
     1374            } else {
     1375                if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( WC()->session->get( 'chosen_shipping_methods', array() ), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
     1376                    $tax_based_on = 'base';
     1377                }
     1378            }
     1379
     1380            if ( 'base' === $tax_based_on ) {
     1381                $store_settings = $this->get_store_settings();
     1382                $country        = $store_settings['country'];
     1383                $state          = $store_settings['state'];
     1384                $postcode       = $store_settings['postcode'];
     1385                $city           = $store_settings['city'];
     1386                $street         = $store_settings['street'];
     1387            } elseif ( 'billing' === $tax_based_on ) {
     1388                $country  = WC()->customer->get_billing_country();
     1389                $state    = WC()->customer->get_billing_state();
     1390                $postcode = WC()->customer->get_billing_postcode();
     1391                $city     = WC()->customer->get_billing_city();
     1392                $street   = WC()->customer->get_billing_address();
     1393            } else {
     1394                $country  = WC()->customer->get_shipping_country();
     1395                $state    = WC()->customer->get_shipping_state();
     1396                $postcode = WC()->customer->get_shipping_postcode();
     1397                $city     = WC()->customer->get_shipping_city();
     1398                $street   = WC()->customer->get_shipping_address();
     1399            }
     1400
     1401            return apply_filters( 'woocommerce_customer_taxable_address', array( $country, $state, $postcode, $city, $street ) );
     1402        }
     1403
     1404        public function is_postal_code_valid( $to_country, $to_state, $to_zip ) {
     1405            $postal_regexes = array(
     1406                'US' => '/^\d{5}([ \-]\d{4})?$/',
     1407                'CA' => '/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ ]?\d[ABCEGHJ-NPRSTV-Z]\d$/',
     1408                'UK' => '/^GIR[ ]?0AA|((AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}))|BFPO[ ]?\d{1,4}$/',
     1409                'FR' => '/^\d{2}[ ]?\d{3}$/',
     1410                'IT' => '/^\d{5}$/',
     1411                'DE' => '/^\d{5}$/',
     1412                'NL' => '/^\d{4}[ ]?[A-Z]{2}$/',
     1413                'ES' => '/^\d{5}$/',
     1414                'DK' => '/^\d{4}$/',
     1415                'SE' => '/^\d{3}[ ]?\d{2}$/',
     1416                'BE' => '/^\d{4}$/',
     1417                'IN' => '/^\d{6}$/',
     1418                'AU' => '/^\d{4}$/',
     1419            );
     1420
     1421            if ( isset( $postal_regexes[ $to_country ] ) ) {
     1422                // SmartCalcs api allows requests with no zip codes outside of the US, mark them as valid
     1423                if ( empty( $to_zip ) ) {
     1424                    if ( 'US' === $to_country ) {
     1425                        return false;
    12341426                    } else {
    1235                         $taxes[ $tax_rate_id ] = $line_item->tax_collectable;
     1427                        return true;
    12361428                    }
    12371429                }
    1238             }
    1239         }
    1240 
    1241         return $taxes;
    1242     }
    1243 
    1244     /**
    1245      * Set customer zip code and state to store if local shipping option set
    1246      *
    1247      * @return array
    1248      */
    1249     public function append_base_address_to_customer_taxable_address( $address ) {
    1250         $tax_based_on = '';
    1251 
    1252         list( $country, $state, $postcode, $city, $street ) = array_pad( $address, 5, '' );
    1253 
    1254         // See WC_Customer get_taxable_address()
    1255         // wc_get_chosen_shipping_method_ids() available since Woo 2.6.2+
    1256         if ( function_exists( 'wc_get_chosen_shipping_method_ids' ) ) {
    1257             if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( wc_get_chosen_shipping_method_ids(), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
    1258                 $tax_based_on = 'base';
    1259             }
    1260         } else {
    1261             if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( WC()->session->get( 'chosen_shipping_methods', array() ), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
    1262                 $tax_based_on = 'base';
    1263             }
    1264         }
    1265 
    1266         if ( 'base' == $tax_based_on ) {
    1267             $store_settings = $this->get_store_settings();
    1268             $postcode = $store_settings['postcode'];
    1269             $city = strtoupper( $store_settings['city'] );
    1270             $street = $store_settings['street'];
    1271         }
    1272 
    1273         if ( '' != $street ) {
    1274             return array( $country, $state, $postcode, $city, $street );
    1275         } else {
    1276             return array( $country, $state, $postcode, $city );
    1277         }
    1278     }
    1279 
    1280     /**
    1281      * Allow street address to be passed when finding rates
    1282      *
    1283      * @param array $matched_tax_rates
    1284      * @param string $tax_class
    1285      * @return array
    1286      */
    1287     public function allow_street_address_for_matched_rates( $matched_tax_rates, $tax_class = '' ) {
    1288         $tax_class         = sanitize_title( $tax_class );
    1289         $location          = WC_Tax::get_tax_location( $tax_class );
    1290         $matched_tax_rates = array();
    1291 
    1292         if ( sizeof( $location ) >= 4 ) {
    1293             list( $country, $state, $postcode, $city, $street ) = array_pad( $location, 5, '' );
    1294 
    1295             $matched_tax_rates = WC_Tax::find_rates( array(
    1296                 'country'   => $country,
    1297                 'state'     => $state,
    1298                 'postcode'  => $postcode,
    1299                 'city'      => $city,
    1300                 'tax_class' => $tax_class,
    1301             ) );
    1302         }
    1303 
    1304         return $matched_tax_rates;
    1305     }
    1306 
    1307     /**
    1308      * Get taxable address.
    1309      * @return array
    1310      */
    1311     public function get_taxable_address() {
    1312         $tax_based_on = get_option( 'woocommerce_tax_based_on' );
    1313 
    1314         // Check shipping method at this point to see if we need special handling
    1315         // See WC_Customer get_taxable_address()
    1316         // wc_get_chosen_shipping_method_ids() available since Woo 2.6.2+
    1317         if ( function_exists( 'wc_get_chosen_shipping_method_ids' ) ) {
    1318             if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( wc_get_chosen_shipping_method_ids(), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
    1319                 $tax_based_on = 'base';
    1320             }
    1321         } else {
    1322             if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( WC()->session->get( 'chosen_shipping_methods', array() ), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
    1323                 $tax_based_on = 'base';
    1324             }
    1325         }
    1326 
    1327         if ( 'base' === $tax_based_on ) {
    1328             $store_settings = $this->get_store_settings();
    1329             $country  = $store_settings['country'];
    1330             $state    = $store_settings['state'];
    1331             $postcode = $store_settings['postcode'];
    1332             $city     = $store_settings['city'];
    1333             $street   = $store_settings['street'];
    1334         } elseif ( 'billing' === $tax_based_on ) {
    1335             $country  = WC()->customer->get_billing_country();
    1336             $state    = WC()->customer->get_billing_state();
    1337             $postcode = WC()->customer->get_billing_postcode();
    1338             $city     = WC()->customer->get_billing_city();
    1339             $street   = WC()->customer->get_billing_address();
    1340         } else {
    1341             $country  = WC()->customer->get_shipping_country();
    1342             $state    = WC()->customer->get_shipping_state();
    1343             $postcode = WC()->customer->get_shipping_postcode();
    1344             $city     = WC()->customer->get_shipping_city();
    1345             $street   = WC()->customer->get_shipping_address();
    1346         }
    1347 
    1348         return apply_filters( 'woocommerce_customer_taxable_address', array( $country, $state, $postcode, $city, $street ) );
    1349     }
    1350 
    1351     public function is_postal_code_valid( $to_country, $to_state, $to_zip ) {
    1352         $postal_regexes = array(
    1353             'US' => '/^\d{5}([ \-]\d{4})?$/',
    1354             'CA' => '/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ ]?\d[ABCEGHJ-NPRSTV-Z]\d$/',
    1355             'UK' => '/^GIR[ ]?0AA|((AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}))|BFPO[ ]?\d{1,4}$/',
    1356             'FR' => '/^\d{2}[ ]?\d{3}$/',
    1357             'IT' => '/^\d{5}$/',
    1358             'DE' => '/^\d{5}$/',
    1359             'NL' => '/^\d{4}[ ]?[A-Z]{2}$/',
    1360             'ES' => '/^\d{5}$/',
    1361             'DK' => '/^\d{4}$/',
    1362             'SE' => '/^\d{3}[ ]?\d{2}$/',
    1363             'BE' => '/^\d{4}$/',
    1364             'IN' => '/^\d{6}$/',
    1365             'AU' => '/^\d{4}$/',
    1366         );
    1367 
    1368         if ( isset( $postal_regexes[ $to_country ] ) ) {
    1369             // SmartCalcs api allows requests with no zip codes outside of the US, mark them as valid
    1370             if ( empty( $to_zip ) ) {
    1371                 if ( $to_country == 'US' ) {
    1372                     return false;
    1373                 } else {
    1374                     return true;
    1375                 }
    1376             }
    1377 
    1378             if ( preg_match( $postal_regexes[ $to_country ], $to_zip ) === 0 ) {
    1379                 $this->_log( ':::: Postal code ' . $to_zip . ' is invalid for country ' . $to_country . ', API request stopped. ::::' );
    1380                 return false;
    1381             }
    1382         }
    1383 
    1384         return true;
    1385     }
    1386 
    1387     /**
    1388      * Return either the post value or settings value of a key
    1389      *
    1390      * @return MIXED
    1391      */
    1392     public function post_or_setting( $key ) {
    1393         $val = null;
    1394 
    1395         if ( isset( $_POST[ 'woocommerce_taxjar-integration_settings' ][ $key ] ) ) {
    1396             $val = $_POST[ 'woocommerce_taxjar-integration_settings' ][ $key ];
    1397         } elseif ( isset( $this->settings[ $key ] ) ) {
    1398             $val = $this->settings[ $key ];
    1399         }
    1400 
    1401         if ( 'yes' == $val ) {
    1402             $val = 1;
    1403         }
    1404 
    1405         if ( 'no' == $val ) {
    1406             $val = 0;
    1407         }
    1408 
    1409         return $val;
    1410     }
    1411 
    1412     /**
    1413      * Check if there is an existing WooCommerce 2.4 API Key
    1414      *
    1415      * @return boolean
    1416      */
    1417     private function existing_api_key() {
    1418         global $wpdb;
    1419         $sql = "SELECT count(key_id)
    1420             FROM {$wpdb->prefix}woocommerce_api_keys
    1421             LEFT JOIN $wpdb->users
    1422             ON {$wpdb->prefix}woocommerce_api_keys.user_id={$wpdb->users}.ID
    1423             WHERE ({$wpdb->users}.user_login LIKE '%taxjar%' OR {$wpdb->prefix}woocommerce_api_keys.description LIKE '%taxjar%');";
    1424         return ( $wpdb->get_var( $sql ) > 0 );
    1425     }
    1426 
    1427     /**
    1428      * Generate Button HTML.
    1429      */
    1430     public function generate_button_html( $key, $data ) {
    1431         $field    = $this->plugin_id . $this->id . '_' . $key;
    1432         $defaults = array(
    1433             'class'             => 'button-secondary',
    1434             'css'               => '',
    1435             'custom_attributes' => array(),
    1436             'desc_tip'          => false,
    1437             'description'       => '',
    1438             'title'             => '',
    1439         );
    1440         $data = wp_parse_args( $data, $defaults );
    1441         ob_start();
    1442         ?>
     1430
     1431                if ( preg_match( $postal_regexes[ $to_country ], $to_zip ) === 0 ) {
     1432                    $this->_log( ':::: Postal code ' . $to_zip . ' is invalid for country ' . $to_country . ', API request stopped. ::::' );
     1433                    return false;
     1434                }
     1435            }
     1436
     1437            return true;
     1438        }
     1439
     1440        /**
     1441         * Return either the post value or settings value of a key
     1442         *
     1443         * @return MIXED
     1444         */
     1445        public function post_or_setting( $key ) {
     1446            $val = null;
     1447
     1448            if ( isset( $_POST['woocommerce_taxjar-integration_settings'][ $key ] ) ) {
     1449                $val = $_POST['woocommerce_taxjar-integration_settings'][ $key ];
     1450            } elseif ( isset( $this->settings[ $key ] ) ) {
     1451                $val = $this->settings[ $key ];
     1452            }
     1453
     1454            if ( 'yes' === $val ) {
     1455                $val = 1;
     1456            }
     1457
     1458            if ( 'no' === $val ) {
     1459                $val = 0;
     1460            }
     1461
     1462            return $val;
     1463        }
     1464
     1465        /**
     1466         * Generate Button HTML.
     1467         */
     1468        public function generate_button_html( $key, $data ) {
     1469            $field    = $this->plugin_id . $this->id . '_' . $key;
     1470            $defaults = array(
     1471                'class'             => 'button-secondary',
     1472                'css'               => '',
     1473                'custom_attributes' => array(),
     1474                'desc_tip'          => false,
     1475                'description'       => '',
     1476                'title'             => '',
     1477            );
     1478            $data     = wp_parse_args( $data, $defaults );
     1479            ob_start();
     1480            ?>
    14431481        <tr valign="top">
    14441482            <th scope="row" class="titledesc">
     
    14541492            </td>
    14551493        </tr>
    1456         <?php
    1457         return ob_get_clean();
     1494            <?php
     1495            return ob_get_clean();
     1496        }
     1497
     1498        /**
     1499         * Sanitize our settings
     1500         */
     1501        public function sanitize_settings( $value, $option ) {
     1502            parse_str( $option['id'], $option_name_array );
     1503            $option_name  = current( array_keys( $option_name_array ) );
     1504            $setting_name = key( $option_name_array[ $option_name ] );
     1505
     1506            if ( in_array( $setting_name, array( 'store_postcode', 'store_city', 'store_street' ), true ) ) {
     1507                return wc_clean( $value );
     1508            }
     1509
     1510            if ( 'api_token' === $setting_name ) {
     1511                return strtolower( wc_clean( $value ) );
     1512            }
     1513
     1514            if ( 'taxjar_download' === $setting_name ) {
     1515                return $this->download_orders->validate_taxjar_download_field( $setting_name );
     1516            }
     1517
     1518            return $value;
     1519        }
     1520
     1521        /**
     1522         * Gets the store's settings and returns them
     1523         *
     1524         * @return array
     1525         */
     1526        public function get_store_settings() {
     1527            $store_address  = get_option( 'woocommerce_store_address' ) ? get_option( 'woocommerce_store_address' ) : $this->settings['store_street'];
     1528            $store_city     = get_option( 'woocommerce_store_city' ) ? get_option( 'woocommerce_store_city' ) : $this->settings['store_city'];
     1529            $store_country  = explode( ':', get_option( 'woocommerce_default_country' ) );
     1530            $store_postcode = get_option( 'woocommerce_store_postcode' ) ? get_option( 'woocommerce_store_postcode' ) : $this->settings['store_postcode'];
     1531
     1532            $store_settings = array(
     1533                'street'   => $store_address,
     1534                'city'     => $store_city,
     1535                'state'    => null,
     1536                'country'  => $store_country[0],
     1537                'postcode' => $store_postcode,
     1538            );
     1539
     1540            if ( isset( $store_country[1] ) ) {
     1541                $store_settings['state'] = $store_country[1];
     1542            }
     1543
     1544            return apply_filters( 'taxjar_store_settings', $store_settings, $this->settings );
     1545        }
     1546
     1547        /**
     1548         * Gets the store's settings and returns them
     1549         *
     1550         * @return void
     1551         */
     1552        public function taxjar_admin_menu() {
     1553            // Simple shortcut menu item under WooCommerce
     1554            add_submenu_page( 'woocommerce', __( 'TaxJar Settings', 'woocommerce' ), __( 'TaxJar', 'woocommerce' ), 'manage_woocommerce', 'admin.php?page=wc-settings&tab=taxjar-integration' );
     1555        }
     1556
     1557        /**
     1558         * Gets the value for a seting from POST given a key or returns false if box not checked
     1559         *
     1560         * @param mixed $key
     1561         * @return mixed $value
     1562         */
     1563        public function get_value_from_post( $key ) {
     1564            if ( isset( $_POST[ $this->plugin_id . $this->id . '_settings' ][ $key ] ) ) {
     1565                return $_POST[ $this->plugin_id . $this->id . '_settings' ][ $key ];
     1566            } else {
     1567                return false;
     1568            }
     1569        }
     1570
     1571        /**
     1572         * Display errors by overriding the display_errors() method
     1573         * @see display_errors()
     1574         */
     1575        public function display_errors() {
     1576            $error_key_values = array(
     1577                'shop_not_linked' => 'There was an error linking this store to your TaxJar account. Please contact [email protected]',
     1578            );
     1579
     1580            foreach ( $this->errors as $key => $value ) {
     1581                $message = $error_key_values[ $value ];
     1582                echo "<div class=\"error\"><p>$message</p></div>";
     1583            }
     1584        }
     1585
     1586        /**
     1587         * Output TaxJar message above tax configuration screen
     1588         */
     1589        public function output_sections_before() {
     1590            echo '<div class="updated taxjar-notice"><p><b>Powered by <a href="https://www.taxjar.com" target="_blank">TaxJar</a></b> ― Your tax rates and settings are automatically configured below.</p><p><a href="admin.php?page=wc-settings&tab=integration&section=taxjar-integration" class="button-primary">Configure TaxJar</a> &nbsp; <a href="https://www.taxjar.com/contact/" class="button" target="_blank">Help &amp; Support</a></p></div>';
     1591        }
     1592
     1593        /**
     1594         * Checks if currently on the WooCommerce new order page
     1595         *
     1596         * @return boolean
     1597         */
     1598        public function on_order_page() {
     1599            global $pagenow;
     1600            return ( in_array( $pagenow, array( 'post-new.php' ), true ) && isset( $_GET['post_type'] ) && 'shop_order' === $_GET['post_type'] );
     1601        }
     1602
     1603        /**
     1604         * Checks if currently on the TaxJar settings page
     1605         *
     1606         * @return boolean
     1607         */
     1608        public function on_settings_page() {
     1609            return ( isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'taxjar-integration' === $_GET['tab'] );
     1610        }
     1611
     1612        /**
     1613         * Admin Assets
     1614         */
     1615        public function load_taxjar_admin_assets() {
     1616            // Add CSS that hides some elements that are known to cause problems
     1617            wp_enqueue_style( 'taxjar-admin-style', plugin_dir_url( __FILE__ ) . 'css/admin.css' );
     1618
     1619            // Load Javascript for TaxJar settings page
     1620            wp_register_script( 'wc-taxjar-admin', plugin_dir_url( __FILE__ ) . '/js/wc-taxjar-admin.js' );
     1621
     1622            wp_localize_script(
     1623                'wc-taxjar-admin',
     1624                'woocommerce_taxjar_admin',
     1625                array(
     1626                    'ajax_url'                   => admin_url( 'admin-ajax.php' ),
     1627                    'transaction_backfill_nonce' => wp_create_nonce( 'taxjar-transaction-backfill' ),
     1628                    'update_nexus_nonce'         => wp_create_nonce( 'taxjar-update-nexus' ),
     1629                    'current_user'               => get_current_user_id(),
     1630                    'integration_uri'            => $this->integration_uri,
     1631                    'connect_url'                => $this->get_connect_url(),
     1632                    'app_url'                    => untrailingslashit( $this->app_uri ),
     1633                )
     1634            );
     1635
     1636            wp_enqueue_script( 'wc-taxjar-admin', array( 'jquery' ) );
     1637
     1638            wp_enqueue_script( 'jquery-ui-datepicker' );
     1639            wp_enqueue_style( 'jquery-ui-datepicker' );
     1640        }
     1641
     1642        /**
     1643         * Generates TaxJar connect popup url
     1644         *
     1645         * @return string - TaxJar connect popup url
     1646         */
     1647        public function get_connect_url() {
     1648            $connect_url  = $this->app_uri . 'smartcalcs/connect/woo/?store=' . urlencode( get_bloginfo( 'url' ) );
     1649            $connect_url .= '&plugin=woo&version=' . WC_Taxjar::$version;
     1650            return esc_url( $connect_url );
     1651        }
     1652
     1653        /**
     1654         * Admin New Order Assets
     1655         */
     1656        public function load_taxjar_admin_new_order_assets() {
     1657            if ( ! $this->on_order_page() ) {
     1658                return;
     1659            }
     1660
     1661            // Load Javascript for WooCommerce new order page
     1662            wp_register_script( 'wc-taxjar-order', plugin_dir_url( __FILE__ ) . '/js/wc-taxjar-order.js' );
     1663            wp_enqueue_script( 'wc-taxjar-order', array( 'jquery' ) );
     1664        }
     1665
     1666        static function is_valid_exemption_type( $exemption_type ) {
     1667            $valid_types = array( 'wholesale', 'government', 'other', 'non_exempt' );
     1668            return in_array( $exemption_type, $valid_types, true );
     1669        }
     1670
     1671        /**
     1672         * Parse tax code from product
     1673         *
     1674         * @return string - tax code
     1675         */
     1676        static function get_tax_code_from_class( $tax_class ) {
     1677            $tax_class = explode( '-', $tax_class );
     1678            $tax_code  = '';
     1679
     1680            if ( isset( $tax_class ) ) {
     1681                $tax_code = end( $tax_class );
     1682            }
     1683
     1684            return strtoupper( $tax_code );
     1685        }
     1686
     1687        /**
     1688         * Create user agent header
     1689         *
     1690         * @return string - user agent header
     1691         */
     1692        static function get_ua_header() {
     1693            $curl_version = '';
     1694            if ( function_exists( 'curl_version' ) ) {
     1695                $curl_version = curl_version();
     1696                $curl_version = $curl_version['version'] . '; ' . $curl_version['ssl_version'];
     1697            }
     1698
     1699            $php_version       = phpversion();
     1700            $taxjar_version    = WC_Taxjar::$version;
     1701            $woo_version       = WC()->version;
     1702            $wordpress_version = get_bloginfo( 'version' );
     1703            $site_url          = get_bloginfo( 'url' );
     1704            $user_agent        = "TaxJar/WooCommerce (PHP $php_version; cURL $curl_version; WordPress $wordpress_version; WooCommerce $woo_version) WC_Taxjar/$taxjar_version $site_url";
     1705            return $user_agent;
     1706        }
     1707
    14581708    }
    14591709
    1460     /**
    1461      * Sanitize our settings
    1462      */
    1463     public function sanitize_settings( $value, $option ) {
    1464         parse_str( $option['id'], $option_name_array );
    1465         $option_name  = current( array_keys( $option_name_array ) );
    1466         $setting_name = key( $option_name_array[ $option_name ] );
    1467 
    1468         if ( in_array( $setting_name, array( 'store_postcode', 'store_city', 'store_street' ) ) ) {
    1469             return wc_clean( $value );
    1470         }
    1471 
    1472         if ( $setting_name == 'api_token' ) {
    1473             return strtolower( wc_clean( $value ) );
    1474         }
    1475 
    1476         if ( $setting_name == 'taxjar_download' ) {
    1477             return $value = $this->download_orders->validate_taxjar_download_field( $setting_name );
    1478         }
    1479 
    1480         return $value;
    1481     }
    1482 
    1483     /**
    1484      * Gets the store's settings and returns them
    1485      *
    1486      * @return array
    1487      */
    1488     public function get_store_settings() {
    1489         $store_address = get_option( 'woocommerce_store_address' ) ? get_option( 'woocommerce_store_address' ) : $this->settings['store_street'];
    1490         $store_city = get_option( 'woocommerce_store_city' ) ? get_option( 'woocommerce_store_city' ) : $this->settings['store_city'];
    1491         $store_country = explode( ':', get_option( 'woocommerce_default_country' ) );
    1492         $store_postcode = get_option( 'woocommerce_store_postcode' ) ? get_option( 'woocommerce_store_postcode' ) : $this->settings['store_postcode'];
    1493 
    1494         $store_settings = array(
    1495             'street' => $store_address,
    1496             'city' => $store_city,
    1497             'state' => null,
    1498             'country' => $store_country[0],
    1499             'postcode' => $store_postcode,
    1500         );
    1501 
    1502         if ( isset( $store_country[1] ) ) {
    1503             $store_settings['state'] = $store_country[1];
    1504         }
    1505 
    1506         return apply_filters( 'taxjar_store_settings', $store_settings, $this->settings );
    1507     }
    1508 
    1509     /**
    1510      * Gets the store's settings and returns them
    1511      *
    1512      * @return void
    1513      */
    1514     public function taxjar_admin_menu() {
    1515         // Simple shortcut menu item under WooCommerce
    1516         add_submenu_page( 'woocommerce', __( 'TaxJar Settings', 'woocommerce' ), __( 'TaxJar', 'woocommerce' ) , 'manage_woocommerce', 'admin.php?page=wc-settings&tab=taxjar-integration' );
    1517     }
    1518 
    1519     /**
    1520      * Gets the value for a seting from POST given a key or returns false if box not checked
    1521      *
    1522      * @param mixed $key
    1523      * @return mixed $value
    1524      */
    1525     public function get_value_from_post( $key ) {
    1526         if ( isset( $_POST[ $this->plugin_id . $this->id . '_settings' ][ $key ] ) ) {
    1527             return $_POST[ $this->plugin_id . $this->id . '_settings' ][ $key ];
    1528         } else {
    1529             return false;
    1530         }
    1531     }
    1532 
    1533     /**
    1534      * Display errors by overriding the display_errors() method
    1535      * @see display_errors()
    1536      */
    1537     public function display_errors() {
    1538         $error_key_values = array(
    1539             'shop_not_linked' => 'There was an error linking this store to your TaxJar account. Please contact [email protected]',
    1540         );
    1541 
    1542         foreach ( $this->errors as $key => $value ) {
    1543             $message = $error_key_values[ $value ];
    1544             echo "<div class=\"error\"><p>$message</p></div>";
    1545         }
    1546     }
    1547 
    1548     /**
    1549      * Output TaxJar message above tax configuration screen
    1550      */
    1551     public function output_sections_before() {
    1552         echo '<div class="updated taxjar-notice"><p><b>Powered by <a href="https://www.taxjar.com" target="_blank">TaxJar</a></b> ― Your tax rates and settings are automatically configured below.</p><p><a href="admin.php?page=wc-settings&tab=integration&section=taxjar-integration" class="button-primary">Configure TaxJar</a> &nbsp; <a href="https://www.taxjar.com/contact/" class="button" target="_blank">Help &amp; Support</a></p></div>';
    1553     }
    1554 
    1555     /**
    1556      * Checks if currently on the WooCommerce new order page
    1557      *
    1558      * @return boolean
    1559      */
    1560     public function on_order_page() {
    1561         global $pagenow;
    1562         return ( in_array( $pagenow, array( 'post-new.php' ) ) && isset( $_GET['post_type'] ) && 'shop_order' == $_GET['post_type'] );
    1563     }
    1564 
    1565     /**
    1566      * Checks if currently on the TaxJar settings page
    1567      *
    1568      * @return boolean
    1569      */
    1570     public function on_settings_page() {
    1571         return ( isset( $_GET['page'] ) && 'wc-settings' == $_GET['page'] && isset( $_GET['tab'] ) && 'taxjar-integration' == $_GET['tab'] );
    1572     }
    1573 
    1574     /**
    1575      * Admin Assets
    1576      */
    1577     public function load_taxjar_admin_assets() {
    1578         // Add CSS that hides some elements that are known to cause problems
    1579         wp_enqueue_style( 'taxjar-admin-style', plugin_dir_url( __FILE__ ) . 'css/admin.css' );
    1580 
    1581         // Load Javascript for TaxJar settings page
    1582         wp_register_script( 'wc-taxjar-admin', plugin_dir_url( __FILE__ ) . '/js/wc-taxjar-admin.js' );
    1583 
    1584         wp_localize_script(
    1585             'wc-taxjar-admin',
    1586             'woocommerce_taxjar_admin',
    1587             array(
    1588                 'ajax_url'                      => admin_url( 'admin-ajax.php' ),
    1589                 'transaction_backfill_nonce'    => wp_create_nonce( 'taxjar-transaction-backfill' ),
    1590                 'update_nexus_nonce'            => wp_create_nonce( 'taxjar-update-nexus' ),
    1591                 'current_user'                  => get_current_user_id(),
    1592                 'integration_uri'               => $this->integration_uri,
    1593                 'connect_url'                   => $this->get_connect_url(),
    1594                 'app_url'                       => untrailingslashit( $this->app_uri )
    1595             )
    1596         );
    1597 
    1598         wp_enqueue_script( 'wc-taxjar-admin' , array( 'jquery' ) );
    1599 
    1600         wp_enqueue_script( 'jquery-ui-datepicker' );
    1601         wp_enqueue_style( 'jquery-ui-datepicker' );
    1602     }
    1603 
    1604      /**
    1605       * Generates TaxJar connect popup url
    1606       *
    1607       * @return string - TaxJar connect popup url
    1608       */
    1609     public function get_connect_url() {
    1610         $connect_url = $this->app_uri . 'smartcalcs/connect/woo/?store=' . urlencode( get_bloginfo( 'url' ) );
    1611         $connect_url .= '&plugin=woo&version=' . WC_Taxjar::$version;
    1612         return esc_url( $connect_url );
    1613     }
    1614 
    1615     /**
    1616      * Admin New Order Assets
    1617      */
    1618     public function load_taxjar_admin_new_order_assets() {
    1619         if ( ! $this->on_order_page() ) {
    1620             return;
    1621         }
    1622 
    1623         // Load Javascript for WooCommerce new order page
    1624         wp_register_script( 'wc-taxjar-order', plugin_dir_url( __FILE__ ) . '/js/wc-taxjar-order.js' );
    1625         wp_enqueue_script( 'wc-taxjar-order' , array( 'jquery' ) );
    1626     }
    1627 
    1628     static function is_valid_exemption_type( $exemption_type ) {
    1629         $valid_types = array( 'wholesale', 'government', 'other', 'non_exempt' );
    1630         return in_array( $exemption_type, $valid_types );
    1631     }
    1632 
    1633     /**
    1634      * Parse tax code from product
    1635      *
    1636      * @param $product - WC_Product
    1637      * @return string - tax code
    1638      */
    1639     static function get_tax_code_from_class( $tax_class ) {
    1640         $tax_class = explode( '-', $tax_class );
    1641         $tax_code = '';
    1642 
    1643         if ( isset( $tax_class ) ) {
    1644             $tax_code = end( $tax_class );
    1645         }
    1646 
    1647         return strtoupper( $tax_code );
    1648     }
    1649 
    1650     /**
    1651      * Create user agent header
    1652      *
    1653      * @return string - user agent header
    1654      */
    1655     static function get_ua_header() {
    1656         $curl_version = '';
    1657         if ( function_exists( 'curl_version' ) ) {
    1658             $curl_version = curl_version();
    1659             $curl_version = $curl_version['version'] . '; ' . $curl_version['ssl_version'];
    1660         }
    1661 
    1662         $php_version = phpversion();
    1663         $taxjar_version = WC_Taxjar::$version;
    1664         $woo_version = WC()->version;
    1665         $wordpress_version = get_bloginfo( 'version' );
    1666         $site_url = get_bloginfo( 'url' );
    1667         $user_agent = "TaxJar/WooCommerce (PHP $php_version; cURL $curl_version; WordPress $wordpress_version; WooCommerce $woo_version) WC_Taxjar/$taxjar_version $site_url";
    1668         return $user_agent;
    1669     }
    1670 
    1671 }
    1672 
    16731710endif;
  • taxjar-simplified-taxes-for-woocommerce/trunk/readme.txt

    r2389903 r2440303  
    33Tags: woocommerce, taxjar, tax, taxes, sales tax, tax calculation, sales tax compliance, sales tax filing
    44Requires at least: 4.2
    5 Tested up to: 5.5.1
    6 Stable tag: 3.2.3
     5Tested up to: 5.6.0
     6Stable tag: 3.2.4
    77License: GPLv2 or later
    88URI: http://www.gnu.org/licenses/gpl-2.0.html
    9 WC requires at least: 3.2.0
    10 WC tested up to: 4.5.2
     9WC requires at least: 3.6.0
     10WC tested up to: 4.8.0
    1111
    1212Trusted by more than 20,000 businesses, TaxJar’s award-winning solution makes it easy to automate sales tax reporting and filing, and determine economic nexus with a single click.
     
    9696
    9797== Changelog ==
     98
     99= 3.2.4 (2020-12-15)
     100* Fix occasional missing PTC from subscription orders
     101* Fix issues that prevented exempt customers from syncing to TaxJar
     102* WooCommerce 4.8.0 and WordPress 5.6 support
    98103
    99104= 3.2.3 (2020-09-28)
  • taxjar-simplified-taxes-for-woocommerce/trunk/taxjar-woocommerce.php

    r2389903 r2440303  
    44 * Plugin URI: https://www.taxjar.com/woocommerce-sales-tax-plugin/
    55 * Description: Save hours every month by putting your sales tax on autopilot. Automated, multi-state sales tax calculation, collection, and filing.
    6  * Version: 3.2.3
     6 * Version: 3.2.4
    77 * Author: TaxJar
    88 * Author URI: https://www.taxjar.com
    9  * WC requires at least: 3.2.0
    10  * WC tested up to: 4.5.2
     9 * WC requires at least: 3.6.0
     10 * WC tested up to: 4.8.0
    1111 *
    1212 * Copyright: © 2014-2019 TaxJar. TaxJar is a trademark of TPS Unlimited, Inc.
     
    4343final class WC_Taxjar {
    4444
    45     static $version = '3.2.3';
    46     public static $minimum_woocommerce_version = '3.2.0';
     45    static $version = '3.2.4';
     46    public static $minimum_woocommerce_version = '3.6.0';
    4747
    4848    /**
Note: See TracChangeset for help on using the changeset viewer.