Files
WooNooW/includes/Api/TaxController.php
dwindown 28bbce5434 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
2025-11-10 12:23:44 +07:00

425 lines
11 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;
$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
);
}
}
}