- PayFast (custom integration): signed form (PHP-style encoding, checked against a real sandbox), ITN checks (signature over all posted fields, PayFast host, VALID from /eng/query/validate, amount vs checkout). - ProviderCheckout records every checkout (amount, metadata, request); webhooks match payments to it and hold a different amount for review (ProviderTransaction.note). - /pay/<id>: payers choose a provider when several are on, and the browser posts PayFast's form from there, so payment links work for every provider. - Site settings → Payments: PayFast card (merchant ID/key, encrypted passphrase, sandbox/live) while PayFast is enabled. - Payer-facing wording no longer names Yoco; legal pages name both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
2.3 KiB
JavaScript
79 lines
2.3 KiB
JavaScript
/**
|
|
* Lightweight in-process cache for AppSetting values.
|
|
* Refreshes every 60 seconds so changes made in the admin settings page
|
|
* take effect without a server restart.
|
|
* Falls back to environment variables when a key is not found in the DB.
|
|
*
|
|
* Encrypted keys (SMTP credentials, Yoco secrets) are automatically decrypted on read.
|
|
*/
|
|
|
|
const prisma = require('../config/db');
|
|
const { decrypt, isEncrypted } = require('./encryption');
|
|
|
|
// Keys stored encrypted in the DB — decrypted transparently on read.
|
|
const ENCRYPTED_KEYS = new Set(['smtp_user', 'smtp_pass', 'yoco_secret_key', 'yoco_webhook_secret', 'payfast_passphrase']);
|
|
|
|
let _cache = {};
|
|
let _lastFetch = 0;
|
|
const TTL_MS = 60 * 1000;
|
|
|
|
async function refreshIfStale() {
|
|
const now = Date.now();
|
|
if (now - _lastFetch < TTL_MS && _lastFetch > 0) return;
|
|
try {
|
|
const rows = await prisma.appSetting.findMany();
|
|
const fresh = {};
|
|
for (const r of rows) fresh[r.key] = r.value;
|
|
_cache = fresh;
|
|
_lastFetch = now;
|
|
} catch {
|
|
// DB unreachable — keep existing cache; will retry next call
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal getter that optionally decrypts encrypted keys.
|
|
* @param {string} key
|
|
* @param {string} [envFallback]
|
|
* @returns {string}
|
|
*/
|
|
function _getSync(key, envFallback = '') {
|
|
const v = _cache[key];
|
|
const raw = (v !== undefined && v !== null && v !== '') ? v : (envFallback || '');
|
|
if (ENCRYPTED_KEYS.has(key) && isEncrypted(raw)) {
|
|
try { return decrypt(raw); } catch { return ''; }
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
/**
|
|
* Async getter — always checks for a stale cache before returning.
|
|
* @param {string} key
|
|
* @param {string} [envFallback]
|
|
* @returns {Promise<string>}
|
|
*/
|
|
async function getSetting(key, envFallback = '') {
|
|
await refreshIfStale();
|
|
return _getSync(key, envFallback);
|
|
}
|
|
|
|
/**
|
|
* Sync wrapper for use in synchronous functions.
|
|
* Uses the current in-memory snapshot; relies on the cache being warmed at startup.
|
|
*/
|
|
const getSettingSync = _getSync;
|
|
|
|
/**
|
|
* Call once at startup to pre-warm the cache so synchronous callers get DB values.
|
|
*/
|
|
async function warmCache() {
|
|
await refreshIfStale();
|
|
}
|
|
|
|
/** Invalidate the cache and immediately re-warm it in the background. */
|
|
function invalidate() {
|
|
_lastFetch = 0;
|
|
refreshIfStale().catch(() => {});
|
|
}
|
|
|
|
module.exports = { getSetting, getSettingSync, warmCache, invalidate, ENCRYPTED_KEYS }; |