'POST', 'callback' => array($this, 'update_order_status'), 'permission_callback' => array($this, 'verify_api_key'), )); register_rest_route('akkedli/v1', '/health', array( 'methods' => 'GET', 'callback' => array($this, 'health_check'), 'permission_callback' => '__return_true', )); // Public entry point carries only non-secret routing data. The credential // is entered later in a capability- and nonce-protected wp-admin POST. register_rest_route('akkedli/v1', '/connect', array( 'methods' => 'GET', 'callback' => array($this, 'handle_connect_redirect'), 'permission_callback' => '__return_true', )); } /** * Permission callback: verify user is a WooCommerce admin */ public function verify_admin_capability() { return current_user_can('manage_woocommerce'); } /** * Connect redirect endpoint * * Receives non-secret connection routing data and redirects to wp-admin. */ public function handle_connect_redirect(WP_REST_Request $request) { if (Akkedli_Connector_State::exists()) { return new WP_Error( 'akkedli_legacy_connection_unavailable', __('Legacy connection setup is unavailable.', 'akkedli-connector'), array('status' => 409) ); } $webhook_url = esc_url_raw($request->get_param('webhook_url')); $return_url = esc_url_raw($request->get_param('return_url')); if (empty($webhook_url)) { return new WP_Error( 'akkedli_missing_params', __('Missing required connection URL.', 'akkedli-connector'), array('status' => 400) ); } $redirect_url = admin_url('admin.php'); $redirect_url = add_query_arg(array( 'page' => 'akkedli-connector', 'action' => 'auto_connect', 'webhook_url' => rawurlencode($webhook_url), 'return_url' => rawurlencode($return_url), ), $redirect_url); wp_safe_redirect(esc_url_raw($redirect_url)); exit; } /** * Verify API key from request header */ public function verify_api_key(WP_REST_Request $request) { $api_key = $request->get_header('X-API-Key'); $stored_key = Akkedli_Connector_State::exists() ? Akkedli_Connector_State::get_legacy_callback_key() : Akkedli_Settings::get_api_key(); if (empty($api_key) || empty($stored_key)) { return new WP_Error( 'akkedli_unauthorized', __('Missing or invalid API key', 'akkedli-connector'), array('status' => 401) ); } if (!hash_equals($stored_key, $api_key)) { return new WP_Error( 'akkedli_unauthorized', __('Invalid API key', 'akkedli-connector'), array('status' => 401) ); } return true; } /** * Update order status from Akkedli verification */ public function update_order_status(WP_REST_Request $request) { $order_id = absint($request->get_param('order_id')); $new_status = sanitize_text_field($request->get_param('new_status')); $verification_result = sanitize_text_field($request->get_param('verification_result')); $verified_at = sanitize_text_field($request->get_param('verified_at')); // Validate required fields if (empty($order_id)) { return new WP_Error( 'akkedli_missing_order_id', __('Order ID is required', 'akkedli-connector'), array('status' => 400) ); } if (empty($new_status)) { return new WP_Error( 'akkedli_missing_status', __('New status is required', 'akkedli-connector'), array('status' => 400) ); } // Get the order $order = wc_get_order($order_id); if (!$order) { return new WP_Error( 'akkedli_order_not_found', __('Order not found', 'akkedli-connector'), array('status' => 404) ); } // Map Akkedli status to WooCommerce status $wc_status = $this->map_status($new_status); if (!$wc_status) { return new WP_Error( 'akkedli_invalid_status', __('Invalid status provided', 'akkedli-connector'), array('status' => 400) ); } $old_status = $order->get_status(); if ('verified' === $verification_result) { $lock = Akkedli_V2_Callback_Claims::acquire_order((string) $order_id, time()); if (!is_array($lock)) { return new WP_Error( 'akkedli_confirmation_conflict', __('Order confirmation is already being processed.', 'akkedli-connector'), array('status' => 409) ); } try { if (!Akkedli_Order_Confirmation::mark_confirmed($order, $verified_at)) { return new WP_Error( 'akkedli_confirmation_failed', __('Order confirmation could not be saved.', 'akkedli-connector'), array('status' => 503) ); } } finally { Akkedli_V2_Callback_Claims::release_order($lock); } $wc_status = $old_status; } else { $order->set_status($wc_status); $order->add_order_note(sprintf( /* translators: %1$s: verification result, %2$s: verification timestamp */ __('Order %1$s via Akkedli verification at %2$s', 'akkedli-connector'), 'cancelled', $verified_at ? $verified_at : current_time('c') )); if ('yes' !== $order->get_meta(Akkedli_Order_Confirmation::META_KEY, true)) { $order->update_meta_data(Akkedli_Order_Confirmation::META_KEY, 'no'); } $order->update_meta_data('_akkedli_verified_at', $verified_at); $order->update_meta_data('_akkedli_verification_result', $verification_result); $order->save(); } return array( 'success' => true, 'order_id' => $order_id, 'old_status' => $old_status, 'new_status' => $wc_status, 'verification_result' => $verification_result, 'message' => __('Order status updated successfully', 'akkedli-connector') ); } /** * Map Akkedli/n8n status to WooCommerce status */ private function map_status($status) { $status_map = array( 'processing' => 'processing', 'completed' => 'completed', 'cancelled' => 'cancelled', 'on-hold' => 'on-hold', 'pending' => 'pending', 'refunded' => 'refunded', 'failed' => 'failed', // Akkedli-specific mappings 'verified' => 'processing', 'confirmed' => 'processing', 'rejected' => 'cancelled', ); return isset($status_map[$status]) ? $status_map[$status] : null; } /** * Health check endpoint */ public function health_check() { return array( 'status' => 'ok', 'plugin' => 'akkedli-connector', 'version' => AKKEDLI_VERSION, 'configured' => Akkedli_Settings::is_configured(), 'timestamp' => current_time('c') ); } }