Skip to content
ms_authorizenet.module 118 KiB
Newer Older
 * MoneySuite Authorize.net Gateway Module
 * Original author: Leighton Whiting - Released under GENERAL PUBLIC LICENSE
 * Current maintenance by multiple MoneySuite users.
 * Re: new initiative: https://www.drupal.org/node/2315653 
 * @todo - Use the hosted form for CIM payments for easier PCI Compliance: http://www.authorize.net/support/CIM_XML_guide.pdf
function ms_authorizenet_help($path, $arg) {
  $output = '';
  switch ($path) {
    case "admin/moneysuite/gateways/ms_authorizenet":
    case "admin/help#ms_authorizenet":
      $output .= '<p>' . t("Authorize.net Payment Gateway Module for MoneySuite. This allows you to
      receive payments using your Authorize.net Account. This gateway supports both Recurring and Non-Recurring
      Orders, using the AIM method for non-recurring orders and the ARB or CIM method for Recurring Orders.") . '</p>';
      $output .= '<p>' . t("The CIM method is recommended because it stores a profile id in the database which allows you to
      securely process future payments without storing any confidential information on your site, making you
      PCI Compliant in that regard. This allows users to easily make future purchases from your site without
      needing to enter their payment details over and over again. It is also used to charge recurring payments
      during Cron.") . '</p>';
      break;
  }
  return $output;
}

/**
 * Access callback that will always return true
 */
function ms_authorizenet_recurring_user_access($user, $rfid) {
  return TRUE;
}

/**
 * Access callback to see if the user can change their cim billing info
 */
function ms_authorizenet_cim_billing_test($cim_id) {
  $cim_profile = ms_authorizenet_get_cim_profile_by_id($cim_id);
  if ($user->uid == $cim_profile->uid OR user_access('administer moneysuite orders')) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_menu().
 */
function ms_authorizenet_menu() {
  $items['ms_authorizenet/silent-post'] = array(
    'page callback' => 'ms_authorizenet_silent_post',
    'access callback' => 'ms_core_view_page_access_test',
    'type' => MENU_CALLBACK,
  );
  $items['ms_authorizenet_arb/cancel/%'] = array(
    'title' => 'Cancel Payments',
    'page callback' => 'ms_authorizenet_cancel_arb_page',
    'page arguments' => array(2),
    'access callback' => 'ms_core_cancel_test',
    'access arguments' => array(2),
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );
  $items['ms_authorizenet_arb/billing/%'] = array(
    'title' => 'Update Billing Information',
    'page callback' => 'ms_authorizenet_arb_billing_page',
    'page arguments' => array(2),
    'access callback' => 'ms_core_cancel_test',
    'access arguments' => array(2),
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );
  $items['ms_authorizenet_arb/modify/%/%'] = array(
    'title' => 'Modify Payments',
    'page callback' => 'ms_authorizenet_modify_arb_page',
    'page arguments' => array(2, 3),
    'access callback' => 'ms_core_modify_test',
    'access arguments' => array(2, 3),
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );
  $items['user/%user/ms_authorizenet_cim/billing/%'] = array(
    'title' => 'Update Billing Information',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('ms_authorizenet_cim_profile_edit_form', 4),
    'access callback' => 'ms_authorizenet_cim_billing_test',
    'access arguments' => array(4),
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );
  $items['ms/checkout/authorize'] = array(
    'title' => 'Checkout',
    'page callback' => 'ms_authorizenet_checkout',
    'access callback' => 'ms_core_view_page_access_test',
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );
  $items['ms_authorizenet/thank-you'] = array(
    'title' => 'Thank You',
    'page callback' => 'ms_authorizenet_thankyou_page',
    'access callback' => 'ms_core_view_page_access_test',
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );

  return $items;
}

/**
 * Implements hook_ms_process_recurring_payment().
 */
function ms_authorizenet_ms_process_recurring_payment($recurring_schedule, $order) {
  // Load some variables
  $cim_profile = ms_authorizenet_get_cim_profile($order->uid);
  $payment_profile = ms_authorizenet_get_payment_profile($order->uid);
  $shipping_profile = ms_authorizenet_get_shipping_profile($order->uid);

  if ($cim_profile AND $payment_profile AND $shipping_profile) {
    // Process the next payment using CIM
    $response = ms_authorizenet_cim_charge($order, $recurring_schedule->main_amount, $cim_profile, $payment_profile, $shipping_profile);

    if ($response['approved']) {
      ms_core_log('ms_authorizenet', 'Processing Recurring CIM Charge for Order #!num. Here are the details: @message',
        array('!num' => $order->oid, '@message' => ms_core_print_r($recurring_schedule)));

      $payment = ms_core_new_payment($order->oid, 'ms_authorizenet_cim', 'rec_payment');

      $payment->transaction = $response['data']['auth_code'];
      $payment->amount = $recurring_schedule->main_amount;
      $payment->currency = 'USD';
      $payment->data = array('response' => $response['data']);

      // Update the address.
      $payment->billing_address = array(
        'first_name' => $payment_profile->billtofirstname,
        'last_name' => $payment_profile->billtolastname,
        'street' => $payment_profile->billtoaddress,
        'city' => $payment_profile->billtocity,
        'state' => $payment_profile->billtostate,
        'zip' => $payment_profile->billtozip,
        'country' => $payment_profile->billtocountry,
        'phone' => $payment_profile->billtophonenumber,
      );
      // Update the contact details.
      $payment->first_name = $payment_profile->billtofirstname;
      $payment->last_name = $payment_profile->billtolastname;

      return $payment;
    }
  }

  return FALSE;
}

/**
 * Implements hook_cron().
 */
function ms_authorizenet_cron() {
  // Check for cards that are expiring soon, and send a notice to the user to change them
  if (variable_get('ms_authorizenet_expiration_reminder_body', '')) {
    $frequency = (int)variable_get('ms_authorizenet_expiration_reminder_frequency', 3);

    $month = (int)date('n');
    $year = (int)date('Y');
    $threshold = (int)variable_get('ms_authorizenet_expiration_reminder_months', 1);

    // Set the month and year variables
    if ($month <= $threshold) {
      $month = (12 - $threshold) + $month;
      $year -= 1;
    } else {
      $month -= $threshold;
    }

    $due_time = REQUEST_TIME - (86400 * $frequency);

    $result = db_query("SELECT p.* FROM {ms_authorizenet_payment_profiles} as p
      LEFT JOIN {ms_orders} as o ON o.uid = p.uid
      LEFT JOIN {ms_recurring_schedules} as r ON r.oid = o.oid
      WHERE p.status = :status AND p.expirationyear <= :expirationyear AND
      p.expirationmonth <= :expirationmonth AND r.gateway = :gateway AND
      r.status = :active AND r.next_payment != :next_payment AND
      p.notified < :due_time",
      array(
        ':status' => 1,
        ':expirationyear' => $year,
        ':expirationmonth' => $month,
        ':gateway' => 'ms_authorizenet_cim',
        ':active' => 'active',
        ':next_payment' => 0,
        ':due_time' => $due_time,
      ));

    foreach ($result as $row) {
      $vars = array(
        'payment_profile' => $row,
      );
      $email_temp = db_query("SELECT mail FROM {users} WHERE uid = :uid", array(':uid' => $row->uid))->fetchField();
      // Send the card expiring soon email
      drupal_mail('ms_authorizenet', 'expirationReminder', $email_temp, language_default(), $vars);

      // Mark when this was last notified
      db_query("UPDATE {ms_authorizenet_payment_profiles} SET notified = :notified WHERE id = :id",
        array(':notified' => REQUEST_TIME, ':id' => $row->id));
    }
  }
}

// ======================================
// Administration Page:
// ======================================

/**
 * Gateway Settings Form.
 */
function ms_authorizenet_admin() {
  $form['api_id_key'] = array(
    '#type' => 'fieldset',
    '#title' => t('API Login ID and Transaction Key'),
    '#description' => t('This information is required to interact with your payment gateway account.  It is different from your login ID and password and may be found through your account settings page.'),
  );
  $form['api_id_key']['ms_authorizenet_api_login_id'] = array(
    '#type' => 'textfield',
    '#title' => t('API Login ID'),
    '#default_value' => variable_get('ms_authorizenet_api_login_id', ''),
  $form['api_id_key']['ms_authorizenet_api_transaction_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Transaction Key'),
    '#default_value' => variable_get('ms_authorizenet_api_transaction_key', ''),
  );

  $form['general'] = array(
    '#type' => 'fieldset',
    '#title' => t('General Settings'),
  );
  $form['aim_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('AIM settings'),
    '#description' => t('These settings pertain to the Authorize.Net AIM payment method for card not present transactions.'),
  );
  $form['aim_settings']['ms_authorizenet_aim_txn_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Transaction mode'),
    '#description' => t('Only specify a developer test account if you login to your account through https://test.authorize.net.<br/>Adjust to live transactions when you are ready to start processing real payments.'),
    '#options' => array(
      'live' => t('Live transactions in a live account'),
      'live_test' => t('Test transactions in a live account'),
      'developer_test' => t('Developer test account transactions'),
    ),
    '#default_value' => variable_get('ms_authorizenet_aim_txn_mode', 'live_test'),
  $form['aim_settings']['ms_authorizenet_aim_email_customer'] = array(
    '#type' => 'checkbox',
    '#title' => t('Tell Authorize.net to e-mail the customer a receipt based on your account settings.'),
    '#default_value' => variable_get('ms_authorizenet_aim_email_customer', FALSE),
  $form['aim_settings']['ms_authorizenet_auth_amount'] = array(
    '#type' => 'textfield',
    '#title' => t('Authorization Amount'),
    '#description' => t("Enter the amount to use for AUTH_ONLY transactions.
      This is used to determine if a card is valid when setting up a recurring payment profile.
      Note that some processors don't allow for $0.00 AUTH_ONLY transactions."),
    '#default_value' => variable_get('ms_authorizenet_auth_amount', 0.00),
  );

  $form['arb_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('ARB settings'),
    '#description' => t('These settings pertain to the Authorize.Net Automated Recurring Billing service.'),
  );
  $form['arb_settings']['ms_authorizenet_arb_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Transaction mode'),
    '#description' => t('Only specify developer mode if you login to your account through https://test.authorize.net.<br />Adjust to production mode when you are ready to start processing real recurring fees.'),
    '#options' => array(
      'production' => t('Production'),
      'developer' => t('Developer test'),
      'disabled' => t('Disabled'),
    ),
    '#default_value' => variable_get('ms_authorizenet_arb_mode', 'disabled'),
  $form['arb_settings']['ms_authorizenet_md5_hash'] = array(
    '#type' => 'textfield',
    '#title' => t('MD5 Hash'),
    '#description' => t('Enter the value here you entered in your Auth.Net account settings.'),
    '#default_value' => variable_get('ms_authorizenet_md5_hash', ''),
  $form['arb_settings']['ms_authorizenet_silent_post_url'] = array(
    '#type' => 'textfield',
    '#title' => t('Silent Post URL'),
    '#attributes' => array('readonly' => 'readonly'),
    '#description' => t('<b>Note:</b> You must enter this Silent Post URL in your Auth.Net account settings to receive notifications for recurring payments and let the system act on these payments (used by Affiliates Suite and others).'),
    '#default_value' => url('ms_authorizenet/silent-post', array('absolute' => TRUE)),
  );

  $form['cim_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('CIM settings'),
    '#description' => t('These settings pertain to the Authorize.Net Customer Information Management service.'),
  );
  $form['cim_settings']['ms_authorizenet_cim_profile'] = array(
    '#type' => 'checkbox',
    '#title' => t('Always create a CIM profile for securely storing CC info for later use.'),
    '#default_value' => variable_get('ms_authorizenet_cim_profile', FALSE),
  $form['cim_settings']['ms_authorizenet_cim_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Transaction mode'),
    '#description' => t('Only specify a developer test account if you login to your account through https://test.authorize.net.<br/>Adjust to live transactions when you are ready to start processing real payments.'),
    '#options' => array(
      'production' => t('Production'),
      'developer' => t('Developer test'),
      'disabled' => t('Disabled'),
    ),
    '#default_value' => variable_get('ms_authorizenet_cim_mode', 'disabled'),
  );

  $form['email'] = array(
    '#type' => 'fieldset',
    '#title' => t('Email Settings'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['email']['ms_authorizenet_expiration_reminder_months'] = array(
    '#type' => 'textfield',
    '#title' => t('Card Expiring Soon Threshold'),
    '#required' => FALSE,
    '#description' => t("How many months before the card expires should the expiring soon email be sent?."),
    '#default_value' => variable_get('ms_authorizenet_expiration_reminder_months', 1),
  $form['email']['ms_authorizenet_expiration_reminder_frequency'] = array(
    '#type' => 'textfield',
    '#title' => t('Card Expiring Soon Reminder Frequency'),
    '#required' => FALSE,
    '#description' => t("Send the Expiring Soon email every X days."),
    '#default_value' => variable_get('ms_authorizenet_expiration_reminder_frequency', 3),
  );
  // FIXME - Migrate this to the core payment profile system and make sure the cards are sending notices properly.
  $form['email']['ms_authorizenet_expiration_reminder_subject'] = array(
    '#type' => 'textfield',
    '#title' => t('Card Expiring Soon Email Subject'),
    '#required' => FALSE,
    '#default_value' => variable_get('ms_authorizenet_expiration_reminder_subject', t("Please update your Billing Info")),
  $form['email']['ms_authorizenet_expiration_reminder_body'] = array(
    '#type' => 'textarea',
    '#title' => t('Card Expiring Soon Email Body'),
    '#description' => t("Leave blank to not send an email."),
    '#required' => FALSE,
    '#default_value' => variable_get('ms_authorizenet_expiration_reminder_body', t("Your credit card [ms_authorizenet_payment_profile:cardNumber] is expiring soon. To avoid interruption of service, please update your billing information here: [ms_authorizenet_payment_profile:updateUrl]
  $form['email']['ms_authorizenet_expiration_reminder_token_help']['tokens'] = array(
    '#theme' => 'token_tree',
    '#token_types' => array('ms_authorizenet_payment_profile', 'user'),
  $form['email']['ms_authorizenet_card_update_confirm_subject'] = array(
    '#type' => 'textfield',
    '#title' => t('Card Updated Confirmation Email Subject'),
    '#required' => FALSE,
    '#default_value' => variable_get('ms_authorizenet_card_update_confirm_subject', t("Billing Information Changed")),
  $form['email']['ms_authorizenet_card_update_confirm_body'] = array(
    '#type' => 'textarea',
    '#title' => t('Card Updated Confirmation Email Body'),
    '#description' => t("Leave blank to not send an email."),
    '#required' => FALSE,
    '#default_value' => variable_get('ms_authorizenet_card_update_confirm_body', t("Your billing information has been successfully updated.
  $form['email']['ms_authorizenet_card_update_confirm_token_help']['tokens'] = array(
    '#theme' => 'token_tree',
    '#token_types' => array('ms_authorizenet_payment_profile', 'user'),
  $form['email']['ms_authorizenet_billing_error_subject'] = array(
    '#type' => 'textfield',
    '#title' => t('Card Billing Error Email Subject'),
    '#required' => FALSE,
    '#default_value' => variable_get('ms_authorizenet_billing_error_subject', t("There was a problem when processing your credit card.")),
  $form['email']['ms_authorizenet_billing_error_body'] = array(
    '#type' => 'textarea',
    '#title' => t('Card Billing Error Email Body'),
    '#description' => t("Leave blank to not send an email."),
    '#required' => FALSE,
    '#default_value' => variable_get('ms_authorizenet_billing_error_body', t("Your credit card [ms_authorizenet_payment_profile:cardNumber] could not be processed. To avoid interruption of service, please update your billing information here: [ms_authorizenet_payment_profile:updateUrl]
  $form['email']['ms_authorizenet_billing_error_token_help']['tokens'] = array(
    '#theme' => 'token_tree',
    '#token_types' => array('ms_authorizenet_payment_profile', 'user'),
    '#dialog' => TRUE,
  );

  return $form;
}

// ======================================
// User Hooks
// ======================================
/**
 * Implements hook_ms_core_billing_info_alter().
 */
function ms_authorizenet_ms_core_billing_info_alter(&$html, $account) {
  if ($cim_profile = ms_authorizenet_get_cim_profile($account->uid)) {
    $payment_profile = $cim_profile->payment_profile;
    $shipping_profile = $cim_profile->shipping_profile;

    $saved_card = (!empty($payment_profile->cardnumber)) ? $payment_profile->cardnumber : t('N/A');

    $billing_address = t('N/A');
    if ($payment_profile && isset($payment_profile->billtoaddress)) {
      $billing_address = t("@address - @city , @state", array(
        '@address' => $payment_profile->billtoaddress,
        '@city' => $payment_profile->billtocity,
        '@state' => $payment_profile->billtostate,
      ));
    }

    $shipping_address = t('N/A');
    if ($shipping_profile && isset($shipping_profile->shiptoaddress)) {
      $shipping_address = t("@address - @city , @state", array(
        '@address' => $shipping_profile->shiptoaddress,
        '@city' => $shipping_profile->shiptocity,
        '@state' => $shipping_profile->shiptostate,
      ));
    }

    $html['ms_authorizenet_cim_profile'][$cim_profile->profile_id] = array(
      '#type' => 'fieldset',
      '#title' => t('Saved Profile - !edit', array('!edit' => l(t('Edit'), 'user/' . $cim_profile->uid . '/ms_authorizenet_cim/billing/' . $cim_profile->profile_id))),
    $html['ms_authorizenet_cim_profile'][$cim_profile->profile_id]['card'] = array(
      '#type' => 'item',
      '#title' => t('Saved Card'),
      '#markup' => $saved_card
    );
    $html['ms_authorizenet_cim_profile'][$cim_profile->profile_id]['billing_address'] = array(
      '#type' => 'item',
      '#title' => t('Billing Address'),
      '#markup' => $billing_address,
    );
    $html['ms_authorizenet_cim_profile'][$cim_profile->profile_id]['shipping_address'] = array(
      '#type' => 'item',
      '#title' => t('Shipping Address'),
      '#markup' => $shipping_address,
    );
  }
}

/**
 * Show a form to edit the cim profile
 */
function ms_authorizenet_cim_profile_edit_form($form, &$form_state, $cim_id) {
  if ($cim_profile = ms_authorizenet_get_cim_profile_by_id($cim_id)) {
    $form['ms_authorizenet_cim_id'] = array(
      '#type' => 'value',
      '#value' => $cim_id,
    );
    if ($payment_profile = ms_authorizenet_get_payment_profile_by_cim_id($cim_profile->profile_id)) {
      $form['ms_authorizenet_payment_profile_id'] = array(
        '#type' => 'value',
        '#value' => $payment_profile->payment_id,
      );
    }
    if ($shipping_profile = ms_authorizenet_get_shipping_profile_by_cim_id($cim_profile->profile_id)) {
      $form['ms_authorizenet_shipping_profile_id'] = array(
        '#type' => 'value',
        '#value' => $shipping_profile->shipping_id,
      );
    }

    $form['ms_authorizenet_card'] = array(
      '#type' => 'fieldset',
      '#title' => t('Credit Card Information'),
      '#collapsible' => FALSE,
      '#collapsed' => FALSE,
      '#description' => t('Please enter the new billing information you would like to use.'),
    );

    // Credit Card Info
    $form['ms_authorizenet_card']['cc_first_name'] = array(
      '#type' => 'textfield',
      '#title' => t('First Name'),
      '#size' => 80,
      '#maxlength' => 120,
      '#required' => TRUE,
      '#desription' => t('The First Name as it appears on the card.'),
      '#default_value' => ($payment_profile) ? $payment_profile->billtofirstname : '',
    );
    $form['ms_authorizenet_card']['cc_last_name'] = array(
      '#type' => 'textfield',
      '#title' => t('Last Name'),
      '#size' => 80,
      '#maxlength' => 120,
      '#required' => TRUE,
      '#desription' => t('The Last Name as it appears on the card.'),
      '#default_value' => ($payment_profile) ? $payment_profile->billtolastname : '',
    );
    $form['ms_authorizenet_card']['cc_number'] = array(
      '#type' => 'textfield',
      '#title' => t('Credit Card Number'),
      '#size' => 40,
      '#maxlength' => 20,
      '#description' => t("The Credit Card Number. This is encrypted for your safety. If you are not changing the credit card number, then you can leave it as it is."),
      '#required' => TRUE,
      '#default_value' => ($payment_profile) ? $payment_profile->cardnumber : '',
    );
    $form['ms_authorizenet_card']['cc_exp_month'] = array(
      '#type' => 'select',
      '#title' => t('Month'),
      '#options' => ms_core_get_months(),
      '#description' => t("The month this credit card expires."),
      '#default_value' => ($payment_profile) ? str_pad($payment_profile->expirationmonth, 2, 0, STR_PAD_LEFT) : '',
      '#required' => TRUE,
    );
    $form['ms_authorizenet_card']['cc_exp_year'] = array(
      '#type' => 'select',
      '#title' => t('Year'),
      '#options' => ms_core_get_years(),
      '#description' => t("The year this credit card expires."),
      '#default_value' => ($payment_profile) ? $payment_profile->expirationyear : '',
      '#required' => TRUE,
    );

    $form['ms_authorizenet_billing'] = array(
      '#type' => 'fieldset',
      '#title' => t('Billing Info'),
      '#collapsible' => FALSE,
      '#collapsed' => FALSE,
    );

    // Billing Info
    $form['ms_authorizenet_billing']['billing_address1'] = array(
      '#type' => 'textfield',
      '#title' => t('Address Line 1'),
      '#size' => 80,
      '#required' => TRUE,
      '#default_value' => ($payment_profile) ? $payment_profile->billtoaddress : '',
    );
    $form['ms_authorizenet_billing']['billing_city'] = array(
      '#type' => 'textfield',
      '#title' => t('City'),
      '#size' => 40,
      '#required' => TRUE,
      '#default_value' => ($payment_profile) ? $payment_profile->billtocity : '',
    );
    $form['ms_authorizenet_billing']['billing_state'] = array(
      '#type' => 'textfield',
      '#title' => t('State'),
      '#size' => 40,
      '#description' => t('Enter the 2 Letter Code for the State'),
      '#required' => TRUE,
      '#default_value' => ($payment_profile) ? $payment_profile->billtostate : '',
    );
    $form['ms_authorizenet_billing']['billing_zip'] = array(
      '#type' => 'textfield',
      '#title' => t('Zip Code'),
      '#size' => 20,
      '#required' => TRUE,
      '#default_value' => ($payment_profile) ? $payment_profile->billtozip : '',
    );
    $form['ms_authorizenet_billing']['billing_country'] = array(
      '#type' => 'select',
      '#title' => t('Country'),
      '#options' => ms_core_get_countries('iso'),
      '#default_value' => 'US',
      '#required' => TRUE,
    );
    $form['ms_authorizenet_billing']['billing_phone'] = array(
      '#type' => 'textfield',
      '#title' => t('Phone Number'),
      '#size' => 40,
      '#required' => FALSE,
      '#default_value' => ($payment_profile) ? $payment_profile->billtophonenumber : '',
    );
    $form['ms_authorizenet_billing']['billing_email'] = array(
      '#type' => 'textfield',
      '#title' => t('Email Address'),
      '#size' => 80,
      '#required' => TRUE,
      '#default_value' => ($cim_profile) ? $cim_profile->email : '',
    );

    $form['ms_authorizenet_shipping'] = array(
      '#type' => 'fieldset',
      '#title' => t('Shipping Address'),
      '#collapsible' => FALSE,
      '#collapsed' => FALSE,
    );
    // Shipping Info
    $form['ms_authorizenet_shipping']['shipping_first_name'] = array(
      '#type' => 'textfield',
      '#title' => t('First Name'),
      '#size' => 80,
      '#maxlength' => 120,
      '#required' => TRUE,
      '#desription' => t('First Name of Shipping Address'),
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptofirstname : '',
    );
    $form['ms_authorizenet_shipping']['shipping_last_name'] = array(
      '#type' => 'textfield',
      '#title' => t('Last Name'),
      '#size' => 80,
      '#maxlength' => 120,
      '#required' => TRUE,
      '#desription' => t('Last Name of Shipping Address'),
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptolastname : '',
    );
    $form['ms_authorizenet_shipping']['shipping_address1'] = array(
      '#type' => 'textfield',
      '#title' => t('Address Line 1'),
      '#size' => 80,
      '#required' => TRUE,
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptoaddress : '',
    );
    $form['ms_authorizenet_shipping']['shipping_city'] = array(
      '#type' => 'textfield',
      '#title' => t('City'),
      '#size' => 40,
      '#required' => TRUE,
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptocity : '',
    );
    $form['ms_authorizenet_shipping']['shipping_state'] = array(
      '#type' => 'textfield',
      '#title' => t('State'),
      '#size' => 40,
      '#description' => t('Enter the 2 Letter Code for the State'),
      '#required' => TRUE,
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptostate : '',
    );
    $form['ms_authorizenet_shipping']['shipping_zip'] = array(
      '#type' => 'textfield',
      '#title' => t('Zip Code'),
      '#size' => 20,
      '#required' => TRUE,
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptozip : '',
    );
    $form['ms_authorizenet_shipping']['shipping_country'] = array(
      '#type' => 'select',
      '#title' => t('Country'),
      '#options' => ms_core_get_countries('iso'),
      '#required' => TRUE,
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptocountry : '',
    );
    $form['ms_authorizenet_shipping']['shipping_phone'] = array(
      '#type' => 'textfield',
      '#title' => t('Phone Number'),
      '#size' => 40,
      '#required' => FALSE,
      '#default_value' => ($shipping_profile) ? $shipping_profile->shiptophonenumber : '',
    );

    $form['#validate'][] = 'ms_core_cc_form_validate';
    $form['#validate'][] = 'ms_authorizenet_billing_info_form_validate';

    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );
    return $form;
  } else {
    drupal_not_found();
  }
}

/**
 * Form validation handler for ms_core_payment_profile_edit_form().
 *
 * Saves a payment profile card for a user.
 */
function ms_authorizenet_cim_payment_profile_edit_form_validate($form, &$form_state) {
  $v = $form_state['values'];

  $billing_address = array(
    'first_name' => trim($v['first_name']),
    'last_name' => trim($v['last_name']),
    'address' => trim($v['address']),
    'city' => trim($v['city']),
    'state' => trim($v['state']),
    'zip' => trim($v['zip']),
    'country' => trim($v['country']),
    'phone' => trim($v['phone']),
    'fax' => '',
  );
  $shipping_address = $billing_address;

  // Update or Create the CIM Profile first
  global $user;
  $account = $user;
  $cim_profile = ms_authorizenet_create_cim_profile($v['email'], $account->uid);

  // If there is no Payment Profile, then create a new one, otherwise update the existing one
  $payment_result = ms_authorizenet_create_payment_profile($account->uid, $cim_profile->profile_id, $billing_address, trim($v['cc_num']), trim($v['exp_month']), trim($v['exp_year']), 0);
  $shipping_result = ms_authorizenet_create_shipping_profile($account->uid, $cim_profile->profile_id, $shipping_address, 0);

  if ($payment_result['success']) {
    $payment_profile = $payment_result['payment_profile'];
    // Set a message
    drupal_set_message(t('Your Billing Information has been successfully created.'));
    // Send a confirmation email
    if (variable_get('ms_authorizenet_card_update_confirm_body', '')) {
      $vars = array(
        'payment_profile' => $payment_profile,
      );
      drupal_mail('ms_authorizenet', 'card_update_confirm', $account->mail, user_preferred_language($account), $vars);
    }
  } else {
    form_set_error('cc_num', t('Error: There was an error creating the payment profile. Please check your information and try again. Details: @details', array('@details' => $payment_result['error'])));
  }
  if ($shipping_result['success']) {
    // Set a message
    drupal_set_message(t('Your Shipping Address has been successfully created.'));
  } else {
    form_set_error('cc_num', t('Error: There was an error creating the shipping profile. Please check your information and try again. Details: @details', array('@details' => $shipping_result['error'])));
  }

  // Redirect to the correct page
  $form_state['redirect'] = 'user/' . $v['uid'] . '/billing-info';
}

/**
 * Implements hook_user_cancel().
 */
function ms_authorizenet_user_cancel($edit, $account, $method) {
  db_delete('ms_authorizenet_cim_profiles')
    ->condition('uid', $account->uid)
    ->execute();
  db_delete('ms_authorizenet_payment_profiles')
    ->condition('uid', $account->uid)
    ->execute();
  db_delete('ms_authorizenet_shipping_profiles')
    ->condition('uid', $account->uid)
    ->execute();

  return;
}

/**
 * Submit Function for the Admin Edit User form
 */
function ms_authorizenet_billing_info_form_validate($form, &$form_state) {
  $v = $form_state['values'];

  $billing_address = array(
    'first_name' => trim($v['cc_first_name']),
    'last_name' => trim($v['cc_last_name']),
    'address' => trim($v['billing_address1']),
    'city' => trim($v['billing_city']),
    'state' => trim($v['billing_state']),
    'zip' => trim($v['billing_zip']),
    'country' => trim($v['billing_country']),
    'phone' => trim($v['billing_phone']),
    'fax' => '',
  );
  $shipping_address = $billing_address;
  if (isset($v['shipping_address1'])) {
    $shipping_address = array(
      'first_name' => trim($v['shipping_first_name']),
      'last_name' => trim($v['shipping_last_name']),
      'address' => trim($v['shipping_address1']),
      'city' => trim($v['shipping_city']),
      'state' => trim($v['shipping_state']),
      'zip' => trim($v['shipping_zip']),
      'country' => trim($v['shipping_country']),
      'phone' => trim($v['shipping_phone']),
      'fax' => '',
    );
  }

  // Update or Create the CIM Profile first
  if (!$cim_profile = ms_authorizenet_get_cim_profile_by_id($v['ms_authorizenet_cim_id'])) {
    global $user;
    $account = $user;
    $cim_profile = ms_authorizenet_create_cim_profile($v['billing_email'], $account->uid);
  } else { // If it exists, update it
    $account = user_load($cim_profile->uid);
    $cim_profile = ms_authorizenet_create_cim_profile($v['billing_email'], $account->uid, NULL, $cim_profile->profile_id);
  $payment_id = ($v['ms_authorizenet_payment_profile_id']) ? $v['ms_authorizenet_payment_profile_id'] : 0;
  $shipping_id = ($v['ms_authorizenet_shipping_profile_id']) ? $v['ms_authorizenet_shipping_profile_id'] : 0;

  // If there is no Payment Profile, then create a new one, otherwise update the existing one
  $payment_result = ms_authorizenet_create_payment_profile($account->uid, $cim_profile->profile_id, $billing_address, trim($v['cc_number']), trim($v['cc_exp_month']), trim($v['cc_exp_year']), $payment_id);
  $shipping_result = ms_authorizenet_create_shipping_profile($account->uid, $cim_profile->profile_id, $shipping_address, $shipping_id);

  if ($payment_result['success']) {
    $payment_profile = $payment_result['payment_profile'];
    // Set a message
    drupal_set_message(t('Your Billing Information has been successfully updated.'));
    // Send a confirmation email
    if (variable_get('ms_authorizenet_card_update_confirm_body', '')) {
      $vars = array(
        'payment_profile' => $payment_profile,
      );
      drupal_mail('ms_authorizenet', 'card_update_confirm', $account->mail, user_preferred_language($account), $vars);
    }
  } else {
    form_set_error('cc_number', t('Error: There was an error updating the payment profile. Please check your information and try again. Details: @details', array('@details' => $payment_result['error'])));
  }
  if ($shipping_result['success']) {
    // Set a message
    drupal_set_message(t('Your Shipping Address has been successfully updated.'));
  } else {
    form_set_error('cc_number', t('Error: There was an error updating the shipping profile. Please check your information and try again. Details: @details', array('@details' => $shipping_result['error'])));
  }
}

/********************************************************************
 * FUNCTIONS
 *******************************************************************/

/**
 * Get the default payment profile for a user
 */
function ms_authorizenet_get_payment_profile($uid) {
  $result = db_query("SELECT * FROM {ms_authorizenet_payment_profiles} WHERE uid = :uid ORDER BY id DESC", array(':uid' => $uid));

  foreach ($result as $payment_profile) {
    return $payment_profile;
  }

  return FALSE;
}

/**
 * Get the default shipping profile for a user
 */
function ms_authorizenet_get_shipping_profile($uid) {
  $result = db_query("SELECT * FROM {ms_authorizenet_shipping_profiles} WHERE uid = :uid ORDER BY id DESC", array(':uid' => $uid));

  foreach ($result as $shipping_profile) {
    return $shipping_profile;
  }

  return FALSE;
}

/**
 * Get the profile for a user
 */
function ms_authorizenet_get_cim_profile($uid) {
  $result = db_query("SELECT * FROM {ms_authorizenet_cim_profiles} WHERE uid = :uid ORDER BY id DESC", array(':uid' => $uid));

  foreach ($result as $profile) {
    return ms_authorizenet_get_cim_profile_by_id($profile->profile_id);
  }

  return FALSE;
}

/**
 * Get the default payment profile for a user
 */
function ms_authorizenet_get_payment_profile_by_id($payment_id) {
  $result = db_query("SELECT * FROM {ms_authorizenet_payment_profiles} WHERE payment_id = :payment_id ORDER BY id DESC",
    array(':payment_id' => $payment_id));

  foreach ($result as $payment_profile) {
    return $payment_profile;
  }

  return FALSE;
}

/**
 * Get the default shipping profile for a user
 */
function ms_authorizenet_get_shipping_profile_by_id($shipping_id) {
  $result = db_query("SELECT * FROM {ms_authorizenet_shipping_profiles} WHERE shipping_id = :shipping_id ORDER BY id DESC",
    array(':shipping_id' => $shipping_id));

  foreach ($result as $shipping_profile) {
    return $shipping_profile;
  }

  return FALSE;
}

/**
 * Get the profile for a user
 */
function ms_authorizenet_get_cim_profile_by_id($profile_id) {
  $result = db_query("SELECT * FROM {ms_authorizenet_cim_profiles} WHERE profile_id = :profile_id ORDER BY id DESC",
    array(':profile_id' => $profile_id));

  foreach ($result as $profile) {
    $profile->payment_profile = ms_authorizenet_get_payment_profile_by_cim_id($profile_id);
    $profile->shipping_profile = ms_authorizenet_get_shipping_profile_by_cim_id($profile_id);
    return $profile;
  }

  return FALSE;
}

/**
 * Ensure that the profile exists on the CIM Server
 */
function ms_authorizenet_check_cim_profile_valid($id) {
  // Create AuthnetCIM object
  $isTest = (variable_get('ms_authorizenet_cim_mode', 'disabled') == 'developer') ? TRUE : FALSE;
  $cim = new AuthnetCIM(variable_get('ms_authorizenet_api_login_id', ''), variable_get('ms_authorizenet_api_transaction_key', ''), $isTest);

  // Check that the profile exists on the CIM Server
  $cim->setParameter('customerProfileId', $id);

  $cim->getCustomerProfile();

  // Get the profile ID returned from the request
  if ($cim->isSuccessful()) {
    return TRUE;
  } else {
		/**
		 * obscuring credit card number in watchdog. Issue https://security.drupal.org/node/161868
		 */
		$aux = $cim;
		if (isset($aux->params["cardNumber"])) $aux->params["cardNumber"] = "XXXX-XXXX-XXXX-XXXX";
		ms_core_log_error('ms_authorizenet', 'There was an error loading the CIM Profile. Error: !error Details: !details',
		array('!error' => $aux->getResponseSummary(), '!details' => ms_core_print_r($aux)));

    // Remove the profile from the database tables if it is not valid
    ms_core_log('ms_authorizenet', 'Removing the invalid CIM Profile record.');
    // Remove the invalid profile from the database
    // db_query("DELETE FROM {ms_authorizenet_cim_profiles} WHERE profile_id = '%s'", $id);
    return FALSE;
  }
}

/**
 * Get the payment profile for a cim profile
 */
function ms_authorizenet_get_payment_profile_by_cim_id($id) {
  $result = db_query("SELECT * FROM {ms_authorizenet_payment_profiles} WHERE profile_id = :id ORDER BY id DESC",
    array(':id' => $id));

  foreach ($result as $payment_profile) {
    return $payment_profile;
  }

  return FALSE;
}

/**
 * Get the shipping profile for a cim profile
 */
function ms_authorizenet_get_shipping_profile_by_cim_id($id) {
  $result = db_query("SELECT * FROM {ms_authorizenet_shipping_profiles} WHERE profile_id = :id ORDER BY id DESC",
    array(':id' => $id));

  foreach ($result as $shipping_profile) {
    return $shipping_profile;
  }

  return FALSE;
}

/**
 * Get the payment profiles for a user
 */
function ms_authorizenet_get_payment_profiles($uid) {
  $payment_profiles = array();
  $result = db_query("SELECT * FROM {ms_authorizenet_payment_profiles} WHERE uid = :uid ORDER BY id DESC",
    array(':uid' => $uid));

  foreach ($result as $payment_profile) {
    $payment_profiles[$payment_profile->payment_id] = t('Credit Card: @number', array('@number' => $payment_profile->cardnumber));
  }

  return $payment_profiles;
}

/**
 * Get the shipping profiles for a user
 */
function ms_authorizenet_get_shipping_profiles($uid) {
  $shipping_profiles = array();
  $result = db_query("SELECT * FROM {ms_authorizenet_shipping_profiles} WHERE uid = :uid ORDER BY id DESC",
    array(':uid' => $uid));