docs: update Rajaongkir snippet and add generic shipping bridge pattern

RAJAONGKIR_INTEGRATION.md:
- Hide country/state/city when Indonesia is the only allowed country
- Make destination_id required for Indonesia-only stores
- Force country to ID in session bridge
- Added billing_destination_id fallback

SHIPPING_BRIDGE_PATTERN.md:
- New generic template for shipping provider integrations
- Documents architecture, hooks, and field types
- Provides copy-paste template for new providers
- Includes checklist for new integrations
This commit is contained in:
Dwindi Ramadhana
2026-01-08 15:20:25 +07:00
parent 56b0040f7a
commit 26faa008cb
2 changed files with 275 additions and 6 deletions

View File

@@ -88,8 +88,8 @@ function woonoow_rajaongkir_search_destinations($request) {
}
// ============================================================
// 2. Add destination field to checkout fields (both billing and shipping)
// Always add for Indonesia zone (no premature country check)
// 2. Add destination field and hide redundant fields for Indonesia
// The destination_id from Rajaongkir contains province/city/subdistrict
// ============================================================
add_filter('woocommerce_checkout_fields', function($fields) {
// Check if Rajaongkir is active
@@ -103,11 +103,47 @@ add_filter('woocommerce_checkout_fields', function($fields) {
return $fields;
}
// Check if Indonesia is the ONLY allowed country
$indonesia_only = count($allowed) === 1 && isset($allowed['ID']);
// If Indonesia only, hide country/state/city fields (Rajaongkir destination has all this)
if ($indonesia_only) {
// Hide billing fields
if (isset($fields['billing']['billing_country'])) {
$fields['billing']['billing_country']['type'] = 'hidden';
$fields['billing']['billing_country']['default'] = 'ID';
$fields['billing']['billing_country']['required'] = false;
}
if (isset($fields['billing']['billing_state'])) {
$fields['billing']['billing_state']['type'] = 'hidden';
$fields['billing']['billing_state']['required'] = false;
}
if (isset($fields['billing']['billing_city'])) {
$fields['billing']['billing_city']['type'] = 'hidden';
$fields['billing']['billing_city']['required'] = false;
}
// Hide shipping fields
if (isset($fields['shipping']['shipping_country'])) {
$fields['shipping']['shipping_country']['type'] = 'hidden';
$fields['shipping']['shipping_country']['default'] = 'ID';
$fields['shipping']['shipping_country']['required'] = false;
}
if (isset($fields['shipping']['shipping_state'])) {
$fields['shipping']['shipping_state']['type'] = 'hidden';
$fields['shipping']['shipping_state']['required'] = false;
}
if (isset($fields['shipping']['shipping_city'])) {
$fields['shipping']['shipping_city']['type'] = 'hidden';
$fields['shipping']['shipping_city']['required'] = false;
}
}
// Destination field definition (reused for billing and shipping)
$destination_field = [
'type' => 'searchable_select',
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
'required' => false, // Frontend will manage this based on country
'required' => $indonesia_only, // Required if Indonesia only
'priority' => 85,
'class' => ['form-row-wide'],
'placeholder' => __('Search destination...', 'woonoow'),
@@ -133,15 +169,27 @@ add_filter('woocommerce_checkout_fields', function($fields) {
// ============================================================
// 3. Bridge WooNooW shipping data to Rajaongkir session
// Sets destination_id in WC session for Rajaongkir to use
// ============================================================
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
// Set country in WC customer
if (!empty($shipping['country'])) {
// Check if Rajaongkir is active
if (!class_exists('Cekongkir_API')) {
return;
}
// For Indonesia-only stores, always set country to ID
$allowed = WC()->countries->get_allowed_countries();
$indonesia_only = count($allowed) === 1 && isset($allowed['ID']);
if ($indonesia_only) {
WC()->customer->set_shipping_country('ID');
WC()->customer->set_billing_country('ID');
} elseif (!empty($shipping['country'])) {
WC()->customer->set_shipping_country($shipping['country']);
WC()->customer->set_billing_country($shipping['country']);
}
// Only process for Indonesia
// Only process Rajaongkir for Indonesia
$country = $shipping['country'] ?? WC()->customer->get_shipping_country();
if ($country !== 'ID') {
// Clear destination for non-Indonesia
@@ -153,6 +201,7 @@ add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
// Get destination_id from shipping data (various possible keys)
$destination_id = $shipping['destination_id']
?? $shipping['shipping_destination_id']
?? $shipping['billing_destination_id']
?? null;
if (empty($destination_id)) {
@@ -165,6 +214,7 @@ add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
// Also set label if provided
$label = $shipping['destination_label']
?? $shipping['shipping_destination_id_label']
?? $shipping['billing_destination_id_label']
?? '';
if ($label) {
WC()->session->set('selected_destination_label', sanitize_text_field($label));

219
SHIPPING_BRIDGE_PATTERN.md Normal file
View File

@@ -0,0 +1,219 @@
# WooNooW Shipping Bridge Pattern
This document describes a generic pattern for integrating any external shipping API with WooNooW's SPA checkout.
---
## Overview
WooNooW provides hooks and endpoints that allow any shipping plugin to:
1. **Register custom checkout fields** (searchable selects, dropdowns, etc.)
2. **Bridge data to the plugin's session/API** before shipping calculation
3. **Display live rates** from external APIs
This pattern is NOT specific to Rajaongkir - it can be used for any shipping provider.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ WooNooW Customer SPA │
├─────────────────────────────────────────────────────────────────┤
│ 1. Checkout loads → calls /checkout/fields │
│ 2. Renders custom fields (e.g., searchable destination) │
│ 3. User fills form → calls /checkout/shipping-rates │
│ 4. Hook triggers → shipping plugin calculates rates │
│ 5. Rates displayed → user selects → order submitted │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Your Bridge Snippet │
├─────────────────────────────────────────────────────────────────┤
│ • woocommerce_checkout_fields → Add custom fields │
│ • register_rest_route → API endpoint for field data │
│ • woonoow/shipping/before_calculate → Set session/data │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Shipping Plugin (Any) │
├─────────────────────────────────────────────────────────────────┤
│ • Reads from WC session or customer data │
│ • Calls external API (Rajaongkir, Sicepat, JNE, etc.) │
│ • Returns rates via get_rates_for_package() │
└─────────────────────────────────────────────────────────────────┘
```
---
## Generic Bridge Template
```php
<?php
/**
* [PROVIDER_NAME] Bridge for WooNooW SPA Checkout
*
* Replace [PROVIDER_NAME], [PROVIDER_CLASS], and [PROVIDER_SESSION_KEY]
* with your shipping plugin's specifics.
*/
// ============================================================
// 1. REST API Endpoint: Search/fetch data from provider
// ============================================================
add_action('rest_api_init', function() {
register_rest_route('woonoow/v1', '/[provider]/search', [
'methods' => 'GET',
'callback' => 'woonoow_[provider]_search',
'permission_callback' => '__return_true', // Public for customer use
'args' => [
'search' => ['type' => 'string', 'required' => false],
],
]);
});
function woonoow_[provider]_search($request) {
$search = sanitize_text_field($request->get_param('search') ?? '');
if (strlen($search) < 3) {
return [];
}
// Check if plugin is active
if (!class_exists('[PROVIDER_CLASS]')) {
return new WP_Error('[provider]_missing', 'Plugin not active', ['status' => 400]);
}
// Call provider's API
// $results = [PROVIDER_CLASS]::search($search);
// Format for WooNooW's SearchableSelect
$formatted = [];
foreach ($results as $r) {
$formatted[] = [
'value' => (string) $r['id'],
'label' => $r['name'],
];
}
return array_slice($formatted, 0, 50);
}
// ============================================================
// 2. Add custom field(s) to checkout
// ============================================================
add_filter('woocommerce_checkout_fields', function($fields) {
if (!class_exists('[PROVIDER_CLASS]')) {
return $fields;
}
$custom_field = [
'type' => 'searchable_select', // or 'select', 'text'
'label' => __('[Field Label]', 'woonoow'),
'required' => true,
'priority' => 85,
'class' => ['form-row-wide'],
'placeholder' => __('Search...', 'woonoow'),
'search_endpoint' => '/[provider]/search', // Relative to /wp-json/woonoow/v1
'search_param' => 'search',
'min_chars' => 3,
];
$fields['billing']['billing_[provider]_field'] = $custom_field;
$fields['shipping']['shipping_[provider]_field'] = $custom_field;
return $fields;
}, 20);
// ============================================================
// 3. Bridge data to provider before shipping calculation
// ============================================================
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
if (!class_exists('[PROVIDER_CLASS]')) {
return;
}
// Get custom field value from shipping data
$field_value = $shipping['[provider]_field']
?? $shipping['shipping_[provider]_field']
?? $shipping['billing_[provider]_field']
?? null;
if (empty($field_value)) {
return;
}
// Set in WC session for the shipping plugin to use
WC()->session->set('[PROVIDER_SESSION_KEY]', $field_value);
// Clear shipping cache to force recalculation
WC()->session->set('shipping_for_package_0', false);
}, 10, 2);
```
---
## Supported Field Types
| Type | Description | Use Case |
|------|-------------|----------|
| `searchable_select` | Dropdown with API search | Destinations, locations, service points |
| `select` | Static dropdown | Service types, delivery options |
| `text` | Free text input | Reference numbers, notes |
| `hidden` | Hidden field | Default values, auto-set data |
---
## WooNooW Hooks Reference
| Hook | Type | Description |
|------|------|-------------|
| `woonoow/shipping/before_calculate` | Action | Called before shipping rates are calculated |
| `woocommerce_checkout_fields` | Filter | Standard WC filter for checkout fields |
---
## Examples
### Example 1: Sicepat Integration
```php
// Endpoint
register_rest_route('woonoow/v1', '/sicepat/destinations', ...);
// Session key
WC()->session->set('sicepat_destination_code', $code);
```
### Example 2: JNE Direct API
```php
// Endpoint for origin selection
register_rest_route('woonoow/v1', '/jne/origins', ...);
register_rest_route('woonoow/v1', '/jne/destinations', ...);
// Multiple session keys
WC()->session->set('jne_origin', $origin);
WC()->session->set('jne_destination', $destination);
```
---
## Checklist for New Integration
- [ ] Identify the shipping plugin's class name
- [ ] Find what session/data it reads for API calls
- [ ] Create REST endpoint for searchable data
- [ ] Add checkout field(s) via filter
- [ ] Bridge data via `woonoow/shipping/before_calculate`
- [ ] Test shipping rate calculation
- [ ] Document in dedicated `[PROVIDER]_INTEGRATION.md`
---
## Related Documentation
- [RAJAONGKIR_INTEGRATION.md](RAJAONGKIR_INTEGRATION.md) - Rajaongkir-specific implementation
- [SHIPPING_INTEGRATION.md](SHIPPING_INTEGRATION.md) - General shipping patterns
- [HOOKS_REGISTRY.md](HOOKS_REGISTRY.md) - All WooNooW hooks