Changeset 2440303
- Timestamp:
- 12/16/2020 02:11:40 AM (5 years ago)
- Location:
- taxjar-simplified-taxes-for-woocommerce/trunk
- Files:
-
- 6 edited
-
CHANGELOG.md (modified) (1 diff)
-
includes/class-taxjar-customer-record.php (modified) (14 diffs)
-
includes/class-wc-taxjar-customer-sync.php (modified) (8 diffs)
-
includes/class-wc-taxjar-integration.php (modified) (3 diffs)
-
readme.txt (modified) (2 diffs)
-
taxjar-woocommerce.php (modified) (2 diffs)
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 1 6 # 3.2.3 (2020-09-28) 2 7 * Add filter to nexus check -
taxjar-simplified-taxes-for-woocommerce/trunk/includes/class-taxjar-customer-record.php
r2147335 r2440303 15 15 } else { 16 16 try { 17 $customer = new WC_Customer( $this->get_record_id() );17 $customer = new WC_Customer( $this->get_record_id() ); 18 18 if ( $customer instanceof WC_Customer ) { 19 19 $this->object = $customer; … … 50 50 51 51 $data = $this->get_data(); 52 if ( empty( $data[ 'customer_id'] ) ) {52 if ( empty( $data['customer_id'] ) ) { 53 53 $this->add_error( __( 'Customer failed validation, customer missing required field: customer_id.', 'wc-taxjar' ) ); 54 54 return false; 55 55 } 56 56 57 if ( empty( $data[ 'exemption_type'] ) ) {57 if ( empty( $data['exemption_type'] ) ) { 58 58 $this->add_error( __( 'Customer failed validation, customer missing required field: exemption_type.', 'wc-taxjar' ) ); 59 59 return false; 60 60 } 61 61 62 if ( empty( $data[ 'name'] ) ) {62 if ( empty( $data['name'] ) ) { 63 63 $this->add_error( __( 'Customer failed validation, customer missing required field: name.', 'wc-taxjar' ) ); 64 64 return false; … … 89 89 public function create_in_taxjar() { 90 90 $data = $this->get_data(); 91 $url = self::API_URI . 'customers';91 $url = self::API_URI . 'customers'; 92 92 $body = wp_json_encode( $data ); 93 93 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 ); 102 105 103 106 $this->set_last_request( $body ); … … 109 112 * @return array|WP_Error - API response or WP_Error if request fails 110 113 */ 111 public function update_in_taxjar() {114 public function update_in_taxjar() { 112 115 $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; 116 119 $body = wp_json_encode( $data ); 117 120 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 ); 127 133 128 134 $this->set_last_request( $body ); … … 134 140 * @return array|WP_Error - API response or WP_Error if request fails 135 141 */ 136 public function delete_in_taxjar() {142 public function delete_in_taxjar() { 137 143 $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( 140 146 'customer_id' => $customer_id, 141 147 ); 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 ); 153 162 154 163 $this->set_last_request( $body ); … … 162 171 public function get_from_taxjar() { 163 172 $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 ); 174 186 175 187 $this->set_last_request( $customer_id ); … … 184 196 $customer_data = array(); 185 197 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(); 218 202 219 203 $country = $this->object->get_shipping_country(); … … 222 206 } 223 207 if ( ! empty( $country ) ) { 224 $customer_data[ 'country'] = $country;208 $customer_data['country'] = $country; 225 209 } 226 210 … … 230 214 } 231 215 if ( ! empty( $state ) ) { 232 $customer_data[ 'state'] = $state;216 $customer_data['state'] = $state; 233 217 } 234 218 … … 238 222 } 239 223 if ( ! empty( $postcode ) ) { 240 $customer_data[ 'zip'] = $postcode;224 $customer_data['zip'] = $postcode; 241 225 } 242 226 … … 246 230 } 247 231 if ( ! empty( $city ) ) { 248 $customer_data[ 'city'] = $city;232 $customer_data['city'] = $city; 249 233 } 250 234 … … 254 238 } 255 239 if ( ! empty( $address ) ) { 256 $customer_data[ 'street'] = $address;240 $customer_data['street'] = $address; 257 241 } 258 242 259 243 $customer_data = apply_filters( 'taxjar_customer_sync_data', $customer_data, $this->object ); 260 $this->data = $customer_data;244 $this->data = $customer_data; 261 245 return $customer_data; 262 246 } 263 247 264 248 /** 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 * 265 279 * @return int|string 266 280 */ … … 269 283 } 270 284 285 /** 286 * Gets the exemption type saved on the user 287 * 288 * @return mixed|string - exemption type 289 */ 271 290 public function get_exemption_type() { 272 $valid_types = array( 'wholesale', 'government', 'other', 'non_exempt' );291 $valid_types = array( 'wholesale', 'government', 'other', 'non_exempt' ); 273 292 $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 ) ) { 275 294 $exemption_type = 'non_exempt'; 276 295 } … … 278 297 } 279 298 299 /** 300 * Get the exempt regions saved on the user 301 * 302 * @return array - array of exemption regions 303 */ 280 304 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(); 282 306 $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(); 287 311 288 312 if ( ! empty( $intersect ) ) { 289 foreach ( $intersect as $region ) {313 foreach ( $intersect as $region ) { 290 314 $exempt_regions[] = array( 291 315 'country' => 'US', 292 'state' => $region 316 'state' => $region, 293 317 ); 294 318 } -
taxjar-simplified-taxes-for-woocommerce/trunk/includes/class-wc-taxjar-customer-sync.php
r2147335 r2440303 26 26 */ 27 27 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 } 39 39 } 40 40 … … 65 65 public function get_customer_meta_fields() { 66 66 $show_fields = apply_filters( 67 'taxjar_customer_meta_fields', array( 68 'exemptions' => array( 67 'taxjar_customer_meta_fields', 68 array( 69 'exemptions' => array( 69 70 'title' => __( 'TaxJar Sales Tax Exemptions', 'wc-taxjar' ), 70 71 'fields' => array( 71 'tax_exemption_type' => array(72 'tax_exemption_type' => array( 72 73 'label' => __( 'Exemption Type', 'wc-taxjar' ), 73 74 'description' => __( 'All customers are presumed non-exempt unless otherwise selected.', 'wc-taxjar' ), … … 90 91 } 91 92 93 /** 94 * Adds customer exemption fields to edit user page 95 * 96 * @param $user 97 */ 92 98 public function add_customer_meta_fields( $user ) { 93 99 if ( ! apply_filters( 'taxjar_current_user_can_edit_customer_meta_fields', current_user_can( 'manage_woocommerce' ), $user->ID ) ) { … … 121 127 $saved_value = esc_attr( get_user_meta( $user->ID, $key, true ) ); 122 128 if ( ! empty( $saved_value ) ) { 123 $saved_value = explode( ',', $saved_value );124 }129 $saved_value = explode( ',', $saved_value ); 130 } 125 131 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 } 131 137 ?> 132 138 <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $selected, $option_key, true ); ?>><?php echo esc_attr( $option_value ); ?></option> … … 143 149 <?php endforeach; ?> 144 150 </table> 145 <?php151 <?php 146 152 endforeach; 147 153 } 148 154 155 /** 156 * Saves tax exemption user meta if necessary 157 * 158 * @param $user_id - Id of user to update 159 */ 149 160 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 ) ) { 151 163 return; 152 164 } 153 165 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 */ 187 255 public function maybe_sync_customer_on_update( $user_id ) { 188 256 $record = TaxJar_Customer_Record::find_active_in_queue( $user_id ); … … 199 267 200 268 $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 */ 203 276 public static function get_customer_exemption_types() { 204 277 return array( 205 'wholesale' => __( 'Wholesale / Resale', 'wc-taxjar' ),278 'wholesale' => __( 'Wholesale / Resale', 'wc-taxjar' ), 206 279 'government' => __( 'Government', 'wc-taxjar' ), 207 'other' => __( 'Other', 'wc-taxjar' ),280 'other' => __( 'Other', 'wc-taxjar' ), 208 281 ); 209 282 } 210 283 284 /** 285 * Creates array of all valid exempt regions (states) 286 * 287 * @return array - available exempt regions 288 */ 211 289 public static function get_all_exempt_regions() { 212 290 return array( … … 265 343 266 344 /** 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 * 268 347 * @param $id - user id 269 348 */ 270 349 public function maybe_delete_customer( $id ) { 271 350 $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 ); 273 352 if ( $last_sync || $hash ) { 274 $record = TaxJar_Customer_Record::find_active_in_queue( $id );353 $record = TaxJar_Customer_Record::find_active_in_queue( $id ); 275 354 if ( ! $record ) { 276 355 $record = new TaxJar_Customer_Record( $id, true ); … … 279 358 $record->delete_in_taxjar(); 280 359 $record->delete(); 281 }282 }360 } 361 } 283 362 284 363 } -
taxjar-simplified-taxes-for-woocommerce/trunk/includes/class-wc-taxjar-integration.php
r2374160 r2440303 10 10 if ( ! class_exists( 'WC_Taxjar_Integration' ) ) : 11 11 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 . '§ion=' . 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 70 206 } 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 . '§ion=' . 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( 283 336 '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 366 460 $settings[] = array( 367 'type' => ' sectionend',461 'type' => 'title', 368 462 ); 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(); 372 464 $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]', 376 470 ); 377 471 $settings[] = array( 378 472 'type' => 'sectionend', 379 473 ); 380 } else {381 474 $settings[] = array( 382 475 '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">★★★★★</a>. Thank you!', 'wc-taxjar' ), 421 477 ); 422 478 $settings[] = array( … … 424 480 ); 425 481 } 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'], 429 704 ); 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'] 437 736 ); 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 ) 440 814 ); 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">★★★★★</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; 489 821 } 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 ); 652 866 } 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 ) 668 893 ); 669 894 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']; 681 908 } 682 909 } 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 ) 700 993 ); 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(); 895 1010 } 896 1011 } 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 957 1082 foreach ( $order->get_items() as $item_key => $item ) { 958 $product_id = $item->get_product_id();1083 $product_id = $item->get_product_id(); 959 1084 $line_item_key = $product_id . '-' . $item_key; 960 1085 961 1086 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 ]; 963 1088 $item_tax = new WC_Order_Item_Tax(); 964 1089 $item_tax->set_rate( $rate_id ); 965 $item_tax->set_order_id( $order _id);1090 $item_tax->set_order_id( $order->get_id() ); 966 1091 $item_tax->save(); 967 1092 } 968 1093 } 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 ) { 1215 1163 $line_items = array(); 1216 1164 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; 1234 1426 } else { 1235 $taxes[ $tax_rate_id ] = $line_item->tax_collectable;1427 return true; 1236 1428 } 1237 1429 } 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 ?> 1443 1481 <tr valign="top"> 1444 1482 <th scope="row" class="titledesc"> … … 1454 1492 </td> 1455 1493 </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§ion=taxjar-integration" class="button-primary">Configure TaxJar</a> <a href="https://www.taxjar.com/contact/" class="button" target="_blank">Help & 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 1458 1708 } 1459 1709 1460 /**1461 * Sanitize our settings1462 */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 them1485 *1486 * @return array1487 */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 them1511 *1512 * @return void1513 */1514 public function taxjar_admin_menu() {1515 // Simple shortcut menu item under WooCommerce1516 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 checked1521 *1522 * @param mixed $key1523 * @return mixed $value1524 */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() method1535 * @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 screen1550 */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§ion=taxjar-integration" class="button-primary">Configure TaxJar</a> <a href="https://www.taxjar.com/contact/" class="button" target="_blank">Help & Support</a></p></div>';1553 }1554 1555 /**1556 * Checks if currently on the WooCommerce new order page1557 *1558 * @return boolean1559 */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 page1567 *1568 * @return boolean1569 */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 Assets1576 */1577 public function load_taxjar_admin_assets() {1578 // Add CSS that hides some elements that are known to cause problems1579 wp_enqueue_style( 'taxjar-admin-style', plugin_dir_url( __FILE__ ) . 'css/admin.css' );1580 1581 // Load Javascript for TaxJar settings page1582 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 url1606 *1607 * @return string - TaxJar connect popup url1608 */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 Assets1617 */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 page1624 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 product1635 *1636 * @param $product - WC_Product1637 * @return string - tax code1638 */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 header1652 *1653 * @return string - user agent header1654 */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 1673 1710 endif; -
taxjar-simplified-taxes-for-woocommerce/trunk/readme.txt
r2389903 r2440303 3 3 Tags: woocommerce, taxjar, tax, taxes, sales tax, tax calculation, sales tax compliance, sales tax filing 4 4 Requires at least: 4.2 5 Tested up to: 5. 5.16 Stable tag: 3.2. 35 Tested up to: 5.6.0 6 Stable tag: 3.2.4 7 7 License: GPLv2 or later 8 8 URI: http://www.gnu.org/licenses/gpl-2.0.html 9 WC requires at least: 3. 2.010 WC tested up to: 4. 5.29 WC requires at least: 3.6.0 10 WC tested up to: 4.8.0 11 11 12 12 Trusted 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. … … 96 96 97 97 == 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 98 103 99 104 = 3.2.3 (2020-09-28) -
taxjar-simplified-taxes-for-woocommerce/trunk/taxjar-woocommerce.php
r2389903 r2440303 4 4 * Plugin URI: https://www.taxjar.com/woocommerce-sales-tax-plugin/ 5 5 * 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. 36 * Version: 3.2.4 7 7 * Author: TaxJar 8 8 * Author URI: https://www.taxjar.com 9 * WC requires at least: 3. 2.010 * WC tested up to: 4. 5.29 * WC requires at least: 3.6.0 10 * WC tested up to: 4.8.0 11 11 * 12 12 * Copyright: © 2014-2019 TaxJar. TaxJar is a trademark of TPS Unlimited, Inc. … … 43 43 final class WC_Taxjar { 44 44 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'; 47 47 48 48 /**
Note: See TracChangeset
for help on using the changeset viewer.