Implement the Punchh Barcode and QR Code Algorithm

Punchh Recommends Printing QR Codes When Possible

Punchh recommends printing QR codes instead of barcodes because QR codes support more store locations than the EAN 13 barcode limit of 8192 locations. However, some POS printers do not support QR codes. If the brand cannot upgrade the store printers, Punchh recommends printing barcodes.

Supporting Barcodes Instead of QR Codes

POS integrations that support barcodes must print unique EAN 13 barcodes on guest receipts. The POS must print barcodes on loyalty and non-loyalty transactions unless a business admin explicitly disables printing barcodes in the location's configuration. Barcodes enable guests to earn points on transactions as their own if they did not check in during the transactions.

POS integrations must send barcodes in specific Punchh POS API calls, such as the Create Redemption, Create Check-in, and Store Receipt Details From POS endpoints. Sending barcode information in the API enables analytics and troubleshooting features in the Punchh dashboard.

Generating Barcode and QR Code

This topic explains how to generate a barcode for use with Punchh. To generate a QR code, you first generate a barcode by following the steps in Generate the Barcode and then convert the barcode to a QR code as described in Generate the QR Code.

Barcode Basics

For use in the API, barcodes are 12 digits long and referred to as the punchh_key.

On the printed receipt, you represent barcodes as a 13-digit number (EAN 13). The EAN 13 barcode is a 12-digit Punchh key and a 1-digit parity bit.

Overview of Generating a Barcode

  • Barcodes are generated from three initial values: rBits, cBits, and iBits. The following table describes these values.

    Value Description
    rBits 22-bit pseudo-random number based on the check ID
    iBits 13-bit short key associated with the store location where the transaction occurs
    cBits 4-bit checksum
  • Once you determine your rBit, cBit, and iBit values (as described later in this topic), you place those values in the correct position within the 40-bit binary field based on a mapping provided to you by Punchh. This mapping is specific to the POS system. Different POS systems use different mappings.

  • After you position the rBit, cBit, and iBit values correctly within the 40-bit binary field, you convert the 40-bit binary number into a 12-digit decimal number for use in the API and add a parity bit for printed receipts.

NOTE

This topic uses example barcode mapping to generate the barcode. As different POS systems use different barcode mappings, contact your Punchh representative for a barcode mapping specific to your POS system.

Example Barcode Values

The following example barcode mapping, short key, and check ID values result in a Punchh key of 194151204278 and an EAN 13 barcode of 1941512042781. Use these example numbers to verify that your barcode algorithm is working correctly.

Example Barcode Mapping

This topic uses the following barcode mapping example. Contact Punchh for a mapping specific to your POS type.

Bit type Bit positions across the 40-bit binary field
rBits (check ID) {0,1,3,4,5,7,8,10,11,13,15,16,17,19,21,23,25,27,29,32,35,37}
iBits (short_key) {6,9,12,18,20,22,24,26,31,33,34,36,38}
cBits (checksum) {2,14,28,30}

Example Short Key

1200

Example Check ID

250

Generate the Barcode

Complete the following steps to generate the barcode on a receipt.

Note: This topic uses the example barcode values. Use the Punchh-provided mapping specific to your POS system to generate a barcode properly.

Step 1: Determine the iBit Value Based on the Location’s Short Key

1. Use the Location Configuration API to obtain the short_key for the location where the transaction occurs. Each store location has its short key.

2. Convert the short key from a decimal number to a 13-bit binary number.

Example

The API returns the short key as 1200

The iBit is 0010010110000

Step 2: Determine the rBit Value Based on the Check ID

1. Determine the rBit value, which is a 22-bit number based on the transaction’s check ID. Use the following information for guidance.

If your check ID is reused daily, calculate your check ID as:

7-bit date + 15-bit check ID.

Calculate the date as an integer number of days from an epoch date (yyyy-mm-dd), taking the 7 least significant bits of the resulting binary number. 

Concatenate the date binary value and the check ID binary value.

Example

19320 days from the epoch date of January 1, 1970, to the transaction date of November 24, 2022

Check ID is 250.

19320 to binary number = 100101101111000

7 least significant bits of 100101101111000 = 1111000

250 to binary number = 11111010

Pad 11111010 to 15 bits = 000000011111010‬

rBit value = 1111000000000011111010‬

(3,932,410 in decimal)

If your check ID is longer than 15 bits, calculate your check ID as:

Use up to the 22 least significant bits of the check ID in binary form, and ensure that the number is unique at a given location for at least 128 days. 

Example

Your check ID converted to binary is 110110101101011011011011

rBit value = 0110101101011011011011

If your check ID is not sequential, maintain a record of the last 128 days of rBit values to ensure that none conflict:

When your check ID is not generated sequentially, you can potentially create duplicate rBit values from different check IDs within 128 days. To address this, store your rBit values in a database or other data store for the last 128 days and implement a strategy that guarantees that none of the rBit values you are generating conflict with another rBit value from the last 128 days for a given location.

Step 3: Calculate the cBit (checksum)

1. Break up the iBit into groups of 4 bits and add all resulting groups of 4 bits together using binary addition.

2. Break up the binary version of the rBit into groups of 4 bits and add all the resulting groups of 4 bits together using binary addition.

3. Add Step 1 and Step 2 results together using binary addition.

4. Take the 4 least significant bits of the result of the previous step; this is the cBit or checksum.

Example

iBit is 0010010110000, rBit is 1111000000000011111010‬.

0010010110000 to groups of 4  => 0000 + 0100 + 1011 + 0000
Add the resulting groups to get 1111

1111000000000011111010‬ to groups of 4 => 0011 + 1100 + 0000 + 0000 + 1111 + 1010
Add the resulting groups to get 101000

Add 1111 + 101000 to get 110111
The least significant 4 bits of 110111 are 0111. This is the checksum number (7 in decimal).

Step 4: Place the cBit, iBit, and rBit Values Into the Correct Positions Based on Your POS Mapping

1. Take an empty 40-bit number. We refer to the least significant bit as bit zero and the most significant bit as bit 39.

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 
↑- bit 39                                bit 0 -↑

2. Enter the 4-bit cBit checksum value into the empty 40-bit binary field based on the cBit positions in your POS map, mapping the least significant checksum bit to the first position in the mapping.

Example

Our cBit checksum value is 0111

The cBit mappings are {2,14,28,30}

Checksum value 0 1 1 1
Binary position 30 28 14 2

The resulting 40-bit binary field is:

0000000000010000000000000100000000000100

3. Enter the 13-bit iBit into the 40-bit binary field based on the iBit positions in your POS map, mapping the least significant short key bit to the first position in the mapping.

Example

Our iBit value is 0010010110000

The iBit mappings are {6,9,12,18,20,22,24,26,31,33,34,36,38}

0 0 1 0 0 1 0 1 1 0 0 0 0
38 36 34 33 31 26 24 22 20 18 12 9 6

The resulting 40-bit field with the iBits and cBits in the correct positions is:

0000010001000100010100000100000000000100

4. Enter the 22-bit rBit value into the 40-bit binary field based on the rBit positions in your POS map, mapping the least significant rBit value to the first position in the mapping.

Example

Our 22-bit rBit value is 1111000000000011111010

The rBit mappings are {0,1,3,4,5,7,8,10,11,13,15,16,17,19,21,23,25,27,29,32,35,37}

1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 1 0
37 35 32 29 27 25 23 21 19 17 16 15 13 11 10 8 7 5 4 3 1 0

The resulting 40-bit field with rBits, iBits, and cBits in the correct position is:

0010110100110100010100000100010110110110

Step 5: Convert the 40-bit Number to a 12-digit Punchh Key and Then to EAN 13 To Print on the Receipt

1. Convert the resulting 40-bit binary number to decimal.

Example

0010110100110100010100000100010110110110 is 194,151,204,278 in decimal notation

2. Set the 39th bit to 1 if the resulting decimal number is less than 100,000,000,000.

Example

194,151,204,278 is greater than 100,000,000,000, so the 39th bit need not set to 1. The 40-bit binary number remains unchanged 0010110100110100010100000100010110110110

3. Convert the final 40-bit number to decimal. In our case, no conversion is required as the 40-bit binary number is greater than 100,000,000,000. This value is the Punchh key, a 12-digit number represented as punchh_key in many API calls and shown as the barcode in the Punchh dashboard.

Example

0010110100110100010100000100010110110110 is 194151204278 in decimal. Use the decimal number as the **punchh_key** in the Punchh APIs.

4. Add a parity bit to the right at the end of the barcode value generated in step 3 to conform to EAN 13 standards when printing the barcode on a receipt.

Example

194151204278 is 1941512042781 with a parity bit and in final EAN 13 format for printing on a receipt.

5. Print the barcode according to the location configuration parameters returned in the Location Configuration API. The following parameters are relevant:

Parameter Description
update_interval Execute the Location Configuration API call at system startup, and then repeat the call on the interval of minutes specified by this parameter. Repeating the call on this interval ensures that updates made from the Punchh dashboard reflect on the printed receipts within the interval time frame.
print_barcodes If false, do not print the barcode, header, or trailer message on the receipt.
header Print this string above the barcode on the receipt.
trailer_X Print these strings beneath the barcode.

Generate the QR Code

To generate a QR code, you first need to generate an EAN 13 barcode by following the steps provided in the Generate the Barcode procedure, and then before printing the barcode on the receipt, convert this barcode to a QR code using a third-party conversion tool. The EAN 13 barcode is composed of a 12-digit Punchh key and a 1-digit parity bit.

Helpful Algorithms

Here is how to get or set bits (C# code):

        private static ulong get_bits(ulong src, int[] map) 
        { 
            ulong dst = 0; 
 
            for (int i = 0; i < map.Length; i++) 
            { 
                dst = set_bit(dst, i, get_bit(src, map[i])); 
            } 
 
            return dst; 
        } 
 
        private static ulong set_bits(ulong src, ulong dst, int[] map) 
        { 
            for (int i = 0; i < map.Length; i++) 
            { 
                dst = set_bit(dst, map[i], get_bit(src, i)); 
            } 
 
            return dst;         
        } 
 
        private static ulong get_bit(ulong number, int bit) 
        { 
            return (ulong)(number & ((ulong)1 << bit)) >> bit; 
        } 
 
        private static ulong set_bit(ulong number, int bit, ulong value) 
        { 
            value = value & 1; 
 
            if (value == 1) 
                return number | ((ulong)1 << bit); 
            else 
                return number & ~((ulong)1 << bit); 
        } 

Here is how to calculate the checksum:

       private static ulong checksum4(ulong short_code, ulong r) 
       { 
           ulong c = 0; 
           // short_code 
           // loop through 4 4-bit pairs (16 bits, although we have 13) 
           for (int i = 0; i < 4; i++) 
           { 
               // Shift to the right by i * 4 bits, then reduce it to a 4 bit number 
               // and add it to the checksum 
               c += short_code >> (i*4) & 0xF; 
           } 
           // r 
           // loop through 6 4-bit pairs (24 bits, although we have 22) 
           for (int i = 0; i < 6; i++) 
           { 
               // Shift to the right by i * 4 bits, then reduce it to a 4 bit number 
               // and add it to the checksum 
               c += r >> (i*4) & 0xF; 
           } 
           // reduce the final checksum to a 4-bit number 
           return (ulong)(c & 0xF); 
       } 

The barcode calculation:

Input:  
  r-number and short_code 
 
        private static int[] rBits = new int[] { comma separated 22 integer numbers }; 
        private static int[] cBits = new int[] { comma separated 4 integer numbers }; 
        private static int[] iBits = new int[] { comma separated 13 integer numbers }; 
 
        ulong barcode = 0; 
        ulong c = checksum4(shortKey, r); 
 
        barcode = set_bits(r, barcode, rBits) 
        barcode = set_bits(c, barcode, cBits); 
        barcode = set_bits(shortKey, barcode, iBits); 

        if (barcode < 100000000000L) 
        { 
            // Set the 40th bit (bit 39)
            // (it is not used in checksum and 
            // that ensures our barcode is exactly 12 digits long) 
            barcode += 549755813888 
        }