modify-qris
Version:
Modify QRIS Generator is a javascript based libraries that enabling flexible payments with customizable amounts for improved efficiency and user experience
449 lines (448 loc) ⢠24.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QRIS = void 0;
const crc16_ccitt_1 = require("./crc16_ccitt");
const errors_1 = require("../errors");
const enums_1 = require("../enums");
class QRIS {
/**
* Sanitizes a string by removing newlines, carriage returns and trimming whitespace
* @param input The string to sanitize
* @returns The sanitized string
*/
static sanitize(input) {
input = input.replace(/\n/g, '');
input = input.replace(/\r/g, '');
return input.trim();
}
/**
* Parse a QRIS code string into structured data
* @param codeString The code string to parse
* @returns Parsed data object
* @throws Error if the code string format is invalid
*/
static parseTag(codeString) {
if (codeString.length < 5) {
throw new errors_1.QRISParseError('QRIS code is too short. min 5 characters.', undefined, 0, codeString);
}
const tag = codeString.substring(0, 2);
const lengthCode = codeString.substring(2, 4);
const length = parseInt(lengthCode, 10);
if (isNaN(length)) {
throw new errors_1.QRISParseError(`Format data length is invalid for tag ${tag}`, tag, 2, lengthCode);
}
if (codeString.length < 4 + length) {
throw new errors_1.QRISParseError(`Data length does not match for tag ${tag}. Required: ${length}, Available: ${codeString.length - 4}`, tag, 4, codeString);
}
const content = codeString.substring(4, 4 + length);
return {
tag,
content,
data: tag + lengthCode + content,
};
}
static modifyContent(data, content) {
const length = content.length;
// Jika content kosong, return object Data kosong
if (length === 0) {
return {
tag: '',
content: '',
data: ''
};
}
// Buat format panjang 2 digit dengan leading zero
const paddedLength = length.toString().padStart(2, '0');
// Return data baru dengan content yang dimodifikasi
return {
tag: data.tag,
content: content,
data: data.tag + paddedLength + content
};
}
static parseAquirerDetail(codeString) {
let result = {
site: null,
mpan: null,
terminal_id: null,
category: null
};
while (codeString.length > 0) {
try {
const data = this.parseTag(codeString);
switch (data.tag) {
case enums_1.AcquirerDetailTags.SITE:
result.site = data;
break;
case enums_1.AcquirerDetailTags.MPAN:
result.mpan = data;
break;
case enums_1.AcquirerDetailTags.TERMINAL_ID:
result.terminal_id = data;
break;
case enums_1.AcquirerDetailTags.CATEGORY:
result.category = data;
break;
default:
// Abaikan tag yang tidak dikenal
break;
}
codeString = codeString.substring(4 + data.content.length);
}
catch (err) {
throw new Error(`Failed to parse Aquirer Detail: ${err.message}`);
}
}
return result;
}
static parseSwitchingDetail(codeString) {
let result = {
site: null,
nmid: null,
category: null
};
while (codeString.length > 0) {
try {
const data = this.parseTag(codeString);
switch (data.tag) {
case enums_1.SwitchingDetailTags.SITE:
result.site = data;
break;
case enums_1.SwitchingDetailTags.NMID:
result.nmid = data;
break;
case enums_1.SwitchingDetailTags.CATEGORY:
result.category = data;
break;
default:
// Abaikan tag yang tidak dikenal
break;
}
codeString = codeString.substring(4 + data.content.length);
}
catch (err) {
throw new Error(`Failed to parse Switching Detail: ${err.message}`);
}
}
return result;
}
static parseAdditionalInformationDetail(codeString) {
// Definisi tag untuk RFU
const RFU_TAGS = Array.from({ length: parseInt(enums_1.AdditionalInformationTags.RFU_END) - parseInt(enums_1.AdditionalInformationTags.RFU_START) + 1 }, (_, i) => (parseInt(enums_1.AdditionalInformationTags.RFU_START) + i).toString().padStart(2, '0'));
// Definisi tag untuk Payment System Specific
const PAYMENT_SYSTEM_TAGS = Array.from({ length: parseInt(enums_1.AdditionalInformationTags.PAYMENT_SYSTEM_SPECIFIC_END) - parseInt(enums_1.AdditionalInformationTags.PAYMENT_SYSTEM_SPECIFIC_START) + 1 }, (_, i) => (parseInt(enums_1.AdditionalInformationTags.PAYMENT_SYSTEM_SPECIFIC_START) + i).toString().padStart(2, '0'));
let result = {
bill_number: null,
mobile_number: null,
store_label: null,
loyalty_number: null,
reference_label: null,
customer_label: null,
terminal_label: null,
purpose_of_transaction: null,
additional_consumer_data_request: null,
merchant_tax_id: null,
merchant_channel: null,
rfu: null,
payment_system_specific: null
};
while (codeString.length > 0) {
try {
const data = this.parseTag(codeString);
switch (data.tag) {
case enums_1.AdditionalInformationTags.BILL_NUMBER:
result.bill_number = data;
break;
case enums_1.AdditionalInformationTags.MOBILE_NUMBER:
result.mobile_number = data;
break;
case enums_1.AdditionalInformationTags.STORE_LABEL:
result.store_label = data;
break;
case enums_1.AdditionalInformationTags.LOYALTY_NUMBER:
result.loyalty_number = data;
break;
case enums_1.AdditionalInformationTags.REFERENCE_LABEL:
result.reference_label = data;
break;
case enums_1.AdditionalInformationTags.CUSTOMER_LABEL:
result.customer_label = data;
break;
case enums_1.AdditionalInformationTags.TERMINAL_LABEL:
result.terminal_label = data;
break;
case enums_1.AdditionalInformationTags.PURPOSE_OF_TRANSACTION:
result.purpose_of_transaction = data;
break;
case enums_1.AdditionalInformationTags.ADDITIONAL_CONSUMER_DATA_REQUEST:
result.additional_consumer_data_request = data;
break;
case enums_1.AdditionalInformationTags.MERCHANT_TAX_ID:
result.merchant_tax_id = data;
break;
case enums_1.AdditionalInformationTags.MERCHANT_CHANNEL:
result.merchant_channel = data;
break;
case RFU_TAGS.includes(data.tag) && data.tag:
result.rfu = data;
break;
case PAYMENT_SYSTEM_TAGS.includes(data.tag) && data.tag:
result.payment_system_specific = data;
break;
default:
// Abaikan tag yang tidak dikenal
break;
}
codeString = codeString.substring(4 + data.content.length);
}
catch (err) {
throw new Error(`Failed to parse Switching Detail: ${err.message}`);
}
}
return result;
}
static parse(qrString) {
// Sanitize string value
qrString = this.sanitize(qrString);
let result = {
version: null,
category: null,
acquirer: null,
acquirer_bank_transfer: null,
switching: null,
merchant_category_code: null,
currency_code: null,
payment_amount: null,
payment_fee_category: null,
payment_fee: null,
country_code: null,
merchant_name: null,
merchant_city: null,
merchant_postal_code: null,
additional_information: null,
crc_code: null
};
while (qrString.length > 0) {
try {
const data = this.parseTag(qrString);
switch (data.tag) {
case enums_1.QRISTags.VERSION: // Version
result.version = data;
break;
case enums_1.QRISTags.CATEGORY: // Category
result.category = data;
break;
case enums_1.QRISTags.ACQUIRER: // Acquirer
try {
const detail = this.parseAquirerDetail(data.content);
result.acquirer = {
tag: data.tag,
content: data.content,
data: data.data,
detail: detail
};
}
catch (err) {
throw new Error(`Invalid parse acquirer for content ${data.content}`);
}
break;
case enums_1.QRISTags.ACQUIRER_BANK_TRANSFER: // AcquirerBankTransfer
try {
const detail = this.parseAquirerDetail(data.content);
result.acquirer = {
tag: data.tag,
content: data.content,
data: data.data,
detail: detail
};
}
catch (err) {
throw new Error(`Invalid parse acquirer bank transfer for content ${data.content}`);
}
break;
case enums_1.QRISTags.SWITCHING: // Switching
try {
const detail = this.parseSwitchingDetail(data.content);
result.switching = {
tag: data.tag,
content: data.content,
data: data.data,
detail: detail
};
}
catch (err) {
throw new Error(`Invalid parse switching for content ${data.content}`);
}
break;
case enums_1.QRISTags.MERCHANT_CATEGORY_CODE: // MerchantCategoryCode
result.merchant_category_code = data;
break;
case enums_1.QRISTags.CURRENCY_CODE: // CurrencyCode
result.currency_code = data;
break;
case enums_1.QRISTags.PAYMENT_AMOUNT: // PaymentAmount
result.payment_amount = data;
break;
case enums_1.QRISTags.PAYMENT_FEE_CATEGORY: // PaymentFeeCategory
result.payment_fee_category = data;
break;
case enums_1.QRISTags.PAYMENT_FEE_FIXED: // PaymentFeeFixed
result.payment_fee = data;
break;
case enums_1.QRISTags.PAYMENT_FEE_PERCENT: // PaymentFeePercent
result.payment_fee = data;
break;
case enums_1.QRISTags.COUNTRY_CODE: // CountryCode
result.country_code = data;
break;
case enums_1.QRISTags.MERCHANT_NAME: // MerchantName
result.merchant_name = data;
break;
case enums_1.QRISTags.MERCHANT_CITY: // MerchantCity
result.merchant_city = data;
break;
case enums_1.QRISTags.MERCHANT_POSTAL_CODE: // MerchantPostalCode
result.merchant_postal_code = data;
break;
case enums_1.QRISTags.ADDITIONAL_INFORMATION: // AdditionalInformation
try {
const detail = this.parseAdditionalInformationDetail(data.content);
result.additional_information = {
tag: data.tag,
content: data.content,
data: data.data,
detail: detail
};
}
catch (err) {
throw new Error(`Invalid parse switching for content ${data.content}`);
}
break;
case enums_1.QRISTags.CRC_CODE: // CRCCode
result.crc_code = data;
break;
default:
// Abaikan tag yang tidak dikenal
break;
}
qrString = qrString.substring(4 + data.content.length);
}
catch (err) {
if (err instanceof errors_1.QRISParseError) {
console.error('\nš« Error Parsing QRIS:');
console.error('āāāāāāāāāāāāāāāāāāāāāāāāāāāā');
console.error(`ā ${err.message}`);
if (err.tag)
console.error(`š Tag: ${err.tag}`);
if (err.position)
console.error(`š Posisi: ${err.position}`);
if (err.rawData)
console.error(`š Data: ${err.rawData}`);
console.error('āāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
}
throw new errors_1.QRISParseError(`Gagal memproses kode QRIS: ${err.message}`, err instanceof errors_1.QRISParseError ? err.tag : undefined);
}
}
return result;
}
static modify(qrisEntity) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
let result = null;
const qrStringConverted = ((_b = (_a = qrisEntity.version) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : '') +
((_d = (_c = qrisEntity.category) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : '') +
((_f = (_e = qrisEntity.acquirer) === null || _e === void 0 ? void 0 : _e.data) !== null && _f !== void 0 ? _f : '') +
((_h = (_g = qrisEntity.switching) === null || _g === void 0 ? void 0 : _g.data) !== null && _h !== void 0 ? _h : '') +
((_k = (_j = qrisEntity.merchant_category_code) === null || _j === void 0 ? void 0 : _j.data) !== null && _k !== void 0 ? _k : '') +
((_m = (_l = qrisEntity.currency_code) === null || _l === void 0 ? void 0 : _l.data) !== null && _m !== void 0 ? _m : '') +
((_p = (_o = qrisEntity.payment_amount) === null || _o === void 0 ? void 0 : _o.data) !== null && _p !== void 0 ? _p : '') +
((_r = (_q = qrisEntity.payment_fee_category) === null || _q === void 0 ? void 0 : _q.data) !== null && _r !== void 0 ? _r : '') +
((_t = (_s = qrisEntity.payment_fee) === null || _s === void 0 ? void 0 : _s.data) !== null && _t !== void 0 ? _t : '') +
((_v = (_u = qrisEntity.country_code) === null || _u === void 0 ? void 0 : _u.data) !== null && _v !== void 0 ? _v : '') +
((_x = (_w = qrisEntity.merchant_name) === null || _w === void 0 ? void 0 : _w.data) !== null && _x !== void 0 ? _x : '') +
((_z = (_y = qrisEntity.merchant_city) === null || _y === void 0 ? void 0 : _y.data) !== null && _z !== void 0 ? _z : '') +
((_1 = (_0 = qrisEntity.merchant_postal_code) === null || _0 === void 0 ? void 0 : _0.data) !== null && _1 !== void 0 ? _1 : '') +
((_3 = (_2 = qrisEntity.additional_information) === null || _2 === void 0 ? void 0 : _2.data) !== null && _3 !== void 0 ? _3 : '') +
((_5 = (_4 = qrisEntity.crc_code) === null || _4 === void 0 ? void 0 : _4.tag) !== null && _5 !== void 0 ? _5 : '') + '04';
const content = crc16_ccitt_1.CRC16CCITT.generateCode(qrStringConverted);
qrisEntity.crc_code = this.modifyContent(qrisEntity.crc_code, content);
return qrisEntity;
}
static isValid(qris) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
const errors = [];
// Helper function untuk validasi field
const isValidField = (tag, message) => {
if (!tag) {
errors.push(message);
}
};
// Validasi version dan category
isValidField((_a = qris.version) === null || _a === void 0 ? void 0 : _a.tag, "Version tag is missing");
isValidField((_b = qris.category) === null || _b === void 0 ? void 0 : _b.tag, "Category tag is missing");
// Validasi category content
if (((_c = qris.category) === null || _c === void 0 ? void 0 : _c.content) !== enums_1.CategoryContent.STATIC &&
((_d = qris.category) === null || _d === void 0 ? void 0 : _d.content) !== enums_1.CategoryContent.DYNAMIC) {
errors.push("Category content undefined");
}
// Validasi acquirer dan detail-detailnya
if (!((_e = qris.acquirer) === null || _e === void 0 ? void 0 : _e.tag)) {
errors.push("Acquirer tag is missing");
}
else {
isValidField((_g = (_f = qris.acquirer.detail) === null || _f === void 0 ? void 0 : _f.site) === null || _g === void 0 ? void 0 : _g.tag, "Acquirer site tag is missing");
isValidField((_j = (_h = qris.acquirer.detail) === null || _h === void 0 ? void 0 : _h.mpan) === null || _j === void 0 ? void 0 : _j.tag, "Acquirer MPAN tag is missing");
isValidField((_l = (_k = qris.acquirer.detail) === null || _k === void 0 ? void 0 : _k.terminal_id) === null || _l === void 0 ? void 0 : _l.tag, "Acquirer terminal id tag is missing");
if (qris.acquirer.tag === enums_1.QRISTags.ACQUIRER) {
isValidField((_o = (_m = qris.acquirer.detail) === null || _m === void 0 ? void 0 : _m.category) === null || _o === void 0 ? void 0 : _o.tag, "Acquirer category tag is missing");
if (!((_p = qris.switching) === null || _p === void 0 ? void 0 : _p.tag)) {
errors.push("Switching tag is missing");
}
else {
isValidField((_r = (_q = qris.switching.detail) === null || _q === void 0 ? void 0 : _q.site) === null || _r === void 0 ? void 0 : _r.tag, "Switching site tag is missing");
isValidField((_t = (_s = qris.switching.detail) === null || _s === void 0 ? void 0 : _s.nmid) === null || _t === void 0 ? void 0 : _t.tag, "Switching NMID tag is missing");
isValidField((_v = (_u = qris.switching.detail) === null || _u === void 0 ? void 0 : _u.category) === null || _v === void 0 ? void 0 : _v.tag, "Switching category tag is missing");
}
}
}
// Validasi payment fee
// this.isValidPaymentFee(qris, errors);
// Validasi field-field wajib lainnya
isValidField((_w = qris.merchant_category_code) === null || _w === void 0 ? void 0 : _w.tag, "Merchant category tag is missing");
isValidField((_x = qris.currency_code) === null || _x === void 0 ? void 0 : _x.tag, "Currency code tag is missing");
isValidField((_y = qris.country_code) === null || _y === void 0 ? void 0 : _y.tag, "Country code tag is missing");
isValidField((_z = qris.merchant_name) === null || _z === void 0 ? void 0 : _z.tag, "Merchant name tag is missing");
isValidField((_0 = qris.merchant_city) === null || _0 === void 0 ? void 0 : _0.tag, "Merchant city tag is missing");
isValidField((_1 = qris.merchant_postal_code) === null || _1 === void 0 ? void 0 : _1.tag, "Merchant postal code tag is missing");
isValidField((_2 = qris.crc_code) === null || _2 === void 0 ? void 0 : _2.tag, "CRC code tag is missing");
return errors;
}
static isValidPaymentFee(qris, errors) {
var _a, _b, _c, _d;
if (!((_a = qris === null || qris === void 0 ? void 0 : qris.payment_fee_category) === null || _a === void 0 ? void 0 : _a.tag) && ((_b = qris.payment_fee) === null || _b === void 0 ? void 0 : _b.tag)) {
errors.push("Payment fee category tag is missing");
}
if (((_c = qris === null || qris === void 0 ? void 0 : qris.payment_fee_category) === null || _c === void 0 ? void 0 : _c.tag) && !((_d = qris.payment_fee) === null || _d === void 0 ? void 0 : _d.tag)) {
errors.push("Payment fee tag is missing");
}
}
static isValidQRIS(qrisParsed) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
const qrStringConverted = ((_b = (_a = qrisParsed.version) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : '') +
((_d = (_c = qrisParsed.category) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : '') +
((_f = (_e = qrisParsed.acquirer) === null || _e === void 0 ? void 0 : _e.data) !== null && _f !== void 0 ? _f : '') +
((_h = (_g = qrisParsed.switching) === null || _g === void 0 ? void 0 : _g.data) !== null && _h !== void 0 ? _h : '') +
((_k = (_j = qrisParsed.merchant_category_code) === null || _j === void 0 ? void 0 : _j.data) !== null && _k !== void 0 ? _k : '') +
((_m = (_l = qrisParsed.currency_code) === null || _l === void 0 ? void 0 : _l.data) !== null && _m !== void 0 ? _m : '') +
((_p = (_o = qrisParsed.payment_amount) === null || _o === void 0 ? void 0 : _o.data) !== null && _p !== void 0 ? _p : '') +
((_r = (_q = qrisParsed.payment_fee_category) === null || _q === void 0 ? void 0 : _q.data) !== null && _r !== void 0 ? _r : '') +
((_t = (_s = qrisParsed.payment_fee) === null || _s === void 0 ? void 0 : _s.data) !== null && _t !== void 0 ? _t : '') +
((_v = (_u = qrisParsed.country_code) === null || _u === void 0 ? void 0 : _u.data) !== null && _v !== void 0 ? _v : '') +
((_x = (_w = qrisParsed.merchant_name) === null || _w === void 0 ? void 0 : _w.data) !== null && _x !== void 0 ? _x : '') +
((_z = (_y = qrisParsed.merchant_city) === null || _y === void 0 ? void 0 : _y.data) !== null && _z !== void 0 ? _z : '') +
((_1 = (_0 = qrisParsed.merchant_postal_code) === null || _0 === void 0 ? void 0 : _0.data) !== null && _1 !== void 0 ? _1 : '') +
((_3 = (_2 = qrisParsed.additional_information) === null || _2 === void 0 ? void 0 : _2.data) !== null && _3 !== void 0 ? _3 : '') +
((_5 = (_4 = qrisParsed.crc_code) === null || _4 === void 0 ? void 0 : _4.tag) !== null && _5 !== void 0 ? _5 : '') + '04';
return ((_6 = qrisParsed.crc_code) === null || _6 === void 0 ? void 0 : _6.content) === crc16_ccitt_1.CRC16CCITT.generateCode(qrStringConverted);
}
}
exports.QRIS = QRIS;