'POST', 'callback' => [ __CLASS__, 'handle_request' ], 'permission_callback' => [ __CLASS__, 'permission_check' ], 'args' => [ 'order_id' => [ 'required' => true, 'validate_callback' => function( $value ) { return is_numeric( $value ) && (int) $value > 0; }, 'sanitize_callback' => 'absint', ], // Single tracking number — kept for backward compatibility. 'tracking_number' => [ 'required' => false, 'sanitize_callback' => 'sanitize_text_field', ], // Multiple tracking numbers — preferred for v2 clients. 'tracking_numbers' => [ 'required' => false, ], 'carrier' => [ 'required' => false, 'sanitize_callback' => 'sanitize_text_field', ], ], ] ); } public static function permission_check( WP_REST_Request $request ) { return self::check_api_key( $request ); } private static function check_api_key( $request ) { $headers = $request->get_headers(); $received_key = null; if ( isset( $headers['x-aisp-key'][0] ) ) { $received_key = $headers['x-aisp-key'][0]; } elseif ( isset( $headers['x_aisp_key'][0] ) ) { $received_key = $headers['x_aisp_key'][0]; } if ( empty( $received_key ) ) { return new WP_Error( 'invalid_key', 'Missing API key.', [ 'status' => 403 ] ); } $received_key = sanitize_text_field( $received_key ); $received_key = rtrim( $received_key, ":\n\r\t " ); $stored_key = get_option( AISP_Order_Tracking_Notification_Settings::OPTION_KEY ); if ( empty( $stored_key ) ) { return new WP_Error( 'no_key', 'API key not configured on server.', [ 'status' => 500 ] ); } if ( ! hash_equals( $stored_key, $received_key ) ) { return new WP_Error( 'invalid_key', 'Invalid API key.', [ 'status' => 403 ] ); } return true; } public static function handle_request( WP_REST_Request $request ) { $order_id = $request->get_param( 'order_id' ); $tracking_number = $request->get_param( 'tracking_number' ); // string or null $tracking_numbers = $request->get_param( 'tracking_numbers' ); // array or null $carrier_raw = $request->get_param( 'carrier' ); // Build a clean array of tracking numbers from whichever param was sent. $trackings = array(); if ( ! empty( $tracking_numbers ) && is_array( $tracking_numbers ) ) { // v2: array of tracking numbers. $trackings = array_values( array_filter( array_map( 'sanitize_text_field', $tracking_numbers ), function( $v ) { return '' !== trim( $v ); } ) ); } elseif ( ! empty( $tracking_number ) ) { // v1 backward compatibility: single tracking number string. $trackings = array( sanitize_text_field( $tracking_number ) ); } if ( ! $order_id || empty( $trackings ) ) { return new WP_Error( 'missing_fields', 'order_id and at least one tracking number (tracking_number or tracking_numbers) are required.', [ 'status' => 400 ] ); } $order = wc_get_order( $order_id ); if ( ! $order ) { return new WP_Error( 'order_not_found', 'Order not found.', [ 'status' => 404 ] ); } $carrier = aisp_order_tracking_notification_Helpers::aisp_normalize_carrier( $carrier_raw ); // Save carrier and tracking array. $order->update_meta_data( aisp_order_tracking_notification_Helpers::META_CARRIER, $carrier ); $order->update_meta_data( aisp_order_tracking_notification_Helpers::META_TRACKING, wp_json_encode( $trackings ) ); $order->save(); // Add internal order note. $summary = aisp_order_tracking_notification_Helpers::aisp_build_tracking_note_summary( $carrier, $trackings ); aisp_order_tracking_notification_Helpers::aisp_add_order_note( $order, sprintf( // translators: %s: carrier + tracking numbers summary. __( 'Tracking received via API — %s', 'aisp-order-tracking-notification' ), $summary ) ); if ( AISP_Order_Tracking_Notification_Settings::is_pickup_order( $order ) ) { return [ 'status' => 'ignored', 'order_id' => $order_id, 'reason' => 'pickup_order', 'message' => 'Pickup order detected. Tracking saved, order not completed.', ]; } $order->update_status( 'completed', 'Tracking received via AISP Order Tracking Notification API' ); return [ 'status' => 'success', 'order_id' => $order_id, 'tracking_numbers' => $trackings, 'carrier_normalized' => $carrier, 'message' => 'Tracking saved and order marked as completed.', ]; } }