You are here

function commerce_sagepay_server_handle_callback in Drupal Commerce SagePay Integration 7

Process the callback that is sent by SagePay Server.

Parameters

string $order_id: The order id.

string $key: The security key.

1 string reference to 'commerce_sagepay_server_handle_callback'
commerce_sagepay_menu in ./commerce_sagepay.module
Implements hook_menu().

File

includes/commerce_sagepay_server.inc, line 234

Code

function commerce_sagepay_server_handle_callback($order_id, $key, $debug_vps = array()) {
  $notification = array();
  $order = commerce_order_load($order_id);
  $payment_key = $order->data['payment_redirect_key'];

  // Check key against supplied value.
  if (!$payment_key === $key) {
    $notification['status'] = 'INVALID';
    $notification['message'] = t('Payment Redirect key did not match.');
  }

  // The function gives us the ability to load in a fake POST dataset for
  // testing purposes. If this is not present, use the $_POST values.
  if (empty($debug_vps)) {
    $vps_data = $_POST;
  }
  if (empty($vps_data)) {
    $notification['status'] = 'ERROR';
    $notification['message'] = t('No Payload returned in the notification POST.');
    watchdog('commerce_sagepay', 'VPS Callback URL accessed with no POST data
    submitted.', array(), WATCHDOG_WARNING);
  }
  if (empty($notification)) {

    // Load transactions with a matching order id, remote transaction id
    // and the status "Started".
    // We need to load the original transaction to identify the charge total.
    $conditions = array(
      'order_id' => $order_id,
      'remote_id' => $vps_data['VPSTxId'],
      'remote_status' => 'STARTED',
    );
    $transactions = commerce_payment_transaction_load_multiple(array(), $conditions);

    // We expect a transaction to be found, so fail if there isn't one.
    if (empty($transactions)) {
      $notification['status'] = 'INVALID';
      $notification['message'] = t('No matching transaction found');
      watchdog('commerce_sagepay', 'No Matching transaction found in Sage
      Pay Server VPS Callback for order %order_id', array(
        '%order_id' => $order_id,
      ), WATCHDOG_ERROR);
    }

    // We expect only ONE transaction to be found, so fail if there are more.
    if (count($transactions) > 1) {
      $notification['status'] = 'INVALID';
      $notification['message'] = t('Multiple matching transaction found');
      watchdog('commerce_sagepay', 'Multiple matching transaction found in
      Sage Pay Server VPS Callback for order %order_id', array(
        '%order_id' => $order_id,
      ), WATCHDOG_ERROR);
    }

    // Verify the transaction.
    $transaction_values = array_values($transactions);
    $transaction = $transaction_values[0];

    // Check we have the correct transaction.
    $payment_method = $transaction->payment_method;
    if ($transaction->payload['VendorTxCode'] == $vps_data["VendorTxCode"]) {

      // Get the total and currency from the original transaction.
      $charge = array(
        'amount' => $transaction->amount,
        'currency_code' => $transaction->currency_code,
      );

      // get the vendor ID
      $vendor_name = variable_get(SAGEPAY_SETTING_VENDOR_NAME);
      if (isset($order->data['sagepay_overrides']['Vendor'])) {
        $vendor_name = $order->data['sagepay_overrides']['Vendor'];
      }

      // Vendor name always needs to be lowercase.
      $vendor_name = drupal_strtolower($vendor_name);

      // Check for tampering.
      $md5_check = array();
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["VPSTxId"]) ? $_REQUEST["VPSTxId"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["VendorTxCode"]) ? $_REQUEST["VendorTxCode"] : '', "VendorTxCode");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["Status"]) ? $_REQUEST["Status"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["TxAuthNo"]) ? $_REQUEST["TxAuthNo"] : '', "Number");
      $md5_check[] = $vendor_name;
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["AVSCV2"]) ? $_REQUEST["AVSCV2"] : '', "Text");
      $md5_check[] = $transaction->payload['SecurityKey'];
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["AddressResult"]) ? $_REQUEST["AddressResult"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["PostCodeResult"]) ? $_REQUEST["PostCodeResult"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["CV2Result"]) ? $_REQUEST["CV2Result"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["GiftAid"]) ? $_REQUEST["GiftAid"] : '', "Number");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["3DSecureStatus"]) ? $_REQUEST["3DSecureStatus"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["CAVV"]) ? $_REQUEST["CAVV"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["AddressStatus"]) ? $_REQUEST["AddressStatus"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["PayerStatus"]) ? $_REQUEST['PayerStatus'] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["CardType"]) ? $_REQUEST["CardType"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["Last4Digits"]) ? $_REQUEST["Last4Digits"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["DeclineCode"]) ? $_REQUEST["DeclineCode"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["ExpiryDate"]) ? $_REQUEST["ExpiryDate"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["FraudResponse"]) ? $_REQUEST["FraudResponse"] : '', "Text");
      $md5_check[] = _commerce_sagepay_clean_input(isset($_REQUEST["BankAuthCode"]) ? $_REQUEST["BankAuthCode"] : '', "Text");
      $str_vpssignature = _commerce_sagepay_clean_input(check_plain($_REQUEST["VPSSignature"]), "Text");
      $str_message = implode('', $md5_check);
      $str_mysignature = strtoupper(md5($str_message));
      if ($str_mysignature == $str_vpssignature) {
        $transaction->payload['vps_status'] = $vps_data['Status'];
        switch ($vps_data['Status']) {
          case 'OK':
            watchdog('commerce_sagepay', 'OK Payment callback received from
            SagePay for order %order_id with status code %status', array(
              '%order_id' => $order_id,
              '%status' => $transaction->payload['Status'],
            ));
            commerce_sagepay_transaction($payment_method, $order, $charge, $vps_data, COMMERCE_PAYMENT_STATUS_SUCCESS, $vps_data['TxType'], $transaction);
            $notification['status'] = 'OK';
            $notification['message'] = t('Transaction notification received.');
            break;
          case 'NOTAUTHED':
            watchdog('commerce_sagepay', 'NOTAUTHED error from SagePay for order %order_id with message %msg', array(
              '%order_id' => $order_id,
              '%msg' => $transaction->payload['StatusDetail'],
            ), WATCHDOG_ALERT);
            commerce_sagepay_transaction($payment_method, $order, $charge, $vps_data, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
            $notification['status'] = 'OK';
            $notification['message'] = $transaction->payload['StatusDetail'];
            $notification['cancel'] = TRUE;
            break;
          case 'REJECTED':
            watchdog('commerce_sagepay', 'REJECTED error from SagePay for order %order_id with message %msg', array(
              '%order_id' => $order_id,
              '%msg' => $transaction->payload['StatusDetail'],
            ), WATCHDOG_ALERT);
            commerce_sagepay_transaction($payment_method, $order, $charge, $vps_data, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
            $notification['status'] = 'OK';
            $notification['message'] = $transaction->payload['StatusDetail'];
            $notification['cancel'] = TRUE;
            break;
          case 'ABORT':
            watchdog('commerce_sagepay', 'ABORT error from SagePay for order %order_id with message %msg', array(
              '%order_id' => $order_id,
              '%msg' => $transaction->payload['StatusDetail'],
            ), WATCHDOG_ALERT);
            commerce_sagepay_transaction($payment_method, $order, $charge, $vps_data, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
            $notification['status'] = 'OK';
            $notification['message'] = $transaction->payload['StatusDetail'];
            $notification['cancel'] = TRUE;
            break;
          case 'FAIL':
            watchdog('commerce_sagepay', 'FAIL error from SagePay for order %order_id with message %msg', array(
              '%order_id' => $order_id,
              '%msg' => $transaction->payload['StatusDetail'],
            ), WATCHDOG_ERROR);
            commerce_sagepay_transaction($payment_method, $order, $charge, $vps_data, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
            $notification['status'] = 'OK';
            $notification['message'] = $transaction->payload['StatusDetail'];
            $notification['cancel'] = TRUE;
            break;
          default:
            watchdog('commerce_sagepay', 'Unknown error from SagePay for order
            %order_id with message %msg', array(
              '%order_id' => $order_id,
              '%msg' => $transaction->payload['StatusDetail'],
            ), WATCHDOG_ERROR);
            commerce_sagepay_transaction($payment_method, $order, $charge, $vps_data, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
            $notification['status'] = 'OK';
            $notification['message'] = 'Unexpected Status code received: ' . $vps_data['Status'];
            $notification['cancel'] = TRUE;
        }
      }
      else {
        $payload = $transaction->payload;
        $payload['vps_status'] = 'TAMPER';
        commerce_sagepay_transaction($payment_method, $order, array(), $payload, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
        $notification['status'] = 'INVALID';
        $notification['message'] = t('MD5 did not match - signs of tampering
          .');
      }
    }
    else {
      $payload = $transaction->payload;
      $payload['vps_status'] = 'TAMPER';
      commerce_sagepay_transaction($payment_method, $order, array(), $payload, COMMERCE_PAYMENT_STATUS_FAILURE, SAGEPAY_REMOTE_STATUS_FAIL, $transaction);
      $notification['status'] = 'INVALID';
      $notification['message'] = t('Vendor TX code did not match - signs of
      tampering
          .');
    }
  }

  // Send response back to SagePay to indicate we have received and processed.
  $eoln = chr(13) . chr(10);
  if (array_key_exists('cancel', $notification)) {
    $notification['redirect'] = url('checkout/' . $order_id . '/payment/back/' . $payment_key, array(
      'absolute' => TRUE,
    ));
  }
  else {
    $notification['redirect'] = url('checkout/' . $order_id . '/payment/return/' . $payment_key, array(
      'absolute' => TRUE,
    ));
  }
  $return_notification = 'Status=' . $notification['status'] . $eoln . 'RedirectURL=' . $notification['redirect'] . $eoln . 'StatusDetail=' . $notification['message'] . $eoln;
  echo $return_notification;
  exit;
}