Currently Being Moderated

Unique Cross-Environment Order Numbers

Posted by Tony McAffee on Jun 24, 2010 2:23:45 PM

One of the pain points that can arise during a development project is the payment gateway's support of recycled order numbers. Live production gateways will track all historical order numbers and generate a duplicate order number error if an existing order number is reused. This is fine in production but troublesome when the payment gateway test servers also behave in this manner. Most payment gateway providers I have worked with in the past offer duplicate checks within an hour or two on their test servers, but beyond that, order numbers can be recycled which is a good thing for development purposes. Some providers, however, appear to extend this duplicate check to a day, week, or may even provide no support for order number reuse.


If you use the database population scripts that come with Elastic Path to facilitate schema/data changes, this limitation can frustrate your development team, who will most likely end up manually tracking used order numbers and making frequent updates to TORDERNUMBERGENERATOR.


If your payment gateway provider doesn't support order number reuse or at least not to the frequency you desire, what can you do?

 

Some Alternatives

The simplest approach would be to externalize the initial order number to the env.config and assign everyone a unique order number block. The problem with this is that each environment would have to keep track of the last order number used and update the env.config with the next number in the block with every new database build.


Another approach would be to externalize the initial order number (or perhaps even the order number incrementer) to a central database. This database would have a simple mapping of environment to next order number and each environment would have a sufficient block assigned to last the duration of the project. The problem with this approach is that many teams/environments are distributed and you may not be able to set up a central database which can be accessed by all environments.

 

A Better Solution

A better solution (though not 100% foolproof) is to construct the initial order number during the database build process using a unique element of the environment as well as a random element to get around frequent rebuilds on the same environment. To do this, we can create a custom Ant task that takes a fixed component (derived from either a fixed prefix or the environment's IP address) and appends a randomly generated number. If the random number is sufficiently large, the likelihood of duplicate hits will be minimal.

 

Creating the Ant Task

The following is an example of a custom Ant task that accepts min and max parameters for the random number component and a prefix. Regardless of whether a fixed prefix or an environment's IP address was provided, the task will use the hash code of the fixed value for the resulting order number in order to work with the out of the box numerical order number incrementer.

public class OrderNumberGeneratorTask  extends Task {
    private String min = null;
    private String max = null;
    private String property = null;
    private String prefix = null;
    private Random random = new Random(System.currentTimeMillis());
 
    @Override
    public void execute() throws BuildException {
         String localPrefix = null;
        if (min == null || min.equals(""))
            throw new BuildException("Min property is missing.");
 
        if (max == null || max.equals(""))
            throw new BuildException("Max property is missing.");
 
        int minInt = Integer.parseInt(min);
        int maxInt = Integer.parseInt(max);
 
        if (minInt > maxInt)
            throw new BuildException("Min is bigger than max.");
 
        localPrefix = prefix;
        if (localPrefix == null) {
             try {
                  localPrefix = InetAddress.getLocalHost().getHostAddress();
               } catch (UnknownHostException e) {
                    throw new BuildException("Unable to get local host address.", e);
               }
        }
        int randomInt = calculateRandom(minInt, maxInt);
 
        getProject().setNewProperty(property, String.valueOf(localPrefix.hashCode()) + String.valueOf(randomInt));
    }
 
    protected int calculateRandom(final int minInt, final int maxInt) {
        return minInt + random.nextInt(maxInt - minInt + 1);
    }

 

Updating the Build Process

To use this task, we need to package the compiled class in a jar and add it to the Maven repository along with an entry in the ant/setup/pom.xml so that it gets included in the ant setup script. Once the class is available on the Ant class path, we add the following task definition and task invocation to the ant/default.xml setup file. This will create the order number and store it in the ep.initial.order.number variable.

<?xml version="1.0" encoding="UTF-8"?>

<project name="ant_default" xmlns:artifact="antlib:org.apache.maven.artifact.ant" xmlns:contrib="antlib:net.sf.antcontrib">

  <taskdef name="ordernumbergenerator" classname="com.elasticpath.antextension.OrderNumberGeneratorTask"></taskdef>
... 
  <ordernumbergenerator min="1" max="1000000" property="ep.initial.order.number"></ordernumbergenerator>
...

 

Now we can use this variable in the database/src/base-insert.xml.vm file as the initial order number of the TORDERNUMBERGENERATOR table.

     <Tordernumbergenerator Uidpk="1" NextOrderNumber="${ep_initial_order_number}" />