Skip to content

Instantly share code, notes, and snippets.

@chengkiang
Last active March 27, 2024 23:35
  • Star 34 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save chengkiang/7e1c4899768245570cc49c7d23bc394c to your computer and use it in GitHub Desktop.
SG PayNow QR Code Generator Sample
String.prototype.padLeft = function (n, str) {
if (n < String(this).length) {
return this.toString();
}
else {
return Array(n - String(this).length + 1).join(str || '0') + this;
}
}
function crc16(s) {
var crcTable = [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];
var crc = 0xFFFF;
var j, i;
for (i = 0; i < s.length; i++) {
c = s.charCodeAt(i);
if (c > 255) {
throw new RangeError();
}
j = (c ^ (crc >> 8)) & 0xFF;
crc = crcTable[j] ^ (crc << 8);
}
return ((crc ^ 0) & 0xFFFF).toString(16).toUpperCase().padStart(4, '0');
}
function generatePayNowStr( opts ) {
const p = [
{ id: '00', value: '01' }, // ID 00: Payload Format Indicator (Fixed to '01')
{ id: '01', value: '12' }, // ID 01: Point of Initiation Method 11: static, 12: dynamic
{
id: '26', value: // ID 26: Merchant Account Info Template
[{ id: '00', value: 'SG.PAYNOW' },
{ id: '01', value: '2' }, // 0 for mobile, 2 for UEN. 1 is not used.
{ id: '02', value: opts.uen }, // PayNow UEN (Company Unique Entity Number)
{ id: '03', value: opts.editable.toString() }, // 1 = Payment amount is editable, 0 = Not Editable
{ id: '04', value: opts.expiry }] // Expiry date (YYYYMMDD)
},
{ id: '52', value: '0000' }, // ID 52: Merchant Category Code (not used)
{ id: '53', value: '702' }, // ID 53: Currency. SGD is 702
{ id: '54', value: opts.amount.toString() }, // ID 54: Transaction Amount
{ id: '58', value: 'SG' }, // ID 58: 2-letter Country Code (SG)
{ id: '59', value: 'COMPANY NAME' }, // ID 59: Company Name
{ id: '60', value: 'Singapore' }, // ID 60: Merchant City
{
id: '62', value: [{ // ID 62: Additional data fields
id: '01', value: opts.refNumber // ID 01: Bill Number
}]
}
]
let str = p.reduce((final, current) => {
if (Array.isArray(current.value)) { //nest loop
current.value = current.value.reduce((f, c) => {
f += c.id + c.value.length.toString().padLeft(2) + c.value;
return f
}, "")
}
final += current.id + current.value.length.toString().padLeft(2) + current.value;
return final
}, "")
// Here we add "6304" to the previous string
// ID 63 (Checksum) 04 (4 characters)
// Do a CRC16 of the whole string including the "6304"
// then append it to the end.
str += '6304' + crc16(str + '6304');
return str;
}
@yongjp
Copy link

yongjp commented Dec 24, 2020

Hi, should'nt it be { id: '01', value: '0' }? Anyways I tried with both value = 0 and value = 1. but it does not seem to work. Mobile no format is +6581234567. I'm using googleapis to generate the QRCode. Could that be the issue?

I used this snippet and it works.

// Line 80 of paynow.js in this gist
{ id: '01', value: '0' },
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="qrcode"></div>
  <script src="paynow.js"></script>
  <script src="qrcode.min.js"></script>
  <script>
    const opts = {
      uen: '+6591234567',
      editable: 0,
      expiry: '20201231',
      amount: 1,
      refNumber: 'ABC123'
    }
    const qrstr = generatePayNowStr(opts);
    new QRCode(document.getElementById("qrcode"), qrstr)
  </script>
</body>

</html>

I put in my mobile number and it works. But with a invalid number, it doesn't. I think there's some validation involved. I am using this QR code library: http://davidshimjs.github.io/qrcodejs/

Yes! It works now. It's the QRCode Generator. Thanks bro and have a Merry X'mas

@chengkiang
Copy link
Author

Hi, should'nt it be { id: '01', value: '0' }? Anyways I tried with both value = 0 and value = 1. but it does not seem to work. Mobile no format is +6581234567. I'm using googleapis to generate the QRCode. Could that be the issue?

I used this snippet and it works.

// Line 80 of paynow.js in this gist
{ id: '01', value: '0' },
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="qrcode"></div>
  <script src="paynow.js"></script>
  <script src="qrcode.min.js"></script>
  <script>
    const opts = {
      uen: '+6591234567',
      editable: 0,
      expiry: '20201231',
      amount: 1,
      refNumber: 'ABC123'
    }
    const qrstr = generatePayNowStr(opts);
    new QRCode(document.getElementById("qrcode"), qrstr)
  </script>
</body>

</html>

I put in my mobile number and it works. But with a invalid number, it doesn't. I think there's some validation involved. I am using this QR code library: http://davidshimjs.github.io/qrcodejs/

Yes! It works now. It's the QRCode Generator. Thanks bro and have a Merry X'mas

Glad it works! Merry Christmas to you as well!

@khkoh
Copy link

khkoh commented Jan 31, 2021

@chengkiang Hi, do you have the updated link? This link has expired https://we.tl/t-Szr1GAs0e2.

Thanks. KH

@chengkiang
Copy link
Author

@chengkiang Hi, do you have the updated link? This link has expired https://we.tl/t-Szr1GAs0e2.

Thanks. KH

Copied to my Dropbox instead. Try this link. https://www.dropbox.com/s/5j7c52f9vugs531/paynow.zip?dl=0

@khkoh
Copy link

khkoh commented Jan 31, 2021

@chengkiang Hi, do you have the updated link? This link has expired https://we.tl/t-Szr1GAs0e2.
Thanks. KH

Copied to my Dropbox instead. Try this link. https://www.dropbox.com/s/5j7c52f9vugs531/paynow.zip?dl=0

Thanks for your quick reply. I will download the specs later. Do you have any idea of the different between SGQR and Paynow? I noticed that business banking app will report error when reading the pay now qr code, but personal banking app has no issues reading the qr code (paynow). I suspect that the business banking app now can read only the SGQR code.

@chengkiang
Copy link
Author

@chengkiang Hi, do you have the updated link? This link has expired https://we.tl/t-Szr1GAs0e2.
Thanks. KH

Copied to my Dropbox instead. Try this link. https://www.dropbox.com/s/5j7c52f9vugs531/paynow.zip?dl=0

Thanks for your quick reply. I will download the specs later. Do you have any idea of the different between SGQR and Paynow? I noticed that business banking app will report error when reading the pay now qr code, but personal banking app has no issues reading the qr code (paynow). I suspect that the business banking app now can read only the SGQR code.

SGQR is the unified QR code for payments that the government developed. It's based on EMV specs. PayNow is just one of the supported SGQR payment methods. I am not sure about the business banking apps though.

@Joshscorp
Copy link

Hey, is there such a thing as Batch PayNow QR? I know you can do it via the online banking, batch paynow, but can we do it via a QR Code? Looks like it is already an array for the items in the QR

@WingFei
Copy link

WingFei commented Mar 24, 2021

Hey, are we able to create a Logo in the middle of the QR Code, others are working perfectly fine. Not really sure the logo part needs to edit in which part of then qrcode.js. Appreciate if you could advise me on this.

@chengkiang
Copy link
Author

Hey, are we able to create a Logo in the middle of the QR Code, others are working perfectly fine. Not really sure the logo part needs to edit in which part of then qrcode.js. Appreciate if you could advise me on this.

You can try a different library which generates a QR code with a logo.
E.g. https://www.npmjs.com/package/qrcode-with-logos
https://www.cssscript.com/qr-code-generator-logo-title/

I have not tried them, but they should work fine.

@andygebert
Copy link

Sorry for the maybe stupid question… Is it possible to add a link to the QR code so that the user can tap the QR to be redirected to the banking application for payment instead of scanning it on his own phone?

@starfishpatkhoo
Copy link

I think you can put the qr code as an image inside a href ? But for our company, we did a save QR code to camera roll / gallery button instead. Cos most banking/wallet apps can load a qr code from the phone gallery..

@DancinParrot
Copy link

DancinParrot commented Jul 5, 2022

Does this work for dbs digibank app? So far from what I've tested, it works perfectly fine for OCBC and Standard Chartered, but not for DBS nor Google Pay.

UPDATE: Solve it. Just need to specify +65, if a phone number is used.

const opts = { uen: '+6593876812', editable: 1, expiry: '20220707', amount: 1, refNumber: '' }

@cstoneskc
Copy link

Hi
Understand all these dynamic QR code are currently written in javascript which I understand is suitable for web-based apps.
Does anyone have a similar codes (or compiled .dll) that can be used for NON web-based Windows10/Windows11 environment?
I have a non-web-based accounting application that runs on Windows10/Windows11 locally which prints Invoices and I would like to include dynamic QR-codes but so far can't find how to do so. If there is such a Windows-based codes or .dll that does so, appreciate someone can point me to it. Looking forward to some pointers.
Skc

@starfishpatkhoo
Copy link

With very specific data payloads, QR code generated is considered invalid by the banking app. The reason for this is that the CRC checksum generated is less than 4 digits long while the ABS PayNow QR Specs state that the CRC checksum field must be exactly 4 characters long. This problem has been verified on both the DBS and OCBC banking apps.

This bug is hard to reproduce because changing just one character or number of the data payload (eg, the expiry date to the following day, or reference number +/- 1, or even amount +/- 1) changes the CRC checksum. So sometimes it is baffling why the QR code does not work for certain days/amounts/references but works for (most) others. In my case, CRC16 generated was E3E, but it needs to be 0E3E.

To fix this problem, in the function crc16(), change the line from:

return ((crc ^ 0) & 0xFFFF).toString(16).toUpperCase();

to

return ((crc ^ 0) & 0xFFFF).toString(16).toUpperCase().padStart(4, '0');

@chengkiang
Copy link
Author

Thanks for the tip! I have made the change in the gist.

@chengkiang
Copy link
Author

Hi Understand all these dynamic QR code are currently written in javascript which I understand is suitable for web-based apps. Does anyone have a similar codes (or compiled .dll) that can be used for NON web-based Windows10/Windows11 environment? I have a non-web-based accounting application that runs on Windows10/Windows11 locally which prints Invoices and I would like to include dynamic QR-codes but so far can't find how to do so. If there is such a Windows-based codes or .dll that does so, appreciate someone can point me to it. Looking forward to some pointers. Skc

You should be able to use the same logic to generate the QR code in other languages/platforms.

@jtham1511
Copy link

@chengkiang Hi, do you have the updated link? This link has expired https://we.tl/t-Szr1GAs0e2.
Thanks. KH

Copied to my Dropbox instead. Try this link. https://www.dropbox.com/s/5j7c52f9vugs531/paynow.zip?dl=0

Hi Cheng Kiang

I just join this chat group. I would like to request for the above spec that you shared before, the link has expired when I clicked on it

@chengkiang
Copy link
Author

@chengkiang Hi, do you have the updated link? This link has expired https://we.tl/t-Szr1GAs0e2.
Thanks. KH

Copied to my Dropbox instead. Try this link. https://www.dropbox.com/s/5j7c52f9vugs531/paynow.zip?dl=0

Hi Cheng Kiang

I just join this chat group. I would like to request for the above spec that you shared before, the link has expired when I clicked on it

Think I accidentally deleted the file from my Dropbox. Have uploaded it again.

https://www.dropbox.com/s/xkfnva44p4pking/paynow.zip?dl=0

@jtham1511
Copy link

jtham1511 commented Dec 4, 2022 via email

@jtham1511
Copy link

jtham1511 commented Dec 4, 2022 via email

@natsu90
Copy link

natsu90 commented Aug 2, 2023

any idea how can we setup webhook upon payment received?

i feel like we should be able to pass an additional parameter, says {id: '63', value: 'https://url-webhook'}

@starfishpatkhoo
Copy link

any idea how can we setup webhook upon payment received?

i feel like we should be able to pass an additional parameter, says {id: '63', value: 'https://url-webhook'}

You need to subscribe to the service from your bank. Or your payment processor. Because ultimately, someone needs to call that webhook, and that someone wants to be paid for calling the webhook.

@chengkiang
Copy link
Author

any idea how can we setup webhook upon payment received?

i feel like we should be able to pass an additional parameter, says {id: '63', value: 'https://url-webhook'}

If I am not wrong, this functionality has to be setup with the bank for business accounts. I noticed in some shops, when you pay via PayNow QR code, they can get a notification in their POS or mobile device.

Regards
CK

@chrislee275
Copy link

Hi Cheng Kiang, are we able to set the expiry date to 10 minutes after the current time?

@chengkiang
Copy link
Author

Hi Cheng Kiang, are we able to set the expiry date to 10 minutes after the current time?

Based on the specifications that I used for this (and it's few years old now), it can only support down to the day. There might be a new version of the specifications, but I am not sure where to find it.

Regards
CK

@chrislee275
Copy link

Hi Cheng Kiang, are we able to set the expiry date to 10 minutes after the current time?

Based on the specifications that I used for this (and it's few years old now), it can only support down to the day. There might be a new version of the specifications, but I am not sure where to find it.

Regards CK

Thanks for the information. Since I'm using this to generate dynamic QR code for a form, can I check if I can set the expiry date to current date + 1 day? Eg: Current date is 20230814, I would like the qr code to expire on 20230815

@Comgen21
Copy link

Comgen21 commented Mar 5, 2024

Hi how to add logo center in QR Code

@chengkiang
Copy link
Author

Hi how to add QR code color

This code only generates a string which you can pass to a QR Code generator to generate the QR code. To have colours, logos, etc., use a QR code generator which supports these features.

@johnaa123
Copy link

why it can't generate dynamic QR code any more?

@johnaa123
Copy link

Hi Cheng Kiang, are we able to set the expiry date to 10 minutes after the current time?

Based on the specifications that I used for this (and it's few years old now), it can only support down to the day. There might be a new version of the specifications, but I am not sure where to find it.
Regards CK

Thanks for the information. Since I'm using this to generate dynamic QR code for a form, can I check if I can set the expiry date to current date + 1 day? Eg: Current date is 20230814, I would like the qr code to expire on 20230815

Do your dynamic QR code still works? How can't get dynamic QR code now with default config { id: '01', value: '12' } ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment