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
220 lines
8.6 KiB
Markdown
220 lines
8.6 KiB
Markdown
# 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
|