187 lines
7.0 KiB
Markdown
187 lines
7.0 KiB
Markdown
# WooNooW Shipping & Integrations Guide
|
|
|
|
**Version:** 2.0.0
|
|
**Status:** Production Ready
|
|
|
|
This document outlines how shipping methods work within WooNooW, and how to bridge complex external/API shipping plugins (such as RajaOngkir or Sicepat) fully into the Customer SPA Order Form.
|
|
|
|
---
|
|
|
|
## 📋 Table of Contents
|
|
|
|
1. [How WooNooW Handles Shipping](#how-woonoow-handles-shipping)
|
|
2. [The Shipping Bridge Pattern](#the-shipping-bridge-pattern)
|
|
3. [RajaOngkir Integration Example](#rajaongkir-integration-example)
|
|
4. [Custom Shipping Addons](#custom-shipping-addons)
|
|
|
|
---
|
|
|
|
## 1. How WooNooW Handles Shipping
|
|
|
|
### Two Types of Shipping Methods
|
|
|
|
WooCommerce Core inherently provides two types of shipping:
|
|
|
|
**1. Static Methods (e.g. Free Shipping, Flat Rate)**
|
|
- Immediate calculation (no API).
|
|
- Configured via basic Address fields (Country, State, City, ZIP).
|
|
|
|
**2. Live Rate Methods (API Based)**
|
|
- Need an API Call (e.g., UPS, FedEx, or local Indonesian couriers like JNE/J&T).
|
|
- May require extremely specific address fragments to calculate rates accurately.
|
|
- *International APIs (UPS/FedEx):* Usually rely on **Postal Code**.
|
|
- *Indonesian APIs (RajaOngkir/Biteship):* Usually rely on **Subdistrict IDs**.
|
|
|
|
### The Problem
|
|
|
|
If a customer uses an Indonesian shipping plugin, the plugin may remove default WooCommerce fields (like `city`/`state`) and inject a custom dropdown that searches an API for a destination. The plugin then writes that Destination ID to the WooCommerce Session and forces shipping recalculation.
|
|
|
|
Because WooNooW's SPA bypasses normal PHP frontend rendering, native injected dropdowns from external plugins will not appear automatically.
|
|
|
|
---
|
|
|
|
## 2. The Shipping Bridge Pattern
|
|
|
|
To make external shipping plugins work natively within WooNooW's SPA checkout, use our Bridge Hook System.
|
|
|
|
You need 3 components:
|
|
1. **REST API Endpoint:** To let the SPA search the provider's valid locations.
|
|
2. **Checkout Field Injection:** To inject a `searchable_select` field into the SPA order form natively.
|
|
3. **Bridge Action Hook:** To take the selected value from the order form and write it to the WooCommerce Session before shipping calculates.
|
|
|
|
### Generic Bridge Skeleton
|
|
|
|
```php
|
|
// 1. Endpoint for SPA to search locations
|
|
add_action('rest_api_init', function() {
|
|
register_rest_route('woonoow/v1', '/provider/search', [
|
|
'methods' => 'GET',
|
|
'callback' => 'my_provider_search_func',
|
|
'permission_callback' => '__return_true',
|
|
]);
|
|
});
|
|
|
|
// 2. Inject field into WooNooW SPA (native JSON structure, not HTML)
|
|
add_filter('woocommerce_checkout_fields', function($fields) {
|
|
if (!class_exists('My_Shipping_Class')) return $fields;
|
|
|
|
$fields['shipping']['shipping_provider_field'] = [
|
|
'type' => 'searchable_select',
|
|
'label' => 'Select Location',
|
|
'required' => true,
|
|
'priority' => 85,
|
|
'search_endpoint' => '/provider/search', // Relative to wp-json/woonoow/v1
|
|
'search_param' => 'search',
|
|
'min_chars' => 3,
|
|
];
|
|
return $fields;
|
|
}, 20);
|
|
|
|
// 3. Bridge Data to Provider Session
|
|
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
|
if (!class_exists('My_Shipping_Class')) return;
|
|
|
|
$val = $shipping['shipping_provider_field'] ?? null;
|
|
if ($val) {
|
|
WC()->session->set('my_provider_session_key', $val);
|
|
// Force recalc
|
|
WC()->session->set('shipping_for_package_0', false);
|
|
}
|
|
}, 10, 2);
|
|
```
|
|
|
|
---
|
|
|
|
## 3. RajaOngkir Integration Example
|
|
|
|
If your user wants RajaOngkir running natively inside the WooNooW SPA checkout, this snippet represents the "Best Practice Bridge".
|
|
|
|
*Note: Drop this into a Code Snippets plugin or functions.php*
|
|
|
|
```php
|
|
<?php
|
|
// 1. Search Endpoint
|
|
add_action('rest_api_init', function() {
|
|
register_rest_route('woonoow/v1', '/rajaongkir/destinations', [
|
|
'methods' => 'GET',
|
|
'callback' => function($req) {
|
|
$search = sanitize_text_field($req->get_param('search') ?? '');
|
|
if (strlen($search) < 3 || !class_exists('Cekongkir_API')) return [];
|
|
|
|
$api = Cekongkir_API::get_instance();
|
|
$results = $api->search_destination_api($search);
|
|
|
|
$formatted = [];
|
|
if (is_array($results)) {
|
|
foreach ($results as $r) {
|
|
$formatted[] = [
|
|
'value' => (string) ($r['id'] ?? ''),
|
|
'label' => $r['label'] ?? $r['text'] ?? '',
|
|
];
|
|
}
|
|
}
|
|
return array_slice($formatted, 0, 50);
|
|
},
|
|
'permission_callback' => '__return_true'
|
|
]);
|
|
});
|
|
|
|
// 2. Register Native SPA Field & cleanup unnecessary native fields
|
|
add_filter('woocommerce_checkout_fields', function($fields) {
|
|
if (!class_exists('Cekongkir_API')) return $fields;
|
|
|
|
// Check if store only ships to Indonesia to safely hide fallback fields
|
|
$allowed = WC()->countries->get_allowed_countries();
|
|
$indonesia_only = (count($allowed) === 1 && isset($allowed['ID']));
|
|
|
|
if ($indonesia_only) {
|
|
$fields['shipping']['shipping_country']['type'] = 'hidden';
|
|
$fields['shipping']['shipping_state']['type'] = 'hidden';
|
|
$fields['shipping']['shipping_city']['type'] = 'hidden';
|
|
$fields['shipping']['shipping_postcode']['type'] = 'hidden';
|
|
}
|
|
|
|
$dest_field = [
|
|
'type' => 'searchable_select',
|
|
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
|
|
'required' => $indonesia_only,
|
|
'priority' => 85,
|
|
'search_endpoint' => '/rajaongkir/destinations',
|
|
'search_param' => 'search',
|
|
'min_chars' => 3,
|
|
'custom_attributes' => ['data-show-for-country' => 'ID'],
|
|
];
|
|
|
|
$fields['billing']['billing_destination_id'] = $dest_field;
|
|
$fields['shipping']['shipping_destination_id'] = $dest_field;
|
|
return $fields;
|
|
}, 20);
|
|
|
|
// 3. Bridge Selection back into WooCommerce specific session key
|
|
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
|
if (!class_exists('Cekongkir_API')) return;
|
|
|
|
$country = $shipping['country'] ?? WC()->customer->get_shipping_country();
|
|
if ($country !== 'ID') return;
|
|
|
|
$dest_id = $shipping['destination_id']
|
|
?? $shipping['shipping_destination_id']
|
|
?? $shipping['billing_destination_id'] ?? null;
|
|
|
|
if ($dest_id) {
|
|
WC()->session->set('selected_destination_id', intval($dest_id));
|
|
WC()->session->set('shipping_for_package_0', false);
|
|
}
|
|
}, 10, 2);
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Custom Shipping Addons
|
|
|
|
If you do not want to use an external plugin and instead wish to build an official WooNooW extension (like `WooNooW Indonesia Shipping` with Biteship capabilities), treat it as a standard WooNooW Addon Module.
|
|
|
|
1. Develop a native React UI component using `ADDONS_GUIDE.md`.
|
|
2. Intercept `woonoow_order_form_after_shipping` via Hook.
|
|
3. Validate and handle the custom data payload via the standard WooCommerce REST endpoints.
|