1. Hidden fields now properly hidden in SPA
- Added billing_postcode and shipping_postcode to isFieldHidden checks
- Fields with type='hidden' from PHP now conditionally rendered
2. Coupons now applied to order total
- Added coupons array to order submission payload
- CartController now calls calculate_totals() before reading discounts
- Returns per-coupon discount amounts {code, discount, type}
3. Shipping now applied to order total
- Already handled in submit() via find_shipping_rate_for_order
- Frontend now sends shipping_method in payload
4. Order details now include shipping/tracking info
- checkout/order/{id} API includes shipping_lines, tracking_number, tracking_url
- account/orders/{id} API includes same shipping/tracking fields
- Tracking info read from multiple plugin meta keys
5. Thank you/OrderDetails page shows shipping method and AWB
- Shipping Method section with courier name and cost
- AWB tracking for processing/completed orders with Track Shipment button
10 KiB
10 KiB
Rajaongkir Integration with WooNooW SPA
This guide explains how to integrate Rajaongkir's destination selector with WooNooW's customer checkout SPA.
Prerequisites
Before using this integration:
- Rajaongkir Plugin Installed & Active
- WooCommerce Shipping Zone Configured
- Go to: WC → Settings → Shipping → Zones
- Add Rajaongkir method to your Indonesia zone
- Valid API Key (Check in Rajaongkir settings)
- Couriers Selected (In Rajaongkir settings)
Code Snippet
Add this to Code Snippets or WPCodebox:
<?php
/**
* Rajaongkir Bridge for WooNooW SPA Checkout
*
* Enables searchable destination field in WooNooW checkout
* and bridges data to Rajaongkir plugin.
*/
// ============================================================
// 1. REST API Endpoint: Search destinations via Rajaongkir API
// ============================================================
add_action('rest_api_init', function() {
register_rest_route('woonoow/v1', '/rajaongkir/destinations', [
'methods' => 'GET',
'callback' => 'woonoow_rajaongkir_search_destinations',
'permission_callback' => '__return_true',
'args' => [
'search' => [
'required' => false,
'type' => 'string',
],
],
]);
});
function woonoow_rajaongkir_search_destinations($request) {
$search = sanitize_text_field($request->get_param('search') ?? '');
if (strlen($search) < 3) {
return [];
}
// Check if Rajaongkir plugin is active
if (!class_exists('Cekongkir_API')) {
return new WP_Error('rajaongkir_missing', 'Rajaongkir plugin not active', ['status' => 400]);
}
// Use Rajaongkir's API class for the search
// NOTE: Method is search_destination_api() not search_destination()
$api = Cekongkir_API::get_instance();
$results = $api->search_destination_api($search);
if (is_wp_error($results)) {
error_log('Rajaongkir search error: ' . $results->get_error_message());
return [];
}
if (!is_array($results)) {
error_log('Rajaongkir search returned non-array: ' . print_r($results, true));
return [];
}
// Format for WooNooW's SearchableSelect component
$formatted = [];
foreach ($results as $r) {
$formatted[] = [
'value' => (string) ($r['id'] ?? ''),
'label' => $r['label'] ?? $r['text'] ?? '',
];
}
// Limit results
return array_slice($formatted, 0, 50);
}
// ============================================================
// 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
if (!class_exists('Cekongkir_API')) {
return $fields;
}
// Check if store sells to Indonesia (check allowed countries)
$allowed = WC()->countries->get_allowed_countries();
if (!isset($allowed['ID'])) {
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;
}
if (isset($fields['billing']['billing_postcode'])) {
$fields['billing']['billing_postcode']['type'] = 'hidden';
$fields['billing']['billing_postcode']['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;
}
if (isset($fields['shipping']['shipping_postcode'])) {
$fields['shipping']['shipping_postcode']['type'] = 'hidden';
$fields['shipping']['shipping_postcode']['required'] = false;
}
}
// Destination field definition (reused for billing and shipping)
$destination_field = [
'type' => 'searchable_select',
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
'required' => $indonesia_only, // Required if Indonesia only
'priority' => 85,
'class' => ['form-row-wide'],
'placeholder' => __('Search destination...', 'woonoow'),
// WooNooW-specific: API endpoint configuration
// NOTE: Path is relative to /wp-json/woonoow/v1
'search_endpoint' => '/rajaongkir/destinations',
'search_param' => 'search',
'min_chars' => 3,
// Custom attribute to indicate this is for Indonesia only
'custom_attributes' => [
'data-show-for-country' => 'ID',
],
];
// Add to billing (used when "Ship to different address" is NOT checked)
$fields['billing']['billing_destination_id'] = $destination_field;
// Add to shipping (used when "Ship to different address" IS checked)
$fields['shipping']['shipping_destination_id'] = $destination_field;
return $fields;
}, 20); // Priority 20 to run after Rajaongkir's own filter
// ============================================================
// 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) {
// 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 Rajaongkir for Indonesia
$country = $shipping['country'] ?? WC()->customer->get_shipping_country();
if ($country !== 'ID') {
// Clear destination for non-Indonesia
WC()->session->__unset('selected_destination_id');
WC()->session->__unset('selected_destination_label');
return;
}
// 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)) {
return;
}
// Set session for Rajaongkir
WC()->session->set('selected_destination_id', intval($destination_id));
// 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));
}
// Clear shipping cache to force recalculation
WC()->session->set('shipping_for_package_0', false);
}, 10, 2);
Testing
1. Test the API Endpoint
After adding the snippet:
GET /wp-json/woonoow/v1/rajaongkir/destinations?search=bandung
Should return:
[
{"value": "1234", "label": "Jawa Barat, Bandung, Kota"},
...
]
2. Test Checkout Flow
- Add product to cart
- Go to SPA checkout:
/store/checkout - Set country to Indonesia
- "Destination" field should appear
- Type 2+ characters to search
- Select a destination
- Rajaongkir rates should appear
Troubleshooting
API returns empty?
Check debug.log for errors:
// Added logging in the search function
error_log('Rajaongkir search error: ...');
Common issues:
- Invalid Rajaongkir API key
- Rajaongkir plugin not active
- API quota exceeded
Field not appearing?
- Ensure snippet is active
- Check if store sells to Indonesia
- Check browser console for JS errors
Rajaongkir rates not showing?
- Check session is set:
add_action('woonoow/shipping/before_calculate', function($shipping) {
error_log('Shipping data: ' . print_r($shipping, true));
}, 5);
- Check Rajaongkir is enabled in shipping zone
Known Limitations
-
Field visibility: Currently field always shows for checkout. Future improvement: hide in React when country ≠ ID.
-
Session timing: Must select destination before calculating shipping.
Related Documentation
- SHIPPING_INTEGRATION.md - General shipping patterns
- HOOKS_REGISTRY.md - WooNooW hooks reference