# McDonald's receipt codes

Receipts from McDonald's in the UK include a code that allows you to complete an online survey as shown in the attached image (in the green box):

After gathering and comparing several receipts I have deduced that the codes use a base 25 alphanumeric system consisting of the following characters:

``````C M 7 W D 6 N 4 R H  F  9  Z  L  3  X  K  Q  G  V  P  B  T  J  Y
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
``````

25 would therefore be `MC`, 26 `MM`, 27 `M7`, etc.

The code for this receipt is `7MXW-NLH4-ZQ3K` and can be broken down as follows:

Code Decimal Meaning
`7MX` 1290 Store ID.
`W` 3 Not sure, but the vast majority of receipts always seem to have `W` here.
`NL` 163 Order ID: last two digits + 125, so can be reversed by `163 % 125` which is 38.
`H4ZQ3K` 90,823,491 Probably the date/time of purchase - more below.

I have noticed that the last number (i.e. what I assume is the purchase date/time) increases with time when comparing receipts.

For example, another code's last 6 characters are `H4F6XN` (90,784,756) and the order was placed on 2022-12-27 19:10:05, just over a day before. A quick comparison:

Order 1 Order 2 Difference
90,823,491 90,784,756 38,735
2022-12-28 20:59:51 2022-12-27 19:10:05 92,986 (seconds)

Dividing the difference of seconds by the difference of the 6 character number:

``````92,986 ÷ 38,735 = 2.4 (approx.)
``````

It would therefore seem that the number increases by 1 every 2.4 seconds. The result of 60 ÷ 25 also happens to be 2.4 which means 1/25th of a minute can be represented by a character from the base 25 system.

Following the assumption of the number increasing by 1 every 2.4 seconds it seems that the first datetime (or "epoch") is approximately `2016-02-01 00:00:00`.

Therefore to decipher the final value of `H4ZQ3K` in the first receipt:

1. 90,823,491 × 2.4 = 217,976,378.4 seconds
2. 2016-02-01 00:00:00 + 217,976,378.4 seconds = 2022-12-28 20:59:38.4

...but note how the predicted timestamp is incorrect - off by 12.6 seconds (the other receipt comes out at 2022-12-27 19:10:14.4 - 9.4 seconds ahead).

I'm stumped as to what's causing the error - does anyone have any ideas?

Some more codes for reference (note how the predicted timestamp is never more or less than 60 seconds):

Code Last 6 chars (decimal) Purchased Predicted Ahead by (seconds)
`7MXW-NLH4-ZQ3K` 90,823,491 2022-12-28 20:59:51 2022-12-28 20:59:38.4 -12.6
`M3NW-YRH4-F6XN` 90,784,756 2022-12-27 19:10:05 2022-12-27 19:10:14.4 +9.4
`MNKW-M6H4-7FQX` 90,662,940 2022-12-24 09:57:46 2022-12-24 09:57:36 -10
`CRGW-ZYHN-KHBP` 90,490,545 2022-12-19 15:01:03 2022-12-19 15:01:48 +45
`CQMW-L9HN-KNC7` 90,488,127 2022-12-19 13:25:56 2022-12-19 13:25:04.8 -51.2
`M9JW-QCH6-PT3Z` 90,170,362 2022-12-10 17:34:42 2022-12-10 17:34:28.8 -13.2
`7NLW-NFH6-7XLV` 89,884,719 2022-12-02 19:08:02 2022-12-02 19:08:45.6 +43.6
`MLZW-Y3HD-YTP9` 89,842,386 2022-12-01 14:55:38 2022-12-01 14:55:26.4 -11.6
`MBQW-RCHD-YNQ9` 89,832,311 2022-12-01 08:12:04 2022-12-01 08:12:26.4 +22.4
`MP4W-6DHM-QNNC` 88,550,775 2022-10-26 17:51:16 2022-10-26 17:51:00 -16
`7HGW-RFRG-9JX9` 85,342,886 2022-07-29 15:15:30 2022-07-29 15:15:26.4 -3.6
`MJFW-YNRK-P66H` 84,690,759 2022-07-11 12:30:01 2022-07-11 12:30:21.6 +20.6
`CRFD-NZRZ-JZGP` 83,179,845 2022-05-30 13:13:26 2022-05-30 13:13:48 +22

Python functions for encoding/decoding:

``````CHARS = "CM7WD6N4RHF9ZL3XKQGVPBTJY"
BASE = len(CHARS)

def encode(num):
encoded = ""
while num >= BASE:
encoded = CHARS[num % BASE] + encoded
num //= BASE
return CHARS[num] + encoded

def decode(encoded):
num = 0
for x, c in enumerate(encoded):
exp = len(encoded) - x - 1
num += (BASE**exp) * CHARS.find(c)
return num
``````
• My first thought is that the last digit is really some sort of checksum and not part of the timestamp. Does the survey website validate and reject codes if you change the last letter to ‘correct’ the timestamp? None of your examples change the minute, only the seconds, and an order number and time to the nearest minute would be enough to identify a given order.
– Rup
Commented Aug 6, 2023 at 22:46
• Huge kudos for getting as far as you have with this BTW, particularly the base 25 and the characters.
– Rup
Commented Aug 6, 2023 at 23:58
• @Rup Thanks! Yeah, the survey website rejects my predicted codes and only accepts the printed ones. I thought about the possibility of it being check digit as well - but then I wonder how that could work - did they invent their own check digit system for base 25 or modify an existing decimal one (if that would even work) etc. - might be a dead end, sadly. Commented Aug 7, 2023 at 10:56
• I was looking at this post, and i was wondering how did you came up with that very specific sequence in base 25, with 25 elements there could be up to 15511210043330985984000000 permutation possible, was the sequence a lucky find or you worked to find it from the available data ? Commented Jan 14 at 8:54
• I made a spreadsheet which contained order data/codes for just under 200 receipts, most of which I found by scraping the McDonald's UK Twitter account. It still took me a while to figure out, but by sorting the data by the "order number" column I eventually noticed some characters repeated every 25 orders. It was pretty trivial to fill in the blanks from there. Commented Jan 14 at 9:18

As suggested in a comment, the final character is indeed a check digit. It appears to be calculated using the Luhn algorithm with 25 as the base instead of 10.

Using the code in photo as an example:

1. The code without a check digit: `7MXWNLH4ZQ3`.
2. Convert to decimal: `[2, 1, 15, 3, 6, 13, 9, 7, 12, 17, 14]`.
3. Start from the right and double every number in an even position.
4. If a number exceeds 24, re-encode it and sum the digits (e.g. 14 × 2 = 28, which becomes `MW`, which becomes `[1, 3]`, which becomes 4).
5. Sum all the digits.
6. If `(total % 25) > 0`, the check digit is `25 - (total % 25)`, otherwise it's `0`.
7. In this case, it's 16, or `K` in the base 25 character set.

Update: I've created a script to do all this, available on GitHub here.

• Huh, I looked at Luhn mod N last night and it said it didn't work for odd Ns so I discounted it. As you've got there numbers that overflow end up as even too so the doubled digits will only end up even, meaning that you could swap the trailing 3 for a 7 and the checksum wouldn't change. But glad you've solved it!
– Rup
Commented Aug 7, 2023 at 15:37

The W is to show its reg20 which is drive thru, anything non drive thru including delivery and app orders will have a C. Also App orders will show as reg300 and the first 2 digits of the middle section is equal to exactly the last 2 digits of the order number following the base25 system

• What's reg20/300 - one of the fields? FWIW I got a D there the other day, not a W or C, which was using the self-order machines.
– Rup
Commented Apr 24 at 17:17
• I've only ever had a `W` at my local store for both drive-through and eating in. Also in the ~180 codes I scraped last year, 11 of them have `D` as mentioned above, 3 have `6`, and 1 has `N`. Commented Apr 24 at 17:34

Reg 20 is the drive thru POS if you use the till and not app or other method. If it's reg 20 then it will be W. Reg 300 is app order instore and will be C as will reg 2 which is instore POS. It may vary depending on store but most of them are the same unless they have more instore POS or dual drive thru.