1. Hidden fields now respected in SPA
- Added isFieldHidden helper to check if PHP sets type to 'hidden'
- Country/state/city fields conditionally rendered based on API response
- Set default country value for hidden country fields (Indonesia-only stores)
2. Coupon discount now shows correct amount
- Added calculate_totals() before reading discount
- Changed coupons response to include {code, discount, type} per coupon
- Added discount_total at root level for frontend compatibility
3. Order details page now shows shipping info and AWB tracking
- Added shipping_lines, tracking_number, tracking_url to Order interface
- Added Shipping Method section with courier name and cost
- Added AWB tracking section for processing/completed orders
- Track Shipment button with link to tracking URL
392 lines
13 KiB
PHP
392 lines
13 KiB
PHP
<?php
|
|
namespace WooNooW\Api\Controllers;
|
|
|
|
use WP_REST_Controller;
|
|
use WP_REST_Response;
|
|
use WP_Error;
|
|
|
|
/**
|
|
* Cart Controller
|
|
* Handles cart operations via REST API
|
|
*/
|
|
class CartController extends WP_REST_Controller {
|
|
|
|
/**
|
|
* Register routes
|
|
*/
|
|
public function register_routes() {
|
|
$namespace = 'woonoow/v1';
|
|
|
|
// Get cart
|
|
register_rest_route($namespace, '/cart', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'get_cart'],
|
|
'permission_callback' => '__return_true',
|
|
]);
|
|
|
|
// Add to cart
|
|
register_rest_route($namespace, '/cart/add', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'add_to_cart'],
|
|
'permission_callback' => '__return_true',
|
|
'args' => [
|
|
'product_id' => [
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'quantity' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'default' => 1,
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'variation_id' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
]);
|
|
|
|
// Update cart item
|
|
register_rest_route($namespace, '/cart/update', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'update_cart_item'],
|
|
'permission_callback' => '__return_true',
|
|
'args' => [
|
|
'cart_item_key' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
],
|
|
'quantity' => [
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
]);
|
|
|
|
// Remove from cart
|
|
register_rest_route($namespace, '/cart/remove', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'remove_from_cart'],
|
|
'permission_callback' => '__return_true',
|
|
'args' => [
|
|
'cart_item_key' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
],
|
|
],
|
|
]);
|
|
|
|
// Clear cart
|
|
register_rest_route($namespace, '/cart/clear', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'clear_cart'],
|
|
'permission_callback' => '__return_true',
|
|
]);
|
|
|
|
// Apply coupon
|
|
register_rest_route($namespace, '/cart/apply-coupon', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'apply_coupon'],
|
|
'permission_callback' => '__return_true',
|
|
'args' => [
|
|
'coupon_code' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
]);
|
|
|
|
// Remove coupon
|
|
register_rest_route($namespace, '/cart/remove-coupon', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'remove_coupon'],
|
|
'permission_callback' => '__return_true',
|
|
'args' => [
|
|
'coupon_code' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get cart contents
|
|
*/
|
|
public function get_cart($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
$cart = WC()->cart;
|
|
|
|
// Calculate totals to ensure discounts are computed
|
|
$cart->calculate_totals();
|
|
|
|
// Format coupons with discount amounts
|
|
$coupons_with_discounts = [];
|
|
foreach ($cart->get_applied_coupons() as $coupon_code) {
|
|
$coupon = new \WC_Coupon($coupon_code);
|
|
$discount = $cart->get_coupon_discount_amount($coupon_code);
|
|
$coupons_with_discounts[] = [
|
|
'code' => $coupon_code,
|
|
'discount' => (float) $discount,
|
|
'type' => $coupon->get_discount_type(),
|
|
];
|
|
}
|
|
|
|
return new WP_REST_Response([
|
|
'items' => $this->format_cart_items($cart->get_cart()),
|
|
'totals' => [
|
|
'subtotal' => $cart->get_subtotal(),
|
|
'subtotal_tax' => $cart->get_subtotal_tax(),
|
|
'discount_total' => $cart->get_discount_total(),
|
|
'discount_tax' => $cart->get_discount_tax(),
|
|
'shipping_total' => $cart->get_shipping_total(),
|
|
'shipping_tax' => $cart->get_shipping_tax(),
|
|
'fee_total' => $cart->get_fee_total(),
|
|
'fee_tax' => $cart->get_fee_tax(),
|
|
'total' => $cart->get_total(''),
|
|
'total_tax' => $cart->get_total_tax(),
|
|
],
|
|
'coupons' => $coupons_with_discounts,
|
|
'discount_total' => (float) $cart->get_discount_total(), // Root level for frontend
|
|
'needs_shipping' => $cart->needs_shipping(),
|
|
'needs_payment' => $cart->needs_payment(),
|
|
'item_count' => $cart->get_cart_contents_count(),
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Add product to cart
|
|
*/
|
|
public function add_to_cart($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
$product_id = $request->get_param('product_id');
|
|
$quantity = $request->get_param('quantity') ?: 1;
|
|
$variation_id = $request->get_param('variation_id') ?: 0;
|
|
|
|
// Validate product
|
|
$product = wc_get_product($product_id);
|
|
if (!$product) {
|
|
return new WP_Error('invalid_product', 'Product not found', ['status' => 404]);
|
|
}
|
|
|
|
// Check stock
|
|
if (!$product->is_in_stock()) {
|
|
return new WP_Error('out_of_stock', 'Product is out of stock', ['status' => 400]);
|
|
}
|
|
|
|
// Add to cart
|
|
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id);
|
|
|
|
if (!$cart_item_key) {
|
|
return new WP_Error('add_to_cart_failed', 'Failed to add product to cart', ['status' => 400]);
|
|
}
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'cart_item_key' => $cart_item_key,
|
|
'message' => sprintf('%s has been added to your cart.', $product->get_name()),
|
|
'cart' => $this->get_cart($request)->data,
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Update cart item quantity
|
|
*/
|
|
public function update_cart_item($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
$cart_item_key = $request->get_param('cart_item_key');
|
|
$quantity = $request->get_param('quantity');
|
|
|
|
// Validate cart item
|
|
$cart = WC()->cart->get_cart();
|
|
if (!isset($cart[$cart_item_key])) {
|
|
return new WP_Error('invalid_cart_item', 'Cart item not found', ['status' => 404]);
|
|
}
|
|
|
|
// Update quantity
|
|
$updated = WC()->cart->set_quantity($cart_item_key, $quantity);
|
|
|
|
if (!$updated) {
|
|
return new WP_Error('update_failed', 'Failed to update cart item', ['status' => 400]);
|
|
}
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'message' => 'Cart updated successfully',
|
|
'cart' => $this->get_cart($request)->data,
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Remove item from cart
|
|
*/
|
|
public function remove_from_cart($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
$cart_item_key = $request->get_param('cart_item_key');
|
|
|
|
// Validate cart item
|
|
$cart = WC()->cart->get_cart();
|
|
if (!isset($cart[$cart_item_key])) {
|
|
return new WP_Error('invalid_cart_item', 'Cart item not found', ['status' => 404]);
|
|
}
|
|
|
|
// Remove item
|
|
$removed = WC()->cart->remove_cart_item($cart_item_key);
|
|
|
|
if (!$removed) {
|
|
return new WP_Error('remove_failed', 'Failed to remove cart item', ['status' => 400]);
|
|
}
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'message' => 'Item removed from cart',
|
|
'cart' => $this->get_cart($request)->data,
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Clear cart
|
|
*/
|
|
public function clear_cart($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
WC()->cart->empty_cart();
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'message' => 'Cart cleared successfully',
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Apply coupon
|
|
*/
|
|
public function apply_coupon($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
$coupon_code = $request->get_param('coupon_code');
|
|
|
|
// Apply coupon
|
|
$applied = WC()->cart->apply_coupon($coupon_code);
|
|
|
|
if (!$applied) {
|
|
return new WP_Error('coupon_failed', 'Failed to apply coupon', ['status' => 400]);
|
|
}
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'message' => 'Coupon applied successfully',
|
|
'cart' => $this->get_cart($request)->data,
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Remove coupon
|
|
*/
|
|
public function remove_coupon($request) {
|
|
if (!function_exists('WC')) {
|
|
return new WP_Error('woocommerce_not_active', 'WooCommerce is not active', ['status' => 500]);
|
|
}
|
|
|
|
// Ensure cart is initialized
|
|
if (is_null(WC()->cart)) {
|
|
wc_load_cart();
|
|
}
|
|
|
|
$coupon_code = $request->get_param('coupon_code');
|
|
|
|
// Remove coupon
|
|
$removed = WC()->cart->remove_coupon($coupon_code);
|
|
|
|
if (!$removed) {
|
|
return new WP_Error('coupon_remove_failed', 'Failed to remove coupon', ['status' => 400]);
|
|
}
|
|
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'message' => 'Coupon removed successfully',
|
|
'cart' => $this->get_cart($request)->data,
|
|
], 200);
|
|
}
|
|
|
|
/**
|
|
* Format cart items for response
|
|
*/
|
|
private function format_cart_items($cart_items) {
|
|
$formatted = [];
|
|
|
|
foreach ($cart_items as $cart_item_key => $cart_item) {
|
|
$product = $cart_item['data'];
|
|
|
|
$formatted[] = [
|
|
'key' => $cart_item_key,
|
|
'product_id' => $cart_item['product_id'],
|
|
'variation_id' => $cart_item['variation_id'],
|
|
'quantity' => $cart_item['quantity'],
|
|
'name' => $product->get_name(),
|
|
'price' => $product->get_price(),
|
|
'subtotal' => $cart_item['line_subtotal'],
|
|
'total' => $cart_item['line_total'],
|
|
'image' => wp_get_attachment_image_url($product->get_image_id(), 'thumbnail'),
|
|
'permalink' => $product->get_permalink(),
|
|
'virtual' => $product->is_virtual(),
|
|
'downloadable' => $product->is_downloadable(),
|
|
];
|
|
}
|
|
|
|
return $formatted;
|
|
}
|
|
}
|