## ✅ 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
7.2 KiB
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):
// ❌ 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:
// 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:
add_filter('woocommerce_default_address_fields', function($fields) {
// Make postal code required for UPS
$fields['postcode']['required'] = true;
return $fields;
});
3. Field Validation Hooks
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
// 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:
// 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:
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:
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:
- Fetches fields via API
- Sees
shipping_subdistrictwithrequired: true - Renders it in Create Order form
- 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_subdistrictfield - 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_notesfield - WooNooW renders it
- ✅ Works!
Case 4: No Custom Fields
- Standard WooCommerce fields only
- WooNooW renders them
- ✅ Works!
Implementation Plan
-
Backend:
- Create
GET /checkout/fieldsendpoint - Return fields with all filters applied
- Include field metadata (type, required, options, etc.)
- Create
-
Frontend:
- Fetch checkout fields on Create Order page
- Render fields dynamically based on API response
- Handle standard + custom field types
- Validate based on
requiredflag
-
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
- Create
CheckoutController.phpwithget_checkout_fieldsendpoint - Update Create Order to fetch and render fields dynamically
- Test with Indonesian shipping addon
- Document for addon developers