Integrate Drupal registration form with ubercart checkout

I had a very interesting experience with Drupal and Ubercart this week. One of my client ask me to implement a membership-based website, provide sophisticated content to premium users. The initial requirement is very simple: add a Package option in Sign up form, if user choose a premium package then require the user to complete the payment before accessing his account. This is a common requirement and any web developer would likely to run into one during his development life. The trouble with me is I never work with Ubercart before so I begun with some analyzing first. The below table is the my analysis about the requirements and challenges.

Requirements Challenges
  • Use Ubercart for payment handling.
  • Integrate with AutoAssignRole module.
  • Reuse Profile fields such as First name, Last name in Checkout form to improve usability
  • Account is not accessible until Payment is completed.
  • Try to write less code
  • No built-in module, or I don't know how to configure Ubercart modules yet.
  • Too complicated for Conditional Actions.
  • Decide which module will create user, Drupal itself, Ubercart or write our own module.

The hardest part is when and who will create the user: Drupal, Ubercart or a custom module. Ubercart can be discarded easily, since it has no integration with Profile module. Drupal won't help you too, because the user is only supposed to be created after the order is completed. I have also thought about using a custom role to grant special permission after the payment is completed, but if I do so, I will need to handle the `unpaid` user, provide a workflow for these kind of users to be able to upgrade again. I thought if I chose that way I would have to write more unnecessary code, so custom module is the last choice for me.

At first, I tried to use ctools to create a multi-step form with two steps, but I found it is too complicated to handle the Ubercart checkout form just as soon as I started the implementation. So I start with the simplest step: allow user to choose packages during registration. And I also want to prevent Drupal from creating user too, so I insert a custom handle at the top array of form #submit handlers.

/** * Get available packages */ function eiab_packages($id = NULL) { static $packages; if ($packages === null) { // load package nodes from database } return $id ? $packages[$id] : $packages; } function eiab_core_form_alter(&$form, &$state, $id) { if ($id == 'user_register') { $packages = eiab_packages(); $form['Package Selection'] = array( '#type' => 'fieldset', '#title' => t('Package selection'), '#weight' => 2, ); $options = array(); foreach ($packages as $package) { $options[$package->nid] = $package->title; } $form['Package Selection']['package'] = array( '#type' => 'radios', '#title' => t('Choose your package'), '#required' => TRUE, '#options' => $options ); array_unshift($form['#submit'], 'eiab_user_company_register'); } }

Note: make sure that your custom module handle is ran before any of other modules. I also used LogginToBoggan which created the user on form submition, we don't want to create user at this step. The form handle is simple too: store the submitted data into Session (or ctools object cache if you want), use Ubercart Cart API to add the package into the cart, and redirect to checkout form.

function eiab_user_company_register($form, &$state) { $package_id = $state['values']['package']; $package = eiab_packages($package_id); // Run it now so we can get the roles. autoassignrole_user('insert', $state['values'], $user, NULL); // Store submitted form information. $_SESSION['company_user'] = $state; uc_cart_remove_item($package_id); uc_cart_add_item($package_id, 1, NULL, NULL, FALSE); drupal_goto('cart/checkout'); }

One small thing to note here is you must run AutoAssignRole hook_user here to get user roles, because AutoAssignRole check the requesting URLs to assign roles so if later it won't work. I have to use AutoAssignRole because there are many types of users in the websites and this is just one type of users (think about content reader and content provider). If you don't use AutoAssignRole, things will be simpler.

The next step is to handle the checkout process, so I use hook_order from Ubercart to track order changes and perform necessary actions at this step.

function eiab_core_order($op, &$order, $arg = NULL) { if ($op == 'update') { // Retrieve submitted user form. $company = $_SESSION['company_user']; if ($company && $order->order_status == 'in_checkout') { $company_user_values = $company['values']; $company_user_values['checkout_pending'] = TRUE; $company_user = user_save(NULL, $company_user_values); db_query("UPDATE {uc_orders} SET primary_email = '%s', uid = %d WHERE order_id = %d", $company_user->mail, $company_user->uid, $order->order_id); unset($_SESSION['company_user']); // Update order object. $order->uid = $company_user->uid; watchdog('user', 'New user created: @user', array('@user' => $company_user->name), WATCHDOG_INFO); } if ($arg == 'completed') { $company_user = user_load($order->uid); if ($company_user && $company_user->status == 0 && $company_user->checkout_pending) { $pass = user_password(); user_save($company_user, array('status' => 1, 'checkout_pending' => NULL, 'pass' => $pass)); $company_user->password = $pass; // Mail token. _user_mail_notify('register_no_approval_required', $company_user); } } } }

Here you should understand two states of an order. If order state is change from in_checkout then the user just finishes the checkout workflow, this step is suitable to create the user now. And completed state means the order has finished, payment has been paid, it's a good time to activate user here. I also use a special flag checkout_pending to avoid updating the wrong user when others work on order.

So you can see that the implement is quite simple. The main trick is to prevent Drupal creating user, store submitted form and create user later. With this implementation, we don't need to integrate with other modules (in my case in Profiles, TermOfUse, LogginToBoggan, Mollom and many others in the future too). AutoAssignRole is a little special (buggy too, it should not depends on requesting URLs to assign user roles, that's why I need to run it hook manually).

If you choose to use a custom module to handle the workflow, make sure you configure the site properly, such as disable the `Add to cart` button for package product. It's a little bit confused if you can purchase the package directly, right ? I'm sure there may be simpler, easier way to address my issue, but in my case, it just works well. Do you have other solutions that works ? Share your thought.

Comments

Hi, sorry for my ignorance, but where do we place those codes respectively?

Just create a custom module and add the above code to your module file. Make sure to rename "eiab" prefix with your module name.

I do not know which version you are working with, but the Drupal 6 2.x Ubercart version has a 'Roles' module in it which assigns roles. Why do you not consider that?

Regards,
Theo Richel

What is wrong with this? http://www.leveltendesign.com/blog/rachel/how-create-memberships-drupal-ubercart

The wrong thing is the Profile module, if I use Ubercart Role module profile fields are ignored. Maybe I should change the name to "Integrate Profile module with Ubercart" to make you less confusing

I had a project should provide minFraud Credit Card Protest service, I want to create a bridge to verify the score is higher enough to process order, any ideas ? It seems no easy to do it..

Sorry for my stupid question. I created one custom module ,placed this code in it. But when I go for registration page I am not getting any extra thing. How I can configure this. ?

if ($packages === null) {
// load package nodes from database
}
what should i load here.

Add new comment