This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/setup/lookupTaxRate.cfm

125 lines
5.2 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfset response = { "OK": false, "taxRate": 0, "message": "", "city": "", "state": "" }>
<cftry>
<!--- Load Claude API Key --->
<cfset CLAUDE_API_KEY = "">
<cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
<cfif fileExists(configPath)>
<cfset configData = deserializeJSON(fileRead(configPath))>
<cfif structKeyExists(configData, "apiKey")>
<cfset CLAUDE_API_KEY = configData.apiKey>
</cfif>
</cfif>
<!--- Get ZIP code from URL or form --->
<cfset zipCode = "">
<cfif structKeyExists(url, "zip")>
<cfset zipCode = trim(url.zip)>
<cfelseif structKeyExists(form, "zip")>
<cfset zipCode = trim(form.zip)>
</cfif>
<!--- Validate ZIP code format (5 digits or 5+4) --->
<cfif NOT reFind("^\d{5}(-\d{4})?$", zipCode)>
<cfset response.message = "Invalid ZIP code format">
<cfoutput>#serializeJSON(response)#</cfoutput>
<cfabort>
</cfif>
<!--- Use just the 5-digit portion --->
<cfset zipCode = left(zipCode, 5)>
<!--- Use Zippopotam.us to get city/state from ZIP --->
<cfhttp url="https://api.zippopotam.us/us/#zipCode#" method="GET" timeout="10" result="httpResult">
</cfhttp>
<cfif NOT httpResult.statusCode CONTAINS "200">
<cfset response.message = "ZIP lookup failed">
<cfoutput>#serializeJSON(response)#</cfoutput>
<cfabort>
</cfif>
<cfset zipData = deserializeJSON(httpResult.fileContent)>
<cfif NOT structKeyExists(zipData, "places") OR NOT isArray(zipData.places) OR arrayLen(zipData.places) EQ 0>
<cfset response.message = "No location data for ZIP">
<cfoutput>#serializeJSON(response)#</cfoutput>
<cfabort>
</cfif>
<cfset place = zipData.places[1]>
<cfset cityName = place["place name"]>
<cfset stateAbbr = uCase(place["state abbreviation"])>
<cfset response.city = cityName>
<cfset response.state = stateAbbr>
<!--- If no Claude API key, return state-only estimate --->
<cfif NOT len(CLAUDE_API_KEY)>
<!--- Fallback: state base rates --->
<cfset 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
}>
<cfif structKeyExists(stateTaxRates, stateAbbr)>
<cfset response.taxRate = stateTaxRates[stateAbbr]>
<cfset response.OK = true>
<cfset response.message = "State base rate only (no API key)">
</cfif>
<cfoutput>#serializeJSON(response)#</cfoutput>
<cfabort>
</cfif>
<!--- Ask Claude for the exact tax rate --->
<cfset requestBody = {
"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."
}]
}>
<cfhttp url="https://api.anthropic.com/v1/messages" method="POST" timeout="30" result="claudeResult">
<cfhttpparam type="header" name="Content-Type" value="application/json">
<cfhttpparam type="header" name="x-api-key" value="#CLAUDE_API_KEY#">
<cfhttpparam type="header" name="anthropic-version" value="2023-06-01">
<cfhttpparam type="body" value="#serializeJSON(requestBody)#">
</cfhttp>
<cfif claudeResult.statusCode CONTAINS "200">
<cfset claudeResponse = deserializeJSON(claudeResult.fileContent)>
<cfif structKeyExists(claudeResponse, "content") AND arrayLen(claudeResponse.content)>
<cfset rateText = trim(claudeResponse.content[1].text)>
<!--- Extract just the number --->
<cfset rateText = reReplace(rateText, "[^0-9.]", "", "all")>
<cfif isNumeric(rateText) AND val(rateText) GT 0 AND val(rateText) LT 20>
<cfset response.taxRate = val(rateText)>
<cfset response.OK = true>
<cfset response.message = "Tax rate for #cityName#, #stateAbbr#">
<cfelse>
<cfset response.message = "Could not parse tax rate">
</cfif>
</cfif>
<cfelse>
<cfset response.message = "Claude API error">
</cfif>
<cfcatch type="any">
<cfset response.message = "Error: #cfcatch.message#">
</cfcatch>
</cftry>
<cfoutput>#serializeJSON(response)#</cfoutput>