12

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):

McDonald's receipt

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
5
  • 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
    Aug 6, 2023 at 22:46
  • 1
    Huge kudos for getting as far as you have with this BTW, particularly the base 25 and the characters.
    – Rup
    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. 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 ?
    – Kiemsel42
    Jan 14 at 8:54
  • 1
    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. Jan 14 at 9:18

3 Answers 3

6

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.

1
  • 1
    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
    Aug 7, 2023 at 15:37
0

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

2
  • 1
    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
    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. Apr 24 at 17:34
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.