You are here

sagepay_token.module in Drupal Commerce SagePay Integration 7

File

modules/sagepay_token/sagepay_token.module
View source
<?php

/**
 * @file
 * sagepay_token.module
 */
module_load_include('inc', 'commerce_sagepay', 'includes/commerce_sagepay_constants');
module_load_include('inc', 'commerce_sagepay', 'includes/commerce_sagepay_common');

/**
 * Implements hook_form_commerce_sagepay_settings_form_alter().
 */
function sagepay_token_form_commerce_sagepay_settings_form_alter(&$form, &$form_state, $form_id) {

  // Add support for Tokens.
  $form['tokens'] = array(
    '#type' => 'fieldset',
    '#title' => t('Token'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['tokens'][SAGEPAY_SETTING_REQUEST_TOKEN] = array(
    '#type' => 'checkbox',
    '#title' => t('Request a token by default when a transaction is created.'),
    '#description' => t('This behaviour can be changed on a per-order basis using <code>hook_sagepay_order_data_alter</code>.'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#default_value' => variable_get(SAGEPAY_SETTING_REQUEST_TOKEN, 0),
  );
}

/**
 * Implements hook_sagepay_order_data_alter().
 *
 * Adds the create token parameter to the order data.
 */
function sagepay_token_sagepay_order_data_alter(&$query) {
  switch ($query['integration_method']) {
    case SAGEPAY_SERVER:
      $query['CreateToken'] = variable_get(SAGEPAY_SETTING_REQUEST_TOKEN, 0);
      break;
    case SAGEPAY_DIRECT:
      $query['CreateToken'] = variable_get(SAGEPAY_SETTING_REQUEST_TOKEN, 0);
      break;
  }
}

/**
 * Implements hook_commerce_payment_method_info_alter().
 *
 * Adds card on file support to direct payment method.
 */
function sagepay_token_commerce_payment_method_info_alter(&$payment_methods) {
  if (array_key_exists('commerce_sagepay_direct', $payment_methods)) {
    $payment_methods['commerce_sagepay_direct']['cardonfile'] = array(
      'update callback' => 'sagepay_token_cardonfile_update',
      'delete callback' => 'sagepay_token_cardonfile_delete',
      'charge callback' => 'sagepay_token_cardonfile_charge',
    );
  }
  if (array_key_exists('commerce_sagepay_server', $payment_methods)) {
    $payment_methods['commerce_sagepay_server']['cardonfile'] = array(
      'update callback' => 'sagepay_token_cardonfile_update',
      'delete callback' => 'sagepay_token_cardonfile_delete',
      'charge callback' => 'sagepay_token_cardonfile_charge',
    );
  }
}

/**
 * Implement support for Commerce Card on File.
 *
 * @param commerce_payment_transaction $transaction
 *  The transaction about to be saved.
 *
 * @return bool
 *  Return TRUE if the transaction is modified ok.
 *  Return FALSE if the transaction does not contain a token in the payload.
 */
function sagepay_token_commerce_payment_transaction_presave($transaction) {
  switch ($transaction->payment_method) {
    case 'commerce_sagepay_server':
    case 'commerce_sagepay_direct':

      // Check if transaction payload contains a token.
      if (!isset($transaction->payload['Token'])) {
        return FALSE;
      }

      // Check if the transaction status is OK so we don't save multiples
      // cards.
      if ($transaction->payload['Status'] != 'OK') {
        return FALSE;
      }
      $save_cardonfile = FALSE;

      // As we have a token, check if the store settings permit us to save it.
      $cardonfile_store = isset($transaction->payload['cardonfile_store']) ? $transaction->payload['cardonfile_store'] : FALSE;
      if (!empty($cardonfile_store)) {

        // card on file selector is present and selected.
        $save_cardonfile = TRUE;
      }
      else {
        if ($cardonfile_store === 0) {

          // card on file selector is present and not selected.
          $save_cardonfile = FALSE;
        }
        else {
          if (variable_get('commerce_cardonfile_storage', '') == 'required') {

            // Card on file settings is set to Required.
            $save_cardonfile = TRUE;
          }
        }
      }
      if (!$save_cardonfile) {
        return;
      }

      // Check Card on file config settings for the a value allowing us to
      // store card on file with no user opt in.
      $order = commerce_order_load($transaction->order_id);
      $uid = $order->uid;
      if ($uid == 0) {
        return FALSE;
      }
      $wrapper = entity_metadata_wrapper('commerce_order', $order);
      $address = $wrapper->commerce_customer_billing->commerce_customer_address
        ->value();
      $card_data = array(
        'uid' => $uid,
        'payment_method' => $transaction->payment_method,
        'instance_id' => $transaction->instance_id,
        'remote_id' => $transaction->payload['Token'],
        'card_type' => $transaction->payload['CardType'],
        'card_number' => $transaction->payload['Last4Digits'],
        'card_name' => $address['first_name'] . ' ' . $address['last_name'],
        'status' => '1',
      );
      $expiry_date = $transaction->payload['ExpiryDate'];
      $expiry_month = substr($expiry_date, 0, 2);
      $expiry_year = '20' . substr($expiry_date, 2, 2);
      $card_data['card_exp_month'] = $expiry_month;
      $card_data['card_exp_year'] = $expiry_year;
      $card_entity = commerce_cardonfile_new($card_data);
      commerce_cardonfile_save($card_entity);
      if ($transaction->payload['cardonfile_instance_default']) {
        commerce_cardonfile_set_default_card($card_entity->card_id);
      }
      return TRUE;
      break;
    default:
      return FALSE;
  }
}

/**
 * Implements hook_cardonfile_update().
 */
function sagepay_token_cardonfile_update($form, $form_state, $payment_method, $card_data) {
  return TRUE;
}

/**
 * Implements hook_cardonfile_delete().
 */
function sagepay_token_cardonfile_delete($form, $form_state, $payment_method, $card_data) {
  $query = array();
  $query['VPSProtocol'] = SAGEPAY_PROTOCOL;
  $query['TxType'] = 'REMOVETOKEN';
  $query['Vendor'] = variable_get(SAGEPAY_SETTING_VENDOR_NAME);
  $query['Token'] = $card_data->remote_id;
  switch (variable_get(SAGEPAY_SETTING_TRANSACTION_MODE)) {
    case SAGEPAY_TXN_MODE_LIVE:
      $url = SAGEPAY_TOKEN_REMOVE_LIVE;
      break;
    case SAGEPAY_TXN_MODE_TEST:
      $url = SAGEPAY_TOKEN_REMOVE_TEST;
      break;
  }
  $query = _commerce_sagepay_array_to_post($query);
  $response = _commerce_sagepay_request_post($url, $query);
  switch ($response['Status']) {
    case 'OK':
      return TRUE;
    case 'MALFORMED':
    case 'INVALID':
      drupal_set_message(t('Error removing saved card. %detail', array(
        '%detail' => $response['StatusDetail'],
      )));
      return FALSE;
  }
  return FALSE;
}

/**
 * Implements Card on File Charge Callback.
 *
 * @param $order object
 *  The order being charged.
 *
 * @param $parent_order object
 *  The parent order from which the recurring order was derived.
 *
 * @return bool
 *  TRUE if the payment was successfully processed.
 */
function sagepay_token_cardonfile_charge($payment_method, $card_data, $order, $charge = NULL) {
  $wrapper = entity_metadata_wrapper('commerce_order', $order);

  // Load customer profile.
  $profile = commerce_customer_profile_load($order->commerce_customer_billing[LANGUAGE_NONE][0]['profile_id']);

  // Get user billing address.
  $billing_address = $profile->commerce_customer_address[LANGUAGE_NONE][0];

  // Get user delivery address.
  $delivery_address = NULL;
  if (isset($order->commerce_customer_shipping)) {
    $delivery_profile = commerce_customer_profile_load($order->commerce_customer_shipping[LANGUAGE_NONE][0]['profile_id']);
    $delivery_address = $delivery_profile->commerce_customer_address[LANGUAGE_NONE][0];
  }
  $pane_values = array();
  $pane_values['cardonfile'] = $card_data->card_id;
  $pane_values['commerce_recurring'] = TRUE;
  $settings = array();
  $query = _commerce_sagepay_encrypted_order($settings, $order, $charge, $billing_address, $delivery_address, SAGEPAY_DIRECT, $pane_values);

  // Turn off 3D Secure and AVS Checks for Card on File charges.
  $query['ApplyAVSCV2'] = '2';
  $query['Apply3DSecure'] = '2';

  // Create a POST to send to SagePay.
  $post = '';
  foreach ($query as $name => $value) {
    $post .= urlencode($name) . '=' . urlencode($value) . '&';
  }

  // Chop off the last &.
  $post = substr($post, 0, -1);

  // Determine the correct url based on the transaction mode.
  switch (variable_get(SAGEPAY_SETTING_TRANSACTION_MODE)) {
    case SAGEPAY_TXN_MODE_LIVE:
      $server_url = SAGEPAY_DIRECT_SERVER_LIVE;
      break;
    case SAGEPAY_TXN_MODE_TEST:
      $server_url = SAGEPAY_DIRECT_SERVER_TEST;
      break;
    case SAGEPAY_TXN_MODE_SHOWPOST:
      $server_url = SAGEPAY_DIRECT_SERVER_SHOWPOST;
      break;
    case SAGEPAY_TXN_MODE_SIMULATION:
      $server_url = SAGEPAY_DIRECT_SERVER_SIMULATION;
      break;
    default:
      $server_url = SAGEPAY_DIRECT_SERVER_SIMULATION;
  }
  drupal_alter('sagepay_url', $server_url, $pane_values);

  // Send post.
  $response = _commerce_sagepay_request_post($server_url, $post);

  // Collect additional data to store in the transaction.
  if (isset($pane_values['credit_card'])) {
    $response['Last4Digits'] = substr($pane_values['credit_card']['number'], 12, 16);
    $response['ExpMonth'] = $pane_values['credit_card']['exp_month'];
    $response['ExpYear'] = $pane_values['credit_card']['exp_year'];
    $response['cardonfile_store'] = isset($pane_values['credit_card']['cardonfile_store']) ? $pane_values['credit_card']['cardonfile_store'] : '0';
    $response['CardType'] = $pane_values['credit_card']['type'];
  }
  $response['Amount'] = $wrapper->commerce_order_total->amount
    ->value();
  $result = commerce_sagepay_process_response($payment_method, $order, $response);
  switch ($response['Status']) {
    case 'NOTAUTHED':
    case 'REJECTED':
    case 'MALFORMED':
    case 'INVALID':
    case 'ERROR':
      $cardonfile_response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_NOT_CHARGEABLE;
      $cardonfile_response['message'] = $response['StatusDetail'];
      break;
    case 'OK':
    case 'AUTHENTICATED':
      $cardonfile_response['code'] = COMMERCE_COF_PROCESS_CODE_METHOD_SUCCESS;
      $cardonfile_response['message'] = $response['StatusDetail'];
      break;
    default:
      $cardonfile_response['code'] = COMMERCE_COF_PROCESS_CODE_CARD_NOT_CHARGEABLE;
      $cardonfile_response['message'] = $response['StatusDetail'];
  }
  return $cardonfile_response;
}

Functions

Namesort descending Description
sagepay_token_cardonfile_charge Implements Card on File Charge Callback.
sagepay_token_cardonfile_delete Implements hook_cardonfile_delete().
sagepay_token_cardonfile_update Implements hook_cardonfile_update().
sagepay_token_commerce_payment_method_info_alter Implements hook_commerce_payment_method_info_alter().
sagepay_token_commerce_payment_transaction_presave Implement support for Commerce Card on File.
sagepay_token_form_commerce_sagepay_settings_form_alter Implements hook_form_commerce_sagepay_settings_form_alter().
sagepay_token_sagepay_order_data_alter Implements hook_sagepay_order_data_alter().