- 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>
58 lines
2.5 KiB
JavaScript
58 lines
2.5 KiB
JavaScript
/**
|
|
* PayFast configuration (Admin → Site Settings → Payments → PayFast).
|
|
*
|
|
* Stored in AppSetting: payfast_merchant_id, payfast_merchant_key, payfast_passphrase
|
|
* (AES-256-GCM encrypted — it signs every payment) and payfast_mode ('sandbox' | 'live',
|
|
* default sandbox). PAYFAST_MERCHANT_ID / PAYFAST_MERCHANT_KEY / PAYFAST_PASSPHRASE /
|
|
* PAYFAST_MODE in .env are fallbacks when the database has no value. The merchant key isn't
|
|
* secret: it travels in the payment form the payer's browser posts to PayFast.
|
|
*/
|
|
|
|
const { getSetting } = require('./settingsCache');
|
|
|
|
const KEY_MERCHANT_ID = 'payfast_merchant_id';
|
|
const KEY_MERCHANT_KEY = 'payfast_merchant_key';
|
|
const KEY_PASSPHRASE = 'payfast_passphrase';
|
|
const KEY_MODE = 'payfast_mode';
|
|
|
|
const HOSTS = { live: 'www.payfast.co.za', sandbox: 'sandbox.payfast.co.za' };
|
|
|
|
async function getPayfastConfig() {
|
|
const [merchantId, merchantKey, passphrase, mode] = await Promise.all([
|
|
getSetting(KEY_MERCHANT_ID, process.env.PAYFAST_MERCHANT_ID),
|
|
getSetting(KEY_MERCHANT_KEY, process.env.PAYFAST_MERCHANT_KEY),
|
|
getSetting(KEY_PASSPHRASE, process.env.PAYFAST_PASSPHRASE),
|
|
getSetting(KEY_MODE, process.env.PAYFAST_MODE),
|
|
]);
|
|
return {
|
|
merchantId: String(merchantId || '').trim(),
|
|
merchantKey: String(merchantKey || '').trim(),
|
|
passphrase: String(passphrase || ''),
|
|
mode: mode === 'live' ? 'live' : 'sandbox',
|
|
};
|
|
}
|
|
|
|
/** PayFast's host for a mode: where payers are sent and where ITNs are confirmed. */
|
|
const payfastHost = (mode) => HOSTS[mode === 'live' ? 'live' : 'sandbox'];
|
|
|
|
/** Where PayFast should post ITNs: BACKEND_URL (or the request's origin) + /api/webhooks/payfast. */
|
|
function payfastNotifyUrl(req) {
|
|
const base = (process.env.BACKEND_URL || (req ? `${req.protocol}://${req.get('host')}` : '')).replace(/\/$/, '');
|
|
return base ? `${base}/api/webhooks/payfast` : '';
|
|
}
|
|
|
|
/** Why PayFast can't reach the notify URL (null when it can). */
|
|
function notifyUrlProblem(url) {
|
|
let u;
|
|
try { u = new URL(url); } catch { return 'Set BACKEND_URL on the server so PayFast knows where to send payment notifications.'; }
|
|
if (['localhost', '127.0.0.1', '::1', '[::1]'].includes(u.hostname)) {
|
|
return 'PayFast can\'t reach localhost, so payments won\'t be confirmed automatically. Use a public BACKEND_URL (or a tunnel) to test notifications.';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
module.exports = {
|
|
KEY_MERCHANT_ID, KEY_MERCHANT_KEY, KEY_PASSPHRASE, KEY_MODE,
|
|
getPayfastConfig, payfastHost, payfastNotifyUrl, notifyUrlProblem,
|
|
};
|