paynow-decoder
Version:
A decoder for PayNow QR codes and payment information
100 lines (99 loc) • 4.69 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PayNowDecoder = void 0;
class PayNowDecoder {
static decode(payload) {
const fields = this.parseFields(payload);
return this.extractPayNowData(fields);
}
static parseFields(payload) {
const fields = [];
let position = 0;
while (position < payload.length) {
const id = payload.substr(position, 2);
position += 2;
const lengthStr = payload.substr(position, 2);
const length = parseInt(lengthStr, 10);
position += 2;
const value = payload.substr(position, length);
position += length;
// Handle nested fields for merchant account info and additional data
if (id === this.MERCHANT_ACCOUNT_INFO || id === this.ADDITIONAL_DATA) {
const nestedFields = this.parseFields(value);
fields.push({ id, length, value, nestedFields });
}
else {
fields.push({ id, length, value });
}
}
return fields;
}
static extractPayNowData(fields) {
const merchantInfoField = fields.find((f) => f.id === this.MERCHANT_ACCOUNT_INFO);
const additionalDataField = fields.find((f) => f.id === this.ADDITIONAL_DATA);
const transactionAmount = this.extractFieldValue(fields, this.TRANSACTION_AMOUNT);
return {
payloadFormat: this.extractFieldValue(fields, this.PAYLOAD_FORMAT_INDICATOR),
pointOfInitiation: this.extractFieldValue(fields, this.POINT_OF_INITIATION_METHOD),
merchantAccountInfo: this.parseMerchantAccountInfo((merchantInfoField === null || merchantInfoField === void 0 ? void 0 : merchantInfoField.nestedFields) || []),
merchantCategoryCode: this.extractFieldValue(fields, this.MERCHANT_CATEGORY_CODE),
currencyCode: this.extractFieldValue(fields, this.TRANSACTION_CURRENCY),
transactionAmount: transactionAmount
? parseFloat(transactionAmount)
: undefined,
countryCode: this.extractFieldValue(fields, this.COUNTRY_CODE),
merchantName: this.extractFieldValue(fields, this.MERCHANT_NAME),
merchantCity: this.extractFieldValue(fields, this.MERCHANT_CITY),
additionalData: this.parseAdditionalData((additionalDataField === null || additionalDataField === void 0 ? void 0 : additionalDataField.nestedFields) || []),
crc: this.extractFieldValue(fields, this.CRC),
};
}
static extractFieldValue(fields, id) {
const field = fields.find((f) => f.id === id);
return field === null || field === void 0 ? void 0 : field.value;
}
static parseMerchantAccountInfo(nestedFields) {
const identifier = this.extractFieldValue(nestedFields, this.MERCHANT_IDENTIFIER);
const rawProxyType = this.extractFieldValue(nestedFields, this.MERCHANT_PROXY_TYPE);
const proxyType = (rawProxyType &&
{
"0": "mobile",
"2": "uen",
"3": "vpa",
}[rawProxyType]) ||
undefined;
const amountEditableStr = this.extractFieldValue(nestedFields, this.MERCHANT_AMOUNT_EDITABLE);
return {
identifier: identifier,
proxyType: proxyType,
proxyValue: this.extractFieldValue(nestedFields, this.MERCHANT_PROXY_VALUE),
amountEditable: amountEditableStr === "1",
expiryDate: this.extractFieldValue(nestedFields, this.MERCHANT_EXPIRY_DATE),
};
}
static parseAdditionalData(nestedFields) {
const referenceNumber = this.extractFieldValue(nestedFields, this.REFERENCE_NUMBER);
return referenceNumber ? { referenceNumber } : undefined;
}
}
exports.PayNowDecoder = PayNowDecoder;
// Root level IDs
PayNowDecoder.PAYLOAD_FORMAT_INDICATOR = "00";
PayNowDecoder.POINT_OF_INITIATION_METHOD = "01";
PayNowDecoder.MERCHANT_ACCOUNT_INFO = "26";
PayNowDecoder.MERCHANT_CATEGORY_CODE = "52";
PayNowDecoder.TRANSACTION_CURRENCY = "53";
PayNowDecoder.TRANSACTION_AMOUNT = "54";
PayNowDecoder.COUNTRY_CODE = "58";
PayNowDecoder.MERCHANT_NAME = "59";
PayNowDecoder.MERCHANT_CITY = "60";
PayNowDecoder.ADDITIONAL_DATA = "62";
PayNowDecoder.CRC = "63";
// Merchant Account Info nested IDs
PayNowDecoder.MERCHANT_IDENTIFIER = "00";
PayNowDecoder.MERCHANT_PROXY_TYPE = "01";
PayNowDecoder.MERCHANT_PROXY_VALUE = "02";
PayNowDecoder.MERCHANT_AMOUNT_EDITABLE = "03";
PayNowDecoder.MERCHANT_EXPIRY_DATE = "04";
// Additional Data nested IDs
PayNowDecoder.REFERENCE_NUMBER = "01";