payfrit-api/api/setup/lookupTaxRate.php
John Mizerek 1f81d98c52 Initial PHP API migration from CFML
Complete port of all 163 API endpoints from Lucee/CFML to PHP 8.3.
Shared helpers in api/helpers.php (DB, auth, request/response, security).
PDO prepared statements throughout. Same JSON response shapes as CFML.
2026-03-14 14:26:59 -07:00

133 lines
4.6 KiB
PHP

<?php
require_once __DIR__ . '/../helpers.php';
runAuth();
/**
* Look up tax rate by ZIP code
* Uses Zippopotam.us for city/state, then Claude API for exact rate.
*
* GET: ?zip=90001
*/
$response = ['OK' => 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);