diff --git a/modules/recurlyjs/README.txt b/modules/recurlyjs/README.txt
new file mode 100644
index 0000000..e441a41
--- /dev/null
+++ b/modules/recurlyjs/README.txt
@@ -0,0 +1,67 @@
+Description
+-----------
+This module provides FormAPI elements for subscriptions, updating billing
+information, and one-time payments through Recurly.com.
+
+EXTREMELY IMPORTANT CAVEAT: While this module provides convienent FormAPI
+elements such as $element['#type'] = 'recurlyjs_subscribe', these elements
+operate entirely through JavaScript and sensitive information never touches
+your server (thus circumventing PCI compliance requirements). This comes with
+the following caveats:
+
+- The recurlyjs elements cannot be on the same page as other elements that need
+  validation, because the credit card will be charged client-side. Validation
+  errors done server-side would be *after* the credit card has been charged.
+  It is recommended to build forms that only contain the recurly element, or
+  use multiple-step forms with only the recurly element on the last page by
+  itself.
+- Users must have JavaScript enabled in their browser to submit payment.
+- RecurlyJS is compatible with IE7 and higher.
+- RecurlyJS recommends jQuery 1.5.2 or higher. Drupal 7 comes with jQuery 1.4.4
+  out of the box, however we are not aware of any issues with using RecurlyJS
+  with this older version of jQuery.
+
+Requirements
+------------
+Drupal 7.x
+Recurly
+Libraries API
+
+API Information
+---------------
+This module provides 3 FormAPI elements that help manage credit cards through
+the Recurly service.
+
+Subscription forms:
+
+$form['subscribe'] = array(
+  '#type' => 'recurlyjs_subscribe',
+  '#plan_code' => 'gold',
+);
+
+Billing update forms:
+
+$form['billing_update'] = array(
+  '#type' => 'recurlyjs_update',
+  '#account_code' => 'hash',
+);
+
+And one-time payment forms:
+
+$form['subscribe'] = array(
+  '#type' => 'recurlyjs_payment',
+  '#amount' => 3000,
+  '#description' => t('Description to show up on invoice'),
+);
+
+There are several other options available for each type, but these basics are
+the most common. For more information see the recurlyjs_element_info() function
+in the recurlyjs.module file.
+
+Support
+-------
+Please use the issue queue for filing bugs with this module at
+http://drupal.org/project/issues/recurly?categories=All
+
+WE DO NOT PROVIDE SUPPORT ON CUSTOM CODING. Please use the issue queue for
+issues that are believed to be bugs or for requesting features.
diff --git a/modules/recurlyjs/includes/recurlyjs.pages.inc b/modules/recurlyjs/includes/recurlyjs.pages.inc
new file mode 100644
index 0000000..9c297e0
--- /dev/null
+++ b/modules/recurlyjs/includes/recurlyjs.pages.inc
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Menu callbacks for the RecurlyJS module.
+ */
+
+/**
+ * Form callback; Modifies the Recurly form at admin/config/services/recurly.
+ */
+function _recurlyjs_form_recurly_settings_form_alter(&$form, &$form_state) {
+
+}
diff --git a/modules/recurlyjs/recurly-element.js b/modules/recurlyjs/recurly-element.js
new file mode 100644
index 0000000..76e7461
--- /dev/null
+++ b/modules/recurlyjs/recurly-element.js
@@ -0,0 +1,35 @@
+/**
+ * @file
+ * FormAPI integration with the Recurly forms.
+ */
+(function ($) {
+
+Drupal.recurly = Drupal.recurly || {};
+Drupal.recurly.beforeInject = function(form) {
+  // Imitate typical Drupal element styling.
+  $(form).find('input[type=text]').addClass('form-text').parent().addClass('form-item');
+  $(form).find('select').addClass('form-select').parent().addClass('form-item');
+
+   // Remove the submit button. This will be triggered by the form submit.
+  $(form).find('.footer').remove();
+};
+
+Drupal.recurly.afterInject = function(form) {
+  // Nested form tags cause issues in IE, except if there are two of them.
+  // See http://anderwald.info/internet/nesting-form-tags-in-xhtml/
+  $(form).before('<form class="dummy-form" action="#" style="display: none"></form>');
+
+  $(form).parents('form:first').submit(function(e) {
+    if ($(this).find('input.recurly-token').val().length === 0) {
+      e.preventDefault();
+      $(form).triggerHandler('submit');
+    }
+  });
+};
+
+Drupal.recurly.successHandler = function(responseToken) {
+  $('.recurly-form-wrapper').find('input.recurly-token').val(responseToken);
+  $('.recurly-form-wrapper').parents('form:first').submit();
+};
+
+})(jQuery);
diff --git a/modules/recurlyjs/recurlyjs.info b/modules/recurlyjs/recurlyjs.info
new file mode 100644
index 0000000..e0d524c
--- /dev/null
+++ b/modules/recurlyjs/recurlyjs.info
@@ -0,0 +1,5 @@
+name = RecurlyJS
+description = Provides a Form API RecurlyJS field and integration with the Field and Entity systems.
+dependencies[] = recurly
+core = 7.x
+package = Recurly
diff --git a/modules/recurlyjs/recurlyjs.module b/modules/recurlyjs/recurlyjs.module
new file mode 100644
index 0000000..c3fcf38
--- /dev/null
+++ b/modules/recurlyjs/recurlyjs.module
@@ -0,0 +1,277 @@
+<?php
+
+/**
+ * @file
+ * Uses RecurlyJS to provide a Form API field to subscribe to a service.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function recurlyjs_menu() {
+  $items['recurlyjs/test'] = array(
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('recurlyjs_test'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+/**
+ * Test page.
+ */
+function recurlyjs_test($form, &$form_state) {
+  $form['payment'] = array(
+    '#type' => 'recurlyjs_subscribe',
+    '#plan_code' => 'sg1-plus',
+  );
+
+  $form['actions']['#type'] = 'actions';
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+  return $form;
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function recurlyjs_element_info() {
+  $common = array(
+    '#input' => TRUE,
+
+    // Global options:
+    // See http://docs.recurly.com/api/recurlyjs/reference#js-config
+    '#country' => 'US',
+    '#currency' => 'USD',
+    '#subdomain' => variable_get('recurly_subdomain', ''),
+    '#vatpercent' => NULL,
+    '#locale' => NULL,
+    '#terms_url' => NULL,
+    '#privacy_url' => NULL,
+
+    // Account options (not displayed to user):
+    // See http://docs.recurly.com/api/recurlyjs/reference#account-object
+    '#account_code' => NULL,
+    '#account_username' => NULL,
+    '#account_email' => NULL,
+    '#account_first_name' => NULL,
+    '#account_last_name' => NULL,
+    '#account_company_name' => NULL,
+
+    // Billing information; prepopulates options:
+    // See http://docs.recurly.com/api/recurlyjs/reference#billing-info-object
+    '#billing_first_name' => NULL,
+    '#billing_last_name' => NULL,
+    '#billing_address1' => NULL,
+    '#billing_address2' => NULL,
+    '#billing_city' => NULL,
+    '#billing_state' => NULL,
+    '#billing_country' => NULL,
+    '#billing_zip' => NULL,
+    '#billing_phone' => NULL,
+    '#billing_vat_number' => NULL,
+
+    // Common options between all forms.
+    '#address_requirement' => 'full',
+  );
+
+  // Element to start a subscription.
+  $types['recurlyjs_subscribe'] = array(
+    '#process' => array('recurlyjs_element_subscription_expand', 'recurlyjs_element_expand'),
+    '#theme' => array('recurlyjs__subscribe', 'recurlyjs'),
+
+    // See http://docs.recurly.com/api/recurlyjs/reference#subscription-object.
+    '#plan_code' => NULL,
+    '#trial_ends_at' => NULL, // ISO 8601 formatted.
+    '#starts_at' => NULL, // ISO 8601 formatted.
+    '#total_billing_cycles' => NULL, // Defaults to infinite.
+    '#first_renewal_date' => NULL, // Defaults one cycle from today.
+    '#coupon_code' => NULL,
+    '#enable_addons' => TRUE,
+    '#enable_coupons' => TRUE,
+  ) + $common;
+
+  // Element to update billing information for an existing subscription.
+  $types['recurlyjs_update'] = array(
+    '#process' => array('recurlyjs_element_update_expand', 'recurlyjs_element_expand'),
+    '#account_code' => NULL,
+    '#theme' => array('recurlyjs__subscribe', 'recurlyjs'),
+  ) + $common;
+
+  // Element to do a one-time payment.
+  $types['recurlyjs_payment'] = array(
+    '#process' => array('recurlyjs_element_transaction_expand', 'recurlyjs_element_expand'),
+    '#theme' => array('recurlyjs__subscribe', 'recurlyjs'),
+
+    // See http://docs.recurly.com/api/recurlyjs/reference#transaction-object
+    '#amount' => NULL, // Specified in cents, i.e. 1023 = $10.23.
+    '#description' => NULL,
+    '#accounting_code' => NULL,
+    '#coupon_code' => NULL,
+    '#enable_addons' => TRUE,
+    '#enable_coupons' => TRUE,
+  ) + $common;
+  return $types;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function recurlyjs_theme() {
+  $items['recurlyjs'] = array(
+    'render element' => 'element',
+    'pattern' => 'recurlyjs__',
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function recurlyjs_form_recurly_settings_form_alter(&$form, &$form_state) {
+  module_load_include('inc', 'recurlyjs', 'includes/recurlyjs.pages');
+  _recurlyjs_form_recurly_settings_form_alter($form, $form_state);
+}
+
+/**
+ * FormAPI #process callback for the recurlyjs_subscription element type.
+ */
+function recurlyjs_element_subscription_expand($element) {
+  recurly_client_initialize();
+
+  $subscription_keys = array(
+    'plan_code' => 'planCode',
+    'trial_ends_at' => NULL,
+    'starts_at' => NULL,
+    'total_billing_cylces' => NULL,
+    'first_renewal_date' => NULL,
+  );
+  $options['subscription'] = array();
+  foreach ($subscription_keys as $form_key => $jsonKey) {
+    if (isset($element['#' . $form_key])) {
+      $options['subscription'][$form_key] = $element['#' . $form_key];
+      if (isset($jsonKey)) {
+        $options[$jsonKey] = $element['#' . $form_key];
+      }
+    }
+  }
+
+  $element['#recurly_options'] = $options;
+  $element['#recurly_operation'] = 'buildSubscriptionForm';
+
+  return $element;
+}
+/**
+ * FormAPI #process callback for the recurlyjs element type.
+ */
+function recurlyjs_element_expand($element) {
+  recurly_client_initialize();
+
+  $config_keys = array(
+    'subdomain' => NULL,
+    'currency' => NULL,
+    'country' => NULL,
+    'vatpercent' => 'VATPercent',
+    'locale' => NULL,
+    'terms_url' => 'termsOfServiceURL',
+    'privacy_url' => 'privacyPolicyURL',
+  );
+  $recurly_config = array();
+  foreach ($config_keys as $form_key => $jsonKey) {
+    if (isset($element['#' . $form_key])) {
+      if (isset($jsonKey)) {
+        $recurly_config[$jsonKey] = $element['#' . $form_key];
+      }
+      else {
+        $recurly_config[$form_key] = $element['#' . $form_key];
+      }
+    }
+  }
+
+  $operation_options = array(
+    'target' => '#' . $element['#id'],
+    'signature' => Recurly_js::sign($element['#recurly_options']),
+  ) + $element['#recurly_options'];
+
+  // Functional settings for RecurlyJS module.
+  $recurly_config['beforeInject'] = 'Drupal.recurly.beforeInject';
+  $recurly_config['afterInject'] = 'Drupal.recurly.afterInject';
+  $recurly_config['successHandler'] = 'Drupal.recurly.successHandler';
+
+
+  // Shorten up and prepare variables for inline JavaScript code.
+  $recurly_config = json_encode($recurly_config);
+  $operation_options = json_encode($operation_options);
+  $recurly_operation = $element['#recurly_operation'];
+
+  // Change the beforeInject option to be an actual function rather than string.
+  $recurly_config = str_replace('"Drupal.recurly.beforeInject"', 'Drupal.recurly.beforeInject', $recurly_config);
+  $recurly_config = str_replace('"Drupal.recurly.afterInject"', 'Drupal.recurly.afterInject', $recurly_config);
+  $recurly_config = str_replace('"Drupal.recurly.successHandler"', 'Drupal.recurly.successHandler', $recurly_config);
+
+  // Add RecurlyJS and the inline code to the page.
+  $element['#attached']['js'][] = recurlyjs_path() . '/build/recurly.js';
+  $element['#attached']['js'][] = drupal_get_path('module', 'recurlyjs') . '/recurly-element.js';
+  $element['#attached']['js'][] = array(
+    'type' => 'inline',
+    'data' => "Recurly.config($recurly_config); Recurly.$recurly_operation($operation_options);",
+  );
+  $element['#attached']['css'][] = recurlyjs_path() . '/themes/default/recurly.css';
+
+  // The token field is populated via JavaScript then confirmed on submission.
+  $element['recurly_token'] = array(
+    '#type' => 'hidden',
+    '#default_value' => '',
+    '#element_validate' => array('recurly_element_token_validate'),
+    '#attributes' => array('class' => array('recurly-token'))
+  );
+
+  return $element;
+}
+
+/**
+ * FormAPI #element_validate handler for Recurly elements.
+ */
+function recurly_element_token_validate($element, &$form_state) {
+  recurly_client_initialize();
+  try {
+    $form_state['recurly_result'] = Recurly_js::fetch($element['#value']);
+  }
+  catch (Recurly_NotFoundError $e) {
+    form_error($element, t('The information could not be processed because the payment gateway returned the following error: @error', array('@error' => $e->getMessage())));
+  }
+}
+
+/**
+ * Retrieve the path to the RecurlyJS library file.
+ */
+function recurlyjs_path() {
+  // If we can find a path in the libraries directory to the Recurly PHP client
+  // library...
+  if (($path = libraries_get_path('recurly-js')) && file_exists($path . '/build/recurly.js')) {
+    // Include the library files and configure authentication.
+    return $path;
+  }
+  else {
+    watchdog('recurly', 'Could not find the Recurly JS client library in sites/all/libraries/recurly-js.', array(), WATCHDOG_ERROR);
+    return FALSE;
+  }
+}
+
+/**
+ * Theme the presentation of the recurlyjs FormAPI element.
+ */
+function theme_recurlyjs($variables) {
+  $element = $variables['element'];
+  $output = '';
+  $output .= '<div class="recurly-form-wrapper">';
+  $output .= '<div id="' . $element['#id'] . '">';
+  $output .= t('This order form requires JavaScript and Cookies to be enabled in your browser.');
+  $output .= '</div>';
+  $output .= drupal_render_children($element);
+  $output .= '</div>';
+  return $output;
+}
diff --git a/recurly.module b/recurly.module
index 0b73461..1dfe78b 100644
--- a/recurly.module
+++ b/recurly.module
@@ -419,7 +419,6 @@
     'company_name' => (string) $recurly_account->company_name,
     'uid' => $uid,
     'updated' => REQUEST_TIME,
-    'data' => serialize($data),
   );
 
   // Add the status based on whatever data we have available.