UC_Worldpay module - a few issues.

mtraherne - October 25, 2009 - 18:31
Project:UC Worldpay
Version:5.x-1.x-dev
Component:Code
Category:bug report
Priority:normal
Assigned:mtraherne
Status:needs work
Description

Hi.

I have been trying to get to grips with the uc_worldpay module, but I'm having a few teething issues. I have added a few watchdog log entries, and patched a couple of times with suggestions.

With the code below, I have got the module to complete the order, add admin comments, send out emails and empty the cart however I still have a few related problems.

Note - to use the Worldpay module the user MUST be signed in - so there are no Anonymous users.

The problems are:

Type php
Date Sunday, October 25, 2009 - 18:06
User Anonymous
Location http://***/cart/worldpay/complete?msgType=authResult&installation=*****
Referrer
Message Invalid argument supplied for foreach() in ****/modules/user/user.module on line 53.
Severity error

Type php
Date Sunday, October 25, 2009 - 18:06
User Anonymous
Location http://****/cart/worldpay/complete?msgType=authResult&installation=235456
Referrer
Message You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1 query: SELECT * FROM users u WHERE in ****/includes/database.mysql.inc on line 174.
Severity error

Also: With the code below, the site WILL process a payment, and clear the cart - however there appear to be a few problems. Most notably RBS Worldpay are telling me that the site is not sending any HTML response after calling the /cart/worldpay/complete

using various debugging methods, I have discovered that the module seems to "stop" after calling:

ca_pull_trigger('uc_checkout_complete', $order, $account);

No amount of patching (including those in http://drupal.org/node/487698) are changing this.

Finally - commenting out this code, there is an HTML response. (As expected) However the link back to the site is broken.
(/cart/complete/finish).

<?php
// $Id: uc_worldpay.module,v 1.1.2.11 2008/10/28 16:58:29 psynaptic Exp $

/**
* @file
* Integrates Worldpay's redirected payment service with Ubercart.
*
* Original development sponsored by www.catorg.co.uk.
*/

/**
* Implementation of hook_menu().
*/
function uc_worldpay_menu($may_cache) {
  if ($may_cache) {
    $items[] = array(
      'path' => 'cart/worldpay/complete',
      'title' => t('Order complete'),
      'callback' => 'uc_worldpay_complete',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
    $items['cart/worldpay/finish'] = array(
      'title' => t('Finished order'),
      'page callback' => 'uc_worldpay_finish_page',
      'access arguments' => array('access content'),
      'type' => MENU_CALLBACK,
   );
  }

  return $items;
}

/**
* Implementation of hook_payment_method().
*/
function uc_worldpay_payment_method() { 
  $methods[] = array(
    'id' => 'worldpay',
    'name' => t('Worldpay'),
    'title' => theme('uc_worldpay_cards'),
    'desc' => t('Redirect to Worldpay to pay by credit card or eCheck.'),
    'callback' => 'uc_payment_method_worldpay',
    'weight' => 3,
    'checkout' => TRUE,
    'no_gateway' => TRUE,
  );

  return $methods;
}

function theme_uc_worldpay_cards() {
  $image_path = drupal_get_path('module', 'uc_worldpay') .'/images/';
  $title = variable_get('uc_worldpay_method_title', t('Worldpay'));
  $output = theme('image', $image_path .'worldpay.png', $title .': ', $title, array('style' => 'position:relative; top:6px;'));

  $card_types = variable_get('uc_worldpay_payment_methods', array('visa', 'electron', 'mastercard', 'maestro', 'switch', 'solo'));
  foreach ($card_types as $card => $title) {
    if ($title != "0") {
      $output .= theme('image', $image_path . $card .'.gif', '', '', array('style' => 'position:relative; top:5px; margin-right:4px;'));
    }
  }

  return $output;
}

/**
* Payment method settings.
*/
function uc_payment_method_worldpay($op, &$arg1) {
  switch ($op) {

    case 'cart-process':
      $_SESSION['pay_method'] = $_POST['pay_method'];
      return;

    case 'settings':
      $form['help_text']['worldpay_settings'] = array(
        '#type' => 'item',
        '#prefix' => '<div class="help">',
        '#value' => t('<h4><strong>Installation instructions</strong></h4>
          <p>For this module to work properly you must configure a few specific options in your Worldpay account under <em>Installation Administration</em> settings:</p>
          <ul><li><strong>Payment Response URL</strong> should be set to: %response_url</li>
          <li><strong>Payment Response enabled?</strong> should be <em>enabled</em></li>
          <li><strong>Enable the Shopper Response</strong> should be <em>enabled</em> to get the Ubercart response page (optional)',
          array('%response_url' => url('cart/worldpay/complete', NULL, NULL, TRUE))),
        '#suffix' => '</div>',
      );
      $form['uc_worldpay_sid'] = array(
        '#type' => 'textfield',
        '#title' => t('Installation ID'),
        '#default_value' => variable_get('uc_worldpay_sid', ''),
        '#size' => 16,
      );
      $form['uc_worldpay_debug'] = array(
        '#type' => 'select',
        '#title' => t('Debug mode'),
        '#multiple' => FALSE,
        '#options' => array('log' => t('Log'), 'screen' => t('Screen'), 'both' => t('Both'), 'none' => t('None')),
        '#default_value' => variable_get('uc_worldpay_debug', 'log'),
      );
      $form['uc_worldpay_checkout_button'] = array(
        '#type' => 'textfield',
        '#title' => t('Order review submit button text'),
        '#description' => t('Alter the text of the submit button on the review order page.'),
        '#default_value' => variable_get('uc_worldpay_checkout_button', t('Submit Order')),
      );
      $form['payment_methods'] = array(
        '#type' => 'fieldset',
        '#title' => t('Payment methods'),
        '#description' => t('Select the payment methods to display in checkout.'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $form['payment_methods']['uc_worldpay_payment_methods'] = array(
        '#type' => 'checkboxes',
        '#default_value' => variable_get('uc_worldpay_payment_methods', array('visa', 'electron', 'mastercard', 'maestro', 'switch', 'solo')),
        '#options' => array(
          'visa' => t('Visa'),
          'electron' => t('Visa Electron'),
          'mastercard' => t('Mastercard'),
          'maestro' => t('Maestro'),
          'switch' => t('Switch'),
          'solo' => t('Solo'),
          'amex' => t('Amex'),
          'diners' => t('Diners'),
          'jcb' => t('JCB'),
          'elv' => t('ELV'),
          'laser' => t('Laser'),
        ),
      );
      $form['payment_parameters'] = array(
        '#type' => 'fieldset',
        '#title' => t('Payment parameters'),
        '#description' => t('These options control what parameters are sent to Worldpay when the customer submits the order.'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $form['payment_parameters']['uc_worldpay_test'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable test mode'),
        '#default_value' => variable_get('uc_worldpay_test', TRUE),
      );
      $disabled = (!variable_get('uc_worldpay_test', TRUE)) ? TRUE : FALSE;     
      $form['payment_parameters']['uc_worldpay_test_result'] = array(
        '#type' => 'select',
        '#title' => t('Test mode result'),
        '#description' => t('Specify the required transaction result when working in test mode.'),
        '#default_value' => variable_get('uc_worldpay_test_result', 'AUTHORISED'),
        '#options' => array('AUTHORISED' => 'Authorised', 'REFUSED' => 'Refused', 'ERROR' => 'Error', 'CAPTURED' => 'Captured'),
        '#disabled' => $disabled,
      );
      $form['payment_parameters']['uc_worldpay_desc'] = array(
        '#type' => 'checkbox',
        '#title' => t('Submit the cart contents as the order description'),
        '#description' => t('Setting this option to true will display the cart contents on the payment page. This could help to reassure customers of exactly what they are paying for.'),
        '#default_value' => variable_get('uc_worldpay_desc', FALSE),
      );
      $form['payment_parameters']['uc_worldpay_edit_contact'] = array(
        '#type' => 'checkbox',
        '#title' => t('Enable editing of contact details on the payment page.'),
        '#default_value' => variable_get('uc_worldpay_show_contact', TRUE),
      );
      $form['payment_parameters']['uc_worldpay_show_contact'] = array(
        '#type' => 'checkbox',
        '#title' => t('Show the contact details on the payment page.'),
        '#default_value' => variable_get('uc_worldpay_show_contact', TRUE),
        '#disabled' => variable_get('uc_worldpay_show_contact', TRUE),
      );
      $form['payment_parameters']['uc_worldpay_lang'] = array(
        '#type' => 'textfield',
        '#title' => t('Payment page language'),
        '#description' => t('Specify the payment page language. Enter a 2-character ISO 639 language code, with optional regionalisation using 2-character country code separated by hyphen. For example "en-GB" specifies UK English.'),
        '#size' => 8,
        '#maxlength' => 6,
        '#default_value' => variable_get('uc_worldpay_lang', 'en-GB'),
      );
      return $form;
  }
}

/**
* Implementation of hook_form_alter().
*/
function uc_worldpay_form_alter($form_id, &$form) {
  if ($form_id == 'uc_cart_checkout_review_form' && ($order_id = intval($_SESSION['cart_order'])) > 0) {
    $order = uc_order_load($order_id);

    if ($order->payment_method == 'worldpay') {

      $country_data = uc_get_country_data(array('country_id' => $order->billing_country));
      $country = $country_data[0]['country_iso_code_2'];

      $cart_contents = uc_cart_get_contents();
      foreach ($cart_contents as $item) {
        $cart_items[] = $item->qty .'x '. $item->title;
      }

      $uc_worldpay_name = substr($order->billing_first_name .' '. $order->billing_last_name, 0, 128);

      $data = array();
      if (variable_get('uc_worldpay_test', TRUE)) {
        $uc_worldpay_name = variable_get('uc_worldpay_test_result', 'AUTHORISED');
        $data = array(
          'testMode' => '100',
        );
      }

      $data += array(
        'instId' => variable_get('uc_worldpay_sid', ''),
        'amount' => uc_currency_format($order->order_total, FALSE, FALSE, '.'),
        'cartId' => $order->order_id,
        'currency' =>  variable_get('uc_currency_code', 'USD'),
        'name' =>  $uc_worldpay_name,
        'address' => ($order->billing_street1 ? $order->billing_street1 .',' : "\n")
                   . ($order->billing_street2 ? $order->billing_street2 .',' : "\n")
                   . ($order->billing_city ? $order->billing_city : ''),
        'state' => uc_get_zone_code($order->billing_zone),
        'postcode' => $order->billing_postal_code,
        'country' => $country,
        'email' => $order->primary_email,
        'tel' => $order->billing_phone,
        'M_uc_cart_id' => uc_cart_get_id(),
        'lang' => variable_get('uc_worldpay_lang', 'en-GB'),
        'M_http_host' => $_SERVER['HTTP_HOST'],
      );

      if (variable_get('uc_worldpay_desc', FALSE)) {
        $data += array(
          'desc' => t("Cart contents: \n!cart", array('!cart' => implode(",\n", $cart_items)))
        );
      }

      if (!variable_get('uc_worldpay_edit_contact', TRUE)) {
        $data += array(
          'fixContact' => '',
        );
      }
     
      if (!variable_get('uc_worldpay_show_contact', TRUE)) {
        $data += array(
          'hideContact' => '',
        );
      }

      foreach ($data as $name => $value) {
        $form[$name] = array(
          '#type' => 'hidden',
          '#value' => $value,
        );
      }

      //$test_server = 'https://select-test.worldpay.com/wcc/purchase';
  $test_server = 'https://secure-test.wp3.rbsworldpay.com/wcc/purchase';
      //$live_server = 'https://select.worldpay.com/wcc/purchase';
  $live_server = "https://secure.wp3.rbsworldpay.com/wcc/purchase";

      $form['#action'] = (variable_get('uc_worldpay_test', TRUE)) ? $test_server : $live_server;
      $form['submit'] = array(
        '#type' => 'submit',
        '#name' => '',
        '#value' => variable_get('uc_worldpay_checkout_button', t('Submit Order')),
      );
    }
  }
}

function uc_worldpay_complete($cart_id = 0) {
  $cart_id = $_POST['cartId'];
  $amount = $_POST['amount'];
  $trans_status = $_POST['transStatus'];
  $card_type = $_POST['cardType'];
  $uc_cart_id = $_POST['M_uc_cart_id'];

  // Stop orders being processed for orders from different hosts.
  if ($_SERVER['HTTP_HOST'] != $_POST['M_http_host']) {
      _uc_worldpay_redirect(t('There was an error with the transaction. The host did not match.'));
      return;
  }

  // Log a new order notification to watchdog.
  $log_entry = t('New order notification for order !order_id.', array('!order_id' => $cart_id));
  $message = t('Returned parameters: <pre>!post</pre>', array('!post' => print_r($_POST, TRUE)));

  // If debug mode is set appropriately, append the returned parameters to the log entry.
  $debug = variable_get('uc_worldpay_debug', 'log');
  if ($debug == 'log' || $debug == 'both') {
    $log_entry .= '<br/>'. $message;
  }
  watchdog('uc_worldpay', $log_entry);

  // If debug mode is set appropriately, print the returned parameters to the screen.
  if ($debug == 'screen' || $debug == 'both') {
    $output .= $message;
  }

  // If the order could not be loaded print an error message and exit.
  if (!$order = uc_order_load($cart_id)) {
      _uc_worldpay_redirect(t('The order could not be found and this transaction cannot continue.'));
      return;
  }

  // If the status of the order is not 'in_checkout' print an error and exit.
  if (uc_order_status_data($order->order_status, 'state') != 'in_checkout') {
      _uc_worldpay_redirect(t('An error has occurred during payment.  Please contact us to ensure your order has submitted.'));
      return;
  }

  if (is_numeric($amount)) {
    switch ($trans_status) {
      case 'Y':
        $output .= t('Your order is complete and payment has been confirmed.');
        $comment = t('Paid by !type, Worldpay order #!order.', array('!type' => $card_type , '!order' => $cart_id));
        uc_payment_enter($order->order_id, 'Worldpay', $amount, 0, NULL, $comment);
        break;
      case 'C':
        $output .= t('Your order has been cancelled.');
        //drupal_set_message(t('Your order has been cancelled.'));
        uc_order_comment_save($order->order_id, 0, t('Payment cancelled by user.'), 'admin');
        //print $output;
        //exit();
        //break;
          _uc_worldpay_redirect($output);
          return;
      default:
        //$output .= t('Your order is pending.');
        //drupal_set_message(t('Your order will be processed as soon as your payment clears at Worldpay.'));
          $output .= t('Your order is pending. Your order will be processed as soon as your payment clears at Worldpay.');
        uc_order_comment_save($order_id, 0, t('!type payment is pending approval at Worldpay.', array('!type' => $card_type)), 'admin');
        break;
    }
  }

  //$output .= uc_cart_complete_sale($order);
  //uc_cart_empty($uc_cart_id);
    // Make sure we register the fact the payment happened and pull relevant triggers. Then
    // redirect to the site itself for the well-done message.
    if ($order->uid == 0) {
      // Check for an existing user account with the e-mail address from checkout.
      $result = db_query("SELECT uid FROM {users} WHERE mail = '%s'", $order->primary_email);

      // If it was found, update the order.
      if ($account = db_fetch_object($result)) {
        $order->uid = $account->uid;
        db_query("UPDATE {uc_orders} SET uid = %d WHERE order_id = %d", $order->uid, $order->order_id);
      }
      else {
        // Get a valid new username.
        if (empty($order->data['new_user']['name'])) {
          $name = uc_store_email_to_username($order->primary_email);
        }
        else {
          $name = $order->data['new_user']['name'];
        }
 
        // Setup the account fields array and save it as a new user.
        $fields = array(
          'name' => $name,
          'mail' => $order->primary_email,
          'init' => $order->primary_email,
          'pass' => empty($order->data['new_user']['pass']) ? user_password(variable_get('uc_pwd_length', 6)) : $order->data['new_user']['pass'],
          'roles' => array(),
          'status' => variable_get('uc_new_customer_status_active', TRUE) ? 1 : 0,
        );
        $account = user_save('', $fields);
 
        // Send the customer their account details if enabled.
        if (variable_get('uc_new_customer_email', TRUE)) {
          // Manually set the password so it appears in the e-mail.
          $account->password = $fields['pass'];
 
          // Send the e-mail through the user module.
          drupal_mail('user', 'register_no_approval_required', $order->primary_email, NULL, array('account' => $account), uc_store_email_from());
        }
 
        // Update the order's uid in this request and in the database.
        $order->uid = $account->uid;
        unset($order->data['new_user']['pass']);
        db_query("UPDATE {uc_orders} SET uid = %d, data = '%s' WHERE order_id = %d", $order->uid, serialize($order->data), $order->order_id);
      }
    }
 
    $status = db_result(db_query("SELECT order_status FROM {uc_orders} WHERE order_id = %d", $order->order_id));
    if (uc_order_status_data($status, 'state') == 'in_checkout') {
      uc_order_update_status($order->order_id, uc_order_state_default('post_checkout'));
    }
 
    // Empty that cart...
    uc_cart_empty($order->uid);

// Add a comment to let sales team know this came in through the site.
uc_order_comment_save($order->order_id, 0, t('Order created through website.'), 'admin');
 
    // Pull triggers
    $account = user_load($order->uid);
    module_invoke_all('uc_checkout_complete', $order, $account);
watchdog('uc_worldpay_test', 'pulling uc_checkout_complete');
    ca_pull_trigger('uc_checkout_complete', $order, $account);
 
    //print $output;
    watchdog('uc_worldpay_test', 'call worldpay redirect');
    _uc_worldpay_redirect($output, $cart_id);
    return;
  }
 
  /*
   * This function displays the confirmation page to redirect the user to the website.
   */
  function _uc_worldpay_redirect($txt, $cart_id = 0) {
watchdog('uc_worldpay_redirect', $txt);
    global $base_url;
 
    if ($cart_id == 0) {
      $url = $base_url;
      $message = "<div class='uc_worldpay_error'>".htmlspecialchars($txt)."</div>";
      $message .= t("Please <a href='@url'>click here to return to the site</a>",
                  array('@url' => $url));
    } else {
      $url = $base_url.'/cart/worldpay/finish';
      $message = "<div class='uc_worldpay_message'>".htmlspecialchars($txt)."</div>";
      $message .= t("Your order has been confirmed. Please <a href='@url'>click here to return to the site</a>",
                  array('@url' => $url));
    }
 
    echo theme('uc_worldpay_redirect', $message);
  }
 
  /*
   * Theme the redirection page
   */
  function theme_uc_worldpay_redirect($message) {
    echo "<html><body>".$message."</body></html>";
  }
 
  /*
   * This is the page we get returned to after everything has been confirmed and
   * the user was informed. This page is simply used to do some clean up.
   */
  function uc_worldpay_finish_page() {
    global $base_url;
 
    // Clear our the session variables used to force the cart workflow.
    unset($_SESSION['cart_order'], $_SESSION['do_complete'], $_SESSION['new_user']);
    drupal_goto($base_url);

  exit();
}

#1

mtraherne - October 25, 2009 - 19:11

Ok

I fixed the two watchdog entries by changing the line (around 396 in my code above)

from

$account = user_load($order->uid);

to
$account = user_load(array('uid' => $order->uid));

Still no html message being sent to worldpay, and still "stopping" when trying to pull the trigger (line 399ish)

 
 

Drupal is a registered trademark of Dries Buytaert.