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/config/environment.cfm
John Pinkyfloyd 4109e3dac4 Enable magic OTP on production for App Store review
Code 123456 will work for any phone number to facilitate
App Store review testing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 16:23:19 -08:00

265 lines
9.1 KiB
Text

<cfscript>
/**
* Environment Configuration
*
* Auto-detects dev vs production based on server hostname.
* No manual flag to change — safe to deploy via git pull to either server.
*/
// ============================================
// ENVIRONMENT FLAG - AUTO-DETECTED
// ============================================
isDevEnvironment = (createObject("java", "java.net.InetAddress").getLocalHost().getHostName() != "biz");
// ============================================
// ERROR HANDLING
// ============================================
// Dev: Show full stack traces
// Prod: Show generic "server error" message
application.showDetailedErrors = isDevEnvironment;
// ============================================
// DEBUG LOGGING
// ============================================
// Dev: Log all API requests/responses
// Prod: Minimal logging
application.debugLogging = isDevEnvironment;
application.logDirectory = expandPath("/api/logs/");
// ============================================
// RATE LIMITING
// ============================================
// Dev: No rate limits
// Prod: Enforce rate limits
application.enableRateLimiting = !isDevEnvironment;
application.rateLimitPerMinute = 60; // requests per minute per IP
// ============================================
// EMAIL HANDLING
// ============================================
// Dev: Send all emails to catch-all address
// Prod: Send to real recipients
application.emailCatchAll = isDevEnvironment ? "dev-emails@payfrit.com" : "";
application.emailEnabled = !isDevEnvironment ? true : false; // Disable emails on dev entirely
// ============================================
// MAGIC OTP (for testing / App Store review)
// ============================================
// Enabled on both dev and production for App Store review
// Code 123456 will work for any phone number
application.MAGIC_OTP_ENABLED = true;
application.MAGIC_OTP_CODE = "123456";
application.MAGIC_PHONE_NUMBERS = ["5555555555", "0000000000"];
// ============================================
// STRIPE MODE
// ============================================
// Already handled in stripe.cfm, but good to have reference
application.stripeTestMode = isDevEnvironment;
// ============================================
// API RESPONSE EXTRAS
// ============================================
// Dev: Include debug info in API responses (timing, queries, etc)
// Prod: Minimal responses
application.includeDebugInResponse = isDevEnvironment;
// ============================================
// SESSION/TOKEN SETTINGS
// ============================================
// Dev: Longer token expiry for easier testing
// Prod: Normal expiry
application.tokenExpiryHours = isDevEnvironment ? 720 : 24; // 30 days vs 1 day
// ============================================
// HELPER FUNCTIONS
// ============================================
function isDev() {
return structKeyExists(application, "isDevEnvironment") && application.isDevEnvironment;
}
function logDebug(message, data = {}) {
if (!application.debugLogging) return;
var logFile = application.logDirectory & "debug_" & dateFormat(now(), "yyyy-mm-dd") & ".log";
var logLine = "[" & timeFormat(now(), "HH:mm:ss") & "] " & message;
if (!structIsEmpty(data)) {
logLine &= " | " & serializeJSON(data);
}
try {
if (!directoryExists(application.logDirectory)) {
directoryCreate(application.logDirectory);
}
fileAppend(logFile, logLine & chr(10));
} catch (any e) {
// Silent fail - don't break app if logging fails
}
}
function apiError(message, detail = "", statusCode = 500) {
var response = {
"OK": false,
"ERROR": "server_error",
"MESSAGE": application.showDetailedErrors ? message : "An error occurred"
};
if (application.showDetailedErrors && len(detail)) {
response["DETAIL"] = detail;
response["STACK"] = "";
}
return response;
}
// ============================================
// PERFORMANCE PROFILING
// ============================================
application.perfEnabled = true;
if (!structKeyExists(application, "perfBuffer")) {
application.perfBuffer = [];
}
/**
* Drop-in replacement for queryExecute() that tracks query count and time.
* Opt-in: use in endpoints where you want accurate DB time breakdown.
*/
function queryTimed(required string sql, any params = [], struct options = {}) {
if (!structKeyExists(request, "_perf_queryCount")) {
request._perf_queryCount = 0;
request._perf_queryTimeMs = 0;
}
var t = getTickCount();
var result = queryExecute(arguments.sql, arguments.params, arguments.options);
var elapsed = getTickCount() - t;
request._perf_queryCount++;
request._perf_queryTimeMs += elapsed;
return result;
}
/**
* Flush the in-memory perf buffer to the ApiPerfLogs MySQL table.
* Thread-safe: duplicates buffer under lock, then inserts outside lock.
*/
function flushPerfBuffer() {
// Auto-create table if it doesn't exist
if (!structKeyExists(application, "_perfTableChecked")) {
try {
queryExecute("
CREATE TABLE IF NOT EXISTS ApiPerfLogs (
ID INT AUTO_INCREMENT PRIMARY KEY,
Endpoint VARCHAR(255) NOT NULL,
TotalMs INT NOT NULL DEFAULT 0,
DbMs INT NOT NULL DEFAULT 0,
AppMs INT NOT NULL DEFAULT 0,
QueryCount INT NOT NULL DEFAULT 0,
ResponseBytes INT NOT NULL DEFAULT 0,
BusinessID INT NOT NULL DEFAULT 0,
UserID INT NOT NULL DEFAULT 0,
LoggedAt DATETIME NOT NULL,
INDEX idx_loggedat (LoggedAt),
INDEX idx_endpoint (Endpoint)
) ENGINE=InnoDB
", {}, { datasource: "payfrit" });
application._perfTableChecked = true;
} catch (any e) {}
}
var batch = [];
lock name="payfrit_perfBuffer" timeout="2" type="exclusive" {
if (structKeyExists(application, "perfBuffer")) {
batch = duplicate(application.perfBuffer);
application.perfBuffer = [];
}
}
if (arrayLen(batch) == 0) return;
try {
var sql = "INSERT INTO ApiPerfLogs (Endpoint, TotalMs, DbMs, AppMs, QueryCount, ResponseBytes, BusinessID, UserID, LoggedAt) VALUES ";
var rows = [];
for (var m in batch) {
arrayAppend(rows,
"('" & replace(m.endpoint, "'", "''", "all") & "',"
& val(m.totalMs) & "," & val(m.dbMs) & "," & val(m.appMs) & ","
& val(m.queryCount) & "," & val(m.responseBytes) & ","
& val(m.businessId) & "," & val(m.userId) & ",'"
& m.loggedAt & "')"
);
}
sql &= arrayToList(rows, ",");
queryExecute(sql, {}, { datasource: "payfrit" });
// Cleanup old data (1% chance per flush)
if (randRange(1, 100) == 1) {
queryExecute(
"DELETE FROM ApiPerfLogs WHERE LoggedAt < DATE_SUB(NOW(), INTERVAL 30 DAY)",
{}, { datasource: "payfrit" }
);
}
} catch (any e) {
// Silent fail - never break the app for profiling
}
}
/**
* Log performance metrics for the current request.
* Called automatically from apiAbort().
*/
function logPerf(numeric responseBytes = 0) {
if (!structKeyExists(application, "perfEnabled") || !application.perfEnabled) return;
if (!structKeyExists(request, "_perf_start")) return;
// Safety valve: don't let buffer grow unbounded if flush is failing
if (structKeyExists(application, "perfBuffer") && arrayLen(application.perfBuffer) > 1000) return;
var totalMs = getTickCount() - request._perf_start;
var dbMs = structKeyExists(request, "_perf_queryTimeMs") ? request._perf_queryTimeMs : 0;
var metric = {
endpoint: structKeyExists(request, "_api_path") ? request._api_path : "unknown",
totalMs: totalMs,
dbMs: dbMs,
appMs: totalMs - dbMs,
queryCount: structKeyExists(request, "_perf_queryCount") ? request._perf_queryCount : 0,
responseBytes: arguments.responseBytes,
businessId: structKeyExists(request, "BusinessID") ? val(request.BusinessID) : 0,
userId: structKeyExists(request, "UserID") ? val(request.UserID) : 0,
loggedAt: dateTimeFormat(now(), "yyyy-mm-dd HH:nn:ss")
};
var shouldFlush = false;
lock name="payfrit_perfBuffer" timeout="1" type="exclusive" {
arrayAppend(application.perfBuffer, metric);
if (arrayLen(application.perfBuffer) >= 100) {
shouldFlush = true;
}
}
if (shouldFlush) {
thread name="perfFlush_#createUUID()#" {
flushPerfBuffer();
}
}
}
// Store in application scope
application.isDevEnvironment = isDevEnvironment;
application.baseUrl = isDevEnvironment ? "https://dev.payfrit.com" : "https://biz.payfrit.com";
application.isDev = isDev;
application.logDebug = logDebug;
application.apiError = apiError;
application.queryTimed = queryTimed;
application.logPerf = logPerf;
application.flushPerfBuffer = flushPerfBuffer;
</cfscript>