diff --git a/includes/Api/OrdersController.php b/includes/Api/OrdersController.php index 8631265..24eb07b 100644 --- a/includes/Api/OrdersController.php +++ b/includes/Api/OrdersController.php @@ -97,6 +97,31 @@ class OrdersController { 'permission_callback' => function () { return current_user_can('manage_woocommerce'); }, ]); + // Calculate shipping rates for given address and cart + register_rest_route('woonoow/v1', '/shipping/calculate', [ + 'methods' => 'POST', + 'callback' => [__CLASS__, 'calculate_shipping'], + 'permission_callback' => function () { return current_user_can('manage_woocommerce'); }, + 'args' => [ + 'items' => [ 'type' => 'array', 'required' => true ], + 'shipping' => [ 'type' => 'object', 'required' => true ], + ], + ]); + + // Calculate order preview with taxes and totals + register_rest_route('woonoow/v1', '/orders/preview', [ + 'methods' => 'POST', + 'callback' => [__CLASS__, 'preview_order'], + 'permission_callback' => function () { return current_user_can('manage_woocommerce'); }, + 'args' => [ + 'items' => [ 'type' => 'array', 'required' => true ], + 'billing' => [ 'type' => 'object', 'required' => false ], + 'shipping' => [ 'type' => 'object', 'required' => false ], + 'shipping_method' => [ 'type' => 'string', 'required' => false ], + 'coupons' => [ 'type' => 'array', 'required' => false ], + ], + ]); + // Retry payment processing for an order register_rest_route('woonoow/v1', '/orders/(?P\d+)/retry-payment', [ 'methods' => 'POST', @@ -1197,6 +1222,176 @@ class OrdersController { return new \WP_REST_Response( $rows, 200 ); } + /** + * Calculate shipping rates for given cart and address + * POST /woonoow/v1/shipping/calculate + */ + public static function calculate_shipping( WP_REST_Request $req ) : \WP_REST_Response { + $p = $req->get_json_params(); + $items = is_array( $p['items'] ?? null ) ? $p['items'] : []; + $shipping = is_array( $p['shipping'] ?? null ) ? $p['shipping'] : []; + + if ( empty( $items ) ) { + return new \WP_REST_Response( [ 'error' => 'items_required' ], 400 ); + } + + try { + // Create a temporary cart to calculate shipping + WC()->cart->empty_cart(); + + // Add items to cart + foreach ( $items as $item ) { + $product_id = absint( $item['product_id'] ?? 0 ); + $qty = max( 1, absint( $item['qty'] ?? 1 ) ); + if ( $product_id ) { + WC()->cart->add_to_cart( $product_id, $qty ); + } + } + + // Set customer shipping address + if ( ! empty( $shipping ) ) { + WC()->customer->set_shipping_country( $shipping['country'] ?? '' ); + WC()->customer->set_shipping_state( $shipping['state'] ?? '' ); + WC()->customer->set_shipping_postcode( $shipping['postcode'] ?? '' ); + WC()->customer->set_shipping_city( $shipping['city'] ?? '' ); + WC()->customer->set_shipping_address( $shipping['address_1'] ?? '' ); + WC()->customer->set_shipping_address_2( $shipping['address_2'] ?? '' ); + } + + // Calculate shipping + WC()->cart->calculate_shipping(); + WC()->cart->calculate_totals(); + + // Get available shipping packages and rates + $packages = WC()->shipping()->get_packages(); + $methods = []; + + foreach ( $packages as $package_key => $package ) { + $rates = $package['rates'] ?? []; + + foreach ( $rates as $rate_id => $rate ) { + /** @var \WC_Shipping_Rate $rate */ + $methods[] = [ + 'id' => $rate_id, + 'method_id' => $rate->get_method_id(), + 'instance_id' => $rate->get_instance_id(), + 'label' => $rate->get_label(), + 'cost' => (float) $rate->get_cost(), + 'taxes' => $rate->get_taxes(), + 'meta_data' => $rate->get_meta_data(), + ]; + } + } + + // Clean up + WC()->cart->empty_cart(); + + return new \WP_REST_Response( [ + 'methods' => $methods, + 'has_methods' => ! empty( $methods ), + ], 200 ); + + } catch ( \Throwable $e ) { + // Clean up on error + WC()->cart->empty_cart(); + + return new \WP_REST_Response( [ + 'error' => 'calculation_failed', + 'message' => $e->getMessage(), + ], 500 ); + } + } + + /** + * Preview order totals (subtotal, shipping, tax, total) + * POST /woonoow/v1/orders/preview + */ + public static function preview_order( WP_REST_Request $req ) : \WP_REST_Response { + $p = $req->get_json_params(); + $items = is_array( $p['items'] ?? null ) ? $p['items'] : []; + $billing = is_array( $p['billing'] ?? null ) ? $p['billing'] : []; + $shipping = is_array( $p['shipping'] ?? null ) ? $p['shipping'] : []; + $shipping_method = sanitize_text_field( $p['shipping_method'] ?? '' ); + $coupons = array_filter( array_map( 'sanitize_text_field', (array) ( $p['coupons'] ?? [] ) ) ); + + if ( empty( $items ) ) { + return new \WP_REST_Response( [ 'error' => 'items_required' ], 400 ); + } + + try { + // Use WooCommerce cart for calculation + WC()->cart->empty_cart(); + + // Add items + foreach ( $items as $item ) { + $product_id = absint( $item['product_id'] ?? 0 ); + $qty = max( 1, absint( $item['qty'] ?? 1 ) ); + if ( $product_id ) { + WC()->cart->add_to_cart( $product_id, $qty ); + } + } + + // Set customer addresses for tax calculation + if ( ! empty( $billing ) ) { + WC()->customer->set_billing_country( $billing['country'] ?? '' ); + WC()->customer->set_billing_state( $billing['state'] ?? '' ); + WC()->customer->set_billing_postcode( $billing['postcode'] ?? '' ); + WC()->customer->set_billing_city( $billing['city'] ?? '' ); + } + + if ( ! empty( $shipping ) ) { + WC()->customer->set_shipping_country( $shipping['country'] ?? '' ); + WC()->customer->set_shipping_state( $shipping['state'] ?? '' ); + WC()->customer->set_shipping_postcode( $shipping['postcode'] ?? '' ); + WC()->customer->set_shipping_city( $shipping['city'] ?? '' ); + } + + // Apply coupons + foreach ( $coupons as $code ) { + WC()->cart->apply_coupon( $code ); + } + + // Set chosen shipping method if provided + if ( $shipping_method ) { + WC()->session->set( 'chosen_shipping_methods', [ $shipping_method ] ); + } + + // Calculate totals + WC()->cart->calculate_shipping(); + WC()->cart->calculate_totals(); + + // Get totals + $result = [ + 'subtotal' => (float) WC()->cart->get_subtotal(), + 'subtotal_tax' => (float) WC()->cart->get_subtotal_tax(), + 'discount_total' => (float) WC()->cart->get_discount_total(), + 'discount_tax' => (float) WC()->cart->get_discount_tax(), + 'shipping_total' => (float) WC()->cart->get_shipping_total(), + 'shipping_tax' => (float) WC()->cart->get_shipping_tax(), + 'cart_contents_tax' => (float) WC()->cart->get_cart_contents_tax(), + 'fee_tax' => (float) WC()->cart->get_fee_tax(), + 'total_tax' => (float) WC()->cart->get_total_tax(), + 'total' => (float) WC()->cart->get_total( 'edit' ), + 'tax_display_cart' => get_option( 'woocommerce_tax_display_cart', 'excl' ), + 'prices_include_tax' => wc_prices_include_tax(), + ]; + + // Clean up + WC()->cart->empty_cart(); + + return new \WP_REST_Response( $result, 200 ); + + } catch ( \Throwable $e ) { + // Clean up on error + WC()->cart->empty_cart(); + + return new \WP_REST_Response( [ + 'error' => 'preview_failed', + 'message' => $e->getMessage(), + ], 500 ); + } + } + public static function countries( WP_REST_Request $req ) : \WP_REST_Response { $rows = []; $states = [];