0

I'm trying to make the order_number be a randomly generated alphanumeric string and use it as the only customer facing unique ID that the customer ever sees relating to their order. However, the auto-incremented order_id still appears in all the checkout and order URLs.

Looking for examples of if this is being done in the wild on https://drupalcommerce.org/showcase, I found Lush, whose order_id increments by multiples of 5 in the checkout and order URLs. It's hard to tell if this is still the order_id or the order_number being displayed in the URL.

A popular solution is to change the order_number into an alphanumeric pattern on hook_commerce_order_presave(), like what’s mentioned here: https://drupalcommerce.org/discussions/8900/how-change-order-number-numerical-default-alpanumeric-pattern. The problem with that is the order number gets a new format, but the order id itself remains the auto-incremented integer and is still displayed in the checkout and order URLs.

Most examples I found recommend altering the commerce_order table and setting the starting order id to 1000 and letting it increment by 1 from there. Like what’s mentioned here: https://drupalcommerce.org/questions/3172/how-can-i-change-starting-order-number. This is a somewhat better solution, but still not as customizable as I would like because the order_id is still incrementing by 1.

The most promising solution seems to be encrypting and decrypting the order_id in the URL using hook_url_inbound_alter() and hook_url_outbound_alter(), like what’s mentioned here: Drupal Commerce | Hide or set an alias to the order number in the checkout path. This however requires the php mcrypt library, which I will not be able to use on my production server.

Any help, examples, or advice would be greatly appreciated!

2 Answers 2

1

Thanks @bob!

Just a bit of background: I'm dealing with a 3rd party order fulfillment company who provides me with an API to generate orders in their Magento system after orders are created in my Drupal Commerce system. The 3rd party passes back the order number from the Magento API, which I store as $order->field_vendor_order_number in Drupal Commerce. Later I actually replace the Drupal Commerce $order->order_number with the custom field I created to temporarily hold the 3rd party's order number. I do this after successfully processing the customer's payment, so unfortunately all the checkout pages leading up to the "thank you" page still have the internal commerce order_id in the url because the order hasn't been created in the 3rd party's system yet, and therefore I don't have the proper order number that I want to show the customer yet.

Here are the rules, actions, and callbacks that send an order to the 3rd party and save the order number they respond with as the new Drupal Commerce order_number:

/**
 * Implements hook_default_rules_configuration().
 */
function example_store_default_rules_configuration() {
  $rules = array();

  /**
   * Add a reaction rule to send the order to 3rd party for fulfillment upon checkout completion.
   */
  $rule = rules_reaction_rule();

  $rule->label = t('Send Order for Fulfillment');
  $rule->tags = array('Example Store');
  $rule->active = TRUE;

  $rule
    ->event('commerce_checkout_complete')
    ->action('example_store_send_order_for_fulfillment', array(
      'commerce_order:select' => 'commerce-order',
    ));

  $rule->weight = 1;

  $rules['example_store_send_order_for_fulfillment_rule'] = $rule;

  /**
   * Add a reaction rule to update the order number to the one assigned by 3rd party fulfillment upon checkout completion.
   */
  $rule = rules_reaction_rule();

  $rule->label = t('Assign Fulfillment Order Number');
  $rule->tags = array('Example Store');
  $rule->active = TRUE;

  $rule
    ->event('commerce_checkout_complete')
    ->action('example_store_assign_fulfillment_order_number', array(
      'commerce_order:select' => 'commerce-order',
    ));

  $rule->weight = 2;

  $rules['example_store_assign_fulfillment_order_number_rule'] = $rule;

  return $rules;
}

/**
 * Implements hook_rules_action_info().
 */
function example_store_rules_action_info() {
  $actions = array();

  $actions['example_store_send_order_for_fulfillment'] = array(
    'label' => t('Send Order for Fulfillment'),
    'parameter' => array(
      'commerce_order' => array(
        'type' => 'commerce_order',
        'label' => t('Order to send for fulfillment'),
      ),
    ),
    'group' => t('Example Store'),
    'callbacks' => array(
      'execute' => 'example_store_send_order_for_fulfillment_callback',
    ),
  );

  $actions['example_store_assign_fulfillment_order_number'] = array(
    'label' => t('Assign Fulfillment Order Number'),
    'parameter' => array(
      'commerce_order' => array(
        'type' => 'commerce_order',
        'label' => t('Order to assign a new fulfillment order number'),
      ),
    ),
    'group' => t('Example Store'),
    'callbacks' => array(
      'execute' => 'example_store_assign_fulfillment_order_number_callback',
    ),
  );

  return $actions;
}

/**
 * Callback for rules action to send the order for fulfillment to 3rd party API and process the response
 * Save the 3rd party order number into a custom field on the order entity
 */
function example_store_send_order_for_fulfillment_callback($order) {
  include_once DRUPAL_ROOT . '/services/ExampleStoreService.php';
  $objExampleStoreService = new ExampleStoreService();
  $arrResponse = $objExampleStoreService->SendOrder($order);

  if ($arrResponse['status'] === 'success') {
    $order->field_vendor_order_number['und'][0]['value'] = $arrResponse['order_number'];
    $order->log = 'Order Number from 3rd party is: '.$order->field_vendor_order_number['und'][0]['value'];
    $order->revision = TRUE;
    commerce_order_save($order);
  }
}

/**
 * Callback for rules action to assign the new order number received from 3rd party fulfillment
 * Take the order number stored in the custom field and actually save it as the order entity's order_number
 */
function example_store_assign_fulfillment_order_number_callback($order) {
  if (isset($order->field_vendor_order_number['und'][0]['value']) && !empty($order->field_vendor_order_number['und'][0]['value'])) {
    $order->order_number = $order->field_vendor_order_number['und'][0]['value'];
    $order->log = 'Order Number '.$order->order_number.' saved.';
    $order->revision = TRUE;
    commerce_order_save($order);
  }
}

However instead of going to the default "Checkout Complete" page, I created a rule that reacts to the checkout_complete event by redirecting to "store/order/[commerce-order:order-number]". Notice it has a weight of 10, to ensure it's the last thing I want to do when reacting to the checkout_complete event.

{ "rules_redirect_to_custom_checkout_complete_page" : {
    "LABEL" : "Redirect to custom checkout complete page",
    "PLUGIN" : "reaction rule",
    "WEIGHT" : "10",
    "TAGS" : [ "Commerce Checkout", "Example Store" ],
    "REQUIRES" : [ "php", "rules", "commerce_checkout" ],
    "ON" : [ "commerce_checkout_complete" ],
    "DO" : [
      { "redirect" : { "url" : "store\/order\/[commerce-order:order-number]\/" } }
    ]
  }
}

I added a hook_menu entry for this, which accepts the order_number instead of the internal commerce order_id as an url argument:

/**
 * Implements hook_menu().
 */
function example_store_menu() {

  $items['store/order/%'] = array(
    'page callback'   => '_example_store_order_view',
    'page arguments'  => array(2),
    'access callback' => TRUE,
    'type'            => MENU_CALLBACK,
  );

  return $items;
}

/**
 * Custom page callback for store/order/%
 * @param  string $order_number - order number from 3rd party fulfillment api
 * @return array - order as renderable array
 */
function _example_store_order_view($order_number) {
  if (!empty($order_number)) {
    $order = commerce_order_load_by_number($order_number);
  }

  if (!empty($order)) {
    $entity_view = entity_view('commerce_order', array($order->order_id => $order), 'customer', NULL, TRUE);
    return theme('example_store_order_complete_thank_you', array('entity_view' => $entity_view));
  }
  else {
    drupal_set_message(t("No order was found for order number ". $order_number . ". Please check your order number to make sure it is correct."), 'error');
    drupal_goto('store');
  }
}

And a custom hook_theme entry to handle the template for this custom "Order Complete Thank You" page, which mainly just displays the rendered order entity. If you have any Google Analytics e-commerce tracking code, this is the template in which you'd want to place those scripts. I'm not displaying it in this stripped down example, but I rigged mine up to only render those scripts on the first visit of this page, that was if the customer refreshes the thank you page, Google Analytics won't see duplicate tracking.

/**
 * Implements hook_theme().
 */
function example_store_theme($existing, $type, $theme, $path) {
  $module_path = drupal_get_path('module', 'example_store');
  return array(
    'example_store_order_complete_thank_you' => array(
      'variables' => array(
        'entity_view' => NULL,
      ),
      'template' => 'example-store-order-complete-thank-you',
      'path' => $module_path.'/templates',
    ),
  );
}

Contents of example-store-order-complete-thank-you.tpl.php:

//Render the order entity
print render($entity_view);
0

i am not sure if you already found an answer to your question, but here's a solution i came up with, after experimenting also with the hook_url_inbound_alter() and hook_url_outbound_alter() suggestion, which by the way doesn't work if additional querystring parameters are added to the url (in my case: ?currency=USD).

So i followed the popular alternative as you mentioned above and wrote this hook on commerce_order_presave :

function hook_commerce_order_presave($order) {
    if (!isset($order->order_id) && $order->status == 'cart') {
        // Presave on first add to cart event when order_id has not yet been assigned

        $last_id = db_query('SELECT MAX(order_id) FROM {commerce_order}')->fetchField();
        $auto_increment = $last_id + 7;
        $sql = "ALTER TABLE {commerce_order} AUTO_INCREMENT = $auto_increment";
        db_query($sql); 
    } else {
       $order->order_number = sprintf('%08d', $order->order_id);
    }
}

As visible in this screenshot, the first presave() fires on first add to cart without a filled in order_id. This was what i was looking for to manipulate the AUTO_INCREMENT value in the commerce_order table. In this example increased with 7, but you could also increase it with a random value.

Additionally, i setted the order_number to the same order_id value, yet zero prefixed to 8 digits.

Hope this brings you closer to your preferred solution.

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.