WP_REST_Server::CREATABLE, 'callback' => [$this, 'handle_order_update'], 'permission_callback' => [$this, 'verify_api_key'], 'args' => [ 'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'], 'status' => ['required' => false, 'sanitize_callback' => 'sanitize_text_field'], 'tracking_number' => ['required' => false, 'sanitize_callback' => 'sanitize_text_field'], 'shipping_carrier' => ['required' => false, 'sanitize_callback' => 'sanitize_text_field'], ], ]); register_rest_route($namespace, '/order/invoice', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'handle_order_invoice'], 'permission_callback' => [$this, 'verify_api_key'], 'args' => [ 'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'], 'pdf_data' => ['required' => true], 'filename' => ['required' => false, 'sanitize_callback' => 'sanitize_file_name'], ], ]); register_rest_route($namespace, '/order/delivery', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'handle_order_delivery'], 'permission_callback' => [$this, 'verify_api_key'], 'args' => [ 'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'], 'pdf_data' => ['required' => true], 'filename' => ['required' => false, 'sanitize_callback' => 'sanitize_file_name'], ], ]); register_rest_route($namespace, '/order/messages', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'handle_order_messages'], 'permission_callback' => [$this, 'verify_api_key'], 'args' => [ 'order_id' => ['required' => true, 'validate_callback' => 'is_numeric'], 'messages' => ['required' => true], ], ]); } /** * Validate Bearer token from Authorization header. */ public function verify_api_key(WP_REST_Request $request): bool|WP_Error { $auth_header = $request->get_header('authorization'); if (empty($auth_header) || !str_starts_with($auth_header, 'Bearer ')) { return new WP_Error('missing_auth', __('Authorization header missing or malformed.', 'fusion-woodoo'), ['status' => 401]); } $provided_key = trim(substr($auth_header, 7)); $stored_key = get_option('fusion_woodoo_api_key', ''); if (empty($stored_key) || !hash_equals($stored_key, $provided_key)) { return new WP_Error('invalid_api_key', __('Invalid API key.', 'fusion-woodoo'), ['status' => 403]); } return true; } /** * POST /order/update — update order status and tracking info. */ public function handle_order_update(WP_REST_Request $request): WP_REST_Response|WP_Error { $order_id = (int) $request->get_param('order_id'); $order = wc_get_order($order_id); if (!$order) { return new WP_Error('order_not_found', __('Order not found.', 'fusion-woodoo'), ['status' => 404]); } if ($status = $request->get_param('status')) { $order->update_meta_data('_odoo_order_status', sanitize_text_field($status)); } if ($tracking = $request->get_param('tracking_number')) { $order->update_meta_data('_odoo_tracking_number', sanitize_text_field($tracking)); } if ($carrier = $request->get_param('shipping_carrier')) { $order->update_meta_data('_odoo_shipping_carrier', sanitize_text_field($carrier)); } $order->save(); return rest_ensure_response(['success' => true, 'order_id' => $order_id]); } /** * POST /order/invoice — store base64-encoded invoice PDF. */ public function handle_order_invoice(WP_REST_Request $request): WP_REST_Response|WP_Error { return $this->save_pdf($request, 'invoices', '_odoo_invoice_pdf'); } /** * POST /order/delivery — store base64-encoded delivery PDF. */ public function handle_order_delivery(WP_REST_Request $request): WP_REST_Response|WP_Error { return $this->save_pdf($request, 'deliveries', '_odoo_delivery_pdf'); } /** * POST /order/messages — store Odoo messages array against the order. */ public function handle_order_messages(WP_REST_Request $request): WP_REST_Response|WP_Error { $order_id = (int) $request->get_param('order_id'); $order = wc_get_order($order_id); if (!$order) { return new WP_Error('order_not_found', __('Order not found.', 'fusion-woodoo'), ['status' => 404]); } $messages = $request->get_param('messages'); if (!is_array($messages)) { return new WP_Error('invalid_messages', __('Messages must be a JSON array.', 'fusion-woodoo'), ['status' => 400]); } // Sanitize each message entry $sanitized = array_map(function($msg) { return [ 'author' => sanitize_text_field($msg['author'] ?? ''), 'date' => sanitize_text_field($msg['date'] ?? ''), 'body' => wp_kses_post($msg['body'] ?? ''), 'type' => sanitize_text_field($msg['type'] ?? 'note'), ]; }, $messages); $order->update_meta_data('_odoo_messages', $sanitized); $order->save(); return rest_ensure_response(['success' => true, 'count' => count($sanitized)]); } /** * Shared logic to decode and store a PDF file. */ private function save_pdf(WP_REST_Request $request, string $folder, string $meta_key): WP_REST_Response|WP_Error { $order_id = (int) $request->get_param('order_id'); $order = wc_get_order($order_id); if (!$order) { return new WP_Error('order_not_found', __('Order not found.', 'fusion-woodoo'), ['status' => 404]); } $pdf_data = $request->get_param('pdf_data'); $decoded = base64_decode($pdf_data, true); if ($decoded === false) { return new WP_Error('invalid_pdf', __('Invalid base64-encoded PDF data.', 'fusion-woodoo'), ['status' => 400]); } $filename = $request->get_param('filename') ?: ($folder . '-' . $order_id . '-' . time() . '.pdf'); $filename = sanitize_file_name($filename); $upload = wp_upload_dir(); $save_dir = $upload['basedir'] . '/fusion-woodoo/' . $folder . '/'; if (!file_exists($save_dir)) { wp_mkdir_p($save_dir); file_put_contents($save_dir . '.htaccess', 'deny from all'); } $file_path = $save_dir . $filename; $bytes = file_put_contents($file_path, $decoded); if ($bytes === false) { return new WP_Error('file_write_error', __('Could not save PDF to disk.', 'fusion-woodoo'), ['status' => 500]); } $relative = str_replace($upload['basedir'], '', $file_path); $order->update_meta_data($meta_key, $relative); $order->save(); return rest_ensure_response(['success' => true, 'path' => $relative]); } }