first commit

This commit is contained in:
ryan.chan 2019-10-28 13:59:20 +08:00
commit e42ccf61aa
13 changed files with 720 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/node_modules/*
example/node_modules/*
example/package-lock.json
package-lock.json

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Ryan Chan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

51
README.md Normal file
View File

@ -0,0 +1,51 @@
## hk-fps
A Nodejs module that help to generate QR code content string of the Hong Kong Fast Payment System.
## Installation
Install with npm
npm install node-fps-hk
and in your code
var fps = require('node-fps-hk')
## Usage
//import module
var fps = require('node-fps-hk')
// set custom variables
fps.setMerchantID("0000001");
fps.setBillNumber("0002");
fps.setStoreLabel("0003");
fps.setLoyaltyNumber("0004");
fps.setCustomerLabel("0005");
fps.setTerminalLabel("0006");
fps.setPurposeOfTransaction("0007");
fps.setMobileNumber("12345678");
fps.setTransactionAmount("5000");
fps.setReferenceLabel("ABCD");
//generate qr content string
var qrContent = fps.generate();
## Example
cd ./example
npm install
node index.js
visit `http://localhost:8080`
## License
MIT
## Useful Links
Please find the specification of the QR Code used in FPS : [https://fps.hkicl.com.hk/eng/fps/merchants/qr_code.php](https://fps.hkicl.com.hk/eng/fps/merchants/qr_code.php)
The QR Code used the **CRC16 CCITT** check sum. Please find more details : [http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html](http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html)

22
example/index.js Normal file
View File

@ -0,0 +1,22 @@
var fps = require('node-fps-hk')
var qrimage = require('qr-image');
var http = require('http');
http.createServer(function (req, res) {
if (req.url == '/') {
fps.setMerchantID("0000001");
fps.setBillNumber("0002");
fps.setStoreLabel("0003");
fps.setLoyaltyNumber("0004");
fps.setCustomerLabel("0005");
fps.setTerminalLabel("0006");
fps.setPurposeOfTransaction("0007");
fps.setMobileNumber("12345678");
fps.setTransactionAmount("5000");
fps.setReferenceLabel("ABCD");
var string = fps.generate();
var code = qrimage.image(string, { type: 'png' });
res.setHeader('Content-type', 'image/png'); //sent qr image to client side
code.pipe(res);
}
}).listen(8080);

17
example/package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"http": "0.0.0",
"node-fps-hk": "file:..",
"qr-image": "^3.2.0"
}
}

1
index.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./src/index.js')

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "node-fps-hk",
"version": "0.1.0",
"description": "A tool to generate the qrcode content string used by Fast Payment System in Hong Kong",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ryanchanplc/node-fps-hk.git"
},
"files": [
"/src",
"./index.js"
],
"author": "Ryan Chan",
"license": "MIT",
"devDependencies": {
"mocha": "^6.2.2",
"rewire": "^4.0.1",
"should": "^13.2.3"
},
"keywords": [
"fps",
"hk",
"payment",
"fast payment system"
]
}

32
src/crc.js Normal file
View File

@ -0,0 +1,32 @@
function computeCheckSum(input, seed) {
var result = seed;
var temp;
var crc_table = generateCRC16Table();
for (var i = 0, len = input.length; i < len; ++i) {
temp = (input[i] ^ (result >> 8)) & 0xff;
result = crc_table[temp] ^ (result << 8);
}
return result;
}
function generateCRC16Table() {
var table = [];
var poly = 0x1021;
var temp;
for (var i = 0; i < 256; ++i) {
temp = (i << 8) & 0xFFFF;
for (var j = 0; j < 8; ++j) {
var bit = (temp & 0x8000)
temp <<= 1;
if (bit) {
temp ^= poly;
}
}
table[i] = temp & 0xFFFF;
}
return table;
}
module.exports = {
computeCheckSum: computeCheckSum
};

68
src/id.js Normal file
View File

@ -0,0 +1,68 @@
module.exports = Object.freeze({
/*
* 00 Payload Format Indicator
*/
PAYLOAD_FORMAT: '00',
/*
* 01 Point of Initiation Method
*/
POINT_OF_INITIATION: '01',
/*
* 26 Reserved for the Faster Payment System for use in Hong Kong
*/
MERCHANT_ACC_INFO: '26',
/*
* 26 00 Globally Unique Identifier
*/
MERCHANT_ACC_INFO_GLOBALLY_UID: '00',
/*
* 26 02 Merchant ID
*/
MERCHANT_ACC_INFO_MERCHANT_ID: '02',
/*
* 52 Point of Initiation Method
*/
MERCHANT_CAT_CODE: '52',
/*
* 53 Transaction Currency
*/
TRANSACTION_CURRENCY: '53',
/*
* 54 Transaction Amount
*/
TRANSACTION_AMOUNT: '54',
/*
* 58 Country Code
*/
COUNTRY_CODE: '58',
/*
* 59 Merchant Name
*/
MERCHANT_NAME: '59',
/*
* 60 Merchant City
*/
MERCHANT_CITY: '60',
/*
* 62 Additional Data Template
*/
ADDITIONAL_DATA: '62',
ADDITIONAL_DATA_BILL_NUMBER: '01',
ADDITIONAL_DATA_MOBILE_NUMBER: '02',
ADDITIONAL_DATA_STORE_LABEL: '03',
ADDITIONAL_DATA_LOYALTY_NUMBER: '04',
ADDITIONAL_DATA_REFERENCE_LABEL: '05',
ADDITIONAL_DATA_CUSTOMER_LABEL: '06',
ADDITIONAL_DATA_TERMINAL_LABEL: '07',
ADDITIONAL_DATA_PURPOSE_OF_TRANSACTION: '08',
ADDITIONAL_DATA_CUSTOMER_DATA_REQUEST: '09',
/*
* 63 Cyclic Redundancy Check
*/
CRC_CHECK: '63', //63
/*
* 64 Merchant Information - Language Template
*/
MERCHANT_INFO: '64',
});

158
src/index.js Normal file
View File

@ -0,0 +1,158 @@
const ID = require("./id");
var Payload = require("./payload");
var CRC = require("./crc");
var _uid = "hk.com.hkicl";
var _merchantID = "";
var _transactionAmount = "";
var _billNumber = "";
var _mobileNumber = "";
var _storeLabel = "";
var _loyaltyNumber = "";
var _referenceLabel = "";
var _customerLabel = "";
var _terminalLabel = "";
var _purposeOfTransaction = "";
var _additionalCustomerDataRequest = "";
function getPayLoadFormat() {
return new Payload(ID.PAYLOAD_FORMAT, "01").toString();
}
function getPointofInitiation() {
if (_transactionAmount || _transactionAmount != "") {
return new Payload(ID.POINT_OF_INITIATION, "12").toString();
} else {
return new Payload(ID.POINT_OF_INITIATION, "11").toString();
}
}
function getMerhantCategoryCode() {
return new Payload(ID.MERCHANT_CAT_CODE, "0000").toString();
}
function getTransactionCurrency() {
return new Payload(ID.TRANSACTION_CURRENCY, "344").toString();
}
function getCountryCode() {
return new Payload(ID.COUNTRY_CODE, "HK").toString();
}
function getMerchantName() {
return new Payload(ID.MERCHANT_NAME, "NA").toString();
}
function getMerchantCity() {
return new Payload(ID.MERCHANT_CITY, "HK").toString();
}
function getMerchantAccountInfo() {
var uid = new Payload(ID.MERCHANT_ACC_INFO_GLOBALLY_UID, _uid).toString();
var merc = new Payload(ID.MERCHANT_ACC_INFO_MERCHANT_ID, _merchantID).toString();
return new Payload(ID.MERCHANT_ACC_INFO, uid + merc).toString();
}
function getTransactionAmount() {
return new Payload(ID.TRANSACTION_AMOUNT, _transactionAmount).toString();
}
function getAdditionalInformation() {
var payload = ""
payload += new Payload(ID.ADDITIONAL_DATA_BILL_NUMBER, _billNumber);
payload += new Payload(ID.ADDITIONAL_DATA_MOBILE_NUMBER, _mobileNumber);
payload += new Payload(ID.ADDITIONAL_DATA_STORE_LABEL, _storeLabel);
payload += new Payload(ID.ADDITIONAL_DATA_LOYALTY_NUMBER, _loyaltyNumber);
payload += new Payload(ID.ADDITIONAL_DATA_REFERENCE_LABEL, _referenceLabel);
payload += new Payload(ID.ADDITIONAL_DATA_CUSTOMER_LABEL, _customerLabel);
payload += new Payload(ID.ADDITIONAL_DATA_TERMINAL_LABEL, _terminalLabel);
payload += new Payload(ID.ADDITIONAL_DATA_PURPOSE_OF_TRANSACTION, _purposeOfTransaction);
payload += new Payload(ID.ADDITIONAL_DATA_CUSTOMER_DATA_REQUEST, _additionalCustomerDataRequest);
return new Payload(ID.ADDITIONAL_DATA, payload).toString();
}
function addCheckSum(string) {
var checkSum = getCheckSUM(string + ID.CRC_CHECK + "04");
return new Payload(ID.CRC_CHECK, checkSum).toString();
}
function getCheckSUM(string) {
var input = Buffer.from(string, 'utf8');
var output = CRC.computeCheckSum(input, 0xffff);
var o = Buffer.from([output >> 8, output & 0xff]);
return o.toString('hex').toUpperCase();
}
function setMerchantID(value) {
_merchantID = value;
}
function setTransactionAmount(value) {
_transactionAmount = parseFloat(value).toFixed(2).toString();
}
function setBillNumber(value) {
_billNumber = value;
}
function setMobileNumber(value) {
_mobileNumber = value;
}
function setStoreLabel(value) {
_storeLabel = value;
}
function setLoyaltyNumber(value) {
_loyaltyNumber = value;
}
function setReferenceLabel(value) {
_referenceLabel = value;
}
function setCustomerLabel(value) {
_customerLabel = value;
}
function setTerminalLabel(value) {
_terminalLabel = value;
}
function setPurposeOfTransaction(value) {
_purposeOfTransaction = value;
}
function setAdditionalCustomerDataRequest(value) {
_additionalCustomerDataRequest = value;
}
function generate() {
var result = "";
result += getPayLoadFormat();
result += getPointofInitiation();
result += getMerchantAccountInfo();
result += getMerhantCategoryCode();
result += getTransactionCurrency();
result += getTransactionAmount();
result += getCountryCode();
result += getMerchantName();
result += getMerchantCity();
result += getAdditionalInformation();
result += addCheckSum(result);
return result;
}
module.exports = {
setAdditionalCustomerDataRequest,
setBillNumber,
setCustomerLabel,
setMerchantID,
setMobileNumber,
setPurposeOfTransaction,
setLoyaltyNumber,
setReferenceLabel,
setStoreLabel,
setTerminalLabel,
setTransactionAmount,
generate
}

22
src/payload.js Normal file
View File

@ -0,0 +1,22 @@
var Payload = function (_id, _value) {
this.id = _id;
this.value = _value;
if (this.value)
this.length = _value.length;
else
this.length = 0;
};
Payload.prototype.toString = function () {
if (this.value == "")
return ""
else
return this.id + this.pad(parseInt(this.length)) + this.value;
}
Payload.prototype.pad = function (num) {
return (num < 10) ? '0' + num.toString() : num.toString();
}
module.exports = Payload;

258
test/crctable.js Normal file
View File

@ -0,0 +1,258 @@
module.exports = [
0x0000,
0x1021,
0x2042,
0x3063,
0x4084,
0x50a5,
0x60c6,
0x70e7,
0x8108,
0x9129,
0xa14a,
0xb16b,
0xc18c,
0xd1ad,
0xe1ce,
0xf1ef,
0x1231,
0x0210,
0x3273,
0x2252,
0x52b5,
0x4294,
0x72f7,
0x62d6,
0x9339,
0x8318,
0xb37b,
0xa35a,
0xd3bd,
0xc39c,
0xf3ff,
0xe3de,
0x2462,
0x3443,
0x0420,
0x1401,
0x64e6,
0x74c7,
0x44a4,
0x5485,
0xa56a,
0xb54b,
0x8528,
0x9509,
0xe5ee,
0xf5cf,
0xc5ac,
0xd58d,
0x3653,
0x2672,
0x1611,
0x0630,
0x76d7,
0x66f6,
0x5695,
0x46b4,
0xb75b,
0xa77a,
0x9719,
0x8738,
0xf7df,
0xe7fe,
0xd79d,
0xc7bc,
0x48c4,
0x58e5,
0x6886,
0x78a7,
0x0840,
0x1861,
0x2802,
0x3823,
0xc9cc,
0xd9ed,
0xe98e,
0xf9af,
0x8948,
0x9969,
0xa90a,
0xb92b,
0x5af5,
0x4ad4,
0x7ab7,
0x6a96,
0x1a71,
0x0a50,
0x3a33,
0x2a12,
0xdbfd,
0xcbdc,
0xfbbf,
0xeb9e,
0x9b79,
0x8b58,
0xbb3b,
0xab1a,
0x6ca6,
0x7c87,
0x4ce4,
0x5cc5,
0x2c22,
0x3c03,
0x0c60,
0x1c41,
0xedae,
0xfd8f,
0xcdec,
0xddcd,
0xad2a,
0xbd0b,
0x8d68,
0x9d49,
0x7e97,
0x6eb6,
0x5ed5,
0x4ef4,
0x3e13,
0x2e32,
0x1e51,
0x0e70,
0xff9f,
0xefbe,
0xdfdd,
0xcffc,
0xbf1b,
0xaf3a,
0x9f59,
0x8f78,
0x9188,
0x81a9,
0xb1ca,
0xa1eb,
0xd10c,
0xc12d,
0xf14e,
0xe16f,
0x1080,
0x00a1,
0x30c2,
0x20e3,
0x5004,
0x4025,
0x7046,
0x6067,
0x83b9,
0x9398,
0xa3fb,
0xb3da,
0xc33d,
0xd31c,
0xe37f,
0xf35e,
0x02b1,
0x1290,
0x22f3,
0x32d2,
0x4235,
0x5214,
0x6277,
0x7256,
0xb5ea,
0xa5cb,
0x95a8,
0x8589,
0xf56e,
0xe54f,
0xd52c,
0xc50d,
0x34e2,
0x24c3,
0x14a0,
0x0481,
0x7466,
0x6447,
0x5424,
0x4405,
0xa7db,
0xb7fa,
0x8799,
0x97b8,
0xe75f,
0xf77e,
0xc71d,
0xd73c,
0x26d3,
0x36f2,
0x0691,
0x16b0,
0x6657,
0x7676,
0x4615,
0x5634,
0xd94c,
0xc96d,
0xf90e,
0xe92f,
0x99c8,
0x89e9,
0xb98a,
0xa9ab,
0x5844,
0x4865,
0x7806,
0x6827,
0x18c0,
0x08e1,
0x3882,
0x28a3,
0xcb7d,
0xdb5c,
0xeb3f,
0xfb1e,
0x8bf9,
0x9bd8,
0xabbb,
0xbb9a,
0x4a75,
0x5a54,
0x6a37,
0x7a16,
0x0af1,
0x1ad0,
0x2ab3,
0x3a92,
0xfd2e,
0xed0f,
0xdd6c,
0xcd4d,
0xbdaa,
0xad8b,
0x9de8,
0x8dc9,
0x7c26,
0x6c07,
0x5c64,
0x4c45,
0x3ca2,
0x2c83,
0x1ce0,
0x0cc1,
0xef1f,
0xff3e,
0xcf5d,
0xdf7c,
0xaf9b,
0xbfba,
0x8fd9,
0x9ff8,
0x6e17,
0x7e36,
0x4e55,
0x5e74,
0x2e93,
0x3eb2,
0x0ed1,
0x1ef0
];

33
test/test.js Normal file
View File

@ -0,0 +1,33 @@
var should = require('should');
var rewire = require("rewire");
var crc = rewire('../src/crc');
var fps = rewire('../src/index');
const expected_CRC = require('./crctable.js');
const testContent = "00020101021226270012hk.com.hkicl0207000000152040000530334454075000.005802HK5902NA6002HK62680104000202081234567803040003040400040504ABCD0604000507040006080400076304"
const checkSUM = "8D1D";
describe('#checkCRCTable', () => {
it('check the crc generated table', done => {
var generateCRC16Table = crc.__get__('generateCRC16Table');
var crcArray = generateCRC16Table();
crcArray.should.eql(expected_CRC)
done();
})
it('check crc checksum', done => {
fps.setMerchantID("0000001");
fps.setBillNumber("0002");
fps.setStoreLabel("0003");
fps.setLoyaltyNumber("0004");
fps.setCustomerLabel("0005");
fps.setTerminalLabel("0006");
fps.setPurposeOfTransaction("0007");
fps.setMobileNumber("12345678");
fps.setTransactionAmount("5000");
fps.setReferenceLabel("ABCD");
var qrContent = fps.generate();
qrContent.should.equal(testContent + checkSUM)
done();
})
});