Recently we built an event registration and online payment tool using Ubercart and the Webform module, based on Drupal 6. There are a number of options when in comes to Drupal-based event registration - the one we've used before is CiviCRM's CiviEvent. Of course, in this and most cases, the CiviCRM suite is overkill and adds far to much complexity when CRM functionality is not required.
Ubercart is definitely the module of choice when it comes to accepting online payment through your own site. However, at the moment, the de facto Ubercart method for doing online event registration is with a module called UC Node Checkout. It's perfect for a one-and-done event registration site, but for a site that is hosting several events simultaneously and on an ongoing basis, it's awkward. This is due to the fact that it maps a content type to a product SKU - so for each event you'd need to create a new content type. Not really very good long-term, particularly when the site administrator is not overly familiar with Drupal or Ubercart.
So, an alternative solution was devised using the Webforms module. The webform stores the registrant's organization's information - and also asks the registrant how many tickets to the event they want, which is the use case the client specified. The webform, after processing the form, adds the event 'tickets' to the cart. This is done with the 'Webform advanced settings' field in the webform configuration, under 'Additional Processing'.
<?php //Please enter the node id of the corresponding product for this event's fees. $nid = 374; $quantity = $form_state['values']['submitted_tree']['quantity']; $event_id = $form_state['values']['details']['nid']; $reg_key = mt_rand(1, 9999); $form_state['values']['submitted'][15] = 'unpaid-' . $reg_key; $data = array('attributes' => array(1 => $event_id, 2 => $reg_key)); uc_cart_add_item($nid, $quantity, $data); //UC API call ?>
The variable $nid references the event ticket product. Anything added to Ubercart's cart must be a product-type node. This is the aspect that must be updated when creating a new event. The other code pulls the Webform node id and creates a 'key' for the form submission, so that we can find it later, after the user has checked out. These are passed to the product as attributes, which must be set up on the product beforehand.
After the form is submitted, the above code is run, and the user is redirected to the cart using the redirect URL option. They then go through the checkout process. In order to track the success of the registration and the collection of the fees, a conditional action was added, triggered by a successful checkout:
<?php foreach ($order->products as $item) { if ($item->data['attributes']['Registration #']) { $nid = $item->data['attributes']['Event #']; $reg = $item->data['attributes']['Registration #']; $quantity = $item->qty; $result = db_query("SELECT `sid` FROM `webform_submitted_data` WHERE `nid` = %d AND `data` = '%s' LIMIT 1", $nid, 'unpaid-' . $reg); $sid = db_result($result); db_query("UPDATE `webform_submitted_data` SET `data` = '%s' WHERE `nid` = %d AND `sid` = %d AND `cid` = %d", 'paid', $nid, $sid, 15); db_query("UPDATE `webform_submitted_data` SET `data` = %d WHERE `nid` = %d AND `sid` = %d AND `cid` = %d", $quantity, $nid, $sid, 14); drupal_set_message("Your registration fees have been paid. Thank you."); } } ?>
It checks through the newly completed order, checking for products with the Registration ID attribute, an indicator of an event ticket purchase. Having found one, it pulls the required information from the product attributes and updates the webform submission to indicate that it's been paid. It also overwrites the quantity in case the user adjusted that in the checkout process.
To create a new event, another event's webform and product must be cloned. The webform needs to be updated to reflect the new product nid, and the attributes need to be added to the new product. It's not foolproof, but it is a relatively easy recipe for taking event registrations.



Comments
Very cool
A couple of months ago I built a site where I had to create event registration functionality for 4 separate events. It was my first foray into the world of Ubercart. I needed to collect information from the user for their registration. I dreamed of a webform/ubercart hybrid solution and Voilå, here it is!
Thank you.
Nice.
This is cool. I was looking at a whole bunch of options, like Webform_productize, but this is far, far simpler, and easier (as the captcha for this comment says: "such swifter" :)
It's also not particularly had to use one single webform with a dropdown for different ticket prices (i.e. cheaper tickets for one day, more expensive for two, or different prices for different ages or financial means), and then use that information to decide which product to add to the cart.
Now I'm wondering if it'd be worth creating a module to do it for you, so that you can select the output product, and map webform fields to product attributes... might be overkill though
Here'd my version:
This has some fairly basic improvements, including the ability to use one rego form for multiple different tickets, listing unpaid/paid as a hidden field in the webform, so that it can be viewed in the table, and not hardcoding the unique id number (ie. cid=15):
This bit goes in the webform "Additional Processing" section
//Get data from form submission. Form also needs a hidden "rego_paid" form, that defaults to "unpaid" $rego_name = $form_state['values']['submitted_tree']['rego_name']; $rego_group = $form_state['values']['submitted_tree']['rego_group']; $rego_ticket = $form_state['values']['submitted_tree']['rego_ticket']; $webform_id = $form_state['values']['details']['nid']; //Only add one rego to cart $quantity = 1; //nicify the regoname, as a safe token, and make a date+name based unique submission id $rego_id = $rego_name . " (" . $rego_group . ")"; $rego_key = date('YmdHis') . preg_replace("/[^a-zA-Z]/", "", $rego_name); <?php //DO NOT INCLUDE <?php tags //Push the unique id back into the form, so that the webform submission can be fround from the product $new_key = array_pop(array_keys($form_state['values']['submitted'])) + 1; $form_state['values']['submitted'][$new_key] = $rego_key; //Node id of the corresponding product for this event's fees, depends on the "ticket price" drop-down. //KEYS NEED TO MATCH! //One day if ($rego_ticket == "one_day") { $nid = 15; } //Two Day elseif ($rego_ticket == "two_day") { $nid = 16; } //attributes: 1 = rego name, 2 = webformID, 3=unique key $data = array('attributes' => array(1 => $rego_id, 2 => $rego_key, 3 => $webform_id)); //UC API call uc_cart_add_item($nid, $quantity, $data);Conditional Action:
Predicate: on payment received
Condition: if order balance
Action:
foreach ($order->products as $item) { if ($item->data['attributes']['Booking for']) { $nid = $item->data['attributes']['webform_id'][0]; $reg = $item->data['attributes']['Booking ID'][0]; //Get the webform submission ID for the product $result = db_query("SELECT `sid` FROM `webform_submitted_data` WHERE `nid` = %d AND `data` = '%s' LIMIT 1", $nid, $reg); $sid = db_result($result); //get the field ID (cid) for rego_paid field for the webform $result = db_query("SELECT `cid` FROM `webform_component` WHERE `nid` = %d AND `form_key` = '%s' LIMIT 1", $nid, 'rego_paid'); $cid = db_result($result); db_query("UPDATE `webform_submitted_data` SET `data` = '%s' WHERE `nid` = %d AND `sid` = %d AND `cid` = %d", 'paid', $nid, $sid, $cid); drupal_set_message("Your registration fees have been paid. Thank you."); } }also, if you're not going to include <br /> tags in your filter html input format for comments, you should include automatic line breaks!
Need help
Hello Ethan.
I'm trying to make sense of your great post & code in trying to apply your solution to a Webform based event registration. But I'm only partially succeeding.
Where I'm obviously failing (in order of discovery):
(1) updating the Webform's hidden status field fails (form's results only shows 'unpaid-xxxx').
(2) On Submitting the Webform, the event seems to be properly added to the cart by the additional processing (that was nice to see). But when, in 'cart', i click the event's name to access the full node, i see that the event_id = 'Event #' and reg_key = 'Registration #'. Doesn't look right to me.
Of course, this is the 'incomplete picture' i get at the end of my learning process.
But what about my process?
Here is my understanding of what i needed to do to make your solution work. Please note that I'm a 10 month old Drupal enthusiast with little php knowledge. But I'm quickly learning :-)
- i created an 'Event' product class in UC
- i created an event001 (nid=37): Skateboard Championship of the World
- added 2 attributes to this product and enabled a single option for each
- Attribute Name = 'Event Id', label = 'event_id', option name = 'Event #'
- Attribute Name = 'Rego Key', label = 'reg_key', option name = 'Registration #'
- I did this thinking it was what you meant when you wrote: "These [$event_id and $reg_key] are passed to the product as attributes, which must be set up on the product beforehand.". Hmmm...?
- i then created a webform (nid = 38) where
- cid of 'Quantity' = 4
- cid of 'Status' (hidden field) = 5
- got the cid number from the database table 'webform_component'
- both appear as type textfield
- In the Additional Processing php code
- $nid is set to 37 (the nid of the Skateboard event product)
- '15' which i assume is your hidden status field, is changed to '5'
- In the conditional action php code
- Ive removed the '' tags.
- changed '15' to '5'
- changed '14' to '4'
- Details for the conditional action
- Enabled
- Condition = Check the order status, Order status: Completed
- Action: Execute custom PHP code
This is where I'm at. Sorry for the long post. I figured the clearer the picture, the easier it would be to find where i went wrong.
Cheers,
Renaud
Demo site: renaudjoubert.com/drpl2
Using cartlinks to communicate between drupal instances
Hi guys,
I'm in a similar, but different situation. My ubercart install is Drupal 5 and my webform content is on a D6 site. So, I used your approach in conjunction with the cart links ubercart trickery, and thought I'd post the code here.
My D6 webforms now must contain 2 hidden fields to allow the 2 drupal systems to communicate:
- rego_form_build_id
- rego_paid
The advanced processing PHP snippet is:
// Get some internal webform values.
$webform_id = $form_values['details']['nid'];
$build_id = str_replace('form-','', $form_values['form_build_id']); // remove 'form-' from the front so cart links dont get confused by the hyphen.
// Save the save these values to a hidden form component (WHICH YOU ADD YOURSELF) called 'rego_form_build_id.
// This stores the value in the database, so the external CART system can retrieve the correct submission.
$build_component_cid = 8; // Customise this component id depending on which component it is in your form.
$form_values['submitted'][$build_component_cid] = $build_id; // Save this val with the form submission.
// Build the hidden attributes portion of the cart link URL. syntax = _a#o#_
// Attribute IDs 1 and 2 are defined as follows, respectively, in the external CART system:
// uc_attribute id 1 = 'webform_nid'
// uc_attribute id 2 = 'webform_token' (or build id)
$cart_link_attrs = 'a1o'. $webform_id. '_a2o'. $build_id;
// CUSTOMISE FORM LOGIC TO ADD APPROPRIATE TICKET/ATTRIBUTE TO USERS CART
if ($form_values['submitted_tree']['ticket_type'] == 'VAL01') {
$node->webform['confirmation'] = 'http://www.domain.com/bookshop/cart/add/e-p150_q1_'. $cart_link_attrs .'_iCARTLINK1?destination=cart';
}
else {
$node->webform['confirmation'] = 'http://www.domain.com/bookshop/cart/add/e-p151_q1_'. $cart_link_attrs .'_iCARTLINK2?destination=cart';
}
?>
The action on the ubercart system presumes the existance of these attributes (like the other scripts on this page).
Conditional Action: Predicate: on payment received Condition: if order balance
foreach ($order->products as $item) {
if ($item->data['attributes']['Webform ID']) {
$nid = $item->data['attributes']['Webform ID'];
$build = $item->data['attributes']['Form build ID'];
// Get the webform submission ID for the product
$result = db_query("SELECT `sid` FROM `webform_submitted_data` WHERE `nid` = %d AND `data` = '%s' LIMIT 1", $nid, $build);
$sid = db_result($result);
// Get the field ID (cid) for rego_paid field for the webform
$result = db_query("SELECT `cid` FROM `webform_component` WHERE `nid` = %d AND `form_key` = '%s' LIMIT 1", $nid, 'rego_paid');
$cid = db_result($result);
db_query("UPDATE `webform_submitted_data` SET `data` = '%s' WHERE `nid` = %d AND `sid` = %d AND `cid` = %d", 'yes', $nid, $sid, $cid);
}
} // end for
?>
I encountered a few problems with this approach. First problem was with the cart links module. The version I'm using only accepts integers as attribute values and options. The form ID worked, but the form build ID didn't. So I applied this patch (http://drupal.org/node/560358) to allow the cart links module to accept strings as options. Now my form_build_id was passed properly through the cark link module.
Next problem was with the default display of product attributes in ubercart. I'm not sure of the severity of this as a security risk (?), but I didn't really want the hidden attributes visible to the users for aesthetic reasons, so I added a javascript which loads only on the /cart page as follows (drupal 5):
$(document).ready(function() { $(".cart-options > li:contains('Webform ID')").hide(); $(".cart-options > li:contains('Form build ID')").hide(); });I was lucky in that both of my drual instances use table prefixing in the same database and I could execute PHP directly from the action. However, I couldn't think what the solution would be for people who have 2 completely separate instances and databases?Would they need to write a module to expose the webform API via the Services module? switch databases and perform queries or is there a preferred way to bootstrap another drupal instance from a PHP snippet?
Marty
Ending subscription without payment
Nice example Ethan
I followed your example and got realy far. But I got stuck in some Webform 3.0 stuff.
In my application it should be possible to conclude the registration without paying.
I can add an option to the form: payment yes/no, wich I have to catch before I do the "add-to-cart" opteration.
How and where can I choose to stop the registration??
Add new comment