fix: thank you page 401 error

- Add public /checkout/order/{id} endpoint with order_key validation
- Update checkout redirect to include order_key parameter
- Update ThankYou page to use new public endpoint with key
- Support both guest (via key) and logged-in (via customer_id) access
This commit is contained in:
Dwindi Ramadhana
2025-12-31 21:42:40 +07:00
parent d7505252ac
commit a87357d890
3 changed files with 620 additions and 539 deletions

View File

@@ -243,7 +243,7 @@ export default function Checkout() {
});
toast.success('Order placed successfully!');
navigate(`/order-received/${data.order_id}`);
navigate(`/order-received/${data.order_id}?key=${data.order_key}`);
} else {
throw new Error(data.error || 'Failed to create order');
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import { useParams, Link, useSearchParams } from 'react-router-dom';
import { useThankYouSettings } from '@/hooks/useAppearanceSettings';
import Container from '@/components/Layout/Container';
import { CheckCircle, ShoppingBag, Package, Truck } from 'lucide-react';
@@ -9,17 +9,22 @@ import { apiClient } from '@/lib/api/client';
export default function ThankYou() {
const { orderId } = useParams<{ orderId: string }>();
const [searchParams] = useSearchParams();
const orderKey = searchParams.get('key');
const { template, headerVisibility, footerVisibility, backgroundColor, customMessage, elements, isLoading: settingsLoading } = useThankYouSettings();
const [order, setOrder] = useState<any>(null);
const [relatedProducts, setRelatedProducts] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchOrderData = async () => {
if (!orderId) return;
try {
const orderData = await apiClient.get(`/orders/${orderId}`) as any;
// Use public order endpoint with key validation
const keyParam = orderKey ? `?key=${orderKey}` : '';
const orderData = await apiClient.get(`/checkout/order/${orderId}${keyParam}`) as any;
setOrder(orderData);
// Fetch related products from first order item
@@ -30,15 +35,16 @@ export default function ThankYou() {
setRelatedProducts(productData.related_products.slice(0, 4));
}
}
} catch (error) {
console.error('Failed to fetch order data:', error);
} catch (err: any) {
console.error('Failed to fetch order data:', err);
setError(err.message || 'Failed to load order');
} finally {
setLoading(false);
}
};
fetchOrderData();
}, [orderId]);
}, [orderId, orderKey]);
if (loading || settingsLoading || !order) {
return (

View File

@@ -32,6 +32,18 @@ class CheckoutController {
'callback' => [ new self(), 'get_fields' ],
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ],
]);
// Public order view endpoint for thank you page
register_rest_route($namespace, '/checkout/order/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => [ new self(), 'get_order' ],
'permission_callback' => '__return_true', // Public, validated via order_key
'args' => [
'key' => [
'type' => 'string',
'required' => false,
],
],
]);
}
/**
@@ -133,6 +145,69 @@ class CheckoutController {
];
}
/**
* Public order view endpoint for thank you page
* Validates access via order_key (for guests) or logged-in customer ID
* GET /checkout/order/{id}?key=wc_order_xxx
*/
public function get_order(WP_REST_Request $r): array {
$order_id = absint($r['id']);
$order_key = sanitize_text_field($r->get_param('key') ?? '');
if (!$order_id) {
return ['error' => __('Invalid order ID', 'woonoow')];
}
$order = wc_get_order($order_id);
if (!$order) {
return ['error' => __('Order not found', 'woonoow')];
}
// Validate access: order_key must match OR user must be logged in and own the order
$valid_key = $order_key && hash_equals($order->get_order_key(), $order_key);
$valid_owner = is_user_logged_in() && get_current_user_id() === $order->get_customer_id();
if (!$valid_key && !$valid_owner) {
return ['error' => __('Unauthorized access to order', 'woonoow')];
}
// Build order items
$items = [];
foreach ($order->get_items() as $item) {
$product = $item->get_product();
$items[] = [
'id' => $item->get_id(),
'product_id' => $product ? $product->get_id() : 0,
'name' => $item->get_name(),
'qty' => (int) $item->get_quantity(),
'price' => (float) $item->get_total() / max(1, $item->get_quantity()),
'total' => (float) $item->get_total(),
'image' => $product ? wp_get_attachment_image_url($product->get_image_id(), 'thumbnail') : null,
];
}
return [
'ok' => true,
'id' => $order->get_id(),
'number' => $order->get_order_number(),
'status' => $order->get_status(),
'subtotal' => (float) $order->get_subtotal(),
'shipping_total' => (float) $order->get_shipping_total(),
'tax_total' => (float) $order->get_total_tax(),
'total' => (float) $order->get_total(),
'currency' => $order->get_currency(),
'currency_symbol' => get_woocommerce_currency_symbol($order->get_currency()),
'payment_method' => $order->get_payment_method_title(),
'billing' => [
'first_name' => $order->get_billing_first_name(),
'last_name' => $order->get_billing_last_name(),
'email' => $order->get_billing_email(),
'phone' => $order->get_billing_phone(),
],
'items' => $items,
];
}
/**
* Submit an order:
* {