PayNowQR

PayNow is now used by default by Lazada, ie. on checkout you are presented with a QR code. Since the phone with my banking apps is not my primary phone, how can one make the payment?

There aren't many docs online. Most code is for QR generators (such as this one), however there is the generic EMV QR spec.

It turns out that there isn't anything special within the QR code: it provides the UEN, bill reference number, and payment amount. So the QR code data can be dumped as text and fed to the following PayNow parser, and those three data can be used to complete the payment via your bank's web app.

import sys
import binascii

Fields = {
"00" : "Format_ID",
"01" : "Proxy_type",
"26" : "Merchant_Account_Info",
"2600" : "Domain",
"2601" : "PayNow_ID_type",
"2602" : "PayNow_ID",
"2603" : "Is_editable",
"2604" : "Expiration",
"52" : "Merchant_category",
"53" : "Transaction_currency",
"54" : "Transaction_amount",
"55" : "Transaction_convenience_fee_is_presented",
"56" : "Transaction_convenience_fee_fixed",
"57" : "Transaction_convenience_fee_pct",
"58" : "Country_code",
"59" : "Merchant_name",
"60" : "Merchant_city",
"61" : "Merchant_postal_code",
"62" : "Bill_Info",
"6201" : "Bill_number",
"6202" : "Bill_phone_number",
"6203" : "Bill_store_label",
"6204" : "Bill_loyalty_number",
"6204" : "Bill_reference_number",
"6206" : "Bill_customer_label",
"6206" : "Bill_terminal_label",
"6208" : "Bill_transaction_purpose",
"6210" : "Bill_merchant_tax_id",
"6211" : "Bill_merchant_channel",
"63" : "CRC"
}

def Transaction_currency_fn(num):
    ''' Lookup the currency code -- https://www.iban.com/currency-codes '''
    return {
        "036" : "AUD", "096" : "BND", "124" : "CAD", "156" : "CNY", "344" : "HKD",
        "356" : "INR", "360" : "IDR", "392" : "JPY", "410" : "KRW", "446" : "MOP",
        "458" : "MYR", "554" : "NZD", "608" : "PHP", "682" : "SAR", "702" : "SGD",
        "704" : "VND", "764" : "THB", "784" : "AED", "826" : "GBP", "840" : "USD",
        "901" : "TWD", "978" : "EUR",
    }[num]

def has_subfield(field_id):
    ''' do any Field IDs have `field_id` as a prefix? '''
    return 1 < len( [ k for k in Fields.keys() if k.startswith( field_id ) ])

def get_field(Fs, i):
    ''' get the next field, its value, and offset increment '''
    f = Fs[ i : i+2 ]
    l = int( Fs[ i+2 : i+4 ])
    return f, Fs[ i+4 : i+4+l ], i+4+l

def scan(fields, reply={}, i=0, prefix=''):
    ''' parse the fields into `reply` '''
    while i < len(fields):
        f,v,i = get_field(fields, i)
        f = prefix + f
        reply[f] = v
        if has_subfield(f):
            scan( v, reply, prefix=f )
    return reply


qr = ' '.join(sys.argv[1:])
assert qr[ -4: ] == '{:04X}'.format( binascii.crc_hqx(qr[ 0:-4 ].encode('utf-8'), 0xFFFF))

for fid,fval in scan(qr).items():
    if has_subfield(fid):
        continue
    for fn in [ v for k,v in globals().items() if k == Fields[fid] + '_fn' ]:
        fval = fn(fval)
    print(fid, Fields[fid], fval)