diff --git a/SHIPPING_FIELD_HOOKS.md b/SHIPPING_FIELD_HOOKS.md new file mode 100644 index 0000000..4416d63 --- /dev/null +++ b/SHIPPING_FIELD_HOOKS.md @@ -0,0 +1,283 @@ +# Shipping Address Fields - Dynamic via Hooks + +## Philosophy: Addon Responsibility, Not Hardcoding + +WooNooW should **listen to WooCommerce hooks** to determine which fields are required, not hardcode assumptions about Indonesian vs International shipping. + +--- + +## The Problem with Hardcoding + +**Bad Approach (What we almost did):** +```javascript +// ❌ DON'T DO THIS +if (country === 'ID') { + showSubdistrict = true; // Hardcoded assumption +} +``` + +**Why it's bad:** +- Assumes all Indonesian shipping needs subdistrict +- Breaks if addon changes requirements +- Not extensible for other countries +- Violates separation of concerns + +--- + +## The Right Approach: Listen to Hooks + +**WooCommerce Core Hooks:** + +### 1. `woocommerce_checkout_fields` Filter +Addons use this to add/modify/remove fields: + +```php +// Example: Indonesian Shipping Addon +add_filter('woocommerce_checkout_fields', function($fields) { + // Add subdistrict field + $fields['shipping']['shipping_subdistrict'] = [ + 'label' => __('Subdistrict'), + 'required' => true, + 'class' => ['form-row-wide'], + 'priority' => 65, + ]; + + return $fields; +}); +``` + +### 2. `woocommerce_default_address_fields` Filter +Modifies default address fields: + +```php +add_filter('woocommerce_default_address_fields', function($fields) { + // Make postal code required for UPS + $fields['postcode']['required'] = true; + + return $fields; +}); +``` + +### 3. Field Validation Hooks +```php +add_action('woocommerce_checkout_process', function() { + if (empty($_POST['shipping_subdistrict'])) { + wc_add_notice(__('Subdistrict is required'), 'error'); + } +}); +``` + +--- + +## Implementation in WooNooW + +### Backend: Expose Checkout Fields via API + +**New Endpoint:** `GET /checkout/fields` + +```php +// includes/Api/CheckoutController.php + +public function get_checkout_fields(WP_REST_Request $request) { + // Get fields with all filters applied + $fields = WC()->checkout()->get_checkout_fields(); + + // Format for frontend + $formatted = []; + + foreach ($fields as $fieldset_key => $fieldset) { + foreach ($fieldset as $key => $field) { + $formatted[] = [ + 'key' => $key, + 'fieldset' => $fieldset_key, // billing, shipping, account, order + 'type' => $field['type'] ?? 'text', + 'label' => $field['label'] ?? '', + 'placeholder' => $field['placeholder'] ?? '', + 'required' => $field['required'] ?? false, + 'class' => $field['class'] ?? [], + 'priority' => $field['priority'] ?? 10, + 'options' => $field['options'] ?? null, // For select fields + 'custom' => $field['custom'] ?? false, // Custom field flag + ]; + } + } + + // Sort by priority + usort($formatted, function($a, $b) { + return $a['priority'] <=> $b['priority']; + }); + + return new WP_REST_Response($formatted, 200); +} +``` + +### Frontend: Dynamic Field Rendering + +**Create Order - Address Section:** + +```typescript +// Fetch checkout fields from API +const { data: checkoutFields = [] } = useQuery({ + queryKey: ['checkout-fields'], + queryFn: () => api.get('/checkout/fields'), +}); + +// Filter shipping fields +const shippingFields = checkoutFields.filter( + field => field.fieldset === 'shipping' +); + +// Render dynamically +{shippingFields.map(field => { + // Standard WooCommerce fields + if (['first_name', 'last_name', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country'].includes(field.key)) { + return ; + } + + // Custom fields (e.g., subdistrict from addon) + if (field.custom) { + return ; + } + + return null; +})} +``` + +**Field Components:** + +```typescript +function StandardField({ field }) { + return ( +
+ + +
+ ); +} + +function CustomField({ field }) { + // Handle custom field types (select, textarea, etc.) + if (field.type === 'select') { + return ( +
+ + +
+ ); + } + + return ; +} +``` + +--- + +## How Addons Work + +### Example: Indonesian Shipping Addon + +**Addon adds subdistrict field:** +```php +add_filter('woocommerce_checkout_fields', function($fields) { + $fields['shipping']['shipping_subdistrict'] = [ + 'type' => 'select', + 'label' => __('Subdistrict'), + 'required' => true, + 'class' => ['form-row-wide'], + 'priority' => 65, + 'options' => get_subdistricts(), // Addon provides this + 'custom' => true, // Flag as custom field + ]; + + return $fields; +}); +``` + +**WooNooW automatically:** +1. Fetches fields via API +2. Sees `shipping_subdistrict` with `required: true` +3. Renders it in Create Order form +4. Validates it on submit + +**No hardcoding needed!** + +--- + +## Benefits + +✅ **Addon responsibility** - Addons declare their own requirements +✅ **No hardcoding** - WooNooW just renders what WooCommerce says +✅ **Extensible** - Works with ANY addon (Indonesian, UPS, custom) +✅ **Future-proof** - New addons work automatically +✅ **Separation of concerns** - Each addon manages its own fields + +--- + +## Edge Cases + +### Case 1: Subdistrict for Indonesian Shipping +- Addon adds `shipping_subdistrict` field +- WooNooW renders it +- ✅ Works! + +### Case 2: UPS Requires Postal Code +- UPS addon sets `postcode.required = true` +- WooNooW renders it as required +- ✅ Works! + +### Case 3: Custom Shipping Needs Extra Field +- Addon adds `shipping_delivery_notes` field +- WooNooW renders it +- ✅ Works! + +### Case 4: No Custom Fields +- Standard WooCommerce fields only +- WooNooW renders them +- ✅ Works! + +--- + +## Implementation Plan + +1. **Backend:** + - Create `GET /checkout/fields` endpoint + - Return fields with all filters applied + - Include field metadata (type, required, options, etc.) + +2. **Frontend:** + - Fetch checkout fields on Create Order page + - Render fields dynamically based on API response + - Handle standard + custom field types + - Validate based on `required` flag + +3. **Testing:** + - Test with no addons (standard fields only) + - Test with Indonesian shipping addon (subdistrict) + - Test with UPS addon (postal code required) + - Test with custom addon (custom fields) + +--- + +## Next Steps + +1. Create `CheckoutController.php` with `get_checkout_fields` endpoint +2. Update Create Order to fetch and render fields dynamically +3. Test with Indonesian shipping addon +4. Document for addon developers diff --git a/TAX_SETTINGS_DESIGN.md b/TAX_SETTINGS_DESIGN.md index 2c6a198..297f8d1 100644 --- a/TAX_SETTINGS_DESIGN.md +++ b/TAX_SETTINGS_DESIGN.md @@ -48,9 +48,42 @@ Tax Settings Page --- -## Predefined Tax Rates by Country +## Predefined Tax Rates - Smart Detection -When user's store country is set, we show the standard rate: +**Source:** WooCommerce General Settings → "Selling location(s)" + +### Scenario 1: Sell to Specific Countries +If user selected specific countries (e.g., Indonesia, Malaysia): +``` +Predefined Tax Rates +├── 🇮🇩 Indonesia: 11% (PPN) +└── 🇲🇾 Malaysia: 6% (SST) +``` + +### Scenario 2: Sell to All Countries +If user selected "Sell to all countries": +``` +Predefined Tax Rates +└── Based on store country: + 🇮🇩 Indonesia: 11% (PPN) + +Additional Tax Rates +└── [+ Add Tax Rate] → Shows all countries +``` + +### Scenario 3: Sell to Specific Continents +If user selected "Asia": +``` +Suggested Tax Rates (Asia) +├── 🇮🇩 Indonesia: 11% +├── 🇲🇾 Malaysia: 6% +├── 🇸🇬 Singapore: 9% +├── 🇹🇭 Thailand: 7% +├── 🇵🇭 Philippines: 12% +└── 🇻🇳 Vietnam: 10% +``` + +### Standard Tax Rates by Country | Country | Standard Rate | Note | |---------|---------------|------| @@ -124,7 +157,23 @@ Add Tax Rate - `POST /settings/tax/rates` - Create tax rate - `PUT /settings/tax/rates/{id}` - Update tax rate - `DELETE /settings/tax/rates/{id}` - Delete tax rate -- `GET /settings/tax/predefined` - Get predefined rates by country +- `GET /settings/tax/suggested` - Get suggested rates based on selling locations + +### Get Selling Locations: +```php +// Get WooCommerce selling locations setting +$selling_locations = get_option('woocommerce_allowed_countries'); +// Options: 'all', 'all_except', 'specific' + +if ($selling_locations === 'specific') { + $countries = get_option('woocommerce_specific_allowed_countries'); + // Returns array: ['ID', 'MY', 'SG'] +} + +// Get store base country +$store_country = get_option('woocommerce_default_country'); +// Returns: 'ID:JB' (country:state) or 'ID' +``` ### Predefined Rates Data: ```json