Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What would be a good order id scheme for a e-commerce solution or paid web service?

Considering the following:

a) you want some confidentiality (as in not telling everybody how many orders you've received).

b) you want a check digit (e.g., using the Verhoeff algorithm) at the end so you can easily tell misspells and help dealing with errors when scanning barcodes, if this is the case.

c) you've to consider time so consumers can sort the order of the orders.

d) should it be all numeric or hexdec, etc?.

e) something that your consumer can say over the phone to the support team and is just enough for identifying the order without the staff having to ask for e-mail, etc, because of security concern.

I'd love to hear some opinions.

PS: any algorithm designed for solving this problem also would be considered a valid answer for me.

like image 839
Henrique Vicente Avatar asked Oct 31 '10 04:10

Henrique Vicente


2 Answers

Here is my solution.

Have a three portion x-y-z where x is the timestamp, y the random code and z is the check digit produced by the concatenation of x and y. But to simplify (make it smaller), x and y are given in a custom base, instead of numeric base 10, but z is still given at base 10.

Examples of IDs you can get with this approach:

  • LP9NTX-8D41-QW6R-9
  • LP9NTY-5H3L-BFS7-5
  • LP9NTZ-RWL3-D619-8
  • LP9NVB-BW74-788W-6
  • LP9NVW-G17D-4911-8

So you can sort by the timestamp (notice how it goes in 'an incremental alphanumeric' order, if you don't know exactly what a numeric base is).

For this I used the digits + uppercase letters of base58 (in the end it won't matter if I used lowercase or upper), which is base62 without some confusing characters. Flickr, bit.ly and others use base58 for making Twitter 'friendly' links and the like.

The Verhoeff::calcsum below is Verhoeff’s Dihedral Group D5 Check by Dahnielson. The only editing I made was to put his code inside a class, so it's just the same.

Here is some code: (*somewhat modified from what I've promised at the lines up there ^)

<?php
    $time_divisor = 3;
    $base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ";//consider using another base **see note below**
    $lower_limit = 50000;//just to avoiding to confuse the user with a lower number
    $upper_limit = 1291467968;//1291467968 == ZZZZZZ in this base I used
    //you can check the limit with base_decode("ZZZZZZ", $base);
    $ptime = (int)($_SERVER['REQUEST_TIME']/$time_divisor);//or time();
    $rand1 = mt_rand($lower_limit, $upper_limi);
    $rand2 = mt_rand($lower_limit, $upper_limi);
    $ptime_b = base_encode($time, $base);
    $rand1_b = base_encode($rand1, $base);
    $rand2_b = base_encode($rand2, $base);

    $order_id = $ptime_b.$rand1_b.$rand2_b.Verhoeff::calcsum($time.$rand1.$rand2);
    echo $order_id;
?>

Just after I finished writing this another possibly of things going wrong came to my mind. I remembered you don't want your consumers feeling insulted. So even thought bad words such as 'f?ck' or '4ss' eventually appearing maybe ok (and they almost certainly will), explicit words (as in changing the '4' for 'a' in the previous word) are definitely not. Because of this I recommend you to use the following alternate base/upper_limit instead:

<?php
    $lower_limit = 27000;//=2111
    $upper_limit = 809999;//=ZZZZ
    $base = "123456789BCDFGHJKLMNPQRSTVWXYZ";//erased -a -e -u
?>

Please note that if you try to use bigger numbers you'll reach PHP's upper int limit as well as the mt_rand limit, which can be seen with mt_getrandmax(). Also, I'd like to say that for what I see mt_rand's entropy is enough.

If you need larger numbers for the random part I recommend just appending a third part to that with something like mt_rand(i, j); where i and j are the min and max values for your base that will increase your order id in $num-chars length (actually I made this, w/ the configuration above).

And at the DB side it's a unique field as to avoid collisions.

Thank you all.

like image 121
Henrique Vicente Avatar answered Jun 04 '23 16:06

Henrique Vicente


How about random string of whatever length you see fit? Use characters that aren't easily confused with other characters for reading over the phone purposes. So on each order call something like:

    public static string GetRandomString(int length)
    {
        char[] chars = "ACDEFGHJKMNPQRTWXY34679".ToCharArray();
        var crypto = new RNGCryptoServiceProvider();
        var data = new byte[length];
        crypto.GetNonZeroBytes(data);
        var result = new StringBuilder(length);
        for (int i = 0; i < data.Length; i++)
        {
            result.Append(chars[data[i] % chars.Length]);
        }

        return result.ToString();
    }

Guessing 8 characters returned from the above method is a 1/282429536481 chance. And you'll keep integrity in the db with a unique constraint right?

like image 37
JeremyWeir Avatar answered Jun 04 '23 17:06

JeremyWeir