Skip to content
All notes

Parsing Lao QR (EMVCo TLV) in the browser

How a Lao QR payment code is structured, how to read it safely on the client, and why the CRC check matters.

  • Lao QR
  • EMVCo
  • TypeScript
  • Payments

Lao QR payment codes follow the EMVCo Merchant-Presented QR format, the same family used by many national QR schemes. Our free Lao QR tool reads these codes entirely in the browser. This note explains how the parser in our lao-qr package works.

The format: tag, length, value

A payload is a flat string of TLV records:

  • Tag — two digits, for example 00 or 59
  • Length — two digits, the number of characters in the value
  • Value — exactly that many characters

Some tags are templates: their value is itself a list of TLV records. In the EMVCo layout these are the merchant account information tags 26–51, the additional data field 62, the language template 64, and the unreserved range 80–99. A parser therefore reads the root level first, then tries to read each template's value as nested TLV.

Fail loudly, with a position

The splitter walks the string and stops at the first problem: a truncated header, a non-numeric tag or length, or a length that runs past the end of the input. Instead of a vague "invalid QR", it returns the error message and the character offset where things went wrong. That makes a broken code easy to debug, and the UI can point at the exact spot.

Templates are handled more gently. If a tag that should be a template does not contain valid TLV, the raw value is kept and a warning is added. Real-world codes are not always perfect, and a tool should show what is there rather than refuse to show anything.

Other checks become warnings too: the Payload Format Indicator (00) must be the first tag with value 01, and a tag that appears twice is flagged.

The CRC is the integrity check

The last record is tag 63, a four-character checksum. EMVCo uses CRC-16/CCITT-FALSE (polynomial 0x1021, initial value 0xFFFF, no reflection). It is computed over the payload up to and including the characters 6304 — the CRC tag and its length — and compared with the stored value.

This is what catches a tampered code. Change the amount in tag 54 and the CRC no longer matches. Our parser reports the CRC as valid only when the values match and 63 is the last tag with length 4. We compute the CRC over UTF-8 bytes, so payloads containing Lao text are handled consistently.

Why in the browser

The parser is a pure function: no network calls, no storage, no side effects. That has three benefits:

  1. The payload never leaves your device.
  2. It is safe to run on untrusted input — there is also an upper limit on payload length, so oversized input is rejected early.
  3. It is easy to unit-test. Our tests cover a standard CRC check value, nested templates, a tampered amount, lowercase CRC hex, missing CRC, truncated values, non-numeric tags, and wrong tag order.

What is still open

Our tool also turns a payload into a QR image, and it refuses to draw one whose CRC does not match. Building brand-new codes with Lao merchant names from scratch is a different job: the EMVCo specification counts lengths in characters, while some implementations count bytes when non-Latin text is present. Before we offer that, we will check length counting against codes made by real bank apps — reading can be forgiving, but generating must be exact.

Try it yourself with the free Lao QR tool.