From 26faa008cb9d9b38066aed7d7227084f55a7f61d Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Thu, 8 Jan 2026 15:20:25 +0700 Subject: [PATCH] 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 --- RAJAONGKIR_INTEGRATION.md | 62 ++++++++++- SHIPPING_BRIDGE_PATTERN.md | 219 +++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 SHIPPING_BRIDGE_PATTERN.md diff --git a/RAJAONGKIR_INTEGRATION.md b/RAJAONGKIR_INTEGRATION.md index 2dba5d3..047c69f 100644 --- a/RAJAONGKIR_INTEGRATION.md +++ b/RAJAONGKIR_INTEGRATION.md @@ -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)); diff --git a/SHIPPING_BRIDGE_PATTERN.md b/SHIPPING_BRIDGE_PATTERN.md new file mode 100644 index 0000000..b8d6e5f --- /dev/null +++ b/SHIPPING_BRIDGE_PATTERN.md @@ -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 + '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