Recently I had the task of updating one of my first Drupal Commerce sites from Commerce Recurring 7.x-1.x (completely deprecated) to 7.x-2.x. The new release of the module is a complete rewrite requiring more of a migration of the data then just a simple update. In the process I also had to update Commerce Card on File to the latest version which is a similar undertaking. I'm documenting the steps to help any of the other 5 people still using commerce recurring 1.x, but also to serve as a reference for how to handle these types of situations in the open source world of Drupal.
Warning: back that ass up first and don't blame me for lost data
Things you'll need:
- Current code base
- Backup of production database
- Local or separate site environment for testing
- Developer account for the payment gateway in use
- Devel module (for executing in code snippets at /devel/php)
Prepare your development environment:
- Don't update any of the modules yet
- Change the payment gateway settings to your developer account
- Place a recurring order with a new account to use for testing later
- Disable outbound emails from this environment (see https://drupal.org/node/201981)
- Disable any of the existing recurring rules
- Remove any code in commerce_recurring_uninstall()
- We want to keep this data, but also need to execute the installation for 7.x-2.x
- Disable and uninstall Commerce Recurring
- Download and enable Commerce Recurring 2.x (and Commerce Recurring UI)
- Add the newly provided recurring interval fields to products that are recurring (ie: commerce_recurring_rec_period)
- If you use any of the other fields (like initial or ending recurring intervals), you'll have to add those and update the script below to copy that data too
Copy the field values for commerce_recurring_interval to commerce_recurring_rec_period. They're the same field type, but have a new name in Commerce Recurring 2.x
<?php
$efq = new EntityFieldQuery();
$results = $efq->entityCondition('entity_type', 'commerce_product')
->fieldCondition('commerce_recurring_interval', 'interval', 1, '>=')
->execute();
// Loop through all of the recurring products and copy their field values.
foreach ($results['commerce_product'] as $result) {
$product = commerce_product_load($result->product_id);
$product->commerce_recurring_rec_period = $product->commerce_recurring_interval;
commerce_product_save($product);
}
?>
Create recurring entities from the old recurring order data.
Note this assumes you have 1 recurring item per order. If you have multiple, you'll need to update the snippet to create an entity for each recurring item on the order
<?php
$efq = new EntityFieldQuery();
$results = $efq->entityCondition('entity_type', 'commerce_order')
->fieldCondition('commerce_recurring_next_due', 'value', strtotime('now'), '>=')
->execute();
foreach ($results['commerce_order'] as $result) {
$order = commerce_order_load($result->order_id);
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$values = array(
'type' =>'product',
'status' => 1,
'start_date' => $order->created,
'due_date' => $order_wrapper->commerce_recurring_next_due->value(),
'end_date' => '',
'uid' => $order->uid,
'quantity' => 1,
'data' => array(
'order_id' => $order->order_id,
),
);
$recurring = commerce_recurring_new($values);
$recurring_wrapper = entity_metadata_wrapper('commerce_recurring', $recurring);
$recurring_efq = new EntityFieldQuery();
$recurring_orders = $recurring_efq->entityCondition('entity_type', 'commerce_order')
->fieldCondition('commerce_recurring_parent_order ', 'order_id', $order->order_id)
->execute();
$recurring_wrapper->commerce_recurring_order[] = $order;
foreach ($recurring_orders['commerce_order'] as $recurring_order) {
$recurring_wrapper->commerce_recurring_order[] = commerce_order_load($recurring_order->order_id);
}
foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
if (!empty($line_item_wrapper->value()->commerce_product)) {
$product = $line_item_wrapper->commerce_product->value();
if (!empty($product->commerce_recurring_interval)) {
$recurring_wrapper->commerce_recurring_ref_product = $line_item_wrapper->commerce_product->product_id->value();
$recurring_wrapper->commerce_recurring_fixed_price = $line_item_wrapper->commerce_unit_price->value();
}
}
}
entity_save('commerce_recurring', $recurring);
}
?>
Verify that you now have the same number of recurring entities as you did recurring order subscriptions.
Download Commerce Card On File 7.x-2.x and run the updates
Loop through the card on file entities one by one and call commerce_cardonfile_set_default_card() to set the latest card as the default.
<?php
$efq = new EntityFieldQuery();
$results = $efq->entityCondition('entity_type', 'commerce_cardonfile')
->execute();
foreach ($results['commerce_cardonfile'] as $cid) {
commerce_cardonfile_set_default_card($cid->card_id);
}
?>
Enable commerce_cardonfile_recurring to provide the new rules for charging the order and creating payment transactions
Commerce Authnet Notice
Be sure https://drupal.org/node/2161989#comment-8605567 has been applied
Be sure re-check that card on file is enabled in your Commerce Authnet settings
Rules Notice
If you have additional rules that should be applied to the recurring orders, make sure https://drupal.org/node/2264965#comment-8836103 has been applied
Testing
Attempt to renew the test order we created before staring the upgrade by setting the due date on the recurring entity to any date before today. Run cron twice to make sure a new order is created and is successfully charged.
Production Steps
Once you've verified that you can create new recurring entities and orders and that they are successfully being charged, you can then re-run these steps on your live site. It's best to put the site in maintenance mode and make several backups along the way.