views:

791

answers:

12

Every order in my online store has a user-facing order number. I'm wondering the best way to generate them. Criteria include:

  • Short
  • Easy to say over the phone (e.g., "m" and "n" are ambiguous)
  • Unique
  • Checksum (overkill? Useful?)
  • Edit: Doesn't reveal how many total orders there have been (a customer might find it unnerving to make your 3rd order)

Right now I'm using the following method (no checksum):

def generate_number                
 possible_values = 'abfhijlqrstuxy'.upcase.split('') | '123456789'.split('')

 record = true
 while record
  random = Array.new(5){possible_values[rand(possible_values.size)]}.join
  record = Order.find(:first, :conditions => ["number = ?", random])
 end          
 self.number = random
end
A: 

Sequentially, starting at 1? What's wrong with that?

(Note: This answer was given before the OP edited the question.)

John at CashCommons
See my edit -- I don't want to reveal how new my store is to my customers.
Horace Loeb
Sequentially, starting at 1234? ;)
John at CashCommons
Customers or more importantly the competition can see how may orders your processing over a period of time.
FlappySocks
Sequentially, starting at 1 and hashing that number?
Ahmet Kakıcı
But then you might end up with an order number of "MNNNMNMNNNMM" (or whatever).
Horace Loeb
You take the number 47 at the bakery and assume you're the 47th customer? Don't overthink. Remember, they see the number "after" they make the purchase.
Jeff O
+1  A: 

At my old place it was the following:

The customer ID (which started at 1001), the sequence of the order they made then the unique ID from the Orders table. That gave us a nice long number of at least 6 digits and it was unique because of the two primary keys.

I suppose if you put dashes or spaces in you could even get us a little insight into the customer's purchasing habits. It isn't mind boggling secure and I guess a order ID would be guessable but I am not sure if there is security risk in that or not.

MrChrister
A: 

Ok, how about this one?

Sequentially, starting at some number (2468) and add some other number to it, say the day of the month that the order was placed.

The number always increases (until you exceed the capacity of the integer type, but by then you probably don't care, as you will be incredibly successful and will be sipping margaritas in some far-off island paradise). It's simple enough to implement, and it mixes things up enough to throw off any guessing as to how many orders you have.

John at CashCommons
A: 

Something like this:

  1. Get sequential order number. Or, maybe, an UNIX timestamp plus two or three random digits (when two orders are placed at the same moment) is fine too.
  2. Bitwise-XOR it with some semi-secret value to make number appear "pseudo-random". This is primitive and won't stop those who really want to investigate how many orders you have, but for true "randomness" you need to keep a (large) permutation table. Or you'll need to have large random numbers, so you won't be hit by the birthday paradox.
  3. Add checkdigit using Verhoeff algorithm (I'm not sure it will have such a good properties for base33, but it shouldn't be bad).
  4. Convert the number to - for example - base 33 ("0-9A-Z", except for "O", "Q" and "L" which can be mistaken with "0" and "1") or something like that. Ease of pronouncation means excluding more letters.
  5. Group the result in some visually readable pattern, like XXX-XXX-XX, so users won't have to track the position with their fingers or mouse pointers.
drdaeman
A: 

How about getting the current time in miliseconds and using that as your order ID?

rodey
A: 

Just one Rube Goldberg-style idea:

You could generate a table with a random set of numbers that is tied to a random period of time:

Time Period          Interleaver
next 2 weeks:        442
following 8 days:    142
following 3 weeks:   580

and so on... this gives you an unlimited number of Interleavers, and doesn't let anyone know your rate of orders because your time periods could be on the order of days and your interleaver is doing a lot of low-tech "mashing" for you.

You can generate this table once, and simply ensure that all Interleavers are unique. You can ensure you don't run out of Interleavers by simply adding more characters into the set, or start by defining longer Interleavers.

So you generate an order ID by getting a sequential number, and using today's Interleaver value, interleave its digits (hence, the name) in between each sequential number's digits. Guaranteed unique - guaranteed confusing.

Example:

Today I have a sequential number 1, so I will generate the order ID:  4412
The next order will be 4422
The next order will be 4432
The 10th order will be 41402

In two weeks my interleaver will change to 142, 
The 200th order will be 210402
The 201th order will be 210412

Eight days later, my interleaver changes to 580:
The 292th order will be 259820

This will be completely confusing but completely deterministic. You can just remove every other digit starting at the 1's place. (except when your order id is only one digit longer than your interleaver)

I didn't say this was the best way - just a Friday idea.

Jeff Meatball Yang
+1  A: 

I'd rather submit the number 347 and get great customer service at a smaller personable website than: G-84e38wRD-45OM at the mega-site and be ignored for a week.

You would not want this as a system id or part of a link, but as a user-friendly number it works.

Jeff O
+4  A: 

As a customer I would be happy with:

year-month-day/short_uid

for example:

2009-07-27/KT1E

It gives room for about 33^4 ~ 1mln orders a day.

niteria
Year without trailling '2', Day of Year and 4 random chars (e.g. 9-256-KT1E) thats enough for 1 million orders every day for 999 years, hows that?
Chris
+1  A: 

Here is an implementation for a system I proposed in an earlier question:

MAGIC = [];
29.downto(0) {|i| MAGIC << 839712541[i]}

def convert(num)
  order = 0
  0.upto(MAGIC.length - 1)  {|i| order = order << 1 | (num[i] ^ MAGIC[i]) }
  order
end

It's just a cheap hash function, but it makes it difficult for an average user to determine how many orders have been processed, or a number for another order. It won't run out of space until you've done 230 orders, which you won't hit any time soon.

Here are the results of convert(x) for 1 through 10:

1:  302841629
2:  571277085
3:   34406173
4:  973930269
5:  437059357
6:  705494813
7:  168623901
8:  906821405
9:  369950493
10: 638385949
Pesto
A: 

You could do it like a postal code: 2b2 b2b

That way it has some kind of checksum (not really, but at least you know it's wrong if there are 2 consecutive numbers or letters). It's easy to read out over the phone, and it doesn't give an indication of how many orders are in the system.

Jon Tackabury
A: 

http://blog.logeek.fr/2009/7/2/creating-small-unique-tokens-in-ruby

>> rand(36**8).to_s(36)
=> "uur0cj2h"
Simone Carletti
A: 

Rather than generating and storing a number, you might try creating an encrypted version that would not reveal the number of orders in the system. Here's an article on exactly that.

Ray