You are here

uc_authorizenet.module in Ubercart 5

Process payments using Authorize.net. Supports AIM and ARB.

Development sponsored by Digital Dollhouse - http://www.digitaldollhouse.com

File

payment/uc_authorizenet/uc_authorizenet.module
View source
<?php

/**
 * @file
 * Process payments using Authorize.net. Supports AIM and ARB.
 *
 * Development sponsored by Digital Dollhouse - http://www.digitaldollhouse.com
 */

/**
 * Implementation of hook_menu().
 */
function uc_authorizenet_menu($may_cache) {
  $items = array();
  global $user;
  if ($may_cache) {
    $items[] = array(
      'path' => 'authnet/silent-post',
      'callback' => 'uc_authorizenet_silent_post',
      'access' => TRUE,
      'type' => MENU_CALLBACK,
    );
  }
  else {

    // User operations menu items.
    if (arg(0) == 'user' && arg(2) == 'recurring' && intval(arg(3)) > 0) {
      $items[] = array(
        'path' => 'user/' . arg(1) . '/recurring/' . arg(3) . '/arb-update',
        'title' => t('Update your payment details'),
        'description' => t('Update the payment details for a recurring fee.'),
        'callback' => 'drupal_get_form',
        'callback arguments' => array(
          'uc_authorizenet_arb_user_update_form',
          arg(1),
          arg(3),
        ),
        'access' => $user->uid == intval(arg(1)) || user_access('administer recurring fees'),
        'type' => MENU_CALLBACK,
      );
      $items[] = array(
        'path' => 'user/' . arg(1) . '/recurring/' . arg(3) . '/arb-cancel',
        'title' => t('Cancel the recurring fee?'),
        'description' => t('Cancel a recurring fee.'),
        'callback' => 'drupal_get_form',
        'callback arguments' => array(
          'uc_authorizenet_arb_user_cancel_form',
          arg(1),
          arg(3),
        ),
        'access' => $user->uid == intval(arg(1)) || user_access('administer recurring fees'),
        'type' => MENU_CALLBACK,
      );
    }

    // Admin operations menu items.
    if (arg(3) == 'recurring' && intval(arg(4)) > 0) {
      if (arg(5) == 'arb-update') {
        $items[] = array(
          'path' => 'admin/store/orders/recurring/' . arg(4) . '/arb-update',
          'title' => t('Update ARB subscription', array(
            '@fee' => arg(4),
          )),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'uc_authorizenet_arb_admin_update_form',
            arg(4),
          ),
          'access' => user_access('administer recurring fees'),
          'type' => MENU_CALLBACK,
        );
      }
      elseif (arg(5) == 'arb-cancel') {
        $items[] = array(
          'path' => 'admin/store/orders/recurring/' . arg(4) . '/arb-cancel',
          'title' => t('Cancel ARB subscription'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'uc_authorizenet_arb_admin_cancel_form',
            arg(4),
          ),
          'access' => user_access('administer recurring fees'),
          'type' => MENU_CALLBACK,
        );
      }
    }
  }
  return $items;
}

/**
 * Implementation of hook_payment_gateway().
 */
function uc_authorizenet_payment_gateway() {
  $gateways[] = array(
    'id' => 'authorizenet',
    'title' => t('Authorize.net'),
    'description' => t('Process credit card payments using the AIM service of Authorize.net.'),
    'settings' => 'uc_authorizenet_settings_form',
    'credit' => 'uc_authorizenet_charge',
    'credit_txn_types' => array(
      UC_CREDIT_AUTH_ONLY,
      UC_CREDIT_PRIOR_AUTH_CAPTURE,
      UC_CREDIT_AUTH_CAPTURE,
      UC_CREDIT_REFERENCE_SET,
      UC_CREDIT_REFERENCE_TXN,
    ),
  );
  return $gateways;
}

/**
 * Callback for payment gateway settings.
 */
function uc_authorizenet_settings_form() {
  if (!function_exists('curl_init')) {
    $form['curl_warning'] = array(
      '#value' => '<div>' . t('The Authorize.net AIM service requires cURL.  Please talk to your system administrator to get this configured.') . '</div>',
    );
  }
  $login_data = _uc_authorizenet_login_data();
  $form['api_id_key'] = array(
    '#type' => 'fieldset',
    '#title' => t('API Login ID and Transaction Key'),
    '#description' => t('This information is required for Ubercart 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']['uc_authnet_api_login_id'] = array(
    '#type' => 'textfield',
    '#title' => t('API Login ID'),
    '#default_value' => variable_get('uc_authnet_api_login_id', ''),
  );
  $form['api_id_key']['uc_authnet_api_transaction_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Transaction Key'),
    '#default_value' => variable_get('uc_authnet_api_transaction_key', ''),
  );
  $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']['uc_authnet_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('uc_authnet_aim_txn_mode', 'live_test'),
  );
  $form['aim_settings']['uc_authnet_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('uc_authnet_aim_email_customer', FALSE),
  );
  $form['aim_settings']['uc_authnet_response_debug'] = array(
    '#type' => 'checkbox',
    '#title' => t('Log full API response messages from Authorize.net for debugging.'),
    '#default_value' => variable_get('uc_authnet_response_debug', FALSE),
  );
  $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']['uc_authnet_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('uc_authnet_arb_mode', 'disabled'),
  );
  $form['arb_settings']['uc_authnet_md5_hash'] = array(
    '#type' => 'textfield',
    '#title' => t('MD5 Hash'),
    '#description' => t('<b>Note:</b> You must first configure credit card encryption before setting this.<br />Enter the value here you entered in your Auth.Net account settings.'),
    '#default_value' => $login_data['md5_hash'],
    '#access' => user_access('administer credit cards'),
  );
  $form['arb_settings']['uc_authnet_report_arb_post'] = array(
    '#type' => 'checkbox',
    '#title' => t('Log reported ARB payments in watchdog.'),
    '#description' => t('Make sure you have set your Silent POST URL in Authorize.Net to @url.', array(
      '@url' => url('authnet/silent-post', NULL, NULL, TRUE),
    )),
    '#default_value' => variable_get('uc_authnet_report_arb_post', FALSE),
  );
  $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']['uc_authnet_cim_profile'] = array(
    '#type' => 'checkbox',
    '#title' => t('Always create a CIM profile for securely storing CC info for later use.'),
    '#default_value' => variable_get('uc_authnet_cim_profile', TRUE),
  );
  $form['cim_settings']['uc_authnet_cim_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('uc_authnet_cim_mode', 'disabled'),
  );
  return $form;
}

/**
 * Implementation of hook_form_alter().
 */
function uc_authorizenet_form_alter($form_id, &$form) {
  if ($form_id == 'uc_payment_gateways_form') {
    $form['#submit']['uc_authorizenet_payment_gateway_settings_submit'] = array();
  }
}

// Submit handler for payment gateway settings form to encrypt fields.
function uc_authorizenet_payment_gateway_settings_submit($form_id, $form_values) {

  // If CC encryption has been configured properly.
  if ($key = uc_credit_encryption_key()) {

    // Setup our encryption object.
    $crypt = new uc_encryption_class();

    // Encrypt the Login ID, Transaction key, and MD5 Hash.
    if (!empty($form_values['uc_authnet_md5_hash'])) {
      variable_set('uc_authnet_md5_hash', $crypt
        ->encrypt($key, $form_values['uc_authnet_md5_hash']));
    }

    // Store any errors.
    uc_store_encryption_errors($crypt, 'uc_authorizenet');
  }
}
function uc_authorizenet_charge($order_id, $amount, $data) {

  // Check for cURL support.
  if (!_uc_authorizenet_curl_check()) {
    return array(
      'success' => FALSE,
    );
  }

  // Load the order.
  $order = uc_order_load($order_id);

  // Perform the appropriate action based on the transaction type.
  switch ($data['txn_type']) {

    // Reference transactions are handled through Authorize.Net's CIM.
    case UC_CREDIT_REFERENCE_TXN:
      return _uc_authorizenet_cim_profile_charge($order, $amount, $data);

    // Set a reference only.
    case UC_CREDIT_REFERENCE_SET:

      // Return the error message if this failed.
      if ($message = _uc_authorizenet_cim_profile_create($order)) {
        return array(
          'success' => FALSE,
          'message' => $message,
        );
      }
      else {
        return array(
          'success' => TRUE,
          'message' => t('New customer profile created successfully at Authorize.Net.'),
        );
      }

    // Accommodate all other transaction types.
    default:
      return _uc_authorizenet_charge($order, $amount, $data);
  }
}

/**
 * Create a CIM profile using an order's data.
 */
function _uc_authorizenet_cim_profile_create($order) {
  $server = variable_get('uc_authnet_cim_mode', 'disabled');

  // Help build the request.
  $request = _uc_authorizenet_cim_profile_create_request($order);

  // Request a profile from auth.net.
  $xml = _uc_authorizenet_xml_api_wrapper('createCustomerProfileRequest', _uc_authorizenet_array_to_xml($request));

  // Parse the response.
  $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  if ($response['resultCode'] == 'Error') {
    uc_order_comment_save($order->order_id, 0, t('Authorize.Net: Creating CIM profile failed.<br />@error - @text', array(
      '@error' => $response['code'],
      '@text' => $response['text'],
    )), 'admin');
    return $response['text'];
  }
  else {
    uc_order_comment_save($order->order_id, 0, t('Authorize.Net: CIM profile created - @id', array(
      '@id' => $response['customerProfileId'],
    )));
  }

  // Save the new credit reference to the db.
  $order->data = uc_credit_log_reference($order->order_id, $response['customerProfileId'], $order->payment_details['cc_number']);
  return '';
}

/**
 * Helper to create the CIM profile creation request.
 */
function _uc_authorizenet_cim_profile_create_request($order) {
  return array(
    'refId' => substr($order->order_id . '-' . time(), 0, 20),
    'profile' => array(
      'merchantCustomerId' => substr($order->uid, 0, 20),
      'description' => substr(t('Order @order taking place at @date', array(
        '@order' => $order->order_id,
        '@date' => format_date(time()),
      )), 0, 255),
      'email' => substr($order->primary_email, 0, 255),
      'paymentProfiles' => array(
        'billTo' => _uc_authorize_cim_xml_billto($order),
        'payment' => array(
          'creditCard' => array(
            'cardNumber' => $order->payment_details['cc_number'],
            'expirationDate' => $order->payment_details['cc_exp_year'] . '-' . str_pad($order->payment_details['cc_exp_month'], 2, '0', STR_PAD_LEFT),
          ),
        ),
      ),
      'shipToList' => _uc_authorize_cim_xml_shipto($order),
    ),
  );
}

/**
 * Use a reference to charge to a CIM profile.
 */
function _uc_authorizenet_cim_profile_charge($order, $amount, $data) {
  global $user;
  $server = variable_get('uc_authnet_cim_mode', 'disabled');

  // Help build the request.
  $request = _uc_authorizenet_cim_profile_charge_request($order, $amount, $data);

  // Check error state.
  if (array_key_exists('errorCode', $request)) {
    $comment[] = $request['text'];
    $result = array(
      'success' => FALSE,
    );
  }
  else {

    // Request a profile from auth.net.
    $xml = _uc_authorizenet_xml_api_wrapper('createCustomerProfileTransactionRequest', _uc_authorizenet_array_to_xml($request));

    // Parse the response.
    $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));

    // Error state.
    if ($response['resultCode'] == 'Error') {
      $result = array(
        'success' => FALSE,
      );
      $comment[] = '(' . $response['resultCode'] . ': ' . $response['text'] . ')';
    }
    else {
      $result = array(
        'success' => TRUE,
      );

      // Build info message.
      $types = uc_credit_transaction_types();
      $comment[] = t('<b>@type:</b> @amount', array(
        '@type' => $types[$data['txn_type']],
        '@amount' => uc_currency_format($amount),
      ));

      // Save a comment to the order.
      uc_order_comment_save($order->order_id, $user->uid, implode('<br />', $comment), 'admin');
    }
  }

  // Build the response to the payment gateway API.
  return $result + array(
    'comment' => implode(', ', $comment),
    'message' => implode('<br />', $comment),
    'uid' => $user->uid,
  );
}

/**
 * Helper for building the request for a CIM profile charge.
 */
function _uc_authorizenet_cim_profile_charge_request($order, $amount, $data) {
  $profile = _uc_authorizenet_cim_profile_get($order, $data['ref_id']);
  if ($profile['resultCode'] == 'Error') {
    return $profile;
  }
  else {
    return array(
      'refId' => substr($order->order_id . '-' . time(), 0, 20),
      'transaction' => array(
        'profileTransAuthCapture' => array(
          'amount' => $amount,
          'customerProfileId' => $profile['customerProfileId'],
          'customerPaymentProfileId' => $profile['customerPaymentProfileId'],
          'order' => array(
            'invoiceNumber' => $order->order_id,
          ),
        ),
      ),
    );
  }
}

/**
 * Get a CIM profile stored at Authorize.Net.
 */
function _uc_authorizenet_cim_profile_get($order, $profile_id) {
  $server = variable_get('uc_authnet_cim_mode', 'disabled');
  $request = array(
    'customerProfileId' => $profile_id,
  );

  // Request a profile from auth.net.
  $xml = _uc_authorizenet_xml_api_wrapper('getCustomerProfileRequest', _uc_authorizenet_array_to_xml($request));

  // Parse the response.
  $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  return $response;
}

/**
 * Get a CIM payment profile stored at auth.net.
 */
function _uc_authorizenet_cim_payment_profile_get($order, $profile_id, $payment_profile_id) {
  $server = variable_get('uc_authnet_cim_mode', 'disabled');
  $request = array(
    'customerProfileId' => $profile_id,
  );

  // Request a profile from auth.net.
  $xml = _uc_authorizenet_xml_api_wrapper('getCustomerPaymentProfileRequest', _uc_authorizenet_array_to_xml($request));

  // Parse the response.
  $response = _uc_authorizenet_cim_parse_response(uc_authorizenet_xml_api($server, $xml));
  return $response['resultCode'] == 'Error' ? FALSE : $response;
}

/**
 * Handles authorizations and captures through AIM at Authorize.Net
 */
function _uc_authorizenet_charge($order, $amount, $data) {
  global $user;

  // Build a description of the order for logging in Auth.Net.
  $description = array();
  foreach ((array) $order->products as $product) {
    $description[] = $product->qty . 'x ' . $product->model;
  }
  $billing_country = uc_get_country_data(array(
    'country_id' => $order->billing_country,
  ));
  $delivery_country = uc_get_country_data(array(
    'country_id' => $order->delivery_country,
  ));

  // Build the POST data for the transaction.
  $submit_data = array(
    // Merchant Information
    'x_login' => variable_get('uc_authnet_api_login_id', ''),
    'x_tran_key' => variable_get('uc_authnet_api_transaction_key', ''),
    // Transaction Information
    'x_version' => '3.1',
    'x_type' => _uc_authorizenet_txn_map($data['txn_type']),
    // 'x_method' => $order->payment_method == 'credit' ? 'CC' : 'ECHECK',
    'x_method' => 'CC',
    // 'x_recurring_billing' => 'FALSE',
    'x_amount' => uc_currency_format($amount, FALSE, FALSE, '.'),
    'x_card_num' => $order->payment_details['cc_number'],
    'x_exp_date' => $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year'],
    'x_card_code' => $order->payment_details['cc_cvv'],
    // 'x_trans_id' => '',
    // 'x_auth_code' => '',
    'x_test_request' => variable_get('uc_authnet_aim_txn_mode', 'live_test') == 'live_test' ? 'TRUE' : 'FALSE',
    // 'x_duplicate_window' => '120',
    // Order Information
    'x_invoice_num' => $order->order_id,
    'x_description' => substr(implode(', ', $description), 0, 255),
    // Customer Information
    'x_first_name' => substr($order->billing_first_name, 0, 50),
    'x_last_name' => substr($order->billing_last_name, 0, 50),
    'x_company' => substr($order->billing_company, 0, 50),
    'x_address' => substr($order->billing_street1, 0, 60),
    'x_city' => substr($order->billing_city, 0, 40),
    'x_state' => substr(uc_get_zone_code($order->billing_zone), 0, 40),
    'x_zip' => substr($order->billing_postal_code, 0, 20),
    'x_country' => !$billing_country ? '' : $billing_country[0]['country_iso_code_2'],
    'x_phone' => substr($order->billing_phone, 0, 25),
    // 'x_fax' => substr('', 0, 25),
    'x_email' => substr($order->primary_email, 0, 255),
    'x_cust_id' => substr($order->uid, 0, 20),
    'x_customer_ip' => substr($_SERVER['REMOTE_ADDR'], 0, 15),
    // Shipping Information
    'x_ship_to_first_name' => substr($order->delivery_first_name, 0, 50),
    'x_ship_to_last_name' => substr($order->delivery_last_name, 0, 50),
    'x_ship_to_company' => substr($order->delivery_company, 0, 50),
    'x_ship_to_address' => substr($order->delivery_street1, 0, 60),
    'x_ship_to_city' => substr($order->delivery_city, 0, 40),
    'x_ship_to_state' => substr(uc_get_zone_code($order->delivery_zone), 0, 40),
    'x_ship_to_zip' => substr($order->delivery_postal_code, 0, 20),
    'x_ship_to_country' => !$delivery_country ? '' : $delivery_country[0]['country_iso_code_2'],
    // Extra Information
    'x_delim_data' => 'TRUE',
    'x_delim_char' => '|',
    'x_encap_char' => '"',
    'x_relay_response' => 'FALSE',
    'x_email_customer' => variable_get('uc_authnet_aim_email_customer', FALSE) ? 'TRUE' : 'FALSE',
  );
  if ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
    $submit_data['x_trans_id'] = $data['auth_id'];
  }

  // Determine the correct URL based on the transaction mode.
  if (variable_get('uc_authnet_aim_txn_mode', 'live_test') == 'developer_test') {
    $post_url = 'https://test.authorize.net/gateway/transact.dll';
  }
  else {
    $post_url = 'https://secure.authorize.net/gateway/transact.dll';
  }

  // Translate the data array into a string we can POST.
  $post_fields = array();
  foreach ($submit_data as $key => $value) {
    $post_fields[] = $key . '=' . urlencode($value);
  }

  // Setup the cURL request.
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $post_url);
  curl_setopt($ch, CURLOPT_VERBOSE, 0);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $post_fields));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  $result = curl_exec($ch);

  // Log any errors to the watchdog.
  if ($error = curl_error($ch)) {
    watchdog('uc_authorizenet', t('cURL error: @error', array(
      '@error' => $error,
    )), WATCHDOG_ERROR);
    return array(
      'success' => FALSE,
    );
  }
  curl_close($ch);
  $response = explode('|', $result);
  if (variable_get('uc_authnet_response_debug', FALSE)) {
    watchdog('uc_authorizenet', t('Debug response: !data', array(
      '!data' => '<pre>' . check_plain(print_r($response, TRUE)) . '</pre>',
    )));
  }

  // Trim off the encapsulating character from the results.
  for ($i = 0; $i < count($response); $i++) {
    $response[$i] = substr($response[$i], 1, strlen($response[$i]) - 2);
  }

  /**
   * Response key index:
   * 0 = Response Code
   * 2 = Response Reason Code
   * 3 = Response Reason Text
   * 4 = Authorization Code
   * 5 = Address Verification Service (AVS) Response
   * 6 = Transaction ID; needed for CREDIT, PRIOR_AUTH_CAPTURE, and VOID transactions.
   * 9 = Amount
   * 11 = Transaction Type
   * 32 = Tax Amount Charged
   * 37 = Transaction Response MD5 Hash
   * 38 = Card Code (CVV) Response
   */

  // If we didn't get an approval response code...
  if ($response[0] != '1') {

    // Fail the charge with the reason text in the decline message.
    $result = array(
      'success' => FALSE,
      'message' => t('Credit card payment declined: @message', array(
        '@message' => $response[3],
      )),
      'uid' => $user->uid,
    );
  }
  else {

    // Build a message for display and comments in the payments table.
    $message = t('Type: @type<br />ID: @id', array(
      '@type' => _uc_authorizenet_txn_type($response[11]),
      '@id' => $response[6],
    ));
    $result = array(
      'success' => TRUE,
      'comment' => $message,
      'message' => $message,
      'data' => array(
        'module' => 'uc_authorizenet',
        'txn_type' => $response[11],
        'txn_id' => $response[6],
      ),
      'uid' => $user->uid,
    );

    // If this was an authorization only transaction...
    if ($data['txn_type'] == UC_CREDIT_AUTH_ONLY) {

      // Log the authorization to the order.
      uc_credit_log_authorization($order->order_id, $response[6], $amount);
    }
    elseif ($data['txn_type'] == UC_CREDIT_PRIOR_AUTH_CAPTURE) {
      uc_credit_log_prior_auth_capture($order->order_id, $data['auth_id']);
    }

    // Create a transaction reference if specified in the payment gateway
    // settings and this is an appropriate transaction type.
    if (variable_get('uc_authnet_cim_profile', FALSE) && in_array($data['txn_type'], array(
      UC_CREDIT_AUTH_ONLY,
      UC_CREDIT_AUTH_CAPTURE,
    ))) {

      // Ignore the returned message for now; that will appear in the comments.
      _uc_authorizenet_cim_profile_create($order);
    }
  }

  // Don't log this as a payment money wasn't actually captured.
  if (in_array($data['txn_type'], array(
    UC_CREDIT_AUTH_ONLY,
  ))) {
    $result['log_payment'] = FALSE;
  }

  // Build an admin order comment.
  $comment = t('<b>@type</b><br /><b>@status:</b> @message<br />Amount: @amount<br />AVS response: @avs', array(
    '@type' => _uc_authorizenet_txn_type($response[11]),
    '@status' => $result['success'] ? t('ACCEPTED') : t('REJECTED'),
    '@message' => $response[3],
    '@amount' => uc_currency_format($response[9]),
    '@avs' => _uc_authorizenet_avs($response[5]),
  ));

  // Add the CVV response if enabled.
  if (variable_get('uc_credit_cvv_enabled', TRUE)) {
    $comment .= '<br />' . t('CVV match: @cvv', array(
      '@cvv' => _uc_authorizenet_cvv($response[38]),
    ));
  }

  // Save the comment to the order.
  uc_order_comment_save($order->order_id, $user->uid, $comment, 'admin');
  return $result;
}

// Receives a payment notification and handles it appropriately.
function uc_authorizenet_silent_post() {
  if (variable_get('uc_authnet_report_arb_post', FALSE)) {
    watchdog('uc_authorizenet', t('!arbSilent POST received: <pre>@post</pre>', array(
      '!arb' => isset($_POST['x_subscription_id']) && isset($_POST['x_subscription_paynum']) ? 'ARB ' : '',
      '@post' => print_r($_POST, TRUE),
    )));
  }

  // Decrypt the Auth.Net API login data.
  $login_data = _uc_authorizenet_login_data();

  // TODO: Modify the MD5 hash to accommodate differences from AIM to ARB.
  // If we're receiving notification for an ARB payment...
  if (isset($_POST['x_subscription_id']) && isset($_POST['x_subscription_paynum'])) {

    // Compare our expected MD5 Hash against what was received.
    $md5 = strtoupper(md5($login_data['md5_hash'] . $_POST['x_trans_id'] . $_POST['x_amount']));

    // Post an error message if the MD5 hash does not validate.
    if ($_POST['x_MD5_Hash'] != $md5) {
      watchdog('uc_authorizenet', t('Invalid ARB payment notification received.'), WATCHDOG_ERROR);
    }
    else {

      // Otherwise, update the recurring fee information in our database.
      $fee = db_fetch_array(db_query("SELECT * FROM {uc_recurring_users} WHERE fee_handler = 'uc_authorizenet' AND data = '%s'", $_POST['x_subscription_id']));

      // Only process if the fee actually exists!
      if (!empty($fee)) {

        // Update the interval counters.
        $fee['remaining_intervals'] -= 1;
        $fee['charged_intervals'] += 1;

        // Set the next expected charge time.
        $fee['next_charge'] = strtotime('+' . $fee['regular_interval'], $fee['next_charge']);

        // Save the new values.
        uc_recurring_fee_save('user', $fee);

        // Log the ARB payment if enabled.
        if (variable_get('uc_authnet_report_arb_post', FALSE)) {
          watchdog('uc_authorizenet', t('ARB payment reported for order @order_id: <pre>@post</pre>', array(
            '@order_id' => $fee['order_id'],
            '@post' => print_r($_POST, TRUE),
          )));
        }

        // Let other modules act on the data.
        module_invoke_all('uc_arb_payment', $fee);
      }
    }
  }
  exit;
}

/**
 * Implementation of hook_recurring_fee().
 */
function uc_authorizenet_recurring_fee($order, $fee) {

  // Don't process the fee if ARB is disabled in the gateway settings.
  if (variable_get('uc_authnet_arb_mode', 'disabled') == 'disabled') {
    return FALSE;
  }
  return uc_authorizenet_arb_create($order, $fee);
}

/**
 * Implementation of hook_recurring_fee_ops().
 */
function uc_authorizenet_recurring_fee_ops($context, $fee) {
  $ops = array();
  switch ($context) {
    case 'fee_admin':
      if ($fee['remaining_intervals'] > 0) {
        $ops[] = l(t('update'), 'admin/store/orders/recurring/' . $fee['rfid'] . '/arb-update');
        $ops[] = l(t('cancel'), 'admin/store/orders/recurring/' . $fee['rfid'] . '/arb-cancel');
      }
      else {
        $ops[] = l(t('delete'), 'admin/store/orders/recurring/' . $fee['rfid'] . '/delete');
      }
      break;
    case 'user':
      $ops[] = l(t('update'), 'user/' . $fee['uid'] . '/recurring/' . $fee['rfid'] . '/arb-update');
      $ops[] = l(t('cancel'), 'user/' . $fee['uid'] . '/recurring/' . $fee['rfid'] . '/arb-cancel');
      break;
  }
  return $ops;
}

/**
 * Sends an XML API Request to Authorize.Net.
 *
 * @param $server
 *   The name of the server to send a request to - 'production' or 'developer'.
 * @param $xml
 *   The XML to send to Authorize.Net.
 * @param $callback
 *   The name of the function that should process the response.
 * @return
 *   TRUE or FALSE indicating the success of the API request.
 */
function uc_authorizenet_xml_api($server, $xml) {

  // Check for cURL support.
  if (!_uc_authorizenet_curl_check()) {
    return FALSE;
  }
  if ($server == 'production') {
    $post_url = 'https://api.authorize.net/xml/v1/request.api';
  }
  elseif ($server == 'developer') {
    $post_url = 'https://apitest.authorize.net/xml/v1/request.api';
  }
  else {
    return FALSE;
  }
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $post_url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    "Content-Type: text/xml",
  ));
  curl_setopt($ch, CURLOPT_HEADER, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  $response = curl_exec($ch);

  // Log any errors to the watchdog.
  if ($error = curl_error($ch)) {
    watchdog('uc_authorizenet', t('cURL error: @error', array(
      '@error' => $error,
    )), WATCHDOG_ERROR);
    return FALSE;
  }
  curl_close($ch);
  return $response;
}

/**
 * Sends an ARB Create request via the XML API.
 *
 * @param $order
 *   The order object containing billing and shipping information.
 * @param $fee
 *   An array of data describing the recurring fee.
 * @return
 *   TRUE or FALSE indicating the success of the request.
 */
function uc_authorizenet_arb_create($order, $fee) {
  $server = variable_get('uc_authnet_arb_mode', 'disabled');

  // Setup variables for the payment schedule.
  list($length, $unit) = explode(' ', $fee->regular_interval);
  list($trial_length, $trial_unit) = explode(' ', $fee->initial_charge);

  // Convert weeks and years to days.
  if ($unit == 'weeks') {
    $length *= 7;
    $unit = 'days';
  }
  elseif ($unit == 'years') {
    $length *= 365;
    $unit = 'days';
  }

  // Get a default SKU if none was supplied.
  if (empty($fee->model)) {
    $fee->model = db_result(db_query("SELECT model FROM {uc_products} WHERE nid = %d", $fee->nid));
  }

  // Make sure we have valid values for Authorize.Net.
  if ($length <= 0 || $unit == 'days' && $length > 365 || $unit == 'months' && $length > 12) {
    watchdog('uc_authorizenet', t('Product @sku has invalid interval settings for Authorize.Net - @length @unit', array(
      '@sku' => $fee->model,
      '@length' => $length,
      '@unit' => $unit,
    )), WATCHDOG_ERROR);
    return FALSE;
  }

  // Get the country data for the billing and shipping information.
  $billing_country = uc_get_country_data(array(
    'country_id' => $order->billing_country,
  ));
  $delivery_country = uc_get_country_data(array(
    'country_id' => $order->delivery_country,
  ));

  // Build the data array for the request.
  $data = array(
    'refId' => substr($order->order_id . '-' . time(), 0, 20),
    'subscription' => array(
      'name' => substr(t('Order @order_id', array(
        '@order_id' => $order->order_id,
      )), 0, 50),
      'paymentSchedule' => array(
        'interval' => array(
          'length' => $length,
          'unit' => $unit,
        ),
        'startDate' => date('Y-m-d', strtotime('+ ' . $fee->initial_charge)),
        'totalOccurrences' => $fee->number_intervals,
        'trialOccurrences' => '0',
      ),
      'amount' => round($fee->fee_amount, 2),
      'trialAmount' => 0,
      'payment' => array(),
      // Data inserted below based on payment method.
      'order' => array(
        'invoiceNumber' => substr($order->order_id, 0, 20),
        'description' => substr(t('Order @order_id - @sku', array(
          '@order_id' => $order->order_id,
          '@sku' => $fee->model,
        )), 0, 255),
      ),
      'customer' => array(
        'id' => substr($order->uid, 0, 20),
        'email' => substr($order->primary_email, 0, 255),
        'phoneNumber' => substr($order->billing_phone, 0, 25),
      ),
      'billTo' => array(
        'firstName' => substr($order->billing_first_name, 0, 50),
        'lastName' => substr($order->billing_last_name, 0, 50),
        'company' => substr($order->billing_company, 0, 50),
        'address' => substr($order->billing_street1, 0, 60),
        'city' => substr($order->billing_city, 0, 40),
        'state' => substr(uc_get_zone_code($order->billing_zone), 0, 2),
        'zip' => substr($order->billing_postal_code, 0, 20),
        'country' => !$billing_country ? '' : $billing_country[0]['country_iso_code_2'],
      ),
      'shipTo' => array(
        'firstName' => substr($order->delivery_first_name, 0, 50),
        'lastName' => substr($order->delivery_last_name, 0, 50),
        'company' => substr($order->delivery_company, 0, 50),
        'address' => substr($order->delivery_street1, 0, 60),
        'city' => substr($order->delivery_city, 0, 40),
        'state' => substr(uc_get_zone_code($order->delivery_zone), 0, 2),
        'zip' => substr($order->delivery_postal_code, 0, 20),
        'country' => !$delivery_country ? '' : $delivery_country[0]['country_iso_code_2'],
      ),
    ),
  );

  // Strip out the shipping info if it isn't necessary.
  if (empty($data['subscription']['shipTo']['firstName'])) {
    unset($data['subscription']['shipTo']);
  }

  // Add the payment information to the data array based on the payment method.
  if ($order->payment_method == 'credit') {
    if ($order->payment_details['cc_exp_month'] < 10) {
      $order->payment_details['cc_exp_month'] = '0' . $order->payment_details['cc_exp_month'];
    }
    $data['subscription']['payment'] = array(
      'creditCard' => array(
        'cardNumber' => $order->payment_details['cc_number'],
        'expirationDate' => $order->payment_details['cc_exp_year'] . '-' . $order->payment_details['cc_exp_month'],
      ),
    );
  }

  // Build the XML string.
  $xml = _uc_authorizenet_xml_api_wrapper('ARBCreateSubscriptionRequest', _uc_authorizenet_array_to_xml($data));

  // Send the request off to the server and get the response.
  $response = uc_authorizenet_xml_api($server, $xml);

  // Fail if the response is empty or FALSE.
  if (!$response) {
    return FALSE;
  }

  // Parse the response into a data array.
  $data = _uc_authorizenet_arb_parse_response($response);
  if ($data['resultCode'] == 'Error') {
    uc_order_comment_save($order->order_id, 0, t('Authorize.Net: Recurring fee for @model failed.<br />@error - @text', array(
      '@model' => $fee->model,
      '@error' => $data['code'],
      '@text' => $data['text'],
    )), 'admin');
    return FALSE;
  }
  $user_fee = array(
    'rfid' => db_next_id('{uc_product_users}_rfid'),
    'uid' => $order->uid,
    'fee_handler' => 'uc_authorizenet',
    'next_charge' => strtotime('+' . $fee->initial_charge),
    'fee_amount' => $fee->fee_amount,
    'regular_interval' => $fee->regular_interval,
    'remaining_intervals' => $fee->number_intervals,
    'charged_intervals' => 0,
    'order_id' => $order->order_id,
    'data' => $data['subscriptionId'],
  );
  uc_recurring_fee_save('user', $user_fee);
  uc_order_comment_save($order->order_id, 0, t('Authorize.Net: Recurring fee setup for @model.<br />Subscription ID: @subscription_id', array(
    '@model' => $fee->model,
    '@subscription_id' => $data['subscriptionId'],
  )), 'admin');
  return TRUE;
}

/**
 * Updates an ARB subscription; for simplicity's sake, payment schedule
 *   information cannot be updated at this time.
 *
 * @param $subscription_id
 *   The ID of the subscription at Authorize.Net.
 * @param $updates
 *   An array of data to update using key/value pairs from the XML API for ARB;
 *     keys should be children of the subscription element in the XML.
 *   See the ARB_guide.pdf from Authorize.Net for ARBCreateSubscriptionRequests.
 * @return
 *   TRUE or FALSE indicating the success of the cancellation.
 */
function uc_authorizenet_arb_update($subscription_id, $updates, $order_id = NULL) {
  $server = variable_get('uc_authnet_arb_mode', 'disabled');
  unset($update['paymentSchedule']);

  // Build the data array for the request.
  $data = array(
    'refId' => substr($order->order_id . '-' . time(), 0, 20),
    'subscriptionId' => $subscription_id,
    'subscription' => $updates,
  );

  // Build the XML string.
  $xml = _uc_authorizenet_xml_api_wrapper('ARBUpdateSubscriptionRequest', _uc_authorizenet_array_to_xml($data));

  // Send the request off to the server and get the response.
  $response = uc_authorizenet_xml_api($server, $xml);

  // Fail if the response is empty or FALSE.
  if (!$response) {
    return FALSE;
  }

  // Parse the response into a data array.
  $data = _uc_authorizenet_arb_parse_response($response);
  if ($data['resultCode'] == 'Error') {
    if (!empty($order_id)) {
      uc_order_comment_save($order_id, 0, t('Authorize.Net: Subscription @subscription_id updated failed.<br />@error - @text', array(
        '@subscription_id' => $subscription_id,
        '@error' => $data['code'],
        '@text' => $data['text'],
      )), 'admin');
    }
    return FALSE;
  }
  uc_order_comment_save($order_id, 0, t('Authorize.Net: Subscription @subscription_id updated.', array(
    '@subscription_id' => $subscription_id,
  )), 'admin');
  return TRUE;
}

// Displays a form to update a subscriptions's CC info.
function uc_authorizenet_arb_admin_update_form($rfid) {
  $form = array();
  $fee = uc_recurring_fee_load('user', $rfid);
  $form['rfid'] = array(
    '#type' => 'value',
    '#value' => $rfid,
  );
  $form['description'] = array(
    '#value' => '<div>' . t('Subscription ID: @subscription_id', array(
      '@subscription_id' => $fee['data'],
    )) . '</div>',
  );
  $form['cc_data'] = array(
    '#type' => 'fieldset',
    '#title' => t('Credit card details'),
    '#theme' => 'uc_payment_method_credit_form',
    '#tree' => TRUE,
  );
  $form['cc_data'] += uc_payment_method_credit_form($order);
  unset($form['cc_data']['cc_policy']);
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Update'),
    '#suffix' => l(t('Cancel'), 'admin/store/orders/recurring'),
  );
  return $form;
}
function uc_authorizenet_arb_admin_update_form_submit($form_id, $form_values) {
  $fee = uc_recurring_fee_load('user', $form_values['rfid']);
  $updates = array(
    'payment' => array(
      'creditCard' => array(
        'cardNumber' => $form_values['cc_data']['cc_number'],
        'expirationDate' => $form_values['cc_data']['cc_exp_year'] . '-' . $form_values['cc_data']['cc_exp_month'],
      ),
    ),
  );
  $result = uc_authorizenet_arb_update($fee['data'], $updates, $fee['order_id']);

  // If the update was successful...
  if ($result) {
    drupal_set_message(t('Subscription data updated at Authorize.Net.'));
  }
  else {
    drupal_set_message(t('Subscription update failed. See order admin comments for more details.'), 'error');
  }
  return 'admin/store/orders/recurring';
}

// Displays a form for customers to update their CC info.
function uc_authorizenet_arb_user_update_form($uid, $rfid) {
  $form = array();
  $fee = uc_recurring_fee_load('user', $rfid);
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $uid,
  );
  $form['rfid'] = array(
    '#type' => 'value',
    '#value' => $rfid,
  );
  $form['description'] = array(
    '#value' => '<div>' . t('Recurring fee order ID: @order_id', array(
      '@order_id' => $fee['order_id'],
    )) . '</div>',
  );
  $form['cc_data'] = array(
    '#type' => 'fieldset',
    '#title' => t('Credit card details'),
    '#theme' => 'uc_payment_method_credit_form',
    '#tree' => TRUE,
  );
  $form['cc_data'] += uc_payment_method_credit_form($order);
  unset($form['cc_data']['cc_policy']);
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Update'),
    '#suffix' => l(t('Cancel'), 'user/' . $uid),
  );
  return $form;
}
function uc_authorizenet_arb_user_update_form_submit($form_id, $form_values) {
  $fee = uc_recurring_fee_load('user', $form_values['rfid']);
  $updates = array(
    'payment' => array(
      'creditCard' => array(
        'cardNumber' => $form_values['cc_data']['cc_number'],
        'expirationDate' => $form_values['cc_data']['cc_exp_year'] . '-' . $form_values['cc_data']['cc_exp_month'],
      ),
    ),
  );
  $result = uc_authorizenet_arb_update($fee['data'], $updates, $fee['order_id']);

  // If the update was successful...
  if ($result) {
    drupal_set_message(t('The payment details for that recurring fee have been updated.'));
  }
  else {
    drupal_set_message(t('An error has occurred while updating your payment details. Please try again and contact us if you are unable to perform the update.'), 'error');
  }
  return 'user/' . $form_values['uid'];
}

/**
 * Cancels an ARB subscription.
 *
 * @param $subscription
 *   The ID of the subscription at Authorize.Net.
 * @param $order_id
 *   Optional. The ID of the order the recurring fee was attached to.
 * @param $fee
 *   Optional. The data array for the recurring fee being canceled.
 * @return
 *   TRUE or FALSE indicating the success of the cancellation.
 */
function uc_authorizenet_arb_cancel($subscription_id, $order_id = NULL, $fee = array()) {
  $server = variable_get('uc_authnet_arb_mode', 'disabled');

  // Build the data array for the request.
  $data = array(
    'refId' => substr($order->order_id . '-' . time(), 0, 20),
    'subscriptionId' => $subscription_id,
  );

  // Build the XML string.
  $xml = _uc_authorizenet_xml_api_wrapper('ARBCancelSubscriptionRequest', _uc_authorizenet_array_to_xml($data));

  // Send the request off to the server and get the response.
  $response = uc_authorizenet_xml_api($server, $xml);

  // Fail if the response is empty or FALSE.
  if (!$response) {
    return FALSE;
  }

  // Parse the response into a data array.
  $data = _uc_authorizenet_arb_parse_response($response);
  if ($data['resultCode'] == 'Error') {
    if (!empty($order_id)) {
      uc_order_comment_save($order_id, 0, t('Authorize.Net: Subscription @subscription_id cancellation failed.<br />@error - @text', array(
        '@subscription_id' => $subscription_id,
        '@error' => $data['code'],
        '@text' => $data['text'],
      )), 'admin');
    }
    return FALSE;
  }
  uc_order_comment_save($order_id, 0, t('Authorize.Net: Subscription @subscription_id cancelled.', array(
    '@subscription_id' => $subscription_id,
  )), 'admin');

  // Let other modules act on the canceled fee.
  if (!empty($fee)) {
    module_invoke_all('uc_arb_cancel', $fee);
  }
  return TRUE;
}

// Displays a confirm form for cancelling a subscription.
function uc_authorizenet_arb_admin_cancel_form($rfid) {
  $form['rfid'] = array(
    '#type' => 'value',
    '#value' => $rfid,
  );
  return confirm_form($form, t('Are you sure you wish to cancel this subscription?'), 'admin/store/orders/recurring', NULL, t('Confirm'), t('Cancel'));
}
function uc_authorizenet_arb_admin_cancel_form_submit($form_id, $form_values) {
  $fee = uc_recurring_fee_load('user', $form_values['rfid']);
  $result = uc_authorizenet_arb_cancel($fee['data'], $fee['order_id'], $fee);

  // If the cancellation was successful...
  if ($result) {
    drupal_set_message(t('Subscription cancelled through Authorize.Net.'));

    // Set the fee's recurring charges to 0.
    uc_recurring_fee_cancel($fee['rfid']);
  }
  else {
    drupal_set_message(t('Subscription cancellation failed. See order admin comments for more details.'), 'error');
  }
  return 'admin/store/orders/recurring';
}

// Displays a confirm form for customers to cancel their fees.
function uc_authorizenet_arb_user_cancel_form($uid, $rfid) {
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $uid,
  );
  $form['rfid'] = array(
    '#type' => 'value',
    '#value' => $rfid,
  );
  return confirm_form($form, t('Are you sure you wish to cancel this fee?'), 'user/' . $uid, t('This action cannot be undone and may result in the termination of subscription services.'), t('Confirm'), t('Cancel'));
}
function uc_authorizenet_arb_user_cancel_form_submit($form_id, $form_values) {
  $fee = uc_recurring_fee_load('user', $form_values['rfid']);
  $result = uc_authorizenet_arb_cancel($fee['data'], $fee['order_id'], $fee);

  // If the cancellation was successful...
  if ($result) {
    drupal_set_message(t('The recurring fee has been canceled.'));

    // Set the fee's recurring charges to 0.
    uc_recurring_fee_cancel($fee['rfid']);
  }
  else {
    drupal_set_message(t('An error has occurred. Please try again and contact us if the problem persists.'), 'error');
  }
  return 'user/' . $uid;
}

/**
 * Wraps XML API request child elements in the request element and includes the
 *   merchant authentication information.
 */
function _uc_authorizenet_xml_api_wrapper($request, $xml) {
  return '<?xml version="1.0" encoding="utf-8"?><' . $request . ' xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"><merchantAuthentication>' . '<name>' . variable_get('uc_authnet_api_login_id', '') . '</name>' . '<transactionKey>' . variable_get('uc_authnet_api_transaction_key', '') . '</transactionKey></merchantAuthentication>' . $xml . '</' . $request . '>';
}

// Converts a hierarchical array of elements into an XML string.
function _uc_authorizenet_array_to_xml($data) {
  $xml = '';

  // Loop through the elements in the data array.
  foreach ($data as $element => $contents) {
    if (is_array($contents)) {

      // Render the element with its child elements.
      $xml .= '<' . $element . '>' . _uc_authorizenet_array_to_xml($contents) . '</' . $element . '>';
    }
    else {

      // Render the element with its contents.
      $xml .= '<' . $element . '>' . htmlspecialchars($contents) . '</' . $element . '>';
    }
  }
  return $xml;
}

// Returns the message text for an AVS response code.
function _uc_authorizenet_avs($code) {
  $text = $code . ' - ';
  switch ($code) {
    case 'A':
      $text .= t('Address (Street) matches, ZIP does not');
      break;
    case 'B':
      $text .= t('Address information not provided for AVS check');
      break;
    case 'E':
      $text .= t('AVS error');
      break;
    case 'G':
      $text .= t('Non-U.S. Card Issuing Bank');
      break;
    case 'N':
      $text .= t('No Match on Address (Street) or ZIP');
      break;
    case 'P':
      $text .= t('AVS not applicable for this transaction');
      break;
    case 'R':
      $text .= t('Retry – System unavailable or timed out');
      break;
    case 'S':
      $text .= t('Service not supported by issuer');
      break;
    case 'U':
      $text .= t('Address information is unavailable');
      break;
    case 'W':
      $text .= t('Nine digit ZIP matches, Address (Street) does not');
      break;
    case 'X':
      $text .= t('Address (Street) and nine digit ZIP match');
      break;
    case 'Y':
      $text .= t('Address (Street) and five digit ZIP match');
      break;
    case 'Z':
      $text .= t('Five digit ZIP matches, Address (Street) does not');
      break;
  }
  return $text;
}

// Returns the message text for a CVV match.
function _uc_authorizenet_cvv($code) {
  $text = $code . ' - ';
  switch ($code) {
    case 'M':
      $text .= t('Match');
      break;
    case 'N':
      $text .= t('No Match');
      break;
    case 'P':
      $text .= t('Not Processed');
      break;
    case 'S':
      $text .= t('Should have been present');
      break;
    case 'U':
      $text .= t('Issuer unable to process request');
      break;
  }
  return $text;
}

// Returns the title of the transaction type.
function _uc_authorizenet_txn_type($type) {
  switch (strtoupper($type)) {
    case 'AUTH_CAPTURE':
      return t('Authorization and capture');
    case 'AUTH_ONLY':
      return t('Authorization only');
    case 'PRIOR_AUTH_CAPTURE':
      return t('Prior authorization capture');
    case 'CAPTURE_ONLY':
      return t('Capture only');
    case 'CREDIT':
      return t('Credit');
    case 'VOID':
      return t('Void');
  }
}

// Returns the Auth.Net transaction type corresponding to a UC type.
function _uc_authorizenet_txn_map($type) {
  switch ($type) {
    case UC_CREDIT_AUTH_ONLY:
      return 'AUTH_ONLY';
    case UC_CREDIT_PRIOR_AUTH_CAPTURE:
      return 'PRIOR_AUTH_CAPTURE';
    case UC_CREDIT_AUTH_CAPTURE:
      return 'AUTH_CAPTURE';
    case UC_CREDIT_CREDIT:
      return 'CREDIT';
    case UC_CREDIT_VOID:
      return 'VOID';
  }
}

// Checks to see if cURL is enabled on the server.
function _uc_authorizenet_curl_check() {

  // Check to see if a known cURL function exists.
  if (!function_exists('curl_init')) {
    drupal_set_message(t('The Authorize.Net module requires cURL.  Please talk to your system administrator to get this configured.'));
    return FALSE;
  }
  return TRUE;
}

/**
 * Map an order's billing information to an array for later XML conversion.
 */
function _uc_authorize_cim_xml_billto($order) {
  $billing_country = uc_get_country_data(array(
    'country_id' => $order->billing_country,
  ));
  return array(
    'firstName' => substr($order->billing_first_name, 0, 50),
    'lastName' => substr($order->billing_last_name, 0, 50),
    'company' => substr($order->billing_company, 0, 50),
    'address' => substr($order->billing_street1, 0, 60),
    'city' => substr($order->billing_city, 0, 40),
    'state' => substr(uc_get_zone_code($order->billing_zone), 0, 2),
    'zip' => substr($order->billing_postal_code, 0, 20),
    'country' => !$billing_country ? '' : $billing_country[0]['country_iso_code_2'],
  );
}

/**
 * Map an order's shipping information to an array for later XML conversion.
 */
function _uc_authorize_cim_xml_shipto($order) {
  $delivery_country = uc_get_country_data(array(
    'country_id' => $order->delivery_country,
  ));
  return array(
    'firstName' => substr($order->delivery_first_name, 0, 50),
    'lastName' => substr($order->delivery_last_name, 0, 50),
    'company' => substr($order->delivery_company, 0, 50),
    'address' => substr($order->delivery_street1, 0, 60),
    'city' => substr($order->delivery_city, 0, 40),
    'state' => substr(uc_get_zone_code($order->delivery_zone), 0, 2),
    'zip' => substr($order->delivery_postal_code, 0, 20),
    'country' => !$delivery_country ? '' : $delivery_country[0]['country_iso_code_2'],
  );
}

/**
 * Parse an Authorize.Net XML CIM API response.
 */
function _uc_authorizenet_cim_parse_response($content) {

  // Find the elements in the XML and build the return array.
  $data = array(
    'refId' => _uc_authorizenet_substr_between($content, 'refId'),
    'resultCode' => _uc_authorizenet_substr_between($content, 'resultCode'),
    'code' => _uc_authorizenet_substr_between($content, 'code'),
    'text' => _uc_authorizenet_substr_between($content, 'text'),
    'customerProfileId' => _uc_authorizenet_substr_between($content, 'customerProfileId'),
    'directResponse' => _uc_authorizenet_substr_between($content, 'directResponse'),
    'customerPaymentProfileId' => _uc_authorizenet_substr_between($content, 'customerPaymentProfileId'),
    'customerAddressId' => _uc_authorizenet_substr_between($content, 'customerAddressId'),
  );
  return $data;
}

// Parse an Authorize.Net XML API response; from sample PHP for ARB.
function _uc_authorizenet_arb_parse_response($content) {

  // Find the elements in the XML and build the return array.
  $data = array(
    'refId' => _uc_authorizenet_substr_between($content, 'refId'),
    'resultCode' => _uc_authorizenet_substr_between($content, 'resultCode'),
    'code' => _uc_authorizenet_substr_between($content, 'code'),
    'text' => _uc_authorizenet_substr_between($content, 'text'),
    'subscriptionId' => _uc_authorizenet_substr_between($content, 'subscriptionId'),
  );
  return $data;
}

// Helper function for parsing responses; adapted from sample PHP for ARB.
function _uc_authorizenet_substr_between($string, $element) {
  $open = '<' . $element . '>';
  $close = '</' . $element . '>';

  // Fail if we can't find the open or close tag for the element.
  if (strpos($string, $open) === FALSE || strpos($string, $close) === FALSE) {
    return FALSE;
  }
  $start = strpos($string, $open) + strlen($open);
  $end = strpos($string, $close);
  return substr($string, $start, $end - $start);
}

// Decrypts the login data for using Auth.Net APIs.
function _uc_authorizenet_login_data() {
  static $data;
  if (!empty($data)) {
    return $data;
  }
  $md5_hash = variable_get('uc_authnet_md5_hash', '');

  // If CC encryption has been configured properly.
  if ($key = uc_credit_encryption_key()) {

    // Setup our encryption object.
    $crypt = new uc_encryption_class();

    // Decrypt the MD5 Hash.
    if (!empty($md5_hash)) {
      $md5_hash = $crypt
        ->decrypt($key, $md5_hash);
    }

    // Store any errors.
    uc_store_encryption_errors($crypt, 'uc_cybersource');
  }
  $data = array(
    'md5_hash' => $md5_hash,
  );
  return $data;
}

Functions

Namesort descending Description
uc_authorizenet_arb_admin_cancel_form
uc_authorizenet_arb_admin_cancel_form_submit
uc_authorizenet_arb_admin_update_form
uc_authorizenet_arb_admin_update_form_submit
uc_authorizenet_arb_cancel Cancels an ARB subscription.
uc_authorizenet_arb_create Sends an ARB Create request via the XML API.
uc_authorizenet_arb_update Updates an ARB subscription; for simplicity's sake, payment schedule information cannot be updated at this time.
uc_authorizenet_arb_user_cancel_form
uc_authorizenet_arb_user_cancel_form_submit
uc_authorizenet_arb_user_update_form
uc_authorizenet_arb_user_update_form_submit
uc_authorizenet_charge
uc_authorizenet_form_alter Implementation of hook_form_alter().
uc_authorizenet_menu Implementation of hook_menu().
uc_authorizenet_payment_gateway Implementation of hook_payment_gateway().
uc_authorizenet_payment_gateway_settings_submit
uc_authorizenet_recurring_fee Implementation of hook_recurring_fee().
uc_authorizenet_recurring_fee_ops Implementation of hook_recurring_fee_ops().
uc_authorizenet_settings_form Callback for payment gateway settings.
uc_authorizenet_silent_post
uc_authorizenet_xml_api Sends an XML API Request to Authorize.Net.
_uc_authorizenet_arb_parse_response
_uc_authorizenet_array_to_xml
_uc_authorizenet_avs
_uc_authorizenet_charge Handles authorizations and captures through AIM at Authorize.Net
_uc_authorizenet_cim_parse_response Parse an Authorize.Net XML CIM API response.
_uc_authorizenet_cim_payment_profile_get Get a CIM payment profile stored at auth.net.
_uc_authorizenet_cim_profile_charge Use a reference to charge to a CIM profile.
_uc_authorizenet_cim_profile_charge_request Helper for building the request for a CIM profile charge.
_uc_authorizenet_cim_profile_create Create a CIM profile using an order's data.
_uc_authorizenet_cim_profile_create_request Helper to create the CIM profile creation request.
_uc_authorizenet_cim_profile_get Get a CIM profile stored at Authorize.Net.
_uc_authorizenet_curl_check
_uc_authorizenet_cvv
_uc_authorizenet_login_data
_uc_authorizenet_substr_between
_uc_authorizenet_txn_map
_uc_authorizenet_txn_type
_uc_authorizenet_xml_api_wrapper Wraps XML API request child elements in the request element and includes the merchant authentication information.
_uc_authorize_cim_xml_billto Map an order's billing information to an array for later XML conversion.
_uc_authorize_cim_xml_shipto Map an order's shipping information to an array for later XML conversion.