@montarist/nestpay-api-v2
Version:
Unofficial comprehensive TypeScript API client for Nestpay payment gateway with 3D Secure support
408 lines • 15.3 kB
JavaScript
;
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