false, "message" => "Missing fields" ]); } $email = trim($_POST['email']); $idType = trim($_POST['id_type']); $idNumber = strtoupper(trim($_POST['id_number'])); /***************************************************** * 2. ID VALIDATION RULES *****************************************************/ function isValidAadhaar($num) { if (!preg_match('/^\d{12}$/', $num)) return false; $d = [ [0,1,2,3,4,5,6,7,8,9], [1,2,3,4,0,6,7,8,9,5], [2,3,4,0,1,7,8,9,5,6], [3,4,0,1,2,8,9,5,6,7], [4,0,1,2,3,9,5,6,7,8], [5,9,8,7,6,0,4,3,2,1], [6,5,9,8,7,1,0,4,3,2], [7,6,5,9,8,2,1,0,4,3], [8,7,6,5,9,3,2,1,0,4], [9,8,7,6,5,4,3,2,1,0] ]; $p = [ [0,1,2,3,4,5,6,7,8,9], [1,5,7,6,2,8,3,0,9,4], [5,8,0,3,7,9,6,1,4,2], [8,9,1,6,0,4,3,5,2,7], [9,4,5,3,1,2,6,8,7,0], [4,2,8,6,5,7,3,9,0,1], [2,7,9,3,8,0,6,4,1,5], [7,0,4,6,9,1,3,2,5,8] ]; $c = 0; $numRev = strrev($num); for ($i = 0; $i < strlen($numRev); $i++) { $c = $d[$c][$p[$i % 8][intval($numRev[$i])]]; } return $c == 0; } function validate_id_number($type, $num, &$reason) { $typeLower = strtolower($type); if ($typeLower === "aadhaar") { if (!preg_match('/^[0-9]{12}$/', $num)) { $reason = "Invalid Aadhaar"; return false; } return true; } if ($typeLower === "pan" || $typeLower === "pan card") { if (!preg_match('/^[A-Z]{5}[0-9]{4}[A-Z]$/', $num)) { $reason = "Invalid PAN"; return false; } return true; } if ($typeLower === "driving license" || $typeLower === "dl") { if (!preg_match('/^[A-Z]{2}[0-9]{2}\s?[0-9]{11}$/', $num)) { $reason = "Invalid Driving License"; return false; } return true; } if ($typeLower === "voter id" || $typeLower === "voter") { if (!preg_match('/^[A-Z]{3}[0-9]{7}$/', $num)) { $reason = "Invalid Voter ID"; return false; } return true; } $reason = "Unknown ID type"; return false; } $reason = ""; if (!validate_id_number($idType, $idNumber, $reason)) { json_exit(["success" => false, "message" => $reason]); } /***************************************************** * 3. SAVE FILES *****************************************************/ $uploadDir = "../uploads/worker_kyc/"; if (!is_dir($uploadDir)) mkdir($uploadDir, 0775, true); function save_file($field, $prefix, $email, &$err) { global $uploadDir; if ($_FILES[$field]['error'] !== UPLOAD_ERR_OK) { $err = "Upload error ($field)"; return null; } $ext = pathinfo($_FILES[$field]['name'], PATHINFO_EXTENSION); $fileName = $prefix . "_" . md5($email . time()) . "." . $ext; $dest = $uploadDir . $fileName; if (!move_uploaded_file($_FILES[$field]['tmp_name'], $dest)) { $err = "Cannot save $field"; return null; } return "uploads/worker_kyc/" . $fileName; } $err = ""; $idFile = save_file("id_file", "id", $email, $err); $policeFile = save_file("police_file", "police", $email, $err); $selfieFile = save_file("selfie_file", "selfie", $email, $err); if (!$idFile || !$policeFile || !$selfieFile) { json_exit(["success" => false, "message" => $err]); } /***************************************************** * 4. IMAGE QUALITY CHECK *****************************************************/ function is_image_ok($path, &$msg) { $full = "../" . $path; $size = @getimagesize($full); if (!$size) { $msg = "Invalid image"; return false; } if ($size[0] < 200) { $msg = "Image too small"; return false; } return true; } if (!is_image_ok($idFile, $reason)) json_exit([ "success" => false, "field" => "id_file", "message" => "ID Error: $reason" ]); if (!is_image_ok($selfieFile, $reason)) json_exit([ "success" => false, "field" => "selfie_file", "message" => "Selfie Error: $reason" ]); /***************************************************** * 5. OCR FIXED *****************************************************/ function ocr_extract_text($apiKey, $filePath, &$rawText, &$error) { global $OCR_ENDPOINT; $full = "../" . $filePath; $post = [ "apikey" => $apiKey, "language" => "eng", "OCREngine" => 2, "file" => new CURLFile($full) ]; $ch = curl_init($OCR_ENDPOINT); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); $result = curl_exec($ch); curl_close($ch); $data = json_decode($result, true); if (!isset($data['ParsedResults'][0]['ParsedText'])) { $error = "OCR failed"; return false; } $rawText = strtoupper($data['ParsedResults'][0]['ParsedText']); return true; } $rawText = ""; if (!ocr_extract_text($OCR_API_KEY, $idFile, $rawText, $reason)) { json_exit([ "success" => false, "field" => "id_file", "kyc_status" => "retry_id", "message" => "OCR failed. Please upload a clearer ID card." ]); } $cleanText = preg_replace('/\s+/', '', $rawText); $cleanID = preg_replace('/\s+/', '', $idNumber); if (strpos($cleanText, $cleanID) === false) { json_exit([ "success" => false, "field" => "id_file", "kyc_status" => "retry_id", "message" => "ID number not detected. Please re-upload a clearer ID image." ]); } /***************************************************** * 6. FACE MATCH FIXED *****************************************************/ function face_compare($apiKey, $apiSecret, $idPath, $selfiePath, &$score, &$error) { global $FACEPP_COMPARE; $idAbs = "../" . $idPath; $selfieAbs = "../" . $selfiePath; $post = [ "api_key" => $apiKey, "api_secret" => $apiSecret, "image_file1" => new CURLFILE($idAbs), "image_file2" => new CURLFILE($selfieAbs) ]; $ch = curl_init($FACEPP_COMPARE); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); $result = curl_exec($ch); curl_close($ch); $data = json_decode($result, true); if (!isset($data["confidence"])) { $error = "Face++ Error"; return false; } $score = floatval($data["confidence"]); return true; } $faceScore = 0; if (!face_compare($FACEPP_API_KEY, $FACEPP_API_SECRET, $idFile, $selfieFile, $faceScore, $reason)) { json_exit(["success" => false, "message" => "Face comparison failed"]); } if ($faceScore < 75) { $stmt = $conn->prepare(" UPDATE workers SET kyc_status='rejected', status='rejected', kyc_id_type=?, kyc_id_number=?, kyc_id_file=?, kyc_police_file=?, kyc_selfie_file=?, verification_notes=CONCAT('Face mismatch (score=', ?, ')') WHERE email=? "); $scoreStr = strval($faceScore); $stmt->bind_param( "sssssss", $idType, $idNumber, $idFile, $policeFile, $selfieFile, $scoreStr, $email ); $stmt->execute(); json_exit([ "success" => false, "kyc_status" => "rejected", "message" => "Face mismatch – verification failed", "score" => $faceScore ]); } /***************************************************** * 7. VERIFIED *****************************************************/ $stmt = $conn->prepare(" UPDATE workers SET kyc_status='verified', status='verified', kyc_id_type=?, kyc_id_number=?, kyc_id_file=?, kyc_police_file=?, kyc_selfie_file=?, verified_at=NOW(), verification_notes='Auto Verified' WHERE email=? "); $stmt->bind_param( "ssssss", $idType, $idNumber, $idFile, $policeFile, $selfieFile, $email ); $stmt->execute(); json_exit([ "success" => true, "message" => "KYC Auto-Verified", "face_score" => $faceScore ]);