hochu-sdelat-mini-servis-po/src/config.js

114 lines
4.2 KiB
JavaScript

import 'dotenv/config';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT_DIR = path.resolve(__dirname, '..');
export const PORT = process.env.PORT || 3000;
// ─── ECDSA P-256 keypair (persisted to keypair.json) ───
const KEYPAIR_FILE = path.join(ROOT_DIR, 'keypair.json');
let ecKeyPair;
if (fs.existsSync(KEYPAIR_FILE)) {
const saved = JSON.parse(fs.readFileSync(KEYPAIR_FILE, 'utf8'));
ecKeyPair = {
privateKey: crypto.createPrivateKey({ key: Buffer.from(saved.privateKey, 'base64'), format: 'der', type: 'pkcs8' }),
publicKey: crypto.createPublicKey({ key: Buffer.from(saved.publicKey, 'base64'), format: 'der', type: 'spki' }),
};
console.log('Loaded ECDSA keypair from keypair.json');
} else {
ecKeyPair = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
const saved = {
privateKey: ecKeyPair.privateKey.export({ type: 'pkcs8', format: 'der' }).toString('base64'),
publicKey: ecKeyPair.publicKey.export({ type: 'spki', format: 'der' }).toString('base64'),
};
fs.writeFileSync(KEYPAIR_FILE, JSON.stringify(saved, null, 2));
console.log('Generated new ECDSA keypair → saved to keypair.json');
}
export { ecKeyPair };
// Uncompressed EC public key point (base64)
const pubKeyDer = ecKeyPair.publicKey.export({ type: 'spki', format: 'der' });
const x509B64 = pubKeyDer.toString('base64');
const uncompressedPoint = pubKeyDer.slice(pubKeyDer.length - 65);
const pkB64 = uncompressedPoint.toString('base64');
const pkTagHash = crypto.createHash('md5').update(pkB64).digest('hex');
// ─── Device identity (persisted to device.json) ───
const DEVICE_FILE = path.join(ROOT_DIR, 'device.json');
let deviceId, installId, pinHash;
if (fs.existsSync(DEVICE_FILE)) {
const saved = JSON.parse(fs.readFileSync(DEVICE_FILE, 'utf8'));
({ deviceId, installId, pinHash } = saved);
console.log('Loaded device identity from device.json');
} else {
deviceId = crypto.randomUUID().toUpperCase();
installId = crypto.randomUUID().toUpperCase();
pinHash = crypto.createHash('md5').update(crypto.randomBytes(16)).digest('hex');
fs.writeFileSync(DEVICE_FILE, JSON.stringify({ deviceId, installId, pinHash }, null, 2));
console.log('Generated new device identity → saved to device.json');
}
export const DEVICE = {
deviceId,
installId,
pk: pkB64,
pkTag: pkTagHash,
pinHash,
x509: x509B64,
};
console.log(' pk:', DEVICE.pk);
console.log(' x509:', DEVICE.x509);
console.log(' pkTag:', DEVICE.pkTag);
// ─── Kaspi Base URLs ───
export const KASPI_ENTRANCE_URL = 'https://entrance-pay.kaspi.kz';
export const KASPI_MTOKEN_URL = 'https://mtoken.kaspi.kz';
export const KASPI_QRPAY_URL = 'https://qrpay.kaspi.kz';
// ─── App version & device constants ───
// Defaults match a known-good Kaspi Pay client. Override via .env if needed.
// ⚠️ The Kaspi API validates these parameters and may reject unknown values.
export const APP = {
version: process.env.APP_VERSION || '4.112.1',
build: process.env.APP_BUILD || '1107',
platform: process.env.APP_PLATFORM || 'iOS',
platformVer: process.env.APP_PLATFORM_VER || '18.4',
locale: process.env.APP_LOCALE || 'ru-RU',
model: process.env.APP_MODEL || 'iPhone16,2',
brand: process.env.APP_BRAND || 'Apple',
deviceName: process.env.APP_DEVICE_NAME || 'iPhone',
screenW: process.env.APP_SCREEN_W || '430.0',
screenH: process.env.APP_SCREEN_H || '932.0',
cfNetwork: process.env.APP_CFNETWORK || 'CFNetwork/3826.400.120',
darwin: process.env.APP_DARWIN || 'Darwin/24.4.0',
};
export const UA_NATIVE = `Kaspi%20Pay/${APP.build} ${APP.cfNetwork} ${APP.darwin}`;
export const UA_BROWSER = `Mozilla/5.0 (iPhone; CPU iPhone OS ${APP.platformVer.replace('.', '_')} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148`;
export const ENTRANCE_HEADERS_BASE = {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'Accept-Language': 'ru',
'Accept-Encoding': 'gzip, deflate, br',
Origin: KASPI_ENTRANCE_URL,
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'User-Agent': UA_BROWSER,
};
export { ROOT_DIR };