3

When I create an Order from a Shopping Cart on the commercetools API (documented here: http://dev.commercetools.com/http-api-projects-orders.html#create-order-from-cart ), I have to set the orderNumber manually. The Number is important because the internal ID of the commercetools platform is long UUID that is not practical in the fulfilment and payment process.

What's the best way to reliably generate sequentially increasing and unique Order Numbers (there is no external system generating them in this case and it would be nice not to have to introduce one for the case)?

nkuehn
  • 138
  • 10
  • 1
    There is no way in the platform per se. The number should probably refer to an external systems order number of some sorts. So if you use commercetools on your own without an external system, you need to add something to your system to provide the sequence number. If you don't need a sequential number, you need use a truely random ID generator - ShortID could probably workhttps://github.com/dylang/shortid - you can use it in the browser. Alternatively, you could probably raise a feature request to support sequence generators in the platform. Other ecommerce platforms got it as well ;) – sebbulon Feb 02 '16 at 13:49

4 Answers4

3

A good way to do this is by using a custom object to contain your sequential order number. On order creation you may attempt to update this custom object http://dev.commercetools.com/http-api-projects-custom-objects.html#create-or-update-a-customobject. If the update succeeds you can use that number for the orderNumber, if it fails, increment and try again.

To recap: When you're about to turn a cart into an order, get the current order number from your custom object. Then, attempt to increment it. If it succeeds, use that number as the order's orderNumber.

svaj
  • 31
  • 2
  • the new link for custom object: https://docs.commercetools.com/api/projects/custom-objects#create-or-update-a-customobject – Windix Nov 03 '20 at 23:26
0

As @svaj says, you can get sequential numbers either for customers or orders by using custom objects. The custom object will act as lock thanks to the version, avoiding that two concurrent requests could get the same number. You can also use recursion to try again in the event of a concurrency error.

This is a working example in ES6 (the same applies for order number). The only thing you have to set is the name of the sequence that will be the container and key of the custom object (For example "customerSequence").

setCustomerNumber({ sequence, value, version }) {
  return customObjectsService
    .save({
      container: sequence,
      key: sequence,
      value,
      version,
    })
    .then(customObject => customObject.value);
},

getCustomerNumber(sequence) {
  return customObjectsService
    .find({ where: `key="${sequence}"` })
    .then(({ results }) => (results.length ? results[0] : { value: 0 }))
    .then(lastValue => {
      return this.setCustomerNumber({
        sequence,
        value: lastValue.value + 1,
        version: lastValue.version,
      }).catch(() => this.getCustomerNumber(sequence)); // We request a new one on error
    });
},

And here you have the unit test of the previous code

describe('when getting the next customer number', () => {
  const sequence = 'customersSequence';

  describe('when existing previous customer number', () => {
    const oldCustomerNumber = 1;
    const newCustomerNumber = oldCustomerNumber + 1;
    const version = 1;

    beforeEach(() => {
      spyOn(customObjectsService, 'find').and.returnValue(
        Promise.resolve({
          results: [
            {
              value: oldCustomerNumber,
              version,
            },
          ],
          total: 1,
        }),
      );
      spyOn(customersService, 'setCustomerNumber').and.returnValue(
        Promise.resolve(newCustomerNumber),
      );
    });

    it('should get a customer number equals to <previous customer number> + 1', done => {
      customersService
        .getCustomerNumber(sequence)
        .then(customerNumber => {
          expect(customObjectsService.find).toHaveBeenCalledWith({
            where: `key="${sequence}"`,
          });
          expect(customersService.setCustomerNumber).toHaveBeenCalledWith({
            sequence,
            value: newCustomerNumber,
            version,
          });
          expect(customerNumber).toBe(newCustomerNumber);
        })
        .then(done, done.fail);
    });
  });

  describe('when NOT existing previous customer number', () => {
    const newCustomerNumber = 1;

    beforeEach(() => {
      spyOn(customObjectsService, 'find').and.returnValue(
        Promise.resolve({
          results: [],
          total: 0,
        }),
      );
      spyOn(customersService, 'setCustomerNumber').and.returnValue(
        Promise.resolve(newCustomerNumber),
      );
    });

    it('should get a customer number equals to 1', done => {
      customersService
        .getCustomerNumber(sequence)
        .then(customerNumber => {
          expect(customObjectsService.find).toHaveBeenCalledWith({
            where: `key="${sequence}"`,
          });
          expect(customersService.setCustomerNumber).toHaveBeenCalledWith({
            sequence,
            value: newCustomerNumber,
            version: undefined,
          });
          expect(customerNumber).toBe(newCustomerNumber);
        })
        .then(done, done.fail);
    });
  });
});
  • 1
    Don't ping in answers. Rather give credit to the person by leaving a link, commenting (when you have the reputation, and if this person asked the question don't worry they saw you... – Xantium Dec 14 '17 at 22:00
0
  • it's simple in node js or javascript just use random number using uuid module and slice it with 5 digit number. Now that number you can use for order number.
sunilsingh
  • 503
  • 1
  • 5
  • 9
-1

If you are writing your integration in Java (using CommerceTools Java SDK) then you can use java.util.concurrent.atomic.AtomicLong. Here is an example that sets an order number to an order after it has been created in CommerceTools.

First define a static variable in your class like this

private static final AtomicLong sequence = new AtomicLong(System.currentTimeMillis() / 1000);

Then this can be used in a method like this

public void setOrderNumber(Order order) throws ShoppingCartException {
    long orderNumber = sequence.incrementAndGet();

    // command to set order number
    SetOrderNumber action = SetOrderNumber.of(String.valueOf(orderNumber));
    OrderUpdateCommand command = OrderUpdateCommand.of(order, action);

    // execute the command
    try {
        // some code to get a Java SDK client
        BlockingSphereClient client = ...;
        this.order = client.executeBlocking(command);
    } catch (Exception e) {
        // log/throw exeception
    }
}
tfarooqi
  • 49
  • 6