UNPKG

@montarist/nestpay-api-v2

Version:

Unofficial comprehensive TypeScript API client for Nestpay payment gateway with 3D Secure support

408 lines 15.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSHA512Hash = exports.createSHA256Hash = exports.hashUtil = exports.HashUtil = void 0; const crypto_1 = __importDefault(require("crypto")); const enums_1 = require("../types/enums"); /** * Hash Version 3 utility class for generating secure hashes for Nestpay API calls * Implementation follows Hash Version 3 specification where ALL parameters are included * in alphabetical order (except storeKey which goes last) */ class HashUtil { constructor(algorithm = enums_1.HashAlgorithm.SHA512, encoding = enums_1.EncodingType.BASE64) { this.version = '3.0'; this.algorithm = algorithm; this.encoding = encoding; } /** * Generate Hash Version 3 for any Nestpay request * ALL parameters are included in alphabetical order (storeKey goes last) * @param params All parameters as key-value pairs * @param storeKey Store key (added at the end) * @param separator Separator character (default: '|' for pipe) * @returns Generated hash string */ generateHashV3(params, storeKey, separator = '|') { this.validateRequired({ storeKey }); // Convert all values to strings and filter out storeKey if accidentally included const cleanParams = {}; for (const [key, value] of Object.entries(params)) { if (key.toLowerCase() !== 'storekey') { cleanParams[key] = String(value || ''); // Convert to string, empty string for null/undefined } } // Sort parameter names alphabetically (case-insensitive) const sortedKeys = Object.keys(cleanParams).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); // Build hash string: sorted_param_values|storeKey const hashValues = []; // Add sorted parameter values for (const key of sortedKeys) { hashValues.push(cleanParams[key]); } // Add storeKey at the end hashValues.push(storeKey); const hashString = hashValues.join(separator); const hash = this.createHash(hashString); return hash; } /** * Generate hash for 3D Secure Classic model (Hash V3) */ generateThreeDClassicHashV3(params, storeKey) { // Set default values according to documentation const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', storetype: params.storetype || '3D', TranType: params.TranType || 'Auth' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for 3D Pay model (Hash V3) */ generateThreeDPayHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', storetype: params.storetype || '3D_PAY', TranType: params.TranType || 'Auth' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for 3D Pay Hosting model (Hash V3) */ generateThreeDPayHostingHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', storetype: params.storetype || '3D_PAY_HOSTING', TranType: params.TranType || 'Auth' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for direct payment (Hash V3) */ generateDirectPaymentHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', TranType: params.TranType || 'Auth' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for refund operation (Hash V3) */ generateRefundHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', TranType: params.TranType || 'Credit' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for void operation (Hash V3) */ generateVoidHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', TranType: params.TranType || 'Void' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for preauth operation (Hash V3) */ generatePreauthHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', TranType: params.TranType || 'PreAuth' }; return this.generateHashV3(fullParams, storeKey); } /** * Generate hash for postauth operation (Hash V3) */ generatePostauthHashV3(params, storeKey) { const fullParams = { ...params, hashAlgorithm: params.hashAlgorithm || 'ver3', TranType: params.TranType || 'PostAuth' }; return this.generateHashV3(fullParams, storeKey); } /** * Verify Hash Version 3 response */ verifyHashV3(receivedParams, receivedHash, storeKey, separator = '|') { if (!receivedParams || !receivedHash || !storeKey) { return false; } try { // Remove hash parameter from received params before verification const paramsForHash = { ...receivedParams }; delete paramsForHash.hash; delete paramsForHash.Hash; delete paramsForHash.HASH; const expectedHash = this.generateHashV3(paramsForHash, storeKey, separator); return this.constantTimeCompare(expectedHash, receivedHash); } catch (error) { return false; } } /** * Debug hash verification - shows detailed comparison */ debugHashVerification(receivedParams, receivedHash, storeKey, separator = '|') { const paramsForHash = { ...receivedParams }; delete paramsForHash.hash; delete paramsForHash.Hash; delete paramsForHash.HASH; // Sort parameter names alphabetically const sortedKeys = Object.keys(paramsForHash).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); // Build hash string manually to show it const hashValues = []; for (const key of sortedKeys) { hashValues.push(String(paramsForHash[key] || '')); } hashValues.push(storeKey); const hashString = hashValues.join(separator); const expectedHash = this.generateHashV3(paramsForHash, storeKey, separator); const isValid = this.constantTimeCompare(expectedHash, receivedHash); return { isValid, expectedHash, receivedHash, parametersUsed: sortedKeys, hashString }; } /** * Generate example hash according to documentation * Example from docs: amount|BillToCompany|BillToName|callbackUrl|clientid|currency|failUrl|hashAlgorithm|Instalment|lang|okurl|refreshtime|rnd|storetype|TranType|storeKey */ generateDocumentationExampleHash(separator = '|') { const params = { amount: '95.93', BillToCompany: 'billToCompany', BillToName: 'name', callbackUrl: 'http://localhost:8080/SampleCodeJSPTest/GateResponseControl.jsp', clientid: '100200127', currency: '949', failUrl: 'http://localhost:8080/SampleCodeJSPTest/GenericVer3ResponseHandler', hashAlgorithm: 'ver3', Instalment: '', // Empty value but still included lang: 'tr', okurl: 'http://localhost:8080/SampleCodeJSPTest/GenericVer3ResponseHandler', refreshtime: '5', rnd: '87954458746', storetype: '3D', TranType: 'Auth' }; const storeKey = 'TEST1234'; // Expected plaintext according to docs: // 95.93|billToCompany|name|http://localhost:8080/SampleCodeJSPTest/GateResponseControl.jsp|100200127|949|http://localhost:8080/SampleCodeJSPTest/GenericVer3ResponseHandler|ver3||tr|http://localhost:8080/SampleCodeJSPTest/GenericVer3ResponseHandler|5|87954458746|3D|Auth|TEST1234 return this.generateHashV3(params, storeKey, separator); } /** * Test both separators and show results */ testSeparators(params, storeKey) { const pipeHash = this.generateHashV3(params, storeKey, '|'); const semicolonHash = this.generateHashV3(params, storeKey, ';'); return { pipe: pipeHash, semicolon: semicolonHash }; } /** * Test with provided example from bank documentation */ testBankExample() { const params = { amount: '94922', clientid: '700688523497', currency: '949', cv2: '**', // Masked in example Ecom_Payment_Card_ExpDate_Month: '', // Empty in example Ecom_Payment_Card_ExpDate_Year: '*', // Masked in example email: 'erenkeskin@montarist.com.tr', failUrl: 'https://acb07e7ddb0b.ngrok-free.app/v1/payment/nestpay/failure', hashAlgorithm: 'ver3', islemtipi: 'Sale', lang: 'tr', oid: '28569dbb-132f-474d-a710-7bb2b93deb9f', okUrl: 'https://acb07e7ddb0b.ngrok-free.app/v1/payment/nestpay/callback', pan: '4508 03** **** 6554', // Masked in example refreshtime: '1', storetype: '3d_pay', taksit: '', // Empty in example tel: '+905437650305' }; const storeKey = 'storekey değeri'; // As provided in example const expectedHash = 'miLhMwX9RcszZx0JzEDEPjkDybUtNxgHzyKEkiybAbxTcru/hD/5ggRxxcfZadXtzhe3Naj6fLsOzciWKvZVEQ=='; const calculatedHash = this.generateHashV3(params, storeKey, '|'); const matches = calculatedHash === expectedHash; return { calculated: calculatedHash, expected: expectedHash, matches }; } // ====== Legacy Methods (kept for backward compatibility) ====== /** * @deprecated Use generateHashV3 instead for Hash Version 3 */ generatePaymentHash(params) { this.validateHashParameters(params); const hashString = this.buildHashString(params); return this.createHash(hashString); } /** * @deprecated Use generateHashV3 instead for Hash Version 3 */ generateThreeDHash(params) { this.validateThreeDHashParameters(params); const hashString = this.build3DHashString(params); return this.createHash(hashString); } /** * Create hash using specified algorithm and encoding with error handling */ createHash(data) { try { const hash = crypto_1.default.createHash(this.algorithm); hash.update(data, 'utf8'); return hash.digest(this.encoding); } catch (error) { throw new Error(`Hash generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Validate required parameters */ validateRequired(params) { for (const [key, value] of Object.entries(params)) { if (value === undefined || value === null || value === '') { throw new Error(`Required parameter '${key}' is missing or empty`); } } } /** * Constant-time string comparison to prevent timing attacks */ constantTimeCompare(a, b) { if (a.length !== b.length) { return false; } let result = 0; for (let i = 0; i < a.length; i++) { result |= a.charCodeAt(i) ^ b.charCodeAt(i); } return result === 0; } // Legacy validation methods validateHashParameters(params) { this.validateRequired(params); } validateThreeDHashParameters(params) { this.validateRequired({ clientId: params.clientId, orderId: params.orderId, amount: params.amount, storeKey: params.storeKey, currency: params.currency, callbackUrl: params.callbackUrl }); } build3DHashString(params) { const { clientId, orderId, amount, callbackUrl, storeKey, currency, installment } = params; return installment ? `${clientId}|${orderId}|${amount}|${callbackUrl}|${storeKey}|${currency}|${installment}` : `${clientId}|${orderId}|${amount}|${callbackUrl}|${storeKey}|${currency}`; } buildHashString(params) { const { clientId, orderId, amount, currency, storeKey } = params; return `${clientId}|${orderId}|${amount}|${currency}|${storeKey}`; } /** * Set hash algorithm with validation */ setAlgorithm(algorithm) { if (!Object.values(enums_1.HashAlgorithm).includes(algorithm)) { throw new Error(`Unsupported hash algorithm: ${algorithm}`); } this.algorithm = algorithm; } /** * Get current hash algorithm */ getAlgorithm() { return this.algorithm; } /** * Set encoding type with validation */ setEncoding(encoding) { if (!Object.values(enums_1.EncodingType).includes(encoding)) { throw new Error(`Unsupported encoding type: ${encoding}`); } this.encoding = encoding; } /** * Get current encoding type */ getEncoding() { return this.encoding; } /** * Get hash utility version */ getVersion() { return this.version; } /** * Get supported algorithms */ static getSupportedAlgorithms() { return Object.values(enums_1.HashAlgorithm); } /** * Get supported encodings */ static getSupportedEncodings() { return Object.values(enums_1.EncodingType); } /** * Create a new instance with specific configuration */ static create(algorithm, encoding) { return new HashUtil(algorithm, encoding); } } exports.HashUtil = HashUtil; /** * Default hash utility instance with SHA512 and Base64 (Hash Version 3 standard) */ exports.hashUtil = new HashUtil(enums_1.HashAlgorithm.SHA512, enums_1.EncodingType.BASE64); /** * Utility function to create hash with SHA256 and Hex encoding */ const createSHA256Hash = (data) => { const sha256Util = new HashUtil(enums_1.HashAlgorithm.SHA256, enums_1.EncodingType.HEX); return sha256Util['createHash'](data); }; exports.createSHA256Hash = createSHA256Hash; /** * Utility function to create hash with SHA512 and Base64 encoding */ const createSHA512Hash = (data) => { const sha512Util = new HashUtil(enums_1.HashAlgorithm.SHA512, enums_1.EncodingType.BASE64); return sha512Util['createHash'](data); }; exports.createSHA512Hash = createSHA512Hash; //# sourceMappingURL=hash.js.map