Files
WooNooW/SHIPPING_FIELD_HOOKS.md
dwindown c1f09041ef docs: Shipping field hooks + Tax selling locations strategy
##  Issue #1: Shipping Fields - Addon Responsibility

Created SHIPPING_FIELD_HOOKS.md documenting:

**The Right Approach:**
-  NO hardcoding (if country === ID → show subdistrict)
-  YES listen to WooCommerce hooks
-  Addons declare their own field requirements

**How It Works:**
1. Addon adds field via `woocommerce_checkout_fields` filter
2. WooNooW fetches fields via API: `GET /checkout/fields`
3. Frontend renders fields dynamically
4. Validation based on `required` flag

**Benefits:**
- Addon responsibility (not WooNooW)
- No hardcoding assumptions
- Works with ANY addon (Indonesian, UPS, custom)
- Future-proof and extensible

**Example:**
```php
// Indonesian Shipping Addon
add_filter('woocommerce_checkout_fields', function($fields) {
    $fields['shipping']['shipping_subdistrict'] = [
        'required' => true,
        // ...
    ];
    return $fields;
});
```

WooNooW automatically renders it!

##  Issue #2: Tax - Grab Selling Locations

Updated TAX_SETTINGS_DESIGN.md:

**Your Brilliant Idea:**
- Read WooCommerce "Selling location(s)" setting
- Show predefined tax rates for those countries
- No re-selecting!

**Scenarios:**
1. **Specific countries** (ID, MY) → Show both rates
2. **All countries** → Show store country + add button
3. **Continent** (Asia) → Suggest all Asian country rates

**Smart Detection:**
```php
$selling_locations = get_option('woocommerce_allowed_countries');
if ($selling_locations === 'specific') {
    $countries = get_option('woocommerce_specific_allowed_countries');
    // Show predefined rates for these countries
}
```

**Benefits:**
- Zero re-selection (data already in WooCommerce)
- Smart suggestions based on user's actual selling regions
- Scales for single/multi-country/continent
- Combines your idea + my proposal perfectly!

## Next: Implementation Plan Ready
2025-11-10 11:40:49 +07:00

284 lines
7.2 KiB
Markdown

# 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 <StandardField key={field.key} field={field} />;
}
// Custom fields (e.g., subdistrict from addon)
if (field.custom) {
return <CustomField key={field.key} field={field} />;
}
return null;
})}
```
**Field Components:**
```typescript
function StandardField({ field }) {
return (
<div className={cn('form-field', field.class)}>
<label>
{field.label}
{field.required && <span className="required">*</span>}
</label>
<input
type={field.type}
name={field.key}
placeholder={field.placeholder}
required={field.required}
/>
</div>
);
}
function CustomField({ field }) {
// Handle custom field types (select, textarea, etc.)
if (field.type === 'select') {
return (
<div className={cn('form-field', field.class)}>
<label>
{field.label}
{field.required && <span className="required">*</span>}
</label>
<select name={field.key} required={field.required}>
{field.options?.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
);
}
return <StandardField field={field} />;
}
```
---
## 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