Files
WooNooW/includes/Api/PaymentsController.php
dwindown 40fb364035 fix: Route priority issue - /order was matched by /{id}
Problem:
POST /payments/gateways/order → 404 'gateway_not_found'

Root Cause:
WordPress REST API matches routes in registration order.
The /gateways/order route was registered AFTER /gateways/{id}.
So /gateways/order was being matched by /gateways/{id} where id='order'.
Then get_gateway('order') returned 'gateway_not_found'.

Solution:
Register specific routes BEFORE dynamic routes:
1. /gateways (list)
2. /gateways/order (specific - NEW POSITION)
3. /gateways/{id} (dynamic)
4. /gateways/{id}/toggle (dynamic with action)

Route Priority Rules:
 Specific routes first
 Dynamic routes last
 More specific before less specific

Before:
/gateways → OK
/gateways/{id} → Matches everything including 'order'
/gateways/{id}/toggle → OK (more specific than {id})
/gateways/order → Never reached!

After:
/gateways → OK
/gateways/order → Matches 'order' specifically
/gateways/{id} → Matches other IDs
/gateways/{id}/toggle → OK

Result:
 /gateways/order now works correctly
 Sorting saves to database
 No more 'gateway_not_found' error

Files Modified:
- PaymentsController.php: Moved /order route before /{id} routes
2025-11-06 14:05:18 +07:00

394 lines
13 KiB
PHP

<?php
/**
* Payments REST API Controller
*
* Provides REST endpoints for payment gateway management.
*
* @package WooNooW
*/
namespace WooNooW\API;
use WooNooW\Compat\PaymentGatewaysProvider;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
class PaymentsController extends WP_REST_Controller {
/**
* Namespace
*/
protected $namespace = 'woonoow/v1';
/**
* Rest base
*/
protected $rest_base = 'payments';
/**
* Register routes
*/
public function register_routes() {
// GET /woonoow/v1/payments/gateways
register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways', [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_gateways'],
'permission_callback' => [$this, 'check_permission'],
],
'schema' => [$this, 'get_gateways_schema'],
]);
// POST /woonoow/v1/payments/gateways/order
// IMPORTANT: Register this BEFORE the dynamic {id} routes to avoid conflicts
register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/order', [
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [$this, 'save_gateway_order'],
'permission_callback' => [$this, 'check_permission'],
'args' => [
'category' => [
'description' => 'Gateway category (manual or online)',
'type' => 'string',
'required' => true,
'enum' => ['manual', 'online'],
],
'order' => [
'description' => 'Array of gateway IDs in desired order',
'type' => 'array',
'required' => true,
'items' => [
'type' => 'string',
],
],
],
],
]);
// GET /woonoow/v1/payments/gateways/{id}
register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/(?P<id>[a-zA-Z0-9_-]+)', [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_gateway'],
'permission_callback' => [$this, 'check_permission'],
'args' => [
'id' => [
'description' => 'Gateway ID',
'type' => 'string',
'required' => true,
],
],
],
]);
// POST /woonoow/v1/payments/gateways/{id}
register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/(?P<id>[a-zA-Z0-9_-]+)', [
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [$this, 'save_gateway'],
'permission_callback' => [$this, 'check_permission'],
'args' => [
'id' => [
'description' => 'Gateway ID',
'type' => 'string',
'required' => true,
],
],
],
]);
// POST /woonoow/v1/payments/gateways/{id}/toggle
register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/(?P<id>[a-zA-Z0-9_-]+)/toggle', [
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [$this, 'toggle_gateway'],
'permission_callback' => [$this, 'check_permission'],
'args' => [
'id' => [
'description' => 'Gateway ID',
'type' => 'string',
'required' => true,
],
'enabled' => [
'description' => 'Enable or disable gateway',
'type' => 'boolean',
'required' => true,
],
],
],
]);
}
/**
* Get all payment gateways
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function get_gateways(WP_REST_Request $request) {
try {
$gateways = PaymentGatewaysProvider::get_gateways();
// Get saved order
$manual_order = get_option('woonoow_payment_gateway_order_manual', []);
$online_order = get_option('woonoow_payment_gateway_order_online', []);
$response = rest_ensure_response([
'gateways' => $gateways,
'order' => [
'manual' => $manual_order,
'online' => $online_order,
],
]);
// Cache for 5 minutes
$response->header('Cache-Control', 'max-age=300');
return $response;
} catch (\Exception $e) {
return new WP_Error(
'get_gateways_failed',
$e->getMessage(),
['status' => 500]
);
}
}
/**
* Get single payment gateway
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function get_gateway(WP_REST_Request $request) {
$gateway_id = $request->get_param('id');
try {
$gateway = PaymentGatewaysProvider::get_gateway($gateway_id);
if ($gateway === null) {
return new WP_Error(
'gateway_not_found',
sprintf('Gateway "%s" not found', $gateway_id),
['status' => 404]
);
}
$response = rest_ensure_response($gateway);
// Cache for 5 minutes
$response->header('Cache-Control', 'max-age=300');
return $response;
} catch (\Exception $e) {
return new WP_Error(
'get_gateway_failed',
$e->getMessage(),
['status' => 500]
);
}
}
/**
* Save gateway settings
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function save_gateway(WP_REST_Request $request) {
$gateway_id = $request->get_param('id');
$settings = $request->get_json_params();
if (empty($settings)) {
return new WP_Error(
'missing_settings',
'No settings provided',
['status' => 400]
);
}
try {
// Debug: Log what we're saving
error_log(sprintf('[WooNooW] Saving gateway %s settings: %s', $gateway_id, json_encode($settings)));
$result = PaymentGatewaysProvider::save_gateway_settings($gateway_id, $settings);
if (is_wp_error($result)) {
error_log(sprintf('[WooNooW] Save failed: %s', $result->get_error_message()));
return $result;
}
// Clear cache before fetching updated gateway
wp_cache_flush();
// Return updated gateway data (fresh from DB)
$gateway = PaymentGatewaysProvider::get_gateway($gateway_id);
// Debug: Log success
error_log(sprintf('[WooNooW] Gateway %s settings saved successfully', $gateway_id));
return rest_ensure_response([
'success' => true,
'message' => 'Gateway settings saved successfully',
'gateway' => $gateway,
]);
} catch (\Exception $e) {
error_log(sprintf('[WooNooW] Save exception: %s', $e->getMessage()));
return new WP_Error(
'save_gateway_failed',
$e->getMessage(),
['status' => 500]
);
}
}
/**
* Toggle gateway enabled status
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function toggle_gateway(WP_REST_Request $request) {
$gateway_id = $request->get_param('id');
$enabled = $request->get_param('enabled');
// Convert to boolean (handles both bool and string "true"/"false")
$enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($enabled === null) {
return new WP_Error(
'invalid_enabled_value',
'The "enabled" parameter must be a boolean',
['status' => 400]
);
}
try {
// Debug: Log what we're trying to do
error_log(sprintf('[WooNooW] Toggling gateway %s to %s', $gateway_id, $enabled ? 'enabled' : 'disabled'));
$result = PaymentGatewaysProvider::toggle_gateway($gateway_id, $enabled);
if (is_wp_error($result)) {
error_log(sprintf('[WooNooW] Toggle failed: %s', $result->get_error_message()));
return $result;
}
// Clear cache before fetching updated gateway
wp_cache_flush();
// Return updated gateway data (fresh from DB)
$gateway = PaymentGatewaysProvider::get_gateway($gateway_id);
// Debug: Log what we got back
error_log(sprintf('[WooNooW] Gateway %s after toggle: enabled=%s', $gateway_id, $gateway['enabled'] ? 'true' : 'false'));
return rest_ensure_response([
'success' => true,
'message' => $enabled ? 'Gateway enabled' : 'Gateway disabled',
'gateway' => $gateway,
]);
} catch (\Exception $e) {
error_log(sprintf('[WooNooW] Toggle exception: %s', $e->getMessage()));
return new WP_Error(
'toggle_gateway_failed',
$e->getMessage(),
['status' => 500]
);
}
}
/**
* Save gateway order
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function save_gateway_order(WP_REST_Request $request) {
$category = $request->get_param('category');
$order = $request->get_param('order');
// Validate category
if (!in_array($category, ['manual', 'online'], true)) {
return new WP_Error(
'invalid_category',
'Category must be either "manual" or "online"',
['status' => 400]
);
}
// Validate order is array
if (!is_array($order)) {
return new WP_Error(
'invalid_order',
'Order must be an array of gateway IDs',
['status' => 400]
);
}
// Save to WordPress options
$option_key = 'woonoow_payment_gateway_order_' . $category;
update_option($option_key, $order, false);
error_log(sprintf('[WooNooW] Saved %s gateway order: %s', $category, implode(', ', $order)));
return rest_ensure_response([
'success' => true,
'message' => 'Gateway order saved successfully',
'category' => $category,
'order' => $order,
]);
}
/**
* Check permission
*
* @return bool True if user has permission
*/
public function check_permission() {
return current_user_can('manage_woocommerce');
}
/**
* Get gateways collection schema
*
* @return array Schema
*/
public function get_gateways_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'payment-gateways',
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'id' => [
'description' => 'Gateway ID',
'type' => 'string',
],
'title' => [
'description' => 'Gateway title',
'type' => 'string',
],
'description' => [
'description' => 'Gateway description',
'type' => 'string',
],
'enabled' => [
'description' => 'Whether gateway is enabled',
'type' => 'boolean',
],
'type' => [
'description' => 'Gateway type',
'type' => 'string',
'enum' => ['manual', 'provider', 'other'],
],
],
],
];
}
}