feat: Tax settings + Checkout fields - Full implementation
## ✅ TAX SETTINGS - COMPLETE ### Backend (TaxController.php): - ✅ GET /settings/tax - Get all tax settings - ✅ POST /settings/tax/toggle - Enable/disable tax - ✅ GET /settings/tax/suggested - Smart suggestions based on selling locations - ✅ POST /settings/tax/rates - Create tax rate - ✅ PUT /settings/tax/rates/{id} - Update tax rate - ✅ DELETE /settings/tax/rates/{id} - Delete tax rate **Predefined Rates:** - Indonesia: 11% (PPN) - Malaysia: 6% (SST) - Singapore: 9% (GST) - Thailand: 7% (VAT) - Philippines: 12% (VAT) - Vietnam: 10% (VAT) - + Australia, NZ, UK, Germany, France, Italy, Spain, Canada **Smart Detection:** - Reads WooCommerce "Selling location(s)" setting - If specific countries selected → Show those rates - If sell to all → Show store base country rate - Zero re-selection needed! ### Frontend (Tax.tsx): - ✅ Toggle to enable/disable tax - ✅ Suggested rates card (based on selling locations) - ✅ Quick "Add Rate" button for suggested rates - ✅ Tax rates list with Edit/Delete - ✅ Add/Edit tax rate dialog - ✅ Display settings (prices include tax, shop/cart display) - ✅ Link to WooCommerce advanced settings **User Flow:** 1. Enable tax toggle 2. See: "🇮🇩 Indonesia: 11% (PPN)" [Add Rate] 3. Click Add Rate 4. Done! Tax working. ## ✅ CHECKOUT FIELDS - COMPLETE ### Backend (CheckoutController.php): - ✅ POST /checkout/fields - Get fields with all filters applied **Features:** - Listens to WooCommerce `woocommerce_checkout_fields` filter - Respects addon hide/show logic: - Checks `hidden` class - Checks `enabled` flag - Checks `hide` class - Respects digital-only products logic (hides shipping) - Returns field metadata: - required, hidden, type, options, priority - Flags custom fields (from addons) - Includes validation rules **How It Works:** 1. Addon adds field via filter 2. API applies all filters 3. Returns fields with metadata 4. Frontend renders dynamically **Example:** ```php // Indonesian Shipping Addon add_filter('woocommerce_checkout_fields', function($fields) { $fields['shipping']['shipping_subdistrict'] = [ 'required' => true, 'type' => 'select', 'options' => get_subdistricts(), ]; return $fields; }); ``` WooNooW automatically: - Fetches field - Sees required=true - Renders it - Validates it ## Benefits: **Tax:** - Zero learning curve (30 seconds setup) - No re-selecting countries - Smart suggestions - Scales for single/multi-country **Checkout Fields:** - Addon responsibility (not hardcoded) - Works with ANY addon - Respects hide/show logic - Preserves digital-only logic - Future-proof ## Next: Frontend integration for checkout fields
This commit is contained in:
@@ -49,6 +49,50 @@ class TaxController extends WP_REST_Controller {
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// 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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,4 +197,228 @@ class TaxController extends WP_REST_Controller {
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user