## Fixed Critical Issues: ### 1. Tax Rates Not Appearing (FIXED ✅) **Root Cause:** get_tax_rates() was filtering by tax_class, but empty tax_class (standard) was not matching. **Solution:** Modified get_tax_rates() to treat empty string as standard class: ```php if ( $tax_class === 'standard' ) { // Match both empty string and 'standard' WHERE tax_rate_class = '' OR tax_rate_class = 'standard' } ``` ### 2. Select Dropdown Not Using Shadcn (FIXED ✅) **Problem:** Native select with manual styling was inconsistent. **Solution:** - Added selectedTaxClass state - Used controlled shadcn Select component - Initialize state when dialog opens/closes - Pass state value to API instead of form data ## Changes: - **Backend:** Fixed get_tax_rates() SQL query - **Frontend:** Converted to controlled Select with state - **UX:** Tax rates now appear immediately after creation ## Testing: - ✅ Add tax rate manually - ✅ Add suggested tax rate - ✅ Rates appear in list - ✅ Select dropdown uses shadcn styling
434 lines
12 KiB
PHP
434 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Tax Settings REST API Controller
|
|
*
|
|
* @package WooNooW
|
|
*/
|
|
|
|
namespace WooNooW\Api;
|
|
|
|
use WP_REST_Controller;
|
|
use WP_REST_Server;
|
|
use WP_REST_Request;
|
|
use WP_REST_Response;
|
|
use WP_Error;
|
|
|
|
class TaxController extends WP_REST_Controller {
|
|
|
|
/**
|
|
* Register routes
|
|
*/
|
|
public function register_routes() {
|
|
$namespace = 'woonoow/v1';
|
|
|
|
// Get tax settings
|
|
register_rest_route(
|
|
$namespace,
|
|
'/settings/tax',
|
|
array(
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => array( $this, 'get_settings' ),
|
|
'permission_callback' => array( $this, 'check_permission' ),
|
|
)
|
|
);
|
|
|
|
// Toggle tax calculation
|
|
register_rest_route(
|
|
$namespace,
|
|
'/settings/tax/toggle',
|
|
array(
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
'callback' => array( $this, 'toggle_tax' ),
|
|
'permission_callback' => array( $this, 'check_permission' ),
|
|
'args' => array(
|
|
'enabled' => array(
|
|
'required' => true,
|
|
'type' => 'boolean',
|
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
|
),
|
|
),
|
|
)
|
|
);
|
|
|
|
// Get suggested tax rates based on selling locations
|
|
register_rest_route(
|
|
$namespace,
|
|
'/settings/tax/suggested',
|
|
array(
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => array( $this, 'get_suggested_rates' ),
|
|
'permission_callback' => array( $this, 'check_permission' ),
|
|
)
|
|
);
|
|
|
|
// Create tax rate
|
|
register_rest_route(
|
|
$namespace,
|
|
'/settings/tax/rates',
|
|
array(
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
'callback' => array( $this, 'create_rate' ),
|
|
'permission_callback' => array( $this, 'check_permission' ),
|
|
)
|
|
);
|
|
|
|
// Update tax rate
|
|
register_rest_route(
|
|
$namespace,
|
|
'/settings/tax/rates/(?P<id>\d+)',
|
|
array(
|
|
'methods' => WP_REST_Server::EDITABLE,
|
|
'callback' => array( $this, 'update_rate' ),
|
|
'permission_callback' => array( $this, 'check_permission' ),
|
|
)
|
|
);
|
|
|
|
// Delete tax rate
|
|
register_rest_route(
|
|
$namespace,
|
|
'/settings/tax/rates/(?P<id>\d+)',
|
|
array(
|
|
'methods' => WP_REST_Server::DELETABLE,
|
|
'callback' => array( $this, 'delete_rate' ),
|
|
'permission_callback' => array( $this, 'check_permission' ),
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check permission
|
|
*/
|
|
public function check_permission() {
|
|
return current_user_can( 'manage_woocommerce' );
|
|
}
|
|
|
|
/**
|
|
* Get tax settings
|
|
*/
|
|
public function get_settings( WP_REST_Request $request ) {
|
|
try {
|
|
$settings = array(
|
|
'calc_taxes' => get_option( 'woocommerce_calc_taxes', 'no' ),
|
|
'prices_include_tax' => get_option( 'woocommerce_prices_include_tax', 'no' ),
|
|
'tax_based_on' => get_option( 'woocommerce_tax_based_on', 'shipping' ),
|
|
'tax_display_shop' => get_option( 'woocommerce_tax_display_shop', 'excl' ),
|
|
'tax_display_cart' => get_option( 'woocommerce_tax_display_cart', 'excl' ),
|
|
'standard_rates' => $this->get_tax_rates( 'standard' ),
|
|
'reduced_rates' => $this->get_tax_rates( 'reduced-rate' ),
|
|
'zero_rates' => $this->get_tax_rates( 'zero-rate' ),
|
|
);
|
|
|
|
return new WP_REST_Response( $settings, 200 );
|
|
} catch ( \Exception $e ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => 'fetch_failed',
|
|
'message' => $e->getMessage(),
|
|
),
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle tax calculation
|
|
*/
|
|
public function toggle_tax( WP_REST_Request $request ) {
|
|
try {
|
|
$enabled = $request->get_param( 'enabled' );
|
|
$value = $enabled ? 'yes' : 'no';
|
|
|
|
update_option( 'woocommerce_calc_taxes', $value );
|
|
|
|
// Clear WooCommerce cache
|
|
\WC_Cache_Helper::invalidate_cache_group( 'taxes' );
|
|
\WC_Cache_Helper::get_transient_version( 'shipping', true );
|
|
|
|
return new WP_REST_Response(
|
|
array(
|
|
'success' => true,
|
|
'enabled' => $enabled,
|
|
'message' => $enabled
|
|
? __( 'Tax calculation enabled', 'woonoow' )
|
|
: __( 'Tax calculation disabled', 'woonoow' ),
|
|
),
|
|
200
|
|
);
|
|
} catch ( \Exception $e ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => 'update_failed',
|
|
'message' => $e->getMessage(),
|
|
),
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get tax rates for a specific class
|
|
*/
|
|
private function get_tax_rates( $tax_class ) {
|
|
global $wpdb;
|
|
|
|
// For 'standard' class, match both empty string and 'standard'
|
|
if ( $tax_class === 'standard' ) {
|
|
$rates = $wpdb->get_results(
|
|
"SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates
|
|
WHERE tax_rate_class = '' OR tax_rate_class = 'standard'
|
|
ORDER BY tax_rate_order ASC"
|
|
);
|
|
} else {
|
|
$rates = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates
|
|
WHERE tax_rate_class = %s
|
|
ORDER BY tax_rate_order ASC",
|
|
$tax_class
|
|
)
|
|
);
|
|
}
|
|
|
|
$formatted_rates = array();
|
|
|
|
foreach ( $rates as $rate ) {
|
|
$formatted_rates[] = array(
|
|
'id' => $rate->tax_rate_id,
|
|
'country' => $rate->tax_rate_country,
|
|
'state' => $rate->tax_rate_state,
|
|
'rate' => $rate->tax_rate,
|
|
'name' => $rate->tax_rate_name,
|
|
'priority' => $rate->tax_rate_priority,
|
|
'compound' => $rate->tax_rate_compound,
|
|
'shipping' => $rate->tax_rate_shipping,
|
|
);
|
|
}
|
|
|
|
return $formatted_rates;
|
|
}
|
|
|
|
/**
|
|
* Get suggested tax rates based on WooCommerce selling locations
|
|
*/
|
|
public function get_suggested_rates( WP_REST_Request $request ) {
|
|
try {
|
|
// Predefined tax rates by country
|
|
$predefined_rates = array(
|
|
'ID' => array( 'country' => 'Indonesia', 'rate' => 11, 'name' => 'PPN (VAT)' ),
|
|
'MY' => array( 'country' => 'Malaysia', 'rate' => 6, 'name' => 'SST' ),
|
|
'SG' => array( 'country' => 'Singapore', 'rate' => 9, 'name' => 'GST' ),
|
|
'TH' => array( 'country' => 'Thailand', 'rate' => 7, 'name' => 'VAT' ),
|
|
'PH' => array( 'country' => 'Philippines', 'rate' => 12, 'name' => 'VAT' ),
|
|
'VN' => array( 'country' => 'Vietnam', 'rate' => 10, 'name' => 'VAT' ),
|
|
'AU' => array( 'country' => 'Australia', 'rate' => 10, 'name' => 'GST' ),
|
|
'NZ' => array( 'country' => 'New Zealand', 'rate' => 15, 'name' => 'GST' ),
|
|
'GB' => array( 'country' => 'United Kingdom', 'rate' => 20, 'name' => 'VAT' ),
|
|
'DE' => array( 'country' => 'Germany', 'rate' => 19, 'name' => 'VAT' ),
|
|
'FR' => array( 'country' => 'France', 'rate' => 20, 'name' => 'VAT' ),
|
|
'IT' => array( 'country' => 'Italy', 'rate' => 22, 'name' => 'VAT' ),
|
|
'ES' => array( 'country' => 'Spain', 'rate' => 21, 'name' => 'VAT' ),
|
|
'CA' => array( 'country' => 'Canada', 'rate' => 5, 'name' => 'GST' ),
|
|
);
|
|
|
|
// Get WooCommerce selling locations
|
|
$selling_locations = get_option( 'woocommerce_allowed_countries', 'all' );
|
|
$suggested = array();
|
|
|
|
if ( $selling_locations === 'specific' ) {
|
|
// User selected specific countries
|
|
$countries = get_option( 'woocommerce_specific_allowed_countries', array() );
|
|
|
|
foreach ( $countries as $country_code ) {
|
|
if ( isset( $predefined_rates[ $country_code ] ) ) {
|
|
$suggested[] = array_merge(
|
|
array( 'code' => $country_code ),
|
|
$predefined_rates[ $country_code ]
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Sell to all countries - suggest store base country
|
|
$store_country = get_option( 'woocommerce_default_country', '' );
|
|
$country_code = substr( $store_country, 0, 2 ); // Extract country code (e.g., 'ID' from 'ID:JB')
|
|
|
|
if ( isset( $predefined_rates[ $country_code ] ) ) {
|
|
$suggested[] = array_merge(
|
|
array( 'code' => $country_code ),
|
|
$predefined_rates[ $country_code ]
|
|
);
|
|
}
|
|
}
|
|
|
|
return new WP_REST_Response(
|
|
array(
|
|
'suggested' => $suggested,
|
|
'all_rates' => $predefined_rates,
|
|
),
|
|
200
|
|
);
|
|
} catch ( \Exception $e ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => 'fetch_failed',
|
|
'message' => $e->getMessage(),
|
|
),
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create tax rate
|
|
*/
|
|
public function create_rate( WP_REST_Request $request ) {
|
|
try {
|
|
global $wpdb;
|
|
|
|
$country = $request->get_param( 'country' );
|
|
$state = $request->get_param( 'state' ) ?? '';
|
|
$rate = $request->get_param( 'rate' );
|
|
$name = $request->get_param( 'name' );
|
|
$tax_class = $request->get_param( 'tax_class' ) ?? '';
|
|
$priority = $request->get_param( 'priority' ) ?? 1;
|
|
$compound = $request->get_param( 'compound' ) ?? 0;
|
|
$shipping = $request->get_param( 'shipping' ) ?? 1;
|
|
|
|
$wpdb->insert(
|
|
$wpdb->prefix . 'woocommerce_tax_rates',
|
|
array(
|
|
'tax_rate_country' => $country,
|
|
'tax_rate_state' => $state,
|
|
'tax_rate' => $rate,
|
|
'tax_rate_name' => $name,
|
|
'tax_rate_priority' => $priority,
|
|
'tax_rate_compound' => $compound,
|
|
'tax_rate_shipping' => $shipping,
|
|
'tax_rate_order' => 0,
|
|
'tax_rate_class' => $tax_class,
|
|
),
|
|
array( '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%s' )
|
|
);
|
|
|
|
$rate_id = $wpdb->insert_id;
|
|
|
|
// Clear cache
|
|
\WC_Cache_Helper::invalidate_cache_group( 'taxes' );
|
|
|
|
return new WP_REST_Response(
|
|
array(
|
|
'success' => true,
|
|
'id' => $rate_id,
|
|
'message' => __( 'Tax rate created', 'woonoow' ),
|
|
),
|
|
201
|
|
);
|
|
} catch ( \Exception $e ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => 'create_failed',
|
|
'message' => $e->getMessage(),
|
|
),
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update tax rate
|
|
*/
|
|
public function update_rate( WP_REST_Request $request ) {
|
|
try {
|
|
global $wpdb;
|
|
|
|
$rate_id = $request->get_param( 'id' );
|
|
$country = $request->get_param( 'country' );
|
|
$state = $request->get_param( 'state' ) ?? '';
|
|
$rate = $request->get_param( 'rate' );
|
|
$name = $request->get_param( 'name' );
|
|
$tax_class = $request->get_param( 'tax_class' ) ?? '';
|
|
$priority = $request->get_param( 'priority' ) ?? 1;
|
|
$compound = $request->get_param( 'compound' ) ?? 0;
|
|
$shipping = $request->get_param( 'shipping' ) ?? 1;
|
|
|
|
$wpdb->update(
|
|
$wpdb->prefix . 'woocommerce_tax_rates',
|
|
array(
|
|
'tax_rate_country' => $country,
|
|
'tax_rate_state' => $state,
|
|
'tax_rate' => $rate,
|
|
'tax_rate_name' => $name,
|
|
'tax_rate_priority' => $priority,
|
|
'tax_rate_compound' => $compound,
|
|
'tax_rate_shipping' => $shipping,
|
|
'tax_rate_class' => $tax_class,
|
|
),
|
|
array( 'tax_rate_id' => $rate_id ),
|
|
array( '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%s' ),
|
|
array( '%d' )
|
|
);
|
|
|
|
// Clear cache
|
|
\WC_Cache_Helper::invalidate_cache_group( 'taxes' );
|
|
|
|
return new WP_REST_Response(
|
|
array(
|
|
'success' => true,
|
|
'message' => __( 'Tax rate updated', 'woonoow' ),
|
|
),
|
|
200
|
|
);
|
|
} catch ( \Exception $e ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => 'update_failed',
|
|
'message' => $e->getMessage(),
|
|
),
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete tax rate
|
|
*/
|
|
public function delete_rate( WP_REST_Request $request ) {
|
|
try {
|
|
global $wpdb;
|
|
|
|
$rate_id = $request->get_param( 'id' );
|
|
|
|
$wpdb->delete(
|
|
$wpdb->prefix . 'woocommerce_tax_rates',
|
|
array( 'tax_rate_id' => $rate_id ),
|
|
array( '%d' )
|
|
);
|
|
|
|
// Also delete rate locations
|
|
$wpdb->delete(
|
|
$wpdb->prefix . 'woocommerce_tax_rate_locations',
|
|
array( 'tax_rate_id' => $rate_id ),
|
|
array( '%d' )
|
|
);
|
|
|
|
// Clear cache
|
|
\WC_Cache_Helper::invalidate_cache_group( 'taxes' );
|
|
|
|
return new WP_REST_Response(
|
|
array(
|
|
'success' => true,
|
|
'message' => __( 'Tax rate deleted', 'woonoow' ),
|
|
),
|
|
200
|
|
);
|
|
} catch ( \Exception $e ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => 'delete_failed',
|
|
'message' => $e->getMessage(),
|
|
),
|
|
500
|
|
);
|
|
}
|
|
}
|
|
}
|