fix(orders): restore frontend pay REST and harden order-center client

Add AKSRT\Payment\OrderCenterRest so /shop/frontend/orders/{n}/pay|cancel
always register on rest_api_init; widen order_number route charset; make
order-center.js use credentials, encodeURIComponent, and clearer 404 errors.
This commit is contained in:
2026-07-25 14:12:40 +08:00
parent 3117081a58
commit 9277674da0
4 changed files with 200 additions and 23 deletions
+40 -14
View File
@@ -51,18 +51,44 @@
}
function request(path, body, method) {
return fetch(restRoot() + path, {
var opts = {
method: method || "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": aksrtData.restNonce
},
body: body === undefined ? undefined : JSON.stringify(body || {})
}).then(function (response) {
return response.json();
"X-WP-Nonce": (aksrtData && aksrtData.restNonce) || ""
}
};
if (body !== undefined) {
opts.body = JSON.stringify(body || {});
}
return fetch(restRoot() + path, opts).then(function (response) {
return response.text().then(function (text) {
var data = {};
try {
data = text ? JSON.parse(text) : {};
} catch (e) {
data = {
success: false,
message: response.status === 404
? "支付接口未找到(404),请刷新页面或联系管理员。"
: ("请求失败(HTTP " + response.status + ")")
};
}
if (typeof data.success === "undefined" && response.ok === false) {
data.success = false;
data.message = data.message || data.code || ("HTTP " + response.status);
}
return data;
});
});
}
function orderPath(orderNumber, suffix) {
var base = "/shop/frontend/orders/" + encodeURIComponent(orderNumber || "");
return suffix ? base + suffix : base;
}
function cartUrl() {
return aksrtData.cartUrl || (aksrtData.routes && aksrtData.routes.cart) || "/";
}
@@ -217,7 +243,7 @@
return;
}
if (triggerBtn) triggerBtn.disabled = true;
var url = restRoot() + "/shop/frontend/orders/" + encodeURIComponent(orderNumber) + "/review-modal";
var url = restRoot() + orderPath(orderNumber, "/review-modal");
window.AksrtRemoteModal.openRemote({
url: url,
className: "modal-mini full-sm",
@@ -274,7 +300,7 @@
return item.file ? fileToDataUrl(item.file) : Promise.resolve(item.url || "");
})).then(function (images) {
images = images.filter(Boolean);
return request("/shop/frontend/orders/" + orderNumber + "/review", {
return request(orderPath(orderNumber, "/review"), {
product_id: productId,
content: comment,
rating_product: scoreProduct,
@@ -349,7 +375,7 @@
close();
// 关闭后查询订单是否已到账
if (orderNumber) {
request("/shop/frontend/orders/" + orderNumber, {}).then(function () {
request(orderPath(orderNumber), {}, "GET").then(function () {
window.location.reload();
}).catch(function () {});
}
@@ -424,7 +450,7 @@
submitBtn.textContent = "提交中...";
}
request("/shop/frontend/orders/" + orderNumber + "/after-sale", {
request(orderPath(orderNumber, "/after-sale"), {
type: formData.get("type") || "refund",
reason: reason,
note: (formData.get("note") || "").trim()
@@ -486,7 +512,7 @@
if (!receiptOrder) return;
if (!window.confirm("确认已收到货物?")) return;
receiptBtn.disabled = true;
request("/shop/frontend/orders/" + receiptOrder + "/confirm-receipt", {})
request(orderPath(receiptOrder, "/confirm-receipt"), {})
.then(function (result) {
receiptBtn.disabled = false;
if (!result || !result.success) {
@@ -509,7 +535,7 @@
var rebuyOrder = rebuyBtn.getAttribute("data-aksrt-order-rebuy");
if (!rebuyOrder) return;
rebuyBtn.disabled = true;
request("/shop/frontend/orders/" + rebuyOrder + "/rebuy", {})
request(orderPath(rebuyOrder, "/rebuy"), {})
.then(function (result) {
rebuyBtn.disabled = false;
if (!result || !result.success) {
@@ -544,7 +570,7 @@
var payMethod = payButton.getAttribute("data-aksrt-order-method") || "";
payButton.disabled = true;
payButton.textContent = "处理中...";
request("/shop/frontend/orders/" + payOrderNumber + "/pay", payMethod ? { payment_method: payMethod } : {})
request(orderPath(payOrderNumber, "/pay"), payMethod ? { payment_method: payMethod } : {})
.then(function (result) {
if (!result.success) {
payButton.disabled = false;
@@ -589,7 +615,7 @@
var cancelOrderNumber = cancelButton.getAttribute("data-aksrt-order-cancel");
cancelButton.disabled = true;
cancelButton.textContent = "取消中...";
request("/shop/frontend/orders/" + cancelOrderNumber + "/cancel")
request(orderPath(cancelOrderNumber, "/cancel"))
.then(function (result) {
if (!result.success) {
cancelButton.disabled = false;
+3
View File
@@ -223,6 +223,9 @@ if (class_exists('\AKSRT\Content\VipService')) {
if (class_exists('\AKSRT\Payment\ChargeCard')) {
\AKSRT\Payment\ChargeCard::get_instance()->init();
}
if (class_exists('\AKSRT\Payment\OrderCenterRest')) {
\AKSRT\Payment\OrderCenterRest::get_instance()->init();
}
if (class_exists('\AKSRT\Credits\BalanceService')) {
\AKSRT\Credits\BalanceService::get_instance()->init();
}
+9 -9
View File
@@ -47,25 +47,25 @@ class ShopController extends ControllerBase {
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/pay', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/pay', [
'methods' => 'POST',
'callback' => [$this, 'pay_frontend_order'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/cancel', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/cancel', [
'methods' => 'POST',
'callback' => [$this, 'cancel_frontend_order'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)', [
'methods' => 'GET',
'callback' => [$this, 'get_frontend_order'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/after-sale', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/after-sale', [
'methods' => 'POST',
'callback' => [$this, 'create_frontend_after_sale'],
'permission_callback' => [$this, 'check_logged_in_permission'],
@@ -239,7 +239,7 @@ class ShopController extends ControllerBase {
]);
// Order single read
register_rest_route(self::API_NAMESPACE, '/shop/orders/(?P<order_number>[A-Za-z0-9]+)', [
register_rest_route(self::API_NAMESPACE, '/shop/orders/(?P<order_number>[A-Za-z0-9_-]+)', [
'methods' => 'GET',
'callback' => [$this, 'get_order'],
'permission_callback' => [$this, 'check_permission'],
@@ -361,13 +361,13 @@ class ShopController extends ControllerBase {
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/confirm-receipt', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/confirm-receipt', [
'methods' => 'POST',
'callback' => [$this, 'confirm_receipt'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/rebuy', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/rebuy', [
'methods' => 'POST',
'callback' => [$this, 'rebuy_frontend_order'],
'permission_callback' => [$this, 'check_logged_in_permission'],
@@ -398,13 +398,13 @@ class ShopController extends ControllerBase {
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/review', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/review', [
'methods' => 'POST',
'callback' => [$this, 'submit_frontend_product_review'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/review-modal', [
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9_-]+)/review-modal', [
'methods' => 'GET',
'callback' => [$this, 'get_frontend_product_review_modal'],
'permission_callback' => [$this, 'check_logged_in_permission'],
+148
View File
@@ -0,0 +1,148 @@
<?php
/**
* Public REST for order-center actions (pay / cancel / detail).
*
* Registered outside the admin SPA bootstrap so front-end order center
* always has routes even if an admin controller fails to load.
*
* @package AKSRT
*/
namespace AKSRT\Payment;
defined('ABSPATH') || exit;
class OrderCenterRest {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('rest_api_init', [$this, 'register_routes'], 20);
}
public function register_routes(): void {
$ns = 'aksrt/v1';
// Allow common order_number charset (AKS… + optional separators).
$on = '(?P<order_number>[A-Za-z0-9_-]+)';
register_rest_route($ns, '/shop/frontend/orders/' . $on . '/pay', [
'methods' => 'POST',
'callback' => [$this, 'pay'],
'permission_callback' => [$this, 'require_logged_in'],
]);
register_rest_route($ns, '/shop/frontend/orders/' . $on . '/cancel', [
'methods' => 'POST',
'callback' => [$this, 'cancel'],
'permission_callback' => [$this, 'require_logged_in'],
]);
register_rest_route($ns, '/shop/frontend/orders/' . $on, [
[
'methods' => 'GET',
'callback' => [$this, 'get_order'],
'permission_callback' => [$this, 'require_logged_in'],
],
[
// order-center.js historically POSTed for refresh — accept both.
'methods' => 'POST',
'callback' => [$this, 'get_order'],
'permission_callback' => [$this, 'require_logged_in'],
],
]);
}
/**
* @return true|\WP_Error
*/
public function require_logged_in() {
if (!is_user_logged_in()) {
return new \WP_Error(
'aksrt_rest_forbidden',
__('请先登录', 'aksrt'),
['status' => 401]
);
}
return true;
}
public function pay(\WP_REST_Request $request): \WP_REST_Response {
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
$params = $request->get_json_params();
if (!is_array($params)) {
$params = [];
}
$payment_method = sanitize_key((string) ($params['payment_method'] ?? ''));
if (class_exists(UserOrders::class)) {
$result = UserOrders::get_instance()->pay_order(get_current_user_id(), $order_number, $payment_method);
} elseif (class_exists('\AKSRT\Shop\Shop')) {
$result = \AKSRT\Shop\Shop::get_instance()->pay_order(
get_current_user_id(),
$order_number,
$payment_method
);
} else {
return $this->error(__('订单服务不可用', 'aksrt'), 500);
}
if (empty($result['success'])) {
return $this->error((string) ($result['message'] ?? __('支付失败', 'aksrt')), 400);
}
return $this->success(['data' => $result['data'] ?? []]);
}
public function cancel(\WP_REST_Request $request): \WP_REST_Response {
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
if (class_exists(UserOrders::class)) {
$result = UserOrders::get_instance()->cancel_order(get_current_user_id(), $order_number);
} elseif (class_exists('\AKSRT\Shop\Shop')) {
$result = \AKSRT\Shop\Shop::get_instance()->cancel_order(get_current_user_id(), $order_number);
} else {
return $this->error(__('订单服务不可用', 'aksrt'), 500);
}
if (empty($result['success'])) {
return $this->error((string) ($result['message'] ?? __('取消订单失败', 'aksrt')), 400);
}
return $this->success(['data' => $result['data'] ?? []]);
}
public function get_order(\WP_REST_Request $request): \WP_REST_Response {
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
$order = null;
if (class_exists(UserOrders::class)) {
$order = UserOrders::get_instance()->get_order_detail_view(get_current_user_id(), $order_number);
} elseif (class_exists('\AKSRT\Shop\Shop')) {
$order = \AKSRT\Shop\Shop::get_instance()->get_order_detail_view(get_current_user_id(), $order_number);
}
if (!$order) {
return $this->error(__('订单不存在', 'aksrt'), 404);
}
return $this->success(['data' => $order]);
}
private function success(array $data = [], int $status = 200): \WP_REST_Response {
return new \WP_REST_Response(array_merge(['success' => true], $data), $status);
}
private function error(string $message, int $status = 400): \WP_REST_Response {
return new \WP_REST_Response([
'success' => false,
'message' => $message,
], $status);
}
}