Files
old-experiments/backend/wordpress/wp-content/plugins/woocommerce-services/classes/class-wc-connect-taxjar-integration.php
2018-06-20 18:28:39 +02:00

905 lines
28 KiB
PHP

<?php
class WC_Connect_TaxJar_Integration {
/**
* @var WC_Connect_API_Client
*/
public $api_client;
/**
* @var WC_Connect_Logger
*/
public $logger;
private $expected_options = array(
// If automated taxes are enabled and user disables taxes we re-enable them
'woocommerce_calc_taxes' => 'yes',
// Users can set either billing or shipping address for tax rates but not shop
'woocommerce_tax_based_on' => 'shipping',
// Rate calculations assume tax not included
'woocommerce_prices_include_tax' => 'no',
// Use no special handling on shipping taxes, our API handles that
'woocommerce_shipping_tax_class' => '',
// API handles rounding precision
'woocommerce_tax_round_at_subtotal' => 'no',
// Rates are calculated in the cart assuming tax not included
'woocommerce_tax_display_shop' => 'excl',
// TaxJar returns one total amount, not line item amounts
'woocommerce_tax_display_cart' => 'excl',
// TaxJar returns one total amount, not line item amounts
'woocommerce_tax_total_display' => 'single',
);
const PROXY_PATH = 'taxjar/v2';
const OPTION_NAME = 'wc_connect_taxes_enabled';
const SETUP_WIZARD_OPTION_NAME = 'woocommerce_setup_automated_taxes';
public function __construct(
WC_Connect_API_Client $api_client,
WC_Connect_Logger $logger
) {
$this->api_client = $api_client;
$this->logger = $logger;
}
public function init() {
// Only enable WCS TaxJar integration if the official TaxJar plugin isn't active.
if ( class_exists( 'WC_Taxjar' ) ) {
return;
}
$store_settings = $this->get_store_settings();
$store_country = $store_settings['store_country_setting'];
// TaxJar supports USA, Canada, Australia, and the European Union
if ( ! $this->is_supported_country( $store_country ) ) {
return;
}
// Add toggle for automated taxes to the core settings page
add_filter( 'woocommerce_tax_settings', array( $this, 'add_tax_settings' ) );
// Settings values filter to handle the hardcoded settings
add_filter( 'woocommerce_admin_settings_sanitize_option', array( $this, 'sanitize_tax_option' ), 10, 2 );
// Settings Page
add_action( 'woocommerce_sections_tax', array( $this, 'output_sections_before' ), 9 );
add_action( 'woocommerce_sections_tax', array( $this, 'output_sections_after' ), 11 );
// Bow out if we're not wanted
if ( ! $this->is_enabled() ) {
return;
}
$this->configure_tax_settings();
// Calculate Taxes at Cart / Checkout
if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
add_action( 'woocommerce_after_calculate_totals', array( $this, 'maybe_calculate_totals' ), 20 );
} else {
add_action( 'woocommerce_calculate_totals', array( $this, 'maybe_calculate_totals' ), 20 );
}
// Calculate Taxes for Backend Orders (Woo 2.6+)
add_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
// Set customer taxable location for local pickup
add_filter( 'woocommerce_customer_taxable_address', array( $this, 'append_base_address_to_customer_taxable_address' ), 10, 1 );
}
/**
* Are automated taxes enabled?
*
* @return bool
*/
public function is_enabled() {
// Migrate automated taxes selection from the setup wizard
if ( get_option( self::SETUP_WIZARD_OPTION_NAME ) ) {
update_option( self::OPTION_NAME, 'yes' );
delete_option( self::SETUP_WIZARD_OPTION_NAME );
return true;
}
return ( wc_tax_enabled() && 'yes' === get_option( self::OPTION_NAME ) );
}
/**
* Add our "automated taxes" setting to the core group.
*
* @param array $tax_settings WooCommerce Tax Settings
*
* @return array
*/
public function add_tax_settings( $tax_settings ) {
$enabled = $this->is_enabled();
$automated_taxes = array(
'title' => __( 'Automated taxes', 'woocommerce-services' ),
'id' => self::OPTION_NAME, // TODO: save in `wc_connect_options`?
'desc_tip' => __( 'Automate your sales tax calculations with WooCommerce Services, powered by Jetpack.', 'woocommerce-services' ),
'desc' => $enabled ? '<p>' . __( 'Powered by WooCommerce Services ― Your tax rates and settings are automatically configured.', 'woocommerce-services' ) . '</p>' : '',
'default' => 'no',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array(
'no' => __( 'Disable automated taxes', 'woocommerce-services' ),
'yes' => __( 'Enable automated taxes', 'woocommerce-services' ),
),
);
// Insert the "automated taxes" setting at the top (under the section title)
array_splice( $tax_settings, 1, 0, array( $automated_taxes ) );
if ( $enabled ) {
// If the automated taxes are enabled, disable the settings that would be reverted in the original plugin
foreach ( $tax_settings as $index => $tax_setting ) {
if ( ! array_key_exists( $tax_setting['id'], $this->expected_options ) ) {
continue;
}
$tax_settings[$index]['custom_attributes'] = array( 'disabled' => true );
}
}
return $tax_settings;
}
/**
* When automated taxes are enabled, overwrite core tax settings that might break the API integration
* This is similar to the original plugin functionality where these options were reverted on page load
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L66-L91
*
* @param mixed $value - option value
* @param array $option - option metadata
* @return string new option value, based on the automated taxes state or $value
*/
public function sanitize_tax_option( $value, $option ) {
if (
//skip unrecognized option format
! is_array( $option )
//skip if unexpected option format
|| ! isset( $option['id'] )
//skip if not enabled or not being enabled in the current request
|| ! $this->is_enabled() && ( ! isset( $_POST[self::OPTION_NAME] ) || 'yes' != $_POST[self::OPTION_NAME] ) ) {
return $value;
}
//the option is currently being enabled - backup the rates and flush the rates table
if ( ! $this->is_enabled() && self::OPTION_NAME === $option['id'] && 'yes' === $value ) {
$this->backup_existing_tax_rates();
return $value;
}
//skip if unexpected option
if ( ! array_key_exists( $option['id'], $this->expected_options ) ) {
return $value;
}
return $this->expected_options[ $option['id'] ];
}
/**
* Overwrite WooCommerce core tax settings if they are different than expected
*
* Ported from TaxJar's plugin and modified to support $this->expected_options
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L66-L91
*/
public function configure_tax_settings() {
foreach( $this->expected_options as $option => $value ) {
//first check the option value - with default memory caching this should help to avoid unnecessary DB operations
if ( get_option( $option ) !== $value ) {
update_option( $option, $value );
}
}
}
/**
* Hack to hide the tax sections for additional tax class rate tables.
*/
public function output_sections_before() {
if ( ! $this->is_enabled() ) {
return;
}
?>
<div style="display: none">
<?php
}
/**
* Hack to hide the tax sections for additional tax class rate tables.
*/
public function output_sections_after() {
if ( ! $this->is_enabled() ) {
return;
}
?></div><?php
}
/**
* TaxJar supports USA, Canada, Australia, and the European Union
* See: https://developers.taxjar.com/api/reference/#countries
*
* @return array Countries supported by TaxJar.
*/
public function get_supported_countries() {
$supported_countries = array_merge(
array(
'US',
'CA',
'AU',
),
WC()->countries->get_european_union_countries()
);
return $supported_countries;
}
/**
* Check if a given country is supported by TaxJar.
*
* @param $country Two character country code.
*
* @return bool Whether or not the country is supported by TaxJar.
*/
public function is_supported_country( $country ) {
return in_array( $country, $this->get_supported_countries() );
}
/**
* Gets the store's location settings.
*
* Modified version of TaxJar's plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L796
*
* @return array
*/
public function get_store_settings() {
$store_settings = array(
'taxjar_zip_code_setting' => WC()->countries->get_base_postcode(),
'store_state_setting' => WC()->countries->get_base_state(),
'store_country_setting' => WC()->countries->get_base_country(),
'taxjar_city_setting' => WC()->countries->get_base_city(),
);
return $store_settings;
}
/**
* @param $message
*/
public function _log( $message ) {
$formatted_message = is_scalar( $message ) ? $message : json_encode( $message );
$this->logger->log( $formatted_message, 'WCS Tax' );
}
/**
* @param $message
*/
public function _error( $message ) {
$formatted_message = is_scalar( $message ) ? $message : json_encode( $message );
$this->logger->error( $formatted_message, 'WCS Tax' );
}
/**
* Wrapper to avoid calling calculate_totals() for admin carts.
*
* @param $wc_cart_object
*/
public function maybe_calculate_totals( $wc_cart_object ) {
if ( ! WC_Connect_Functions::should_send_cart_api_request() ) {
return;
}
$this->calculate_totals( $wc_cart_object );
}
/**
* Calculate tax / totals using TaxJar at checkout
*
* Unchanged from the TaxJar plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L475
*
* @return void
*/
public function calculate_totals( $wc_cart_object ) {
global $woocommerce;
// Skip calculations for WC Subscription recurring totals, tax rate already available
if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
if ( 'recurring_total' == WC_Subscriptions_Cart::get_calculation_type() ) {
return;
}
}
// Get all of the required customer params
$taxable_address = $woocommerce->customer->get_taxable_address(); // returns unassociated array
$taxable_address = is_array( $taxable_address ) ? $taxable_address : array();
$to_country = isset( $taxable_address[0] ) && ! empty( $taxable_address[0] ) ? $taxable_address[0] : false;
$to_state = isset( $taxable_address[1] ) && ! empty( $taxable_address[1] ) ? $taxable_address[1] : false;
$to_zip = isset( $taxable_address[2] ) && ! empty( $taxable_address[2] ) ? $taxable_address[2] : false;
$to_city = isset( $taxable_address[3] ) && ! empty( $taxable_address[3] ) ? $taxable_address[3] : false;
$line_items = array();
$cart_taxes = array();
foreach ( $wc_cart_object->coupons as $coupon ) {
if ( method_exists( $coupon, 'get_id' ) ) { // Woo 3.0+
$limit_usage_qty = get_post_meta( $coupon->get_id(), 'limit_usage_to_x_items', true );
if ( $limit_usage_qty ) {
$coupon->set_limit_usage_to_x_items( $limit_usage_qty );
}
}
}
foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$id = $product->get_id();
$quantity = $cart_item['quantity'];
$unit_price = wc_format_decimal( $product->get_price() );
$line_subtotal = wc_format_decimal( $cart_item['line_subtotal'] );
$discount = wc_format_decimal( $cart_item['line_subtotal'] - $cart_item['line_total'] );
$tax_class = explode( '-', $product->get_tax_class() );
$tax_code = '';
if ( ! $product->is_taxable() ) {
$tax_code = '99999';
}
if ( isset( $tax_class ) && is_numeric( end( $tax_class ) ) ) {
$tax_code = end( $tax_class );
}
// Get WC Subscription sign-up fees for calculations
if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
if ( 'none' == WC_Subscriptions_Cart::get_calculation_type() ) {
if ( class_exists( 'WC_Subscriptions_Synchroniser' ) ) {
WC_Subscriptions_Synchroniser::maybe_set_free_trial();
}
$unit_price = WC_Subscriptions_Cart::set_subscription_prices_for_calculation( $unit_price, $product );
}
}
if ( $unit_price && $line_subtotal ) {
array_push($line_items, array(
'id' => $id . '-' . $cart_item_key,
'quantity' => $quantity,
'product_tax_code' => $tax_code,
'unit_price' => $unit_price,
'discount' => $discount,
));
}
}
$this->calculate_tax( array(
'to_city' => $to_city,
'to_state' => $to_state,
'to_country' => $to_country,
'to_zip' => $to_zip,
'shipping_amount' => $woocommerce->shipping->shipping_total,
'line_items' => $line_items,
) );
if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
do_action( 'woocommerce_cart_reset', $wc_cart_object, false );
do_action( 'woocommerce_before_calculate_totals', $wc_cart_object );
new WC_Cart_Totals( $wc_cart_object );
}
foreach ( $this->line_items as $line_item_key => $line_item ) {
if ( isset( $cart_taxes[ $this->rate_ids[ $line_item_key ] ] ) ) {
$cart_taxes[ $this->rate_ids[ $line_item_key ] ] += $line_item->tax_collectable;
} else {
$cart_taxes[ $this->rate_ids[ $line_item_key ] ] = $line_item->tax_collectable;
}
}
// Store the rate ID and the amount on the cart's totals
$wc_cart_object->tax_total = $this->item_collectable;
$wc_cart_object->shipping_tax_total = $this->shipping_collectable;
$wc_cart_object->taxes = $cart_taxes;
if ( isset( $this->rate_ids['shipping'] ) ) {
$wc_cart_object->shipping_taxes = array(
$this->rate_ids['shipping'] => $this->shipping_collectable,
);
}
foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$line_item_key = $product->get_id() . '-' . $cart_item_key;
if ( isset( $this->line_items[ $line_item_key ] ) ) {
$wc_cart_object->cart_contents[ $cart_item_key ]['line_tax'] = $this->line_items[ $line_item_key ]->tax_collectable;
}
}
}
/**
* Calculate tax / totals using TaxJar for backend orders
*
* Unchanged from the TaxJar plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L569
*
* @return void
*/
public function calculate_backend_totals( $order_id ) {
$order = wc_get_order( $order_id );
$to_country = isset( $_POST['country'] ) ? strtoupper( wc_clean( $_POST['country'] ) ) : false;
$to_state = isset( $_POST['state'] ) ? strtoupper( wc_clean( $_POST['state'] ) ) : false;
$to_zip = isset( $_POST['postcode'] ) ? strtoupper( wc_clean( $_POST['postcode'] ) ) : false;
$to_city = isset( $_POST['city'] ) ? strtoupper( wc_clean( $_POST['city'] ) ) : false;
$line_items = array();
if ( method_exists( $order, 'get_shipping_total' ) ) {
$shipping = $order->get_shipping_total(); // Woo 3.0+
} else {
$shipping = $order->get_total_shipping(); // Woo 2.6
}
foreach ( $order->get_items() as $item_key => $item ) {
if ( is_object( $item ) ) { // Woo 3.0+
$id = $item->get_product_id();
$quantity = $item->get_quantity();
$discount = wc_format_decimal( $item->get_subtotal() - $item->get_total() );
$tax_class = explode( '-', $item->get_tax_class() );
} else { // Woo 2.6
$id = $item['product_id'];
$quantity = $item['qty'];
$discount = wc_format_decimal( $item['line_subtotal'] - $item['line_total'] );
$tax_class = explode( '-', $item['tax_class'] );
}
$product = wc_get_product( $id );
$unit_price = $product->get_price();
$tax_code = '';
if ( ! $product->is_taxable() ) {
$tax_code = '99999';
}
if ( isset( $tax_class[1] ) && is_numeric( $tax_class[1] ) ) {
$tax_code = $tax_class[1];
}
if ( $unit_price ) {
array_push($line_items, array(
'id' => $id . '-' . $item_key,
'quantity' => $quantity,
'product_tax_code' => $tax_code,
'unit_price' => $unit_price,
'discount' => $discount,
));
}
}
$this->calculate_tax( array(
'to_city' => $to_city,
'to_state' => $to_state,
'to_country' => $to_country,
'to_zip' => $to_zip,
'shipping_amount' => $shipping,
'line_items' => $line_items,
) );
// Add tax rates manually for Woo 3.0+
// Woo 2.6 adds the rates automatically
foreach ( $order->get_items() as $item_key => $item ) {
if ( is_object( $item ) ) { // Woo 3.0+
$product_id = $item->get_product_id();
}
$line_item_key = $product_id . '-' . $item_key;
if ( isset( $this->rate_ids[ $line_item_key ] ) ) {
$rate_id = $this->rate_ids[ $line_item_key ];
if ( class_exists( 'WC_Order_Item_Tax' ) ) { // Woo 3.0+
$item_tax = new WC_Order_Item_Tax();
$item_tax->set_rate( $rate_id );
$item_tax->set_order_id( $order_id );
$item_tax->save();
}
}
}
}
/**
* Set customer zip code and state to store if local shipping option set
*
* Unchanged from the TaxJar plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c587/includes/class-wc-taxjar-integration.php#L653
*
* @return array
*/
public function append_base_address_to_customer_taxable_address( $address ) {
$store_settings = $this->get_store_settings();
$tax_based_on = '';
list( $country, $state, $postcode, $city ) = $address;
// See WC_Customer get_taxable_address()
// wc_get_chosen_shipping_method_ids() available since Woo 2.6.2+
if ( function_exists( 'wc_get_chosen_shipping_method_ids' ) ) {
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 ) {
$tax_based_on = 'base';
}
} else {
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 ) {
$tax_based_on = 'base';
}
}
if ( 'base' == $tax_based_on ) {
$postcode = $store_settings['taxjar_zip_code_setting'];
$city = strtoupper( $store_settings['taxjar_city_setting'] );
}
return array( $country, $state, $postcode, $city );
}
/**
* Calculate sales tax using SmartCalcs
*
* Direct from the TaxJar plugin, without Nexus check.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L256
*
*
* @return void
*/
public function calculate_tax( $options = array() ) {
global $woocommerce;
$this->_log( ':::: TaxJar Plugin requested ::::' );
// Process $options array and turn them into variables
$options = is_array( $options ) ? $options : array();
extract( array_replace_recursive(array(
'to_country' => null,
'to_state' => null,
'to_zip' => null,
'to_city' => null,
'shipping_amount' => null, // $woocommerce->shipping->shipping_total
'line_items' => null
), $options) );
// Initalize some variables & properties
$store_settings = $this->get_store_settings();
$customer = $woocommerce->customer;
$this->tax_rate = 0;
$this->amount_to_collect = 0;
$this->item_collectable = 0;
$this->shipping_collectable = 0;
$this->freight_taxable = 1;
$this->line_items = array();
$this->has_nexus = 0;
$this->rate_ids = array();
// Strict conditions to be met before API call can be conducted
if ( empty( $to_country ) || empty( $to_zip ) || $customer->is_vat_exempt() ) {
return false;
}
// Setup Vars for API call
$to_zip = explode( ',' , $to_zip );
$to_zip = array_shift( $to_zip );
$from_country = $store_settings['store_country_setting'];
$from_state = $store_settings['store_state_setting'];
$from_zip = $store_settings['taxjar_zip_code_setting'];
$from_city = $store_settings['taxjar_city_setting'];
$shipping_amount = is_null( $shipping_amount ) ? 0.0 : $shipping_amount;
$this->_log( ':::: TaxJar API called ::::' );
$body = array(
'from_country' => $from_country,
'from_state' => $from_state,
'from_city' => $from_city,
'from_zip' => $from_zip,
'to_country' => $to_country,
'to_state' => $to_state,
'to_city' => $to_city,
'to_zip' => $to_zip,
'shipping' => $shipping_amount,
'line_items' => $line_items,
'plugin' => 'woo',
);
$response = $this->smartcalcs_cache_request( wp_json_encode( $body ) );
if ( isset( $response ) ) {
// Log the response
$this->_log( 'Received: ' . $response['body'] );
// Decode Response
$taxjar_response = json_decode( $response['body'] );
$taxjar_response = $taxjar_response->tax;
// Update Properties based on Response
$this->has_nexus = (int) $taxjar_response->has_nexus;
$this->amount_to_collect = $taxjar_response->amount_to_collect;
$this->tax_rate = $taxjar_response->rate;
$this->freight_taxable = (int) $taxjar_response->freight_taxable;
if ( ! empty( $taxjar_response->breakdown ) ) {
if ( ! empty( $taxjar_response->breakdown->shipping ) ) {
$this->shipping_collectable = $taxjar_response->breakdown->shipping->tax_collectable;
}
if ( ! empty( $taxjar_response->breakdown->line_items ) ) {
$line_items = array();
foreach ( $taxjar_response->breakdown->line_items as $line_item ) {
$line_items[ $line_item->id ] = $line_item;
}
$this->line_items = $line_items;
}
}
$this->item_collectable = $this->amount_to_collect - $this->shipping_collectable;
}
// Remove taxes if they are set somehow and customer is exempt
if ( $customer->is_vat_exempt() ) {
$wc_cart_object->remove_taxes();
} elseif ( $this->has_nexus ) {
// Use Woo core to find matching rates for taxable address
$location = array(
'to_country' => $to_country,
'to_state' => $to_state,
'to_zip' => $to_zip,
'to_city' => $to_city,
);
// Add line item tax rates
foreach ( $this->line_items as $line_item_key => $line_item ) {
$line_item_key_split = explode( '-', $line_item_key );
$product_id = $line_item_key_split[0];
$product = wc_get_product( $product_id );
$tax_class = $product->get_tax_class();
$this->create_or_update_tax_rate( $line_item_key, $location, $line_item->combined_tax_rate * 100, $tax_class );
}
// Add shipping tax rate
$this->create_or_update_tax_rate( 'shipping', $location, $this->tax_rate * 100 );
} // End if().
} // End calculate_tax().
/**
* Add or update a native WooCommerce tax rate
*
* Unchanged from the TaxJar plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L396
*
* @return void
*/
public function create_or_update_tax_rate( $line_item_key, $location, $rate, $tax_class = '' ) {
$tax_rate = array(
'tax_rate_country' => $location['to_country'],
'tax_rate_state' => $location['to_state'],
'tax_rate_name' => sprintf( "%s Tax", $location['to_state'] ),
'tax_rate_priority' => 1,
'tax_rate_compound' => false,
'tax_rate_shipping' => $this->freight_taxable,
'tax_rate' => $rate,
'tax_rate_class' => $tax_class,
);
$wc_rate = WC_Tax::find_rates( array(
'country' => $location['to_country'],
'state' => $location['to_state'],
'postcode' => $location['to_zip'],
'city' => $location['to_city'],
'tax_class' => $tax_class,
) );
if ( ! empty( $wc_rate ) ) {
$this->_log( ':: Tax Rate Found ::' );
$this->_log( $wc_rate );
// Get the existing ID
$rate_id = key( $wc_rate );
// Update Tax Rates with TaxJar rates ( rates might be coming from a cached taxjar rate )
$this->_log( ':: Updating Tax Rate To ::' );
$this->_log( $tax_rate );
WC_Tax::_update_tax_rate( $rate_id, $tax_rate );
} else {
// Insert a rate if we did not find one
$this->_log( ':: Adding New Tax Rate ::' );
$rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
WC_Tax::_update_tax_rate_postcodes( $rate_id, wc_clean( $location['to_zip'] ) );
WC_Tax::_update_tax_rate_cities( $rate_id, wc_clean( $location['to_city'] ) );
}
$this->_log( 'Tax Rate ID Set for ' . $line_item_key . ': ' . $rate_id );
$this->rate_ids[ $line_item_key ] = $rate_id;
}
/**
* Wrap SmartCalcs API requests in a transient-based caching layer.
*
* Modified from TaxJar's plugin (removed use of TLC Transients)
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L463
*
* @param $json
*
* @return mixed|WP_Error
*/
public function smartcalcs_cache_request( $json ) {
$cache_key = 'wcs_tax_' . hash( 'md5', $json );
$response = get_transient( $cache_key );
if ( false === $response ) {
$response = $this->smartcalcs_request( $json );
if ( 200 == wp_remote_retrieve_response_code( $response ) ) {
set_transient( $cache_key, $response, HOUR_IN_SECONDS );
}
}
return $response;
}
/**
* Make a TaxJar SmartCalcs API request through the WCS proxy.
*
* Modified from TaxJar's plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L440
*
* @param $json
*
* @return array|WP_Error
*/
public function smartcalcs_request( $json ) {
$path = trailingslashit( self::PROXY_PATH ) . 'taxes';
$this->_log( 'Requesting: ' . $path . ' - ' . $json );
$response = $this->api_client->proxy_request( $path, array(
'method' => 'POST',
'headers' => array(
'Content-Type' => 'application/json',
),
'body' => $json,
) );
if ( is_wp_error( $response ) ) {
$this->_error( 'Error retrieving the tax rates. Received (' . $response->get_error_code() . '): ' . $response->get_error_message() );
} elseif ( 200 == $response['response']['code'] ) {
return $response;
} else {
$this->_error( 'Error retrieving the tax rates. Received (' . $response['response']['code'] . '): ' . $response['body'] );
}
}
/**
* Exports existing tax rates to a CSV and clears the table.
*
* Ported from TaxJar's plugin.
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/42cd4cd0/taxjar-woocommerce.php#L75
*/
public function backup_existing_tax_rates() {
global $wpdb;
// Export Tax Rates
$rates = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates
ORDER BY tax_rate_order
LIMIT %d, %d
",
0,
10000
) );
ob_start();
$header =
__( 'Country Code', 'woocommerce' ) . ',' .
__( 'State Code', 'woocommerce' ) . ',' .
__( 'ZIP/Postcode', 'woocommerce' ) . ',' .
__( 'City', 'woocommerce' ) . ',' .
__( 'Rate %', 'woocommerce' ) . ',' .
__( 'Tax Name', 'woocommerce' ) . ',' .
__( 'Priority', 'woocommerce' ) . ',' .
__( 'Compound', 'woocommerce' ) . ',' .
__( 'Shipping', 'woocommerce' ) . ',' .
__( 'Tax Class', 'woocommerce' ) . "\n";
echo $header;
foreach ( $rates as $rate ) {
if ( $rate->tax_rate_country ) {
echo esc_attr( $rate->tax_rate_country );
} else {
echo '*';
}
echo ',';
if ( $rate->tax_rate_state ) {
echo esc_attr( $rate->tax_rate_state );
} else {
echo '*';
}
echo ',';
$locations = $wpdb->get_col( $wpdb->prepare( "SELECT location_code FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE location_type='postcode' AND tax_rate_id = %d ORDER BY location_code", $rate->tax_rate_id ) );
if ( $locations ) {
echo esc_attr( implode( '; ', $locations ) );
} else {
echo '*';
}
echo ',';
$locations = $wpdb->get_col( $wpdb->prepare( "SELECT location_code FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE location_type='city' AND tax_rate_id = %d ORDER BY location_code", $rate->tax_rate_id ) );
if ( $locations ) {
echo esc_attr( implode( '; ', $locations ) );
} else {
echo '*';
}
echo ',';
if ( $rate->tax_rate ) {
echo esc_attr( $rate->tax_rate );
} else {
echo '0';
}
echo ',';
if ( $rate->tax_rate_name ) {
echo esc_attr( $rate->tax_rate_name );
} else {
echo '*';
}
echo ',';
if ( $rate->tax_rate_priority ) {
echo esc_attr( $rate->tax_rate_priority );
} else {
echo '1';
}
echo ',';
if ( $rate->tax_rate_compound ) {
echo esc_attr( $rate->tax_rate_compound );
} else {
echo '0';
}
echo ',';
if ( $rate->tax_rate_shipping ) {
echo esc_attr( $rate->tax_rate_shipping );
} else {
echo '0';
}
echo ',';
echo "\n";
} // End foreach().
$csv = ob_get_contents();
ob_end_clean();
$upload_dir = wp_upload_dir();
file_put_contents( $upload_dir['basedir'] . '/taxjar-wc_tax_rates-' . date( 'm-d-Y' ) . '-' . time() . '.csv', $csv );
// Delete all tax rates
$wpdb->query( 'TRUNCATE ' . $wpdb->prefix . 'woocommerce_tax_rates' );
$wpdb->query( 'TRUNCATE ' . $wpdb->prefix . 'woocommerce_tax_rate_locations' );
}
}