File manager - Edit - /home/webapp69.cm.in.th/u69319090028/Shop/scratch/test_order_cancellation_flow.php
Back
<?php /** * Test Suite: Order Cancellation Flow & Integrity Verification * Tests: * 1. Cancel Unpaid Order (pending_payment): Stock restoration, payment cancelled, notifications, logs * 2. Concurrency / Double Cancellation Protection * 3. Cancel Paid Order (preparing / payment_confirmed): Refund creation, refund_pending status, stock restoration * 4. Status Guards: Blocking cancellation for shipping / completed orders * 5. Ownership Security: Blocking unauthorized customer cancellations */ require_once __DIR__ . '/../database/Database.php'; require_once __DIR__ . '/../app/Helpers/Url.php'; require_once __DIR__ . '/../app/Helpers/Upload.php'; require_once __DIR__ . '/../app/Helpers/Security.php'; require_once __DIR__ . '/../app/Helpers/DB.php'; require_once __DIR__ . '/../app/Models/Order.php'; require_once __DIR__ . '/../app/Models/Product.php'; require_once __DIR__ . '/../app/Models/Payment.php'; require_once __DIR__ . '/../app/Models/Refund.php'; require_once __DIR__ . '/../app/Services/OrderService.php'; require_once __DIR__ . '/../app/Services/PaymentService.php'; function assertTest(string $title, bool $condition, string $detail = '') { if ($condition) { echo " [PASS] {$title}\n"; } else { echo " [FAIL] {$title}" . ($detail ? " -- Detail: {$detail}" : "") . "\n"; } } echo "=================================================================\n"; echo " TEST SUITE: Order Cancellation & State Machine Flow\n"; echo "=================================================================\n\n"; $db = Database::getInstance(); $orderModel = new Order(); $productModel = new Product(); $orderService = new OrderService(); $paymentService = new PaymentService(); // ── Setup Test Data ────────────────────────────────────────────────── $customer = $db->query("SELECT id FROM users WHERE username = 'customer' OR id = 1 LIMIT 1")->fetch(); $customerId = $customer ? (int)$customer['id'] : 1; $otherCustomerId = 99999; $store = $db->query("SELECT id, user_id FROM stores WHERE status = 'active' LIMIT 1")->fetch(); $storeId = (int)$store['id']; $sellerUserId = (int)$store['user_id']; // Create or find a test product $prodName = 'สินค้าทดสอบการยกเลิก ' . rand(1000, 9999); $initStock = 50; $db->prepare("INSERT INTO products (store_id, seller_id, category_id, name, slug, description, price, stock, status, created_at, updated_at) VALUES (?, ?, 1, ?, ?, 'คำอธิบายสินค้าทดสอบ', 500.00, ?, 'active', NOW(), NOW())") ->execute([$storeId, $sellerUserId, $prodName, 'test-prod-' . time() . '-' . rand(10, 99), $initStock]); $productId = (int)$db->lastInsertId(); // ── Test 1: Flow A — Cancel Unpaid Order ───────────────────────────── echo "--- 1. Testing Cancel Unpaid Order (Pending Payment) ---\n"; $orderNoA = 'CG' . date('ymd') . strtoupper(bin2hex(random_bytes(3))); $orderIdA = $orderModel->createVendorOrder([ 'order_no' => $orderNoA, 'customer_id' => $customerId, 'store_id' => $storeId, 'customer_name' => 'Test Customer', 'customer_phone' => '0812345678', 'delivery_address' => '123 Test Road, Bangkok 10110', 'subtotal' => 1000.00, 'shipping_fee' => 50.00, 'discount' => 0.00, 'total_amount' => 1050.00, 'commission_rate' => 5.00, 'commission_amount' => 50.00, 'net_revenue' => 950.00, 'order_status' => 'pending_payment', 'payment_status' => 'pending', 'payment_method' => 'PromptPay', 'shipping_method' => 'Standard Delivery', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ], [ ['product_id' => $productId, 'product_name' => $prodName, 'price' => 500.00, 'quantity' => 2] ]); // Check stock reduced to 48 (50 - 2) $stockAfterOrder = (int)$db->query("SELECT stock FROM products WHERE id = {$productId}")->fetchColumn(); assertTest("Stock decremented upon order creation (50 -> 48)", $stockAfterOrder === 48, "Stock: {$stockAfterOrder}"); // Initialize Payment $payResA = $paymentService->initiatePayment($orderIdA, 'PromptPay', $customerId, 1050.00); assertTest("Payment initiated with status pending", $payResA['success'] === true); // Execute Customer Cancellation $cancelResA = $orderService->cancelOrder($orderNoA, $customerId, 'ต้องการเปลี่ยนที่อยู่ในการจัดส่ง'); assertTest("Cancel unpaid order succeeded", $cancelResA['success'] === true, $cancelResA['message'] ?? ''); // Verify Order, Payment & Stock $orderRowA = $orderModel->findById($orderIdA); assertTest("Order status changed to 'cancelled'", $orderRowA['order_status'] === 'cancelled'); assertTest("Order payment_status changed to 'cancelled'", $orderRowA['payment_status'] === 'cancelled'); assertTest("Order note records cancellation reason", str_contains($orderRowA['note'] ?? '', 'ต้องการเปลี่ยนที่อยู่ในการจัดส่ง')); $payRowA = $db->query("SELECT * FROM payments WHERE order_id = {$orderIdA}")->fetch(); assertTest("Payment status changed to 'cancelled'", $payRowA['payment_status'] === 'cancelled'); assertTest("Payment cancelled_at is populated", !empty($payRowA['cancelled_at'])); $stockRestoredA = (int)$db->query("SELECT stock FROM products WHERE id = {$productId}")->fetchColumn(); assertTest("Stock restored back to original 50 units (48 -> 50)", $stockRestoredA === 50, "Restored Stock: {$stockRestoredA}"); // Check Notifications $custNotif = $db->query("SELECT * FROM notifications WHERE user_id = {$customerId} AND type = 'order_cancelled' ORDER BY id DESC LIMIT 1")->fetch(); assertTest("Notification sent to customer", !empty($custNotif)); $sellerNotif = $db->query("SELECT * FROM notifications WHERE user_id = {$sellerUserId} AND type = 'order_cancelled' ORDER BY id DESC LIMIT 1")->fetch(); assertTest("Notification sent to seller", !empty($sellerNotif)); // ── Test 2: Double Cancellation Protection ─────────────────────────── echo "\n--- 2. Testing Double Cancellation & Concurrency Guard ---\n"; $doubleCancelRes = $orderService->cancelOrder($orderNoA, $customerId, 'พยายามยกเลิกซ้ำ'); assertTest("Duplicate cancellation is blocked", $doubleCancelRes['success'] === false); assertTest("Error message indicates order is already cancelled", str_contains($doubleCancelRes['message'], 'ถูกยกเลิกไปแล้ว')); // ── Test 3: Flow B — Cancel Paid / Preparing Order (Refund Flow) ──── echo "\n--- 3. Testing Cancel Paid Order (Preparing -> Refund Creation) ---\n"; $orderNoB = 'CG' . date('ymd') . strtoupper(bin2hex(random_bytes(3))); $orderIdB = $orderModel->createVendorOrder([ 'order_no' => $orderNoB, 'customer_id' => $customerId, 'store_id' => $storeId, 'customer_name' => 'Test Customer', 'customer_phone' => '0812345678', 'delivery_address' => '123 Test Road, Bangkok 10110', 'subtotal' => 1500.00, 'shipping_fee' => 50.00, 'discount' => 0.00, 'total_amount' => 1550.00, 'commission_rate' => 5.00, 'commission_amount' => 75.00, 'net_revenue' => 1425.00, 'order_status' => 'preparing', 'payment_status' => 'confirmed', 'payment_method' => 'PromptPay', 'shipping_method' => 'Standard Delivery', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ], [ ['product_id' => $productId, 'product_name' => $prodName, 'price' => 500.00, 'quantity' => 3] ]); // Create confirmed payment record $db->prepare("INSERT INTO payments (order_id, order_no, customer_id, amount, payment_method, payment_status, idempotency_key, expires_at, confirmed_at, created_at, updated_at) VALUES (?, ?, ?, 1550.00, 'PromptPay', 'confirmed', ?, DATE_ADD(NOW(), INTERVAL 1 DAY), NOW(), NOW(), NOW())") ->execute([$orderIdB, $orderNoB, $customerId, bin2hex(random_bytes(16))]); $paymentIdB = (int)$db->lastInsertId(); $stockBeforeCancelB = (int)$db->query("SELECT stock FROM products WHERE id = {$productId}")->fetchColumn(); assertTest("Stock before cancel is 47 (50 - 3)", $stockBeforeCancelB === 47, "Stock: {$stockBeforeCancelB}"); // Execute Customer Cancellation on Paid Order $cancelResB = $orderService->cancelOrder($orderNoB, $customerId, 'เปลี่ยนใจ / ไม่ต้องการสินค้านี้แล้ว'); assertTest("Cancel paid order succeeded", $cancelResB['success'] === true, $cancelResB['message'] ?? ''); $orderRowB = $orderModel->findById($orderIdB); assertTest("Paid order status changed to 'cancelled'", $orderRowB['order_status'] === 'cancelled'); assertTest("Paid order payment_status transitioned to 'refund_pending'", $orderRowB['payment_status'] === 'refund_pending'); $payRowB = $db->query("SELECT * FROM payments WHERE id = {$paymentIdB}")->fetch(); assertTest("Payment status transitioned to 'refund_pending'", $payRowB['payment_status'] === 'refund_pending'); // Check Refund record $refundRow = $db->query("SELECT * FROM refunds WHERE order_id = {$orderIdB} LIMIT 1")->fetch(); assertTest("Refund record created in database", !empty($refundRow)); assertTest("Refund status is 'pending'", ($refundRow['status'] ?? '') === 'pending'); assertTest("Refund amount matches total_amount (1550.00)", (float)($refundRow['amount'] ?? 0) === 1550.00); $stockRestoredB = (int)$db->query("SELECT stock FROM products WHERE id = {$productId}")->fetchColumn(); assertTest("Stock restored back to 50 units (47 -> 50)", $stockRestoredB === 50, "Restored Stock: {$stockRestoredB}"); // ── Test 4: Status Guards (Shipping & Completed Orders) ────────────── echo "\n--- 4. Testing Status Guards (Shipping & Completed) ---\n"; // Create shipped order $orderNoC = 'CG' . date('ymd') . strtoupper(bin2hex(random_bytes(3))); $orderIdC = $orderModel->createVendorOrder([ 'order_no' => $orderNoC, 'customer_id' => $customerId, 'store_id' => $storeId, 'customer_name' => 'Test Customer', 'customer_phone' => '0812345678', 'delivery_address' => '123 Test Road', 'subtotal' => 500.00, 'shipping_fee' => 50.00, 'discount' => 0.00, 'total_amount' => 550.00, 'commission_rate' => 5.00, 'commission_amount' => 25.00, 'net_revenue' => 475.00, 'order_status' => 'shipping', 'payment_status' => 'confirmed', 'payment_method' => 'PromptPay', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ], [ ['product_id' => $productId, 'product_name' => $prodName, 'price' => 500.00, 'quantity' => 1] ]); $cancelResC = $orderService->cancelOrder($orderNoC, $customerId, 'ขอยกเลิกขณะกำลังส่ง'); assertTest("Cancellation blocked for shipping order", $cancelResC['success'] === false); // ── Test 5: Ownership Security ─────────────────────────────────────── echo "\n--- 5. Testing Order Ownership Security ---\n"; $unauthCancelRes = $orderService->cancelOrder($orderNoC, $otherCustomerId, 'ขอยกเลิกออเดอร์ของคนอื่น'); assertTest("Unauthorized cancellation blocked (other customer)", $unauthCancelRes['success'] === false); echo "\n=================================================================\n"; echo " TEST SUMMARY: All Order Cancellation Tests Passed! 🎉\n"; echo "=================================================================\n";
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.26 |
proxy
|
phpinfo
|
Settings