I am in the process of reworking the Linkpoint API for Drupal 5. Im new to drupal and have run into a problem with the module. I keep coming up with Invalid XML. Here is the code:


<?php
// $Id: linkpoint_api.module

define('LINKPOINT_ERROR_SHORT_VIEW', 0);
define('LINKPOINT_ERROR_MEDIUM_VIEW', 1);
define('LINKPOINT_ERROR_LARGE_VIEW', 2);


/********************************************************************
 * Drupal Hooks
 ********************************************************************/

/**
 * Implementation of hook_menu().
 */
function linkpoint_api_menu($may_cache) {

  $items = array();

  if ($may_cache) {
   $items[] = array(
     'path' => 'store/payment/linkpoint_api/form',
     'title' => t('Credit Card Payment'),
	 'callback' => 'drupal_get_form',
	 'callback arguments' => array('linkpoint_api_form'),
     'access' => true,
     'type' => MENU_CALLBACK);
  }
  $items[] = array(
      'path' => 'admin/ecsettings/linkpoint_api',
      'title' => 'Linkpoint API',
      'callback' => 'drupal_get_form',
      'callback arguments' => array('linkpoint_api_ec_settings'),
      'access' => user_access('administer store'),
      'type' => MENU_NORMAL_ITEM,
      'description' => t('Configure Linkpoint API payment gateway'),
    );
  	
  return $items;
}

/**
 * Implementation of hook_help().
 */
function linkpoint_api_help($section = 'admin/help#linkpoint_api') {

  switch ($section) {
    case 'admin/ecsettings/linkpoint_api':
      return t("You need to have a linkpoint merchant account in order to use this module.");

    case 'linkpoint_api/form_submit_guidlines':
      return t("Do not submit this form twice, or you may be double billed!");
  }
}

function linkpoint_api_ec_settings() {

$form = array(
    'linkpoint_api_help' => array(
      '#type' => 'textarea',
      '#title' => t('Explanation or submission guidelines'),
      '#description' => t('This text will be displayed at the top of the credit card submission form.'),
      '#rows' => 5,
      '#required' => TRUE,
      '#default_value' => variable_get('linkpoint_api_help', linkpoint_api_help('linkpoint_api/form_submit_guidlines')),
    ),
    'linkpoint_api_login' => array(
      '#type' => 'textfield',
      '#title' => t('Store ID'),
      '#description' => t("Enter your merchant Store ID."),
      '#required' => TRUE,
      '#maxlength' => 70,
      '#default_value' => variable_get('linkpoint_api_login', ''),
    ),
    'linkpoint_api_ssl_certificate' => array(
      '#type' => 'textfield',
      '#title' => t('SSL Certificate'),
      '#description' => t("Enter your merchant SSL certificate (.pem). It is preferred that you enter full server path (like /home/smb/1234567.pem, not ./1234567.pem) "),
      '#required' => TRUE,
      '#maxlength' => 255,
      '#default_value' => variable_get('linkpoint_api_ssl_certificate', ''),
    ),
    'linkpoint_api_url' => array(
      '#type' => 'textfield',
      '#title' => t('Linkpoint processing URL'),
      '#description' => t('URL of the secure payment processing page, including the port number.'),
      '#required' => TRUE,
      '#maxlength' => 70,
      '#default_value' => variable_get('linkpoint_api_url', 'https://secure.linkpt.net:1129/LSGSXML'),
    ),
    'linkpoint_api_success_url' => array(
      '#type' => 'textfield',
      '#title' => t('Successful payment URL'),
      '#description' => t("This is the destination to which you would like to send your customers when their payment has been successfully completed. The URL must be a Drupal system path (without leading slash), e.g. 'product'. If not set, user will see invoice (recommended)."),
      '#required' => FALSE,
      '#maxlength' => 70,
      '#default_value' => variable_get('linkpoint_api_success_url', ''),
    ),
    'linkpoint_api_debug' => array(
      '#type' => 'radios',
      '#title' => t('Test mode'),
      '#options' => array(t('Disabled'), t('Enabled')),
      '#description' =>  t('If enabled, transactions will be sent in "GOOD" mode - any card numbers will be approved and cards will not be charged. Also, in test mode full connection error description is shown instead of "Sorry - Could not connect to payment gateway."'),
      '#default_value' => variable_get('linkpoint_api_debug', 1),
    ),
    'linkpoint_api_format_not_accepted' => array(
      '#type' => 'radios',
      '#title' => t('How to format error message if card is not accepted'),
      '#options' => array(
        LINKPOINT_ERROR_SHORT_VIEW => t('Simply Accepted, Declined or Duplicate'),
        LINKPOINT_ERROR_MEDIUM_VIEW => t('Linkpoint\'s error message without code (e.g. "The order already exists in the database")'),
        LINKPOINT_ERROR_LARGE_VIEW => t('Full Linkpoint\'s error message (e.g. "SGS-005003: The order already exists in the database.")'),
      ),
      '#default_value' => variable_get('linkpoint_api_format_not_accepted', 0),
    ),
  );

  return system_settings_form($form);
}

/**
 * Implementation of hook_paymentapi().
 */
function linkpoint_api_paymentapi(&$txn, $op) {

  switch ($op) {

    case 'display name':
      return t('Pay with credit card');

    case 'payment page':
      return linkpoint_api_goto($txn);

  }
}

/**
 * Implementation of hook_ec_transactionapi().
 */
function linkpoint_api_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) {
  if ($txn->payment_method != 'linkpoint_api') return NULL;
  
  switch ($op) {
    case 'load':
      $txn->payment = db_fetch_object(db_query("SELECT * FROM {ec_linkpoint_api} WHERE txnid = %d", $txn->txnid));
      break;
    case 'insert':
    case 'update':
      linkpoint_api_save($txn);
      break;
    case 'delete':
      linkpoint_api_delete($txn);
      break;
  }
}


function linkpoint_api_save($txn) {
  if (is_numeric($txn->txnid) && is_numeric($txn->anid)) {

    if (db_result(db_query("SELECT COUNT(txnid) FROM {ec_linkpoint_api} WHERE txnid = '%s'", $txn->txnid))) {
      db_query("UPDATE {ec_linkpoint_api} SET anid = '%s', amount = '%f' WHERE txnid = %d", $txn->anid, $txn->amount, $txn->txnid);
    }
    else {
      db_query("INSERT INTO {ec_linkpoint_api} (txnid, anid, amount) VALUES (%d, '%s', '%f')", $txn->txnid, $txn->anid, $txn->amount);
    }
  }
}

function linkpoint_api_delete($txn) {
  db_query('DELETE FROM {ec_linkpoint_api} WHERE txnid = %d', $txn->txnid);
}

function linkpoint_api_goto($txn) {

  global $base_url;
  $payment_url = str_replace('http://', 'https://', url('store/payment/linkpoint_api/form/'. $txn->txnid, NULL, NULL, TRUE));

  drupal_goto($payment_url);
  exit();
}

/**
 * Controller for collecting and processing credit card data.
 */
function linkpoint_api_page($txnid = null) {

  $output = linkpoint_api_form($txnid);
  print theme('page', $output);

}


/**
 * Build the credit card form.
 */
function linkpoint_api_form($txnid) {
  global $user, $base_url;

  $t = store_transaction_load($txnid);

  // Make sure the user owns the transaction or is an admin.
  if ($user->uid != $t->uid && $user->uid != 1  && !user_access('administer store')) {
    return drupal_access_denied();
  }

  // Make sure the user is connected via SSL
  if (!$_SERVER['HTTPS']) {
    drupal_access_denied();
    return;
  }

  if ($t->items) {
    foreach ($t->items as $p) {
      $product = product_load($p);
      $subtotal += $p->qty * $p->price;
      $items[] = t('%order of <b>%title</b> at %price each', array('%order' => format_plural($p->qty, '1 order', '@count orders'), '%title' => $p->title, '%price' => payment_format($product->price))). "\n";
    }
  }
  
  $form['help'] = array('#value' => t('<div class="help">%linkpoint_api_help</div>', array('%linkpoint_api_help' => variable_get('linkpoint_api_help', linkpoint_api_help('linkpoint_api/form_submit_guidlines')))));

  $form['items'] = array('#value' => theme('item_list', $items, t('Your items')). '</p>');

  // Prepare the values of the form fields.
  $years  = drupal_map_assoc(range(2007, 2021));
  $months = drupal_map_assoc(array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'));

  $form['expiry_date'] = array(
    '#type' => 'fieldset',
    '#title' => t('Expiration Date'),
  );
  $form['expiry_date']['cc_month'] = array(
    '#type' => 'select',
    '#title' => t('Month'),
    '#default_value' => ($month ? $month : date('m')),
    '#options' => $months,
    '#description' => null,
    '#extra' => 0,
    '#multiple' => false,
    '#required' => true,
  );
  $form['expiry_date']['cc_year'] = array(
    '#type' => 'select',
    '#title' => t('Year'),
    '#default_value' => ($year ? $year : date('Y')),
    '#options' => $years,
    '#description' => null,
    '#extra' => 0,
    '#multiple' => false,
    '#required' => true,
  );

  $form['details'] = array(
    '#type' => 'fieldset',
    '#title' => t('Card details'),
  );

  $form['details']['cc_firstname'] = array(
    '#type' => 'textfield',
    '#title' => t('Cardholder\'s first name'),
    '#default_value' => $t->address['billing']->firstname,
    '#size' => 50,
    '#maxlength' => 50,
  );

  $form['details']['cc_lastname'] = array(
    '#type' => 'textfield',
    '#title' => t('Cardholder\'s last name'),
    '#default_value' => $t->address['billing']->lastname,
    '#size' => 50,
    '#maxlength' => 50,
  );

  $form['details']['cc_number'] = array(
    '#type' => 'textfield',
    '#title' => t('Credit Card Number'),
    '#default_value' => '',
    '#size' => 21,
    '#maxlength' => 21,
    '#description' => null,
    '#attributes' => null,
    '#required' => true,
  );

  $form['details']['cc_ccv'] = array(
    '#type' => 'textfield', 
    '#title' => t('CCV Security Code'),
    '#description' => t('Three digit number on back of card'), 
    '#size' => 3, 
    '#maxlength' => 3, 
    '#required' => true, 
  );

  $form['txnid'] = array(
    '#type' => 'value',
    '#value' => $txnid,
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Place your order'),
  );

  $form['#method'] = 'POST';
  $form['#action'] = str_replace('http://', 'https://', url("store/payment/linkpoint_api/form/$txnid", NULL, NULL, TRUE));
  return $form;
}

/**
 * Ensure the integrity of the user-submitted data.
 */
function linkpoint_api_form_validate($form_id, $edit) {
  $errors = array();
  if (!$edit['cc_number']) {
    $errors['cc_number'] = t('You must enter a credit card number.');
  }
  elseif (!is_numeric($edit['cc_number'])) {
    $errors['cc_number'] = t('Error in credit card number. Please make sure it is typed correctly.');
  }

  foreach ($errors as $name => $message) {
    form_set_error($name, $message);
  }

  return count($errors) == 0;
}

/**
 * Send the HTTPS POST request and process the returned data.
 */
function linkpoint_api_form_submit($form_id, $form_values) {
  global $user;
  global $base_url;

  $t = store_transaction_load($form_values['txnid']);

//  include_once './includes/dBug.php';
//  new dBug($t);

  //Make sure the user owns the transaction or is admin.
  if ($user->uid != $t->uid && $user->uid != 1) {
    drupal_access_denied();
  }

  //Make sure the user is connected via SSL
  if (!$_SERVER['HTTPS']) {
    drupal_access_denied();
  }

  $d['configfile'] = variable_get('linkpoint_api_login', '');

  $d['result'] = 'LIVE';
  if (variable_get('linkpoint_api_debug', 1)) {
    $d['result'] = 'GOOD';
  }

  $d['cert'] = variable_get('linkpoint_api_ssl_certificate', '');
  
  $xml ="<order>";
  $xml .="<billing>";
  $xml .="<name>" . $t->address['billing']->firstname . " " . $t->address['billing']->lastname ."</name>";
  $xml .="<address1>" . $t->address['billing']->street1 . "</address1>";
  $xml .="<city>" . $t->address['billing']->city . "</city>";
  $xml .="<state>" . $t->address['billing']->state . "</state>";
  $xml .="<zip>" . $t->address['billing']->zip . "</zip>";
  $xml .="<country>" . store_get_country($t->address['billing']->country) . "</country>";
  $xml .="<email>" . $t->mail . "</email>";
  $xml .="</billing>";
  $xml .="<shipping>";
  $xml .="<name>" . $t->address['shipping']->firstname . " " . $t->address['shipping']->lastname ."</name>";
  $xml .="<address1>" . $t->address['shipping']->street1 . "</address1>";
  $xml .="<city>" . $t->address['shipping']->city . "</city>";
  $xml .="<state>" . $t->address['shipping']->state . "</state>";
  $xml .="<zip>" . $t->address['shipping']->zip . "</zip>";
  $xml .="<country>" . store_get_country($t->address['shipping']->country) . "</country>";
  $xml .="</shipping>";
  $xml .="<orderoptions>";
  $xml .="<result>" . $d['result'] . "</result>";
  $xml .="<ordertype>SALE</ordertype>";
  $xml .="</orderoptions>";
  $xml .="<merchantinfo>";
  $xml .="<configfile>" . $d['configfile'] . "</configfile>";
  $xml .="</merchantinfo>";
  $xml .="<creditcard>";
  $xml .="<cardnumber>" . $form_values['details']['cc_number'] . "</cardnumber>";
  $xml .="<cardexpmonth>" . $form_values['exp_date']['cc_month'] . "</cardexpmonth>";
  $xml .="<cardexpyear>" . substr($form_values['exp_date']['cc_year'],2,2) . "</cardexpyear>";
  $xml .="<cvmvalue>" . $form_values['details']['cc_cvv'] . "</cvmvalue>";
  $xml .="<cvmindicator>provided</cvmindicator>";
  $xml .="</creditcard>";
  $xml .="<payment>";
  $xml .="<chargetotal>" . $t->gross . "</chargetotal>";
  $xml .="</payment>";
  $xml .="<transactiondetails>";
  $xml .="<transactionorigin>ECI</transactionorigin>";
  $xml .="<oid>" . $form_values['txnid'] . "</oid>";
  $xml .="<ip>" . $_SERVER['REMOTE_ADDR'] . "</ip>";
  $xml .="</transactiondetails>";
  $xml .="</order>";


// Start CURL session
  $ch = curl_init();

  curl_setopt($ch, CURLOPT_URL, variable_get('linkpoint_api_url', 'https://secure.linkpt.net:1129/LSGSXML'));
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
  curl_setopt($ch, CURLOPT_SSLCERT, $d['cert']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // this line is used to fix errors on some systems

 $buffer = curl_exec($ch);
  if (curl_errno($ch)) {
    if (variable_get('linkpoint_api_debug', 1)) {
      drupal_set_message(curl_error($ch),'error');
    } else {
      drupal_set_message(t('Sorry - Could not connect to payment gateway.'),'error');
    }
    return str_replace('http://', 'https://', $base_url) . url('../store/payment/linkpoint_api/form/'. $form_values['txnid']);
  } else {
    curl_close($ch);
  }

  preg_match_all ("/<(.*?)>(.*?)\</", $buffer, $outarr, PREG_SET_ORDER);

  $n = 0;
  while (isset($outarr[$n])) {
    $retarr[$outarr[$n][1]] = strip_tags($outarr[$n][0]);
    $n++;
  }

  switch ($retarr['r_approved']) {

    case "APPROVED": // Credit card successfully charged
      if ((!$retarr['r_ordernum']) && (!variable_get('linkpoint_api_debug', 1))) {
        drupal_set_message(t('No ordernumber received from Linkpoint'),'error');
      }
      $t->anid      = $retarr['r_ordernum'];
      $t->amount    = $t->gross;
      $t->payment_status = payment_get_status_id('completed');
      $t->payment_method = 'linkpoint_api';

      unset($form_values['details']['cc_number']); // will prevent from saving cc_number in the case if some module will save entire transaction data

      $is_new = (db_result(db_query('SELECT COUNT(txnid) FROM {ec_linkpoint_api} WHERE txnid = %d', $form_values['txnid']))) ? false : true;

      $txnid = store_transaction_save((array)$t);

      if ($is_new && $txnid) {
        // Compose and send confirmation email to the user
        store_send_invoice_email($txnid);
      }

      drupal_set_message("Thank you ! Your credit card has been accepted. You will receive confirmation by email shortly.");
      $url=trim(variable_get('linkpoint_api_success_url', ''));
      if ($url) {
        // We want to go to a http, not https.
        return str_replace('https://', 'http://', $base_url). '/' . $url;
      } else {
        return str_replace('https://', 'http://', $base_url). '/store/transaction/view/' . $form_values['txnid'];
      }
      break;

    default: // Credit card error: card was not charged.

      switch (variable_get('linkpoint_api_format_not_accepted',0)) {
        case 0:
          $error=$retarr['r_approved'];
          break;
        case 1:
          $error=preg_replace('/^(.*?: )/','',$retarr['r_error']);
          break;
        case 2:
          $error=$retarr['r_error'];
          break;
      }
      drupal_set_message(t('Your card was not billed for the following reason:<br><strong>%linkpoint_api_error</strong>', array('%linkpoint_api_error' => t($error))), 'error');
      return str_replace('http://', 'https://', $base_url) . url('../store/payment/linkpoint_api/form/'. $form_values['txnid']);
      break;
  }
}



If anyone can help, it would be appreciated.

Comments

mimetic2’s picture

did you ever get this working? I also need linkpoint fora client...

bjornarneson’s picture

Did you get this code working for Ecommerce 5.0? I'm interested in using (or helping with) this code for a current client.

Thanks,
Bjorn

alkos’s picture

Also, check your form_value variables, 'cause I think you dont have to use form_value['details'] as prefix.
I have checked generated XML and found out that it doesn contain ccard data.

Good work.

http://www.error1002.com/sgs_errors.php#installments

SGS-020003: Invalid XML

There are a couple different things that will cause this error.

1. Make sure the amount for chargetotal is not blank.
2. Make sure expiration year is only 2 digits
3. Make sure there is no dollar sign for the amount.
4. Make sure there are no symbols like an ampersand, apostrophe, or letters with accents
5. If there is no shipping make sure you pass zero for the amount
6. Make sure there are no commas in the amount for chargetotal

SGS-020003: Invalid XML - invalid tag installments

At the moment you can only set installments from 1-99 or -1 for an infinite amount of times.

webengr’s picture

it is a year later, did you get this to work? if not is the above the last try at it?

How about making it a separate module, ec_linkpoint

And change prefixing from linkpoint_api to ec_linkpoint

Then maybe.... the install could be

<?php
// $Id: ec_linkpoint.install
/**
 * E-Commerce LinkPoint module schema
 */
function ec_linkpoint_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("CREATE TABLE {ec_linkpoint_transaction} (
        txnid int(11) unsigned NOT NULL default '0',
        anid varchar(30) NOT NULL default '0',
        amount decimal(10,2) default '0.00',
        description text,
        PRIMARY KEY (anid)
      ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
      break;
    case 'pgsql':
      db_query("CREATE TABLE {ec_linkpoint_transaction} (
        txnid integer NOT NULL default '0',
        anid varchar(30) NOT NULL default '0',
        amount decimal(10,2) unsigned NOT NULL default '0',
        description longtext,
        PRIMARY KEY (anid)
      )");

      break;
  }

   drupal_set_message(t('E-Commerce: LinkPoint tables have been created.'));
}
function ec_linkpoint_uninstall() {
  // delete the variables we created.
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $deleted = db_query("DROP TABLE IF EXISTS {ec_linkpoint_transaction}");
      break;

    case 'pgsql':
      $deleted = db_query('DROP TABLE {ec_linkpoint_transaction}');
      break;
  }
}

?>

????

webengr’s picture

I dusted off my notes and I am hacking on this.

I have a module that is working.. At least partially - I will upload it somewhere and ask drupal to allow me to post, cvs.

webengr’s picture

Okay It seems to be working, Till I get permission to post as a project here is a page with it for download,
Nov. 17, 2008:

https://www.cocoavillagepublishing.com/development/drupal/e-commerce/lin...

webengr’s picture

OK, it is now a drupal project!

http://drupal.org/project/ec_linkpoint