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:
@@ -88,8 +88,8 @@ function woonoow_rajaongkir_search_destinations($request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 2. Add destination field to checkout fields (both billing and shipping)
|
// 2. Add destination field and hide redundant fields for Indonesia
|
||||||
// Always add for Indonesia zone (no premature country check)
|
// The destination_id from Rajaongkir contains province/city/subdistrict
|
||||||
// ============================================================
|
// ============================================================
|
||||||
add_filter('woocommerce_checkout_fields', function($fields) {
|
add_filter('woocommerce_checkout_fields', function($fields) {
|
||||||
// Check if Rajaongkir is active
|
// Check if Rajaongkir is active
|
||||||
@@ -103,11 +103,47 @@ add_filter('woocommerce_checkout_fields', function($fields) {
|
|||||||
return $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 definition (reused for billing and shipping)
|
||||||
$destination_field = [
|
$destination_field = [
|
||||||
'type' => 'searchable_select',
|
'type' => 'searchable_select',
|
||||||
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
|
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
|
||||||
'required' => false, // Frontend will manage this based on country
|
'required' => $indonesia_only, // Required if Indonesia only
|
||||||
'priority' => 85,
|
'priority' => 85,
|
||||||
'class' => ['form-row-wide'],
|
'class' => ['form-row-wide'],
|
||||||
'placeholder' => __('Search destination...', 'woonoow'),
|
'placeholder' => __('Search destination...', 'woonoow'),
|
||||||
@@ -133,15 +169,27 @@ add_filter('woocommerce_checkout_fields', function($fields) {
|
|||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 3. Bridge WooNooW shipping data to Rajaongkir session
|
// 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) {
|
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
||||||
// Set country in WC customer
|
// Check if Rajaongkir is active
|
||||||
if (!empty($shipping['country'])) {
|
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_shipping_country($shipping['country']);
|
||||||
WC()->customer->set_billing_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();
|
$country = $shipping['country'] ?? WC()->customer->get_shipping_country();
|
||||||
if ($country !== 'ID') {
|
if ($country !== 'ID') {
|
||||||
// Clear destination for non-Indonesia
|
// 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)
|
// Get destination_id from shipping data (various possible keys)
|
||||||
$destination_id = $shipping['destination_id']
|
$destination_id = $shipping['destination_id']
|
||||||
?? $shipping['shipping_destination_id']
|
?? $shipping['shipping_destination_id']
|
||||||
|
?? $shipping['billing_destination_id']
|
||||||
?? null;
|
?? null;
|
||||||
|
|
||||||
if (empty($destination_id)) {
|
if (empty($destination_id)) {
|
||||||
@@ -165,6 +214,7 @@ add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
|||||||
// Also set label if provided
|
// Also set label if provided
|
||||||
$label = $shipping['destination_label']
|
$label = $shipping['destination_label']
|
||||||
?? $shipping['shipping_destination_id_label']
|
?? $shipping['shipping_destination_id_label']
|
||||||
|
?? $shipping['billing_destination_id_label']
|
||||||
?? '';
|
?? '';
|
||||||
if ($label) {
|
if ($label) {
|
||||||
WC()->session->set('selected_destination_label', sanitize_text_field($label));
|
WC()->session->set('selected_destination_label', sanitize_text_field($label));
|
||||||
|
|||||||
219
SHIPPING_BRIDGE_PATTERN.md
Normal file
219
SHIPPING_BRIDGE_PATTERN.md
Normal 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
|
||||||
Reference in New Issue
Block a user