# 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