feat(shipping): searchable state fields and addon hook

1. Admin OrderForm: Changed billing & shipping state from Select to
   SearchableSelect for better UX (consistent with country field)

2. OrdersController: Replaced Rajaongkir-specific hardcoded session
   handling with generic filter hook:
   do_action('woonoow/shipping/before_calculate', $shipping, $items)

3. Added RAJAONGKIR_INTEGRATION.md with:
   - Hook documentation
   - Code snippet to bridge Rajaongkir with WooNooW
   - Frontend integration examples
   - Troubleshooting guide
This commit is contained in:
Dwindi Ramadhana
2026-01-08 11:00:55 +07:00
parent 83836298ec
commit 786e01c8f6
3 changed files with 232 additions and 21 deletions

204
RAJAONGKIR_INTEGRATION.md Normal file
View File

@@ -0,0 +1,204 @@
# Rajaongkir Integration with WooNooW
This guide explains how to bridge the Rajaongkir shipping plugin with WooNooW's admin order form and checkout flow.
---
## The Challenge
Rajaongkir doesn't use standard WooCommerce address fields. Instead of using `city` and `state`, it requires a **destination ID** from its own Indonesian location database.
**Standard WooCommerce Flow:**
```
Country → State → City → Postcode
```
**Rajaongkir Flow:**
```
Country (ID) → Destination ID (subdistrict level)
```
---
## WooNooW Integration Hook
WooNooW provides a hook that fires **before** shipping calculation. This allows plugins like Rajaongkir to set session variables or prepare any data they need.
### Hook: `woonoow/shipping/before_calculate`
```php
do_action( 'woonoow/shipping/before_calculate', $shipping_data, $items );
```
**Parameters:**
- `$shipping_data` (array) - The shipping address from frontend:
- `country` - Country code (e.g., 'ID')
- `state` - State code
- `city` - City name
- `postcode` - Postal code
- `address_1` - Street address
- `address_2` - Additional address (optional)
- + Any custom fields added by addons
- `$items` (array) - Cart items being shipped
---
## Code Snippet: Rajaongkir Bridge
Add this code to your theme's `functions.php` or via a code snippets plugin:
```php
<?php
/**
* Bridge Rajaongkir plugin with WooNooW shipping calculation.
*
* This code listens for WooNooW's shipping hook and sets the
* Rajaongkir session variables it needs to calculate rates.
*/
add_action( 'woonoow/shipping/before_calculate', function( $shipping, $items ) {
// Only process for Indonesia
if ( empty( $shipping['country'] ) || $shipping['country'] !== 'ID' ) {
// Clear Rajaongkir session for non-ID countries
WC()->session->__unset( 'selected_destination_id' );
WC()->session->__unset( 'selected_destination_label' );
return;
}
// Check if destination_id is provided by frontend
if ( ! empty( $shipping['destination_id'] ) ) {
WC()->session->set( 'selected_destination_id', $shipping['destination_id'] );
WC()->session->set( 'selected_destination_label', $shipping['destination_label'] ?? $shipping['city'] );
return;
}
// Fallback: Try to lookup destination from city name
// This requires the Rajaongkir database lookup
$destination = rajaongkir_lookup_destination( $shipping['city'], $shipping['state'] );
if ( $destination ) {
WC()->session->set( 'selected_destination_id', $destination['id'] );
WC()->session->set( 'selected_destination_label', $destination['label'] );
}
}, 10, 2 );
/**
* Helper: Lookup Rajaongkir destination by city/state name.
*
* This is a simplified example. Implement based on your Rajaongkir data structure.
*/
function rajaongkir_lookup_destination( $city, $state ) {
// Query the Rajaongkir location database
// This depends on how your Rajaongkir plugin stores location data
// Example using transient/option storage:
$locations = get_option( 'cekongkir_destinations', [] );
foreach ( $locations as $location ) {
if (
stripos( $location['city'], $city ) !== false ||
stripos( $location['district'], $city ) !== false
) {
return [
'id' => $location['id'],
'label' => $location['label'],
];
}
}
return null;
}
```
---
## Frontend Integration (Optional)
To add a proper Rajaongkir destination selector to WooNooW's admin order form, you need to:
### 1. Register Custom Address Fields
```php
add_filter( 'woonoow/checkout/address_fields', function( $fields ) {
// Only for Indonesia
if ( WC()->customer && WC()->customer->get_shipping_country() === 'ID' ) {
$fields['destination_id'] = [
'type' => 'hidden',
'required' => true,
];
$fields['destination_selector'] = [
'type' => 'custom',
'component' => 'RajaongkirDestinationSelector',
'label' => __( 'Destination', 'woonoow-rajaongkir' ),
'required' => true,
];
}
return $fields;
} );
```
### 2. Enqueue JavaScript for Destination Search
```php
add_action( 'woonoow/admin/enqueue_scripts', function() {
wp_enqueue_script(
'woonoow-rajaongkir',
plugins_url( 'assets/js/woonoow-integration.js', __FILE__ ),
[ 'woonoow-admin' ],
'1.0.0',
true
);
wp_localize_script( 'woonoow-rajaongkir', 'WNW_RAJAONGKIR', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'rajaongkir_search' ),
] );
} );
```
---
## Testing
1. Install and configure Rajaongkir plugin
2. Add the code snippet above
3. Go to WooNooW → Orders → Create New Order
4. Add a product
5. Set country to Indonesia
6. Fill in city/state (or use destination selector if implemented)
7. Click "Calculate Shipping"
8. Verify Rajaongkir rates appear
---
## Troubleshooting
### Rates not appearing?
1. Check browser DevTools Network tab for `/shipping/calculate` response
2. Look for `debug` object in response:
```json
{
"methods": [...],
"debug": {
"packages_count": 1,
"cart_items_count": 1,
"address": { ... }
}
}
```
3. Verify Rajaongkir session is set:
```php
error_log( 'destination_id: ' . WC()->session->get( 'selected_destination_id' ) );
```
### Session not persisting?
Make sure WooCommerce session is initialized before the hook runs. The hook fires during REST API calls where session may not be initialized by default.
---
## Related Documentation
- [SHIPPING_INTEGRATION.md](SHIPPING_INTEGRATION.md) - General shipping integration guide
- [SHIPPING_METHOD_TYPES.md](SHIPPING_METHOD_TYPES.md) - Shipping method types explanation
- [Rajaongkir Plugin Documentation](https://cekongkir.com/docs)

View File

@@ -1126,16 +1126,13 @@ export default function OrderForm({
</div>
<div>
<Label>{__('State/Province')}</Label>
<Select value={bState} onValueChange={setBState}>
<SelectTrigger className="w-full"><SelectValue placeholder={__('Select state')} /></SelectTrigger>
<SelectContent className="max-h-64">
{bStateOptions.length ? bStateOptions.map(o => (
<SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
)) : (
<SelectItem value="__none__" disabled>{__('N/A')}</SelectItem>
)}
</SelectContent>
</Select>
<SearchableSelect
options={bStateOptions}
value={bState}
onChange={setBState}
placeholder={bStateOptions.length ? __('Select state') : __('N/A')}
disabled={!bStateOptions.length}
/>
</div>
</>
)}
@@ -1177,7 +1174,7 @@ export default function OrderForm({
{field.label}
{field.required && <span className="text-destructive ml-1">*</span>}
</Label>
{field.type === 'select' && field.options ? (
{field.type === 'select' && field.options && field.key !== 'shipping_state' ? (
<Select
value={shippingData[fieldKey] || ''}
onValueChange={(v) => setShippingData({ ...shippingData, [fieldKey]: v })}
@@ -1199,6 +1196,14 @@ export default function OrderForm({
placeholder={field.placeholder || __('Select country')}
disabled={oneCountryOnly}
/>
) : field.key === 'shipping_state' && field.options ? (
<SearchableSelect
options={Object.entries(field.options).map(([value, label]: [string, any]) => ({ value, label }))}
value={shippingData.state || ''}
onChange={(v) => setShippingData({ ...shippingData, state: v })}
placeholder={field.placeholder || __('Select state')}
disabled={!Object.keys(field.options).length}
/>
) : field.type === 'textarea' ? (
<Textarea
value={shippingData[fieldKey] || ''}

View File

@@ -1485,16 +1485,18 @@ class OrdersController {
WC()->customer->set_billing_postcode( $postcode );
WC()->customer->set_billing_city( $city );
// Support for Rajaongkir plugin - set destination in session
// Rajaongkir uses session-based destination instead of standard address fields
if ( $country === 'ID' && ! empty( $shipping['destination_id'] ) ) {
WC()->session->set( 'selected_destination_id', $shipping['destination_id'] );
WC()->session->set( 'selected_destination_label', $shipping['destination_label'] ?? $city );
} else {
// Clear Rajaongkir session data for non-ID countries
WC()->session->__unset( 'selected_destination_id' );
WC()->session->__unset( 'selected_destination_label' );
}
/**
* Allow shipping addons to prepare session/data before shipping calculation.
*
* This hook allows third-party shipping plugins (like Rajaongkir, Biteship, etc.)
* to set any session variables or prepare data they need before WooCommerce
* calculates shipping rates.
*
* @since 1.0.0
* @param array $shipping The shipping address data from frontend (country, state, city, postcode, address_1, etc.)
* @param array $items The cart items being shipped
*/
do_action( 'woonoow/shipping/before_calculate', $shipping, $items ?? [] );
}
// Calculate shipping