Drupal Commerce Marketplace: Filling in the Missing Pieces (Part One)

image

Overview

When it comes to establishing an online presence for e-commerce, Drupal Commerce is a solid solution. Along with its core e-commerce features (shopping cart, order management, product categories, tax calculation, etc.) it is wired to an array of contributed modules that supplement the e-commerce business requirements: multiple payment gateways (PayPal, bank transfers, credit cards, etc.), shipping services with different carriers (FedEx, USPS, UPS, DHL, etc.), pricing rules, product inventory management, discount rates, sales reports, multiple currencies, etc. That ecosystem of core commerce features, in addition to extensible commerce related contributed modules, makes Drupal a solid solution for e-commerce presence. The only caveat is that out-of-the-box, Drupal Commerce is not geared toward a multistore framework - a host site for various merchants in a marketplace (think Etsy) - where each merchant has their own store, distinct payment methods wired to the merchant's respective payment gateway accounts, unique shipping services, distinct products per store, multiple store fronts for each merchant (different product lines), as well as oversight from the host site owner on the individual merchants: i.e. revenue by store, responsiveness to customers and feedback, stores' ratings, etc. Consequently, the business use case for a recent client was challenging.

Drupal Commerce 2.x on Drupal 8 is a complete makeover of Commerce on Drupal 7 and, in essence, it features support for multiple stores. But when we started our project, some of the essential contributed modules were not ported to Drupal 8, especially those related to payment gateways and shipping methods. So, in the interest of time, we developed the project using Drupal 7 with the intent to migrate to Drupal 8 in the near future. Our research lead us to a sandbox Drupal 7 Marketplace module. After some evaluation, we concluded that it was a great start for us (Maciej Zgadzaj has done PHENOMENAL work on this sandbox project); and we decided to continue building on his work. Through our research and discovery phase, we knew the tasks required to make the sandbox project a workable solution for our use case. In this blog, we will highlight the unfinished pieces we identified and what we did to bridge the gap. Again, this sandbox saved us tons of development hours and kudos to Maciej Zgadzaj for taking the initiative to start this effort.

Main Issue

The underlying structure of the Marketplace sandbox module creates an order group id associating orders from multiple merchants. If a cart checkout transaction includes line items from multiple stores, each store would have a unique order number referencing the store's line items. Meanwhile, these distinct order ids share the same parent group order id to identify the transaction. So, a checkout transaction includes multiple distinct order ids all referencing the parent group order id. Additionally each order id is associated to a store for processing. While this is an elegant solution, the actual issue was that the contributed modules functionalities, i.e. shipping calculation, inventory management, customer order mail notifications, merchant notifications, and so on, did not account for a checkout transaction that included multiple order ids (one for each merchant) upon triggering the hook_commerce_checkout_complete hook; a conventional single store e-commerce implementation would have a single order id per checkout transaction. Therefore, contributed modules were triggering only on the last order id in the cart while not processing the other order id for other stores.  With that said, these were the issues we encountered:

  • Only the last order id state would change to “Pending” (as it should) after completing the checkout process. The other order ids for the other merchant(s) would remain in the “Checkout Review” state.
  • The product inventory would be reduced only on items in last order in the cart.
  • Shipping calculation would be accurate only on items in last order in the cart.
  • Customer email confirmation would include line items from the last order in the cart.
  • Only the site owner, not individual merchants, would get alerted that a sales transaction was completed in their respective store.
  • Upon cancelling the entire transaction post checkout, only the last order state would revert to “Cancelled”.
  • Upon cancelling a transaction post checkout, the product inventory count would only increment on products from last order.
  • Adjusting product stock levels if quantity changes or if a line item is deleted post checkout.

Most of our custom code was in the "hook_commerce_checkout_complete" hook; below is the resolution to set the orders state to "Pending" for all orders upon complete checkout. 

Resolution for changing order state on all orders

function my_module_commerce_checkout_complete($order){
    try {
        // Get group order id from commerce order using last order id in cart
        $order_group = db_select('commerce_order', 'co')
            ->fields('co', array('order_group'))
            ->condition('order_id', $order->order_number)
            ->condition('type', 'commerce_order', '=')
            ->execute()
            ->fetchField();

        // Get all orders ids associated with the group order id
        $orders = db_select('commerce_order', 'co')
            ->fields('co', array('order_id'))
            ->condition('order_group', $order_group, '=')
            ->condition('type', 'commerce_order', '=')
            ->execute();

        // Set order status for fetched orders to pending
        foreach ($orders as $order) {
            $commerce_order = commerce_order_load($order->order_id);
            commerce_order_status_update($commerce_order, 'pending', $skip_save = FALSE, $revision = NULL, $log = 'Setting order status to Pending');
            $orders_list[] = $order->order_id;
        }
     catch (Exception $ex) {
        drupal_set_message(t('Failed setting order state: ' . $ex->getMessage()), 'error');
    }
}

I will continue this series on the resolutions we achieved in subsequent blog posts. Meanwhile, feel free to send us your comments or questions. Thanks for reading!

 

Subscribe to Our Newsletter

Stay In Touch