false, 'taxRate' => 0, 'message' => '', 'city' => '', 'state' => '']; try { // Load Claude API key $CLAUDE_API_KEY = ''; $configPath = __DIR__ . '/../../config/claude.json'; if (file_exists($configPath)) { $configData = json_decode(file_get_contents($configPath), true); $CLAUDE_API_KEY = $configData['apiKey'] ?? ''; } // Get ZIP code $zipCode = trim($_GET['zip'] ?? $_POST['zip'] ?? ''); // Validate ZIP format if (!preg_match('/^\d{5}(-\d{4})?$/', $zipCode)) { $response['message'] = 'Invalid ZIP code format'; jsonResponse($response); } $zipCode = substr($zipCode, 0, 5); // Zippopotam.us lookup $ch = curl_init("https://api.zippopotam.us/us/$zipCode"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $zipResult = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { $response['message'] = 'ZIP lookup failed'; jsonResponse($response); } $zipData = json_decode($zipResult, true); if (empty($zipData['places'][0])) { $response['message'] = 'No location data for ZIP'; jsonResponse($response); } $place = $zipData['places'][0]; $cityName = $place['place name'] ?? ''; $stateAbbr = strtoupper($place['state abbreviation'] ?? ''); $response['city'] = $cityName; $response['state'] = $stateAbbr; // State base rates fallback $stateTaxRates = [ 'AL' => 4, 'AK' => 0, 'AZ' => 5.6, 'AR' => 6.5, 'CA' => 7.25, 'CO' => 2.9, 'CT' => 6.35, 'DE' => 0, 'FL' => 6, 'GA' => 4, 'HI' => 4, 'ID' => 6, 'IL' => 6.25, 'IN' => 7, 'IA' => 6, 'KS' => 6.5, 'KY' => 6, 'LA' => 4.45, 'ME' => 5.5, 'MD' => 6, 'MA' => 6.25, 'MI' => 6, 'MN' => 6.875, 'MS' => 7, 'MO' => 4.225, 'MT' => 0, 'NE' => 5.5, 'NV' => 6.85, 'NH' => 0, 'NJ' => 6.625, 'NM' => 4.875, 'NY' => 4, 'NC' => 4.75, 'ND' => 5, 'OH' => 5.75, 'OK' => 4.5, 'OR' => 0, 'PA' => 6, 'RI' => 7, 'SC' => 6, 'SD' => 4.2, 'TN' => 7, 'TX' => 6.25, 'UT' => 6.1, 'VT' => 6, 'VA' => 5.3, 'WA' => 6.5, 'WV' => 6, 'WI' => 5, 'WY' => 4, 'DC' => 6, ]; if (empty($CLAUDE_API_KEY)) { if (isset($stateTaxRates[$stateAbbr])) { $response['taxRate'] = $stateTaxRates[$stateAbbr]; $response['OK'] = true; $response['message'] = 'State base rate only (no API key)'; } jsonResponse($response); } // Ask Claude for exact tax rate $requestBody = json_encode([ 'model' => 'claude-sonnet-4-20250514', 'max_tokens' => 100, 'temperature' => 0, 'messages' => [[ 'role' => 'user', 'content' => "What is the current combined sales tax rate (state + county + city + special districts) for $cityName, $stateAbbr (ZIP code $zipCode)? Return ONLY a single number representing the percentage, like 10.25 for 10.25%. No text, no explanation, just the number.", ]], ]); $ch = curl_init('https://api.anthropic.com/v1/messages'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $requestBody, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', "x-api-key: $CLAUDE_API_KEY", 'anthropic-version: 2023-06-01', ], ]); $claudeResult = curl_exec($ch); $claudeHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($claudeHttpCode === 200) { $claudeResponse = json_decode($claudeResult, true); if (!empty($claudeResponse['content'][0]['text'])) { $rateText = trim($claudeResponse['content'][0]['text']); $rateText = preg_replace('/[^0-9.]/', '', $rateText); $rateVal = (float)$rateText; if ($rateVal > 0 && $rateVal < 20) { $response['taxRate'] = $rateVal; $response['OK'] = true; $response['message'] = "Tax rate for $cityName, $stateAbbr"; } else { $response['message'] = 'Could not parse tax rate'; } } } else { $response['message'] = 'Claude API error'; } } catch (Exception $e) { $response['message'] = 'Error: ' . $e->getMessage(); } jsonResponse($response);