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
}