UNPKG

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
"use strict"; 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;