Files
WooNooW/SHIPPING_BRIDGE_PATTERN.md
Dwindi Ramadhana 26faa008cb 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
2026-01-08 15:20:25 +07:00

8.6 KiB

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
/**
 * [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

// Endpoint
register_rest_route('woonoow/v1', '/sicepat/destinations', ...);

// Session key
WC()->session->set('sicepat_destination_code', $code);

Example 2: JNE Direct API

// 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