/** * Update Business Settings * Updates settings for the currently selected business * * POST: { * TaxRatePercent: 8.25 (percentage, will be converted to decimal) * -- OR -- * TaxRate: 0.0825 (decimal, stored directly) * } * * Requires: request.BusinessID (set by auth middleware) */ function apiAbort(obj) { writeOutput(serializeJSON(obj)); abort; } function readJsonBody() { raw = toString(getHttpRequestData().content); if (isNull(raw) || len(trim(raw)) EQ 0) { apiAbort({ OK: false, ERROR: "missing_body" }); } try { parsed = deserializeJSON(raw); } catch (any e) { apiAbort({ OK: false, ERROR: "bad_json", MESSAGE: "Invalid JSON body" }); } if (!isStruct(parsed)) { apiAbort({ OK: false, ERROR: "bad_json", MESSAGE: "JSON must be an object" }); } return parsed; } if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) { apiAbort({ OK: false, ERROR: "no_business_selected" }); } try { data = readJsonBody(); updates = []; params = { businessId: request.BusinessID }; // Handle tax rate (accept either percent or decimal) if (structKeyExists(data, "TaxRatePercent") && isNumeric(data.TaxRatePercent)) { taxRate = data.TaxRatePercent / 100; if (taxRate < 0 || taxRate > 0.5) { apiAbort({ OK: false, ERROR: "invalid_tax_rate", MESSAGE: "Tax rate must be between 0% and 50%" }); } arrayAppend(updates, "TaxRate = :taxRate"); params.taxRate = { value: taxRate, cfsqltype: "cf_sql_decimal" }; } else if (structKeyExists(data, "TaxRate") && isNumeric(data.TaxRate)) { taxRate = data.TaxRate; if (taxRate < 0 || taxRate > 0.5) { apiAbort({ OK: false, ERROR: "invalid_tax_rate", MESSAGE: "Tax rate must be between 0 and 0.5" }); } arrayAppend(updates, "TaxRate = :taxRate"); params.taxRate = { value: taxRate, cfsqltype: "cf_sql_decimal" }; } // Add more updatable fields as needed if (structKeyExists(data, "Name") && len(trim(data.Name))) { arrayAppend(updates, "Name = :businessName"); params.businessName = { value: left(trim(data.Name), 100), cfsqltype: "cf_sql_varchar" }; } if (structKeyExists(data, "Phone")) { arrayAppend(updates, "Phone = :phone"); params.phone = { value: left(trim(data.Phone), 20), cfsqltype: "cf_sql_varchar" }; } // Track whether we need to update address separately hasAddressUpdate = false; addrFields = {}; if (structKeyExists(data, "Address") && len(trim(data.Address))) { hasAddressUpdate = true; addrFields.Line1 = left(trim(data.Address), 100); } if (structKeyExists(data, "City")) { hasAddressUpdate = true; addrFields.City = left(trim(data.City), 50); } if (structKeyExists(data, "Zip")) { hasAddressUpdate = true; addrFields.ZIPCode = left(trim(data.Zip), 10); } if (arrayLen(updates) == 0 && !hasAddressUpdate) { apiAbort({ OK: false, ERROR: "no_fields", MESSAGE: "No valid fields to update" }); } // Build and execute Businesses update if (arrayLen(updates) > 0) { sql = "UPDATE Businesses SET " & arrayToList(updates, ", ") & " WHERE ID = :businessId"; queryTimed(sql, params, { datasource: "payfrit" }); } // Update address in Addresses table if needed if (hasAddressUpdate) { qExistingAddr = queryTimed(" SELECT ID FROM Addresses WHERE (BusinessID = :businessId OR ID = (SELECT AddressID FROM Businesses WHERE ID = :businessId)) AND IsDeleted = 0 LIMIT 1 ", { businessId: request.BusinessID }, { datasource: "payfrit" }); if (qExistingAddr.recordCount > 0) { addrUpdates = []; addrParams = { addrId: qExistingAddr.ID }; if (structKeyExists(addrFields, "Line1")) { arrayAppend(addrUpdates, "Line1 = :line1"); addrParams.line1 = { value: addrFields.Line1, cfsqltype: "cf_sql_varchar" }; } if (structKeyExists(addrFields, "City")) { arrayAppend(addrUpdates, "City = :city"); addrParams.city = { value: addrFields.City, cfsqltype: "cf_sql_varchar" }; } if (structKeyExists(addrFields, "ZIPCode")) { arrayAppend(addrUpdates, "ZIPCode = :zip"); addrParams.zip = { value: addrFields.ZIPCode, cfsqltype: "cf_sql_varchar" }; } if (arrayLen(addrUpdates) > 0) { queryTimed("UPDATE Addresses SET " & arrayToList(addrUpdates, ", ") & " WHERE ID = :addrId", addrParams, { datasource: "payfrit" }); // Auto-geocode address in background geocodeAddrId = qExistingAddr.ID; } } } // Return updated settings q = queryTimed(" SELECT b.ID, b.Name, b.TaxRate, b.Phone FROM Businesses b WHERE b.ID = :businessId LIMIT 1 ", { businessId: request.BusinessID }, { datasource: "payfrit" }); qAddr = queryTimed(" SELECT a.Line1, a.City, a.ZIPCode, s.Abbreviation AS State FROM Addresses a LEFT JOIN tt_States s ON s.ID = a.StateID WHERE (a.BusinessID = :businessId OR a.ID = (SELECT AddressID FROM Businesses WHERE ID = :businessId)) AND a.IsDeleted = 0 LIMIT 1 ", { businessId: request.BusinessID }, { datasource: "payfrit" }); taxRateRaw = isNumeric(q.TaxRate) ? q.TaxRate : 0; taxRatePercent = taxRateRaw * 100; writeOutput(serializeJSON({ "OK": true, "MESSAGE": "Settings updated", "SETTINGS": { "BusinessID": q.ID, "Name": q.Name, "TaxRate": taxRateRaw, "TaxRatePercent": taxRatePercent, "Address": qAddr.recordCount > 0 ? (qAddr.Line1 ?: "") : "", "City": qAddr.recordCount > 0 ? (qAddr.City ?: "") : "", "State": qAddr.recordCount > 0 ? (qAddr.State ?: "") : "", "Zip": qAddr.recordCount > 0 ? (qAddr.ZIPCode ?: "") : "", "Phone": q.Phone ?: "", "Email": "" } })); } catch (any e) { apiAbort({ OK: false, ERROR: "server_error", MESSAGE: e.message }); }