Auto-populate tax rate from ZIP code using API Ninjas

When business info step loads with a ZIP code, automatically looks up
the combined sales tax rate and pre-fills the field. User can still
edit if needed. Field gets light green background to indicate auto-fill.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-02-13 11:52:56 -08:00
parent 26e5d92a03
commit 76f089d1b9
2 changed files with 96 additions and 0 deletions

View file

@ -0,0 +1,72 @@
<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": "" }>
<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)>
<!--- Call API Ninjas Sales Tax API --->
<cfhttp url="https://api.api-ninjas.com/v1/salestax?zip_code=#zipCode#" method="GET" timeout="10" result="httpResult">
<cfhttpparam type="header" name="X-Api-Key" value="free">
</cfhttp>
<cfif httpResult.statusCode CONTAINS "200">
<cfset apiData = deserializeJSON(httpResult.fileContent)>
<!--- API returns an array, get first result --->
<cfif isArray(apiData) AND arrayLen(apiData) GT 0>
<cfset taxData = apiData[1]>
<!--- overall_rate is decimal (e.g., 0.095 for 9.5%) --->
<cfif structKeyExists(taxData, "overall_rate")>
<cfset overallRate = val(taxData.overall_rate)>
<!--- Convert to percentage --->
<cfset response.taxRate = round(overallRate * 10000) / 100>
<cfset response.OK = true>
<cfset response.message = "Tax rate found">
<!--- Include breakdown for transparency --->
<cfif structKeyExists(taxData, "state_rate")>
<cfset response.stateRate = round(val(taxData.state_rate) * 10000) / 100>
</cfif>
<cfif structKeyExists(taxData, "county_rate")>
<cfset response.countyRate = round(val(taxData.county_rate) * 10000) / 100>
</cfif>
<cfif structKeyExists(taxData, "city_rate")>
<cfset response.cityRate = round(val(taxData.city_rate) * 10000) / 100>
</cfif>
<cfelse>
<cfset response.message = "No rate data in response">
</cfif>
<cfelse>
<cfset response.message = "No results for ZIP code">
</cfif>
<cfelse>
<cfset response.message = "API returned status: #httpResult.statusCode#">
</cfif>
<cfcatch type="any">
<cfset response.message = "Error: #cfcatch.message#">
</cfcatch>
</cftry>
<cfoutput>#serializeJSON(response)#</cfoutput>

View file

@ -2136,6 +2136,30 @@
</button>
</div>
`);
// Auto-lookup tax rate based on ZIP code
if (zip && zip.length >= 5) {
lookupTaxRate(zip);
}
}
// Lookup tax rate from ZIP code and populate the field
async function lookupTaxRate(zipCode) {
const taxInput = document.getElementById('bizTaxRate');
if (!taxInput) return;
try {
const resp = await fetch(`/api/setup/lookupTaxRate.cfm?zip=${encodeURIComponent(zipCode)}`);
const data = await resp.json();
if (data.OK && data.taxRate > 0) {
taxInput.value = data.taxRate;
taxInput.style.backgroundColor = '#f0fff4'; // Light green to indicate auto-filled
console.log('Tax rate auto-populated:', data.taxRate + '%');
}
} catch (err) {
console.log('Tax rate lookup failed (user can enter manually):', err);
}
}
function confirmBusinessInfo() {