I have a client that sells digital products and whose setup was composed of:

  • An old Inventory Software system that handled download permissions.
  • A custom Wordpress installation that provided customer access.
  • A custom Wordpress shopping cart.

All of this software was built around 10 years ago, was poorly documented and kept failing - a true nightmare. After a discussion with my client, we decided to merge all of these systems into a single one, powered by Wordpress and WooCoomerce. His team was already familiar with Wordpress, so the move to WooCommerce was a good technical and business choice - no one would need extra training, and the implementation effort would not be high because we’d leverage existing tools.

The migration was a success - the new system is faster and very reliable, and the client is having great results with it (read: more sales $$$). The overall process was pretty straightforward, but I had a hard time automating the customers migration from the old platform to the new platform. In this case, I needed to:

  1. Notify each customer about his new account.
  2. Send the customer a new receipt, with new download instructions.
  3. Preserve the order dates to not screw with the order statistics.

After lots of research and testing, I came up with the script below. Basically, it imports a list of orders from a CSV file, and then creates, as necessary, new users and orders on WooCommerce. I’ve included a few explanatory comments on the body of the script.

<?php

if (php_sapi_name() !== 'cli') {
  die("This script is meant to be run from the command line");
}

set_time_limit(0);
ini_set('max_execution_time', 0);

function find_wordpress_base_path() {
  $dir = dirname(__FILE__);

  do {
    if( file_exists($dir."/wp-config.php") ) {
      return $dir;
    }
  } while($dir = realpath("$dir/.."));

  return null;
}

define('BASE_PATH', find_wordpress_base_path()."/");
define('WP_USE_THEMES', false);
global $wp, $wp_query, $wp_the_query, $wp_rewrite, $wp_did_header;
require(BASE_PATH . 'wp-load.php');

/**
 * Helper function: it maps the original product ID to the new product ID.
 * @param  string $originId
 * @return int
 */
function getProductId($originId) {
  $ids = [
      23 => 2141
    , 24 => 2142
    , 25 => 2143
    , 26 => 2145
    , 27 => 2146
    , 28 => 2147
    , 29 => 2148
  ];

  if (array_key_exists($originId, $ids)) {
    return $ids[$originId];
  }

  return false;
}

if (!isset($argv[1]) || !file_exists($argv[1])) {
  echo "The input file was not specified or it does not exist.\n";
  exit;
}

// Read the file informed via STDIN.
$csv = fopen($argv[1], 'r');

$data = [];

while (($row = fgetcsv($csv)) !== false) {
  // Extract the parameters from the current row.
  list(
      $email
    , $firstName
    , $lastName
    , $phone
    , $address
    , $city
    , $state
    , $zipcode
    , $country
    , $createdAt
    , $products) = $row;

  $products = array_filter(array_unique(explode(',', $products)));

  // Check if we have a valid number of products; if not, we skip this row.
  if (count($products) == 0) {
    continue;
  }

  // Check if the user already exists.
  $userId = email_exists($email);

  // Create a new user and notify him.
  if (!$userId && !username_exists($email)) {
    $password = wp_generate_password(8, false);
    $userId   = wc_create_new_customer($email, '', $password);
  }

  // If we can't find/create an user, it logs an error.
  if (is_wp_error($userId)) {
    error_log("Failed creating user {$email}.");
    continue;
  }

  // Prepare the customer address for the order.
  $customerAddress = [
      'first_name' => $firstName
    , 'last_name'  => $lastName
    , 'company'    => ''
    , 'email'      => $email
    , 'phone'      => $phone
    , 'address_1'  => $address
    , 'address_2'  => ''
    , 'city'       => $city
    , 'state'      => $state
    , 'postcode'   => $zipcode
    , 'country'    => $country
  ];

  // Create a new order, and set the billing and shipping addresses.
  $order = wc_create_order(['customer_id' => $userId]);
  $order->set_address($customerAddress, 'billing');
  $order->set_address($customerAddress, 'shipping');

  foreach ($products as $originId) {
    $productId = getProductId($originId);

    if ($productId) {
      // Fetch the product from the database.
      $product = new WC_Product($productId);

      if ($product) {
        // Add the product to the order.
        // In this case, we only add a single unit of each product.
        $order->add_product($product, 1);
      }
    }
  }

  // Persist the order.
  $order->calculate_totals();

  // Confirm the order payment and add a note to it.
  $order->add_order_note('Order automatically imported from legacy system.');
  $order->payment_complete();

  // Update the order date to the original order date.
  // This is useful to not screw with the order statistics.
  wp_update_post([
      'ID'                => $order->id
    , 'post_date'         => $createdAt
    , 'post_date_gmt'     => $createdAt
    , 'post_modified'     => $createdAt
    , 'post_modified_gmt' => $createdAt
  ], true);
}

To run it, you must upload it to the Wordpress root directory, SSH into the server and call it with the required arguments:

php import.php users_dump.csv