Deploy: 2026-05-22 23:55:19

This commit is contained in:
Hamza-Ayed
2026-05-22 23:55:19 +03:00
parent 7bf0933efb
commit 4860519f39
15 changed files with 1280 additions and 102 deletions

View File

@@ -45,41 +45,47 @@ $router->get('/api/auth/me', [\App\Controllers\AuthController::class, 'me
// WhatsApp Gateway Routes
$router->get('/api/whatsapp/status', [\App\Controllers\WhatsAppController::class, 'status'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/whatsapp/qr', [\App\Controllers\WhatsAppController::class, 'requestQr'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/whatsapp/qr', [\App\Controllers\WhatsAppController::class, 'requestQr'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/whatsapp/disconnect', [\App\Controllers\WhatsAppController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/whatsapp/webhook', [\App\Controllers\WhatsAppController::class, 'webhook']); // No AuthMiddleware (Protected by WEBHOOK_SECRET internally)
// Phase 4 & 5: CRM, Templates & Campaigns Routes
$router->get('/api/contacts', [\App\Controllers\ContactController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/contacts', [\App\Controllers\ContactController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/contacts', [\App\Controllers\ContactController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->get('/api/groups', [\App\Controllers\GroupController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/groups', [\App\Controllers\GroupController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/groups/add', [\App\Controllers\GroupController::class, 'addContact'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/groups/bulk-add', [\App\Controllers\GroupController::class, 'bulkAddContacts'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/groups', [\App\Controllers\GroupController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/groups/add', [\App\Controllers\GroupController::class, 'addContact'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/groups/bulk-add', [\App\Controllers\GroupController::class, 'bulkAddContacts'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->get('/api/templates', [\App\Controllers\TemplateController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/templates', [\App\Controllers\TemplateController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/templates', [\App\Controllers\TemplateController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->get('/api/campaigns', [\App\Controllers\CampaignController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/campaigns', [\App\Controllers\CampaignController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/campaigns', [\App\Controllers\CampaignController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->get('/api/chatbot/rules', [\App\Controllers\ChatbotController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/chatbot/rules',[\App\Controllers\ChatbotController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/chatbot/generate-prompt-from-audio', [\App\Controllers\ChatbotController::class, 'generatePromptFromAudio'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/chatbot/rules',[\App\Controllers\ChatbotController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/chatbot/generate-prompt-from-audio', [\App\Controllers\ChatbotController::class, 'generatePromptFromAudio'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
// Custom Integration Endpoints Routes (Phase 5)
$router->get('/api/endpoints', [\App\Controllers\EndpointController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/endpoints', [\App\Controllers\EndpointController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->delete('/api/endpoints', [\App\Controllers\EndpointController::class, 'delete'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/endpoints', [\App\Controllers\EndpointController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->delete('/api/endpoints', [\App\Controllers\EndpointController::class, 'delete'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
// Salla Platform Integration Routes (Phase 6+)
$router->get('/api/integrations/salla/auth', [\App\Controllers\SallaController::class, 'auth']);
$router->get('/api/integrations/salla/callback', [\App\Controllers\SallaController::class, 'callback']);
$router->get('/api/integrations/salla/status', [\App\Controllers\SallaController::class, 'status'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/integrations/salla/disconnect',[\App\Controllers\SallaController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class]);
$router->get('/api/integrations/salla/status', [\App\Controllers\SallaController::class, 'status'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/integrations/salla/disconnect',[\App\Controllers\SallaController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/webhooks/salla', [\App\Controllers\SallaController::class, 'webhook']);
// WooCommerce Store Integration Routes
$router->post('/api/integrations/woocommerce/connect', [\App\Controllers\WooCommerceController::class, 'connect'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->get('/api/integrations/woocommerce/status', [\App\Controllers\WooCommerceController::class, 'status'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/integrations/woocommerce/disconnect', [\App\Controllers\WooCommerceController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
$router->post('/api/webhooks/woocommerce', [\App\Controllers\WooCommerceController::class, 'webhook']);
// Mock External API for Entaleq Driver Info (Used to fetch real-time driver data)
$router->post('/api/external/driver-info', function ($request, $response) {

View File

@@ -0,0 +1,35 @@
<?php
require_once dirname(__DIR__) . '/app/bootstrap.php';
use App\Core\Database;
header('Content-Type: text/plain; charset=utf-8');
try {
echo "Connecting to database...\n";
$pdo = Database::getConnection();
$sqlFile = dirname(__DIR__) . '/create_saas_and_woocommerce_tables.sql';
if (!file_exists($sqlFile)) {
throw new \Exception("SQL file not found at: " . $sqlFile);
}
echo "Reading SQL file...\n";
$sql = file_get_contents($sqlFile);
echo "Executing SQL statements...\n";
$pdo->exec($sql);
echo "Migration completed successfully!\n";
// Verify tables
$stmt = $pdo->query("SHOW TABLES");
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo "Current database tables:\n";
foreach ($tables as $t) {
echo "- $t\n";
}
} catch (\Exception $e) {
echo "Migration failed: " . $e->getMessage() . "\n";
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* WooCommerce Integration & SaaS Limit Verification Simulation
* Run this on the server: php backend/public/test_woocommerce_limits.php
*/
require_once dirname(__DIR__) . '/app/bootstrap.php';
use App\Core\Database;
use App\Models\CompanySubscription;
use App\Models\CompanySubscriptionUsage;
use App\Models\WooCommerceStore;
use App\Services\WooCommerceService;
echo "=== Starting SaaS & WooCommerce Integration Tests ===\n\n";
$companyId = 999; // Mock company for testing limits
$phone = "966555555555"; // Mock Saudi number
// Ensure clean database state for this mock company
Database::execute("DELETE FROM company_subscriptions WHERE company_id = ?", [$companyId]);
Database::execute("DELETE FROM company_subscription_usage WHERE company_id = ?", [$companyId]);
Database::execute("DELETE FROM woocommerce_stores WHERE company_id = ?", [$companyId]);
Database::execute("DELETE FROM companies WHERE id = ?", [$companyId]);
// 1. Create Mock Company
echo "1. Creating mock company...\n";
Database::execute("INSERT INTO companies (id, name, created_at) VALUES (?, 'Mock Merchant Co', NOW())", [$companyId]);
// 2. Add WooCommerce Mock Connection details
echo "2. Saving mock WooCommerce store credentials...\n";
$mockStoreUrl = "https://mock-woo-store.com";
$mockConsumerKey = "ck_1234567890123456789012345678901234567890";
$mockConsumerSecret = "cs_1234567890123456789012345678901234567890";
$mockWebhookSecret = "webhook_secret_xyz";
$storeId = WooCommerceStore::saveStore(
$companyId,
$mockStoreUrl,
$mockConsumerKey,
$mockConsumerSecret,
$mockWebhookSecret
);
echo " WooCommerce Store saved. ID: $storeId\n";
// Verify decryption
$store = WooCommerceStore::findByCompany($companyId);
$decrypted = WooCommerceStore::getDecryptedCredentials($store);
if ($decrypted['consumer_key'] === $mockConsumerKey && $decrypted['consumer_secret'] === $mockConsumerSecret) {
echo " ✅ Credentials decryption verified successfully.\n";
} else {
echo " ❌ Credentials decryption FAILED.\n";
}
// 3. Test Phone Trailing Digit Matching
echo "3. Testing phone trailing digit comparison helper...\n";
$testCases = [
['+966555555555', '0555555555', true],
['00966555555555', '966555555555', true],
['0555555555', '555555555', true],
['962799999999', '0799999999', true],
['12345', '12345', true],
['12345', '54321', false],
];
foreach ($testCases as $case) {
$res = WooCommerceService::comparePhones($case[0], $case[1]);
$status = ($res === $case[2]) ? "PASS" : "FAIL";
echo " Compare '{$case[0]}' with '{$case[1]}': " . ($res ? "MATCH" : "NO MATCH") . " ($status)\n";
}
// 4. Test Subscription Limits and Dynamic Reset (تصفير ديناميكي)
echo "\n4. Testing Subscription Limits and Usage...\n";
// Insert a Custom Plan for testing
// ID 999 Plan: Max 3 requests, 1 voice note, 1 ocr
Database::execute("INSERT INTO subscription_plans (id, name, price, max_sessions, max_requests, max_voice_requests, max_ocr_requests, features) VALUES (999, 'Test Plan', 0.00, 1, 3, 1, 1, '{}') ON DUPLICATE KEY UPDATE max_requests=3, max_voice_requests=1, max_ocr_requests=1");
// Create active subscription starting 5 days ago and ending 25 days from now
$startsAt = date('Y-m-d H:i:s', strtotime('-5 days'));
$endsAt = date('Y-m-d H:i:s', strtotime('+25 days'));
Database::execute(
"INSERT INTO company_subscriptions (company_id, plan_id, status, starts_at, ends_at) VALUES (?, 999, 'active', ?, ?)",
[$companyId, $startsAt, $endsAt]
);
$activeSub = CompanySubscription::findActiveByCompany($companyId);
echo " Active Subscription found: Plan ID {$activeSub['plan_id']}\n";
echo " Billing cycle starts: {$activeSub['starts_at']}, ends: {$activeSub['ends_at']}\n";
// Test dynamic usage record initialization
$usage = CompanySubscriptionUsage::getOrCreateCurrentUsage($companyId, $activeSub);
echo " Initialized usage: Requests={$usage['request_count']}, Voice={$usage['voice_count']}, OCR={$usage['ocr_count']}\n";
// Check limits
echo " Checking initial limits:\n";
echo " - Has request limit? " . (CompanySubscriptionUsage::hasRemainingLimit($companyId, 'request') ? "YES" : "NO") . "\n";
echo " - Has voice limit? " . (CompanySubscriptionUsage::hasRemainingLimit($companyId, 'voice') ? "YES" : "NO") . "\n";
echo " - Has OCR limit? " . (CompanySubscriptionUsage::hasRemainingLimit($companyId, 'ocr') ? "YES" : "NO") . "\n";
// Increment and check limits
echo " Incrementing request and voice usage...\n";
CompanySubscriptionUsage::incrementUsage($companyId, 'request');
CompanySubscriptionUsage::incrementUsage($companyId, 'voice');
$usage = CompanySubscriptionUsage::getOrCreateCurrentUsage($companyId, $activeSub);
echo " Current usage: Requests={$usage['request_count']}, Voice={$usage['voice_count']}, OCR={$usage['ocr_count']}\n";
echo " - Has remaining voice limit? " . (CompanySubscriptionUsage::hasRemainingLimit($companyId, 'voice') ? "YES" : "NO") . "\n";
// Exceed request limits
echo " Exceeding request limits...\n";
CompanySubscriptionUsage::incrementUsage($companyId, 'request'); // Request 2
CompanySubscriptionUsage::incrementUsage($companyId, 'request'); // Request 3
$usage = CompanySubscriptionUsage::getOrCreateCurrentUsage($companyId, $activeSub);
echo " Current usage: Requests={$usage['request_count']}\n";
echo " - Has remaining request limit? " . (CompanySubscriptionUsage::hasRemainingLimit($companyId, 'request') ? "YES" : "NO") . "\n";
// 5. Test Webhook Signature verification logic
echo "\n5. Testing Webhook Signature verification...\n";
$mockPayload = json_encode(['id' => 1025, 'status' => 'completed', 'total' => '150.00']);
$signature = base64_encode(hash_hmac('sha256', $mockPayload, $mockWebhookSecret, true));
echo " Calculated Signature: $signature\n";
// Verify matching logic
$calculatedSig = base64_encode(hash_hmac('sha256', $mockPayload, $mockWebhookSecret, true));
if (hash_equals($calculatedSig, $signature)) {
echo " ✅ Signature verification logic PASSED.\n";
} else {
echo " ❌ Signature verification logic FAILED.\n";
}
// Clean up mock company after tests
Database::execute("DELETE FROM company_subscriptions WHERE company_id = ?", [$companyId]);
Database::execute("DELETE FROM company_subscription_usage WHERE company_id = ?", [$companyId]);
Database::execute("DELETE FROM woocommerce_stores WHERE company_id = ?", [$companyId]);
Database::execute("DELETE FROM companies WHERE id = ?", [$companyId]);
echo "\n=== Tests Completed successfully! ===\n";