feat(checkout): implement dynamic shipping rate fetching
Backend: - Added /checkout/shipping-rates REST endpoint - Returns available shipping methods from matching zone - Triggers woonoow/shipping/before_calculate hook for Rajaongkir Frontend: - Added ShippingRate interface and state - Added fetchShippingRates with 500ms debounce - Replaced hardcoded shipping options with dynamic rates - Added loading and empty state handling - Added shipping_method to order submission payload This fixes: - Rajaongkir rates not appearing (now fetched from API) - Free shipping showing despite disabled (now from WC zones)
This commit is contained in:
@@ -50,6 +50,12 @@ class CheckoutController {
|
||||
],
|
||||
],
|
||||
]);
|
||||
// Get available shipping rates for given address
|
||||
register_rest_route($namespace, '/checkout/shipping-rates', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ new self(), 'get_shipping_rates' ],
|
||||
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -773,4 +779,123 @@ class CheckoutController {
|
||||
'default_country' => $default_country,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available shipping rates for given address
|
||||
* POST /checkout/shipping-rates
|
||||
* Body: { shipping: { country, state, city, postcode, destination_id? }, items: [...] }
|
||||
*/
|
||||
public function get_shipping_rates(WP_REST_Request $r): array {
|
||||
$payload = $r->get_json_params();
|
||||
$shipping = $payload['shipping'] ?? [];
|
||||
$items = $payload['items'] ?? [];
|
||||
|
||||
$country = wc_clean($shipping['country'] ?? '');
|
||||
$state = wc_clean($shipping['state'] ?? '');
|
||||
$city = wc_clean($shipping['city'] ?? '');
|
||||
$postcode = wc_clean($shipping['postcode'] ?? '');
|
||||
|
||||
if (empty($country)) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'rates' => [],
|
||||
'message' => 'Country is required',
|
||||
];
|
||||
}
|
||||
|
||||
// Trigger hook for plugins to set session data (e.g., Rajaongkir destination_id)
|
||||
do_action('woonoow/shipping/before_calculate', $shipping, $items);
|
||||
|
||||
// Set customer location for shipping calculation
|
||||
if (WC()->customer) {
|
||||
WC()->customer->set_shipping_country($country);
|
||||
WC()->customer->set_shipping_state($state);
|
||||
WC()->customer->set_shipping_city($city);
|
||||
WC()->customer->set_shipping_postcode($postcode);
|
||||
}
|
||||
|
||||
// Build package for shipping calculation
|
||||
$contents = [];
|
||||
$contents_cost = 0;
|
||||
foreach ($items as $item) {
|
||||
$product = wc_get_product($item['product_id'] ?? 0);
|
||||
if (!$product) continue;
|
||||
$qty = max(1, (int)($item['quantity'] ?? $item['qty'] ?? 1));
|
||||
$price = (float) wc_get_price_to_display($product);
|
||||
$contents[] = [
|
||||
'data' => $product,
|
||||
'quantity' => $qty,
|
||||
'line_total' => $price * $qty,
|
||||
];
|
||||
$contents_cost += $price * $qty;
|
||||
}
|
||||
|
||||
$package = [
|
||||
'destination' => [
|
||||
'country' => $country,
|
||||
'state' => $state,
|
||||
'city' => $city,
|
||||
'postcode' => $postcode,
|
||||
],
|
||||
'contents' => $contents,
|
||||
'contents_cost' => $contents_cost,
|
||||
'applied_coupons' => [],
|
||||
'user' => ['ID' => get_current_user_id()],
|
||||
];
|
||||
|
||||
// Get matching shipping zone
|
||||
$zone = WC_Shipping_Zones::get_zone_matching_package($package);
|
||||
if (!$zone) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'rates' => [],
|
||||
'message' => 'No shipping zone matches your location',
|
||||
];
|
||||
}
|
||||
|
||||
// Get enabled shipping methods from zone
|
||||
$methods = $zone->get_shipping_methods(true);
|
||||
$rates = [];
|
||||
|
||||
foreach ($methods as $method) {
|
||||
// Check if method has rates (some methods like live rate need to calculate)
|
||||
if (method_exists($method, 'get_rates_for_package')) {
|
||||
$method_rates = $method->get_rates_for_package($package);
|
||||
foreach ($method_rates as $rate) {
|
||||
$rates[] = [
|
||||
'id' => $rate->get_id(),
|
||||
'label' => $rate->get_label(),
|
||||
'cost' => (float) $rate->get_cost(),
|
||||
'method_id' => $rate->get_method_id(),
|
||||
'instance_id' => $rate->get_instance_id(),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Fallback for simple methods
|
||||
$method_id = $method->id . ':' . $method->get_instance_id();
|
||||
$cost = 0;
|
||||
|
||||
// Try to get cost from method
|
||||
if (isset($method->cost)) {
|
||||
$cost = (float) $method->cost;
|
||||
} elseif (method_exists($method, 'get_option')) {
|
||||
$cost = (float) $method->get_option('cost', 0);
|
||||
}
|
||||
|
||||
$rates[] = [
|
||||
'id' => $method_id,
|
||||
'label' => $method->get_title(),
|
||||
'cost' => $cost,
|
||||
'method_id' => $method->id,
|
||||
'instance_id' => $method->get_instance_id(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'rates' => $rates,
|
||||
'zone_name' => $zone->get_zone_name(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user