- Tax rate: Use Zippopotam (free, no key) to get state, then lookup from built-in state+local rate tables instead of API Ninjas - Prices: Extract prices from Toast __OO_STATE__ MenuItem objects when visible HTML prices are missing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
94 lines
3.7 KiB
Text
94 lines
3.7 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": "", "state": "" }>
|
|
|
|
<!--- State base sales tax rates (2024) - local rates may add 1-5% more --->
|
|
<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
|
|
}>
|
|
|
|
<!--- Estimated average local rate addition by state (rough estimates) --->
|
|
<cfset stateLocalAdd = {
|
|
"AL": 5.2, "AZ": 2.8, "AR": 3, "CA": 1.5, "CO": 4.8,
|
|
"GA": 4, "IL": 2.5, "KS": 2.5, "LA": 5.5, "MO": 4,
|
|
"NV": 1.4, "NM": 2.7, "NY": 4.5, "NC": 2.3, "OH": 1.5,
|
|
"OK": 4.5, "SC": 2, "SD": 2, "TN": 2.5, "TX": 2,
|
|
"UT": 1.3, "WA": 3, "WI": 0.6
|
|
}>
|
|
|
|
<cftry>
|
|
<!--- 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 state from ZIP (free, no API key) --->
|
|
<cfhttp url="https://api.zippopotam.us/us/#zipCode#" method="GET" timeout="10" result="httpResult">
|
|
</cfhttp>
|
|
|
|
<cfif httpResult.statusCode CONTAINS "200">
|
|
<cfset zipData = deserializeJSON(httpResult.fileContent)>
|
|
|
|
<cfif structKeyExists(zipData, "places") AND isArray(zipData.places) AND arrayLen(zipData.places) GT 0>
|
|
<cfset place = zipData.places[1]>
|
|
<cfset stateAbbr = uCase(place["state abbreviation"])>
|
|
<cfset response.state = stateAbbr>
|
|
|
|
<!--- Look up state base rate --->
|
|
<cfif structKeyExists(stateTaxRates, stateAbbr)>
|
|
<cfset baseRate = stateTaxRates[stateAbbr]>
|
|
|
|
<!--- Add estimated local rate if available --->
|
|
<cfset localAdd = 0>
|
|
<cfif structKeyExists(stateLocalAdd, stateAbbr)>
|
|
<cfset localAdd = stateLocalAdd[stateAbbr]>
|
|
</cfif>
|
|
|
|
<cfset totalRate = baseRate + localAdd>
|
|
<!--- Round to 2 decimal places --->
|
|
<cfset response.taxRate = round(totalRate * 100) / 100>
|
|
<cfset response.OK = true>
|
|
<cfset response.stateRate = baseRate>
|
|
<cfset response.localRate = localAdd>
|
|
<cfset response.message = "Estimated rate for #place['place name']#, #stateAbbr#">
|
|
<cfelse>
|
|
<cfset response.message = "Unknown state: #stateAbbr#">
|
|
</cfif>
|
|
<cfelse>
|
|
<cfset response.message = "No location data for ZIP">
|
|
</cfif>
|
|
<cfelse>
|
|
<cfset response.message = "ZIP lookup failed: #httpResult.statusCode#">
|
|
</cfif>
|
|
|
|
<cfcatch type="any">
|
|
<cfset response.message = "Error: #cfcatch.message#">
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cfoutput>#serializeJSON(response)#</cfoutput>
|