--- ecommerce/contrib/paypalpro/paypalpro.module	2006/05/31 04:53:03  1.13
+++ ecommerce/contrib/paypalpro/paypalpro.module	2006/07/12 01:20:56
@@ -27,15 +27,7 @@ function paypalpro_menu($may_cache) {
   if ($may_cache) {
     $items[] = array(
       'path' => 'paypalpro/form', 'title' => t('Credit card payment'),
-      'callback' => 'paypalpro_page', 'access' => TRUE, 
-      'type' => MENU_CALLBACK);
-    $items[] = array(
-      'path' => 'paypalpro/redirect', 'title' => t('Express checkout redirect'),
-      'callback' => 'paypalpro_express_checkout_redirect', 'access' => TRUE, 
-      'type' => MENU_CALLBACK);
-    $items[] = array(
-      'path' => 'paypalpro/express', 'title' => t('PayPal Express Checkout'),
-      'callback' => 'paypalpro_express_checkout', 'access' => TRUE, 
+      'callback' => 'paypalpro_form', 'access' => TRUE, 
       'type' => MENU_CALLBACK);
   }
 
@@ -43,7 +35,7 @@ function paypalpro_menu($may_cache) {
 }
 
 /**
- * Implementation of Drupal _settings() hook.
+ * Implementation of E-Commerce _settings() hook.
  *
  * @return form   Form used to configure the paypalpro module.
  */
@@ -141,200 +133,302 @@ function paypalpro_ec_settings() {
 }
 
 /**
- * Implementation of Drupal _page() hook.
+ * Implementation of _form() hook.  This form is used to collect credit card
+ * information.
  *
- * @param $txnid  Optional transaction id.
+ * @param $txnid  Transaction id.
+ * @param form    Credit card form.
  */
-function paypalpro_page($txnid = NULL) {
-  $edit = $_POST['edit'];
-  $op = $_POST['op'];
+function paypalpro_form($txnid = NULL) {
+  global $user;
 
-  switch ($op) {
-    case t('Place your order'):
-      if (paypalpro_validate($edit)) {
-        paypalpro_process($edit);
-      }
-      else {
-        $output = paypalpro_form($edit['txnid']);
-      }
-      break;
+  $t = store_transaction_load($txnid);
+  // make sure the current users owns this transaction (or is the site admin)
+  // if configured, require that the user access this page via https://
+  if (($user->uid != $t->uid && $user->uid != 1) ||
+    (variable_get('paypalpro_secure', 1) && !$_SERVER['HTTPS'])) {
+    drupal_access_denied();
+    return; /* make sure no more output is returned */
+  }
 
-    default:
-      $output = paypalpro_form($txnid);
+
+  // prepare the values of the form fields
+  $years  = drupal_map_assoc(range(2004, 2020));
+  $months = drupal_map_assoc(array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'));
+  // array includes credit card images
+  $paypalpro_cc_types = array(
+    '<img alt="Visa" src="https://www.paypal.com/en_US/i/logo/logo_ccVisa.gif" border="0"> '. t('Visa'),
+    '<img alt=" Mastercard" src="https://www.paypal.com/en_US/i/logo/logo_ccMC.gif" border="0"> '. t('MasterCard'),
+    '<img alt=" Discover" src="https://www.paypal.com/en_US/i/logo/logo_ccDiscover.gif" border="0"> '. t('Discover'),
+    '<img alt=" American Express" src="https://www.paypal.com/en_US/i/logo/logo_ccAmex.gif" border="0"> '. t('American Express')
+  );
+
+
+  $form['#method'] = 'POST';
+  // display optional form help text
+  $form['help'] = array(
+    '#type' => 'markup',
+    '#prefix' => '<div class="help">',
+    '#suffix' => '</div>',
+    '#value' => variable_get('paypalpro_form_help', ''),
+  );
+
+  // display all items being purchased
+  if ($t->items) {
+    foreach ($t->items as $p) {
+      $product = product_load((object)$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['items'] = array(
+    '#prefix' => '<p>',
+    '#suffix' => '</p>',
+    '#value' => theme('item_list', $items, t('Your items')),
+  );
+
+  $form['cc'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Credit card details'),
+  );
+  
+  // Credit card information
+  $form['cc']['name'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Enter your name as it appears on the card',
+  );
+  $form['cc']['name']['cc_first_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('First name'),
+    '#default_value' => $t->billing_firstname,
+    '#size' => 21,
+    '#maxlength' => 42,
+    '#description' => '',
+    '#attributes' => NULL,
+    '#required' => TRUE,
+  );
+  $form['cc']['name']['cc_middle_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Middle name or initial'),
+    '#size' => 21,
+    '#maxlength' => 42,
+    '#description' => '',
+  );
+  $form['cc']['name']['cc_last_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Last name'),
+    '#default_value' => $t->billing_lastname,
+    '#size' => 21,
+    '#maxlength' => 42,
+    '#description' => '',
+    '#attributes' => NULL,
+    '#required' => TRUE,
+  );
+
+
+  // the card type and card numbers
+  $form['cc']['number'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Select a credit card type and enter your card number',
+  );
+  $form['cc']['number']['cc_type'] = array(
+    '#type' => 'radios',
+    '#title' => t('Card type'),
+    '#options' => $paypalpro_cc_types,
+    '#description' => t('Select the type of credit card you would like to use to make your payment.'),
+    '#required' => NULL,
+    '#attributes' => NULL,
+  );
+  // todo: allow numbers with spaces and dashes (convert on-the-fly)
+  $form['cc']['number']['cc_number'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Card number'),
+    '#size' => 21,
+    '#maxlength' => 21,
+    '#description' => t('Please enter your credit card number without any spaces or dashes.'),
+    '#attributes' => NULL,
+    '#required' => TRUE,
+  );
+  $form['cc']['number']['cvv2'] = array(
+    '#type' => 'textfield',
+    '#title' => t('CCV2'),
+    '#size' => 3,
+    '#maxlength' => 4,
+    '#description' => t('The CCV2 is a 3 digit number located on the back of Visa, MasterCard and Discover credit cards in the signature panel, and a 4 digit number located on the front of an American Express card above and to the right of the imprinted card number.  This number is used to provide additional security to internet orders.'),
+    '#attributes' => NULL,
+    '#required' => TRUE,
+  );
+
+  // the expiration date
+  $form['cc']['date'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Select your credit card\'s expiration date'),
+  );
+  $form['cc']['date']['cc_month'] = array(
+    '#type' => 'select',
+    '#title' => t('Month'),
+    '#default_value' => date('m'),
+    '#options' => $months,
+    '#description' => NULL,
+    '#extra' => 0,
+    '#multiple' => false,
+    '#required' => TRUE,
+  );
+  $form['cc']['date']['cc_year'] = array(
+    '#type' => 'select',
+    '#title' => t('Year'),
+    '#default_value' => date('Y'),
+    '#options' => $years,
+    '#description' => NULL,
+    '#extra' => 0,
+    '#multiple' => false,
+    '#required' => TRUE,
+  );
+
+  $form['txnid'] = array(
+    '#type' => 'hidden',
+    '#value' => $txnid,
+  );
+  $form[] = array(
+    '#type' => 'submit',
+    '#value' => t('Place your order'),
+  );
 
-  print theme('page', $output);
+  return drupal_get_form('paypalpro', $form);
 }
 
 /**
  * Implementation of Drupal _validate() hook.
  *
+ * @param $form_id  Our form id
  * @param  $edit    Form array to validate.
  * @return boolean  True if form validates, false if not.
  */
-function paypalpro_validate($edit) {
-  $valid = TRUE;
+function paypalpro_validate($form_id, $form_values) {
   $paypalpro_cc_types = array(t('Visa'), t('MasterCard'), t('Discover'), t('American Express'));
   $type = 'invalid';
 
-  if (!$edit['cc_first_name']) {
-    form_set_error('cc_first_name', t('Please enter your first name how it appear on your credit card.'));
-    $valid = FALSE;
-  }
-  if (!$edit['cc_last_name']) {
-    form_set_error('cc_last_name', t('Please enter your last name how it appear on your credit card.'));
-    $valid = FALSE;
-  }
-  if (!$edit['cc_number']) {
-    form_set_error('cc_number', t('Please enter a credit card number.'));
-    $valid = FALSE;
-  }
-  elseif (!is_numeric($edit['cc_number'])) {
+  if (!is_numeric($form_values['cc_number'])) {
     form_set_error('cc_number', t('Please enter a valid credit card number.'));
-    $valid = FALSE;
   }
-  elseif (($edit['cc_year'] < date('Y')) || ($edit['cc_year'] <= date('Y')) &&
-                                            ($edit['cc_month'] < date('m'))) {
+  if (($form_values['cc_year'] < date('Y')) || ($form_values['cc_year'] <= date('Y')) &&
+                                            ($form_values['cc_month'] < date('m'))) {
     form_set_error('cc_month', t('Your credit card has expired.  Please try another card.'));
-    $valid = FALSE;
   }
-  else {
-    // Verify that the credit card type matches the number of digits in the
-    // credit card.
-    $length = strlen($edit['cc_number']);
-    if ($length == 13) {
-      if (substr($edit['cc_number'], 0, 1) == '4') {
-        $type = t('Visa');
-      }
-    }
-    elseif ($length == 16) {
-      if (substr($edit['cc_number'], 0, 1) == '4') {
-        $type = t('Visa');
-      }
-      if (substr($edit['cc_number'], 0, 1) == '5') {
-        $type = t('MasterCard');
-      }
-      elseif (substr($edit['cc_number'], 0, 4) == '6011') {
-        $type = t('Discover');
-      }
-    }
-    elseif ($length == 15) {
-      if (substr($edit['cc_number'], 0, 1) == '3') {
-        $type = t('American Express');
-      }
-    }
-    if ($type != $paypalpro_cc_types[$edit['cc_type']]) {
-      form_set_error('cc_number', t('The credit card number you have entered is not a valid %type credit card number.  Please fix the credit card type, or re-enter the credit card number.', array('%type' => $paypalpro_cc_types[$edit['cc_type']])));
-      $valid = FALSE;
+
+  // Verify that the credit card type matches the number of digits in the
+  // credit card.
+  $length = strlen($form_values['cc_number']);
+  if ($length == 13) {
+    if (substr($form_values['cc_number'], 0, 1) == '4') {
+      $type = t('Visa');
     }
-    // TODO:  Different cards refer to this number with a different term.
-    //  Visa = CVV2 (card verification value)
-    //  MasterCard = CVC2 (card validation code)
-    //  Discover = Cardmember ID
-    //  American Express = CID (Card Identification Number)
-    elseif (!$edit['cvv2']) {
-      form_set_error('cvv2', t('Please enter a CCV2 number.'));
-      $valid = FALSE;
+  }
+  elseif ($length == 16) {
+    if (substr($form_values['cc_number'], 0, 1) == '4') {
+      $type = t('Visa');
     }
-    elseif (!is_numeric($edit['cvv2'])) {
-      form_set_error('cvv2', t('Please enter a valid CCV2 number.  Non-numeric characters are not allowed.'));
-      $valid = FALSE;
+    if (substr($form_values['cc_number'], 0, 1) == '5') {
+      $type = t('MasterCard');
     }
-    elseif (($edit['cc_type'] == 3) && (strlen($edit['cvv2']) != 4)) {
-      form_set_error('cvv2', t('Please enter a valid 4 digit CCV2 number.  The CCV2 number on your %type credit card is located on the front above and to the right of the imprinted card number.', array('%type' => $paypalpro_cc_types[$edit['cc_type']])));
-      $valid = FALSE;
+    elseif (substr($form_values['cc_number'], 0, 4) == '6011') {
+      $type = t('Discover');
     }
-    elseif (($edit['cc_type'] != 3) && (strlen($edit['cvv2']) != 3)) {
-      form_set_error('cvv2', t('Please enter a valid 3 digit CCV2 number.  The CCV2 number on your %type credit card is located on the back in the signature panel after the credit card number.', array('%type' => $paypalpro_cc_types[$edit['cc_type']])));
-      $valid = FALSE;
+  }
+  elseif ($length == 15) {
+    if (substr($form_values['cc_number'], 0, 1) == '3') {
+      $type = t('American Express');
     }
   }
+  if ($type != $paypalpro_cc_types[$form_values['cc_type']]) {
+    form_set_error('cc_number', t('The credit card number you have entered is not a valid %type credit card number.  Please fix the credit card type, or re-enter the credit card number.', array('%type' => $paypalpro_cc_types[$form_values['cc_type']])));
+  }
 
-  return $valid;
+  // TODO:  Different cards refer to this number with a different term.
+  //  Visa = CVV2 (card verification value)
+  //  MasterCard = CVC2 (card validation code)
+  //  Discover = Cardmember ID
+  //  American Express = CID (Card Identification Number)
+  if (!is_numeric($form_values['cvv2'])) {
+    form_set_error('cvv2', t('Please enter a valid CCV2 number.  Non-numeric characters are not allowed.'));
+  }
+  elseif (($form_values['cc_type'] == 3) && (strlen($form_values['cvv2']) != 4)) {
+    form_set_error('cvv2', t('Please enter a valid 4 digit CCV2 number.  The CCV2 number on your %type credit card is located on the front above and to the right of the imprinted card number.', array('%type' => $paypalpro_cc_types[$form_values['cc_type']])));
+  }
+  elseif (($form_values['cc_type'] != 3) && (strlen($form_values['cvv2']) != 3)) {
+    form_set_error('cvv2', t('Please enter a valid 3 digit CCV2 number.  The CCV2 number on your %type credit card is located on the back in the signature panel after the credit card number.', array('%type' => $paypalpro_cc_types[$form_values['cc_type']])));
+  }
 }
 
 /**
- * Implementation of e-commerce _form() hook.  This form is used to collect
- * credit card information.
+ * Process a credit card transaction.  Makes a curl connection to PayPal's API
+ * server to validate the credit card.  We manually process the returned SOAP
+ * string rather than using PEAR and PayPal's PHP API.  After a succesful
+ * transaction, the transaction information is stored in the local database.
+ * After a failed transaction, the user is redirected back to the credit card
+ * form and provided a helpful error to explain what is wrong.
  *
- * @param $txnid  Transaction id.
- * @param form    Credit card form.
+ * @param $edit   The $edit array.
  */
-function paypalpro_form($txnid) {
+function paypalpro_submit($form_id, $form_values) {
   global $user, $base_url;
 
-  if ($_POST['edit']) {
-    $edit = $_POST['edit'];
-  }
-  else if ($_SESSION['edit']) {
-    // paypalpro_goto saves the edit array in the session
-    $edit = $_SESSION['edit'];
-  }
-  // it doesn't hurt to unset this even if it's not set
-  unset ($_SESSION['edit']);
-
-  // array includes credit card images
-  $paypalpro_cc_types = array('<img alt="Visa" src="https://www.paypal.com/en_US/i/logo/logo_ccVisa.gif" border="0"> '. t('Visa'), '<img alt=" Mastercard" src="https://www.paypal.com/en_US/i/logo/logo_ccMC.gif" border="0"> '. t('MasterCard'), '<img alt=" Discover" src="https://www.paypal.com/en_US/i/logo/logo_ccDiscover.gif" border="0"> '. t('Discover'), '<img alt=" American Express" src="https://www.paypal.com/en_US/i/logo/logo_ccAmex.gif" border="0"> '. t('American Express'));
+  $t = store_transaction_load($form_values['txnid']);
 
-  $t = store_transaction_load($txnid);
+  // TODO: validate url, cert, etc...
 
-  // make sure the current users owns this transaction (or is the site admin)
-  if ($user->uid != $t->uid && $user->uid != 1) {
-    drupal_access_denied();
-  }
+  // submit a SOAP DoDirectPaymentRequest message with curl
+  $errno = $error = 0;
+  $exec_return = paypalpro_make_SOAP_request(paypalpro_paymentrequest($t, $form_values), $errno, $error);
 
-  // if configured, require that the user access this page via https://
-  if (variable_get('paypalpro_secure', 1) && !$_SERVER['HTTPS']) {
-    drupal_access_denied();
+  // an error here indicates that the module isn't properly configured
+  if ($errno) {
+    drupal_set_message(t('PayPalPro configuration error: '). $error, 'error');
   }
 
-  // display optional form help text
-  $output = t('<div class="help">%paypalpro_form_help</div>', array('%paypalpro_form_help' => variable_get('paypalpro_form_help', '')));
-
-  // display all items being purchased
-  if ($t->items) {
-    foreach ($t->items as $p) {
-      $product = product_load((object)$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";
-    }
-  }
-  $output .= '<p>'. theme('item_list', $items, t('Your items')). '</p>';
+  // parse the paymentreply SOAP message, look for 'Success' or 'Failure'
+  if (strpos($exec_return, 'Success')) {
+    drupal_set_message(t('Transaction approved.  Thank you for your order!'));
+    $t->proid = paypalpro_parse_xml($exec_return, '<TransactionID>', '</TransactionID>');
+    // TODO: fix this when non-USD are supported
+    $t->amount = paypalpro_parse_xml($exec_return, '<Amount xsi:type="cc:BasicAmountType" currencyID="USD">', '</Amount>');
 
-  // prepare the values of the form fields
-  $years  = drupal_map_assoc(range(2004, 2020));
-  $months = drupal_map_assoc(array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'));
+    // set e-commerce API transaction payment status to 'completed'.
+    $t->payment_status = payment_get_status_id('completed');
+    // transaction handled by paypalpro module
+    $t->payment_method = 'paypalpro';
 
-  // the name as it appears on the card
-  $group = form_textfield(t('First name'), 'cc_first_name', $edit['cc_first_name'], 21, 42, '', NULL, TRUE);
-  $group .= form_textfield(t('Middle name or initial'), 'cc_middle_name', $edit['cc_middle_name'], 21, 42, '');
-  $group .= form_textfield(t('Last name'), 'cc_last_name', $edit['cc_last_name'], 21, 42, '', NULL, TRUE);
-  $form = form_group('Enter your name as it appears on the card', $group);
+    $is_new = (db_result(db_query('SELECT COUNT(txnid) FROM {ec_paypalpro} WHERE txnid = %d', $t->txnid))) ? FALSE : TRUE;
+    $txnid = store_transaction_save((array)$t);
 
-  // the card type and card numbers
-  $group = form_radios(t('Card type'), 'cc_type', $edit['cc_type'], $paypalpro_cc_types, t('Select the type of credit card you would like to use to make your payment.'), NULL, NULL, TRUE);
-  // todo: allow numbers with spaces and dashes (convert on-the-fly)
-  $group .= form_textfield(t('Card number'), 'cc_number', $edit['cc_number'], 21, 21, t('Please enter your credit card number without any spaces or dashes.'), NULL, TRUE);
-  $group .= form_textfield(t('CCV2'), 'cvv2', $edit['cvv2'], 3, 4, t('The CCV2 is a 3 digit number located on the back of Visa, MasterCard and Discover credit cards in the signature panel, and a 4 digit number located on the front of an American Express card above and to the right of the imprinted card number.  This number is used to provide additional security to internet orders.'), NULL, TRUE);
-  $form .= form_group('Select a credit card type and enter your card number', $group);
+    if ($is_new && $txnid) {
+      // compose and send confirmation email to the user
+      store_send_invoice_email($txnid);
+    }
 
-  // the expiration date
-  $group = form_select(t('Month'), 'cc_month', ($edit['cc_month'] ? $edit['cc_month'] : date('m')), $months, NULL, 0, false, TRUE);
-  $group .= form_select(t('Year'), 'cc_year', ($edit['cc_year'] ? $edit['cc_year'] : date('Y')), $years, NULL, 0, false, TRUE);
-  $form .= form_group(t('Select your credit card\'s expiration date'), $group);
-
-  $output .= form_group(t('Credit card details'), $form);
-  $output .= form_hidden('txnid', $txnid);
-  $output .= form_submit(t('Place your order'));
+    // transaction complete, return to http://
+    // if configured for secure connections, rewrite http:// a http://
+    $base = '';
+    if (variable_get('paypalpro_secure', 1)) {
+      $base = str_replace('http://', 'https://', $base_url) . '/';
+    }
 
-  if (variable_get('paypalpro_secure', 1)) {
-    $base = str_replace('http://', 'https://', $base_url);
+    drupal_goto($base . url(strtr(variable_get('paypalpro_success_url', 'store/transaction/view/%txnid'), array('%txnid' => $txnid)), NULL, NULL, TRUE));
+    exit();
   }
   else {
-    $base = $base_url;
+    // transaction failed
+    if (strpos($exec_return, 'Failure')) {
+      $errors = paypalpro_get_errors($exec_return);
+      foreach ($errors as $error) {
+        paypalpro_set_error($error);
+      }
+    }
+    else {
+      $output .= drupal_set_message('<b>Communication error.</b>  Failed to connect to the authentication server.  Please try again later.', 'error');
+    }
+    paypalpro_goto($t);
   }
-
-  return form($output, 'POST', $base .'/'. url("paypalpro/form/". $txnid));
 }
 
 /**
@@ -392,221 +486,15 @@ function paypalpro_ec_transactionapi(&$t
 }
 
 /**
- * Redirects the user to PayPal's secure site.  The goal is to have them log 
- * in, select an address, and to obtain a token from PayPal used to actually 
- * charge the user's account.  There is no return from this function, we 
- * either redirect to PayPal's website, or on an error we go back to the 
- * shopping cart.
- *
- * TODO: the redirect is ugly, it would be nice to display a more Drupal-ish 
- * page.
- */
-function paypalpro_express_checkout_redirect() {
-  $txn = ec_checkout_get_data();
-
-  // submit a SOAP setexpresscheckoutrequest message with curl
-  $SOAPrequest = paypalpro_setExpressCheckoutRequest($txn->gross);
-
-  $ch = curl_init();
-  curl_setopt($ch, CURLOPT_URL, variable_get('paypalpro_url', 'https://api.sandbox.paypal.com/2.0/'));
-  curl_setopt($ch, CURLOPT_SSLCERT, variable_get('paypalpro_sslcert', ''));
-  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
-  curl_setopt($ch, CURLOPT_POSTFIELDS, $SOAPrequest);
-  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-  $exec_return = curl_exec($ch);
-  if ($errno = curl_errno($ch)) {
-    $error = curl_error($ch);
-  }
-  curl_close($ch);
-
-  if (strpos($exec_return, 'Success')) {
-    $token = paypalpro_parse_xml($exec_return, '<Token xsi:type="ebl:ExpressCheckoutTokenType">', '</Token');
-
-    // redirect to PayPal's website
-    print "
-<html><head>
-<script language=JavaScript type=text/javascript>
-  <!--;
-  function submitForm() {
-    document.myForm.submit();
-    setTimeout('self.close();',100000); 
-  }
-  //-->
-</script>
-</head>
-
-<body onLoad=javascript:submitForm()>
-  <p>
-    <font size=\"4\" color=\"#000080\">". t('One moment please, contacting PayPal.') ."</font></span></b>
-    <img src=\"https://www.paypal.com/en_US/i/scr/period_ani.gif\">
-  </p>
-
-  <form name=\"myForm\" method=\"post\" action=\"";
-  print variable_get('paypalpro_express_url', 'https://www.sandbox.paypal.com/cgi-bin/webscr');
-  print "\">
-    <input type=\"hidden\" name=\"cmd\" value=\"_express-checkout\">
-    <input type=\"hidden\" name=\"cn\" value=\"Special Instructions:\">
-    <input type=\"hidden\" name=\"bn\" value=\"MPL\">
-    <input type=\"hidden\" name=\"token\" value=\"". $token ."\">
-  </form>
-</body>
-</html>";
-  }
-  else {
-    if (strpos($exec_return, 'Failure')) {
-      $errors = paypalpro_get_errors($exec_return);
-      foreach ($errors as $error) {
-        paypalpro_set_error($error);
-      }
-    }
-    else {
-      $output .= drupal_set_message('<b>Communication error.</b>  Failed to connect to the authentication server.  Please try again later.', 'error');
-      if ($error) {
-        // Curl errors: http://curl.haxx.se/libcurl/c/libcurl-errors.html
-        $output .= drupal_set_message("(libcurl error #$errno: $error)", 'error');
-      }
-    }
-    drupal_goto('cart/checkout');
-  }
-}
-
-/**
- * This is a replacement for cart_checkout_form() when checking out with 
- * PayPal Express Checkout.
- */
-function paypalpro_express_checkout() {
-  global $user;
-
-  $token = array_key_exists('token', $_GET) ? $_GET['token'] : '';
-
-  if ($token) {
-    // submit a SOAP getexpresscheckoutdetails message with curl
-    $SOAPrequest = paypalpro_getExpressCheckoutDetails($token);
-
-    $ch = curl_init();
-    curl_setopt($ch, CURLOPT_URL, variable_get('paypalpro_url', 'https://api.sandbox.paypal.com/2.0/'));
-    curl_setopt($ch, CURLOPT_SSLCERT, variable_get('paypalpro_sslcert', ''));
-    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
-    curl_setopt($ch, CURLOPT_POSTFIELDS, $SOAPrequest);
-    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-    $exec_return = curl_exec($ch);
-    if ($errno = curl_errno($ch)) {
-      $error = curl_error($ch);
-    }
-    curl_close($ch);
-
-    if (strpos($exec_return, 'Success')) {
-      $edit = ec_checkout_get_data();
-      $shippable = FALSE;
-      foreach ($edit->items as $item) {
-        if (product_is_shippable($item->nid)) {
-          $shippable = TRUE;
-          break;
-        }
-      }
-
-      // prepare checkout form
-      $edit->firstname = paypalpro_parse_xml($exec_return, '<FirstName xmlns="urn:ebay:apis:eBLBaseComponents">', '</FirstName>');
-      $edit->lastname = paypalpro_parse_xml($exec_return, '<LastName xmlns="urn:ebay:apis:eBLBaseComponents">', '</LastName>');
-      $edit->street1 = paypalpro_parse_xml($exec_return, '<Street1 xsi:type="xs:string">', '</Street1>');
-      $edit->street2 = paypalpro_parse_xml($exec_return, '<Street2 xsi:type="xs:string">', '</Street2>');
-      $edit->city = paypalpro_parse_xml($exec_return, '<CityName xsi:type="xs:string">', '</CityName>');
-      $edit->state = paypalpro_parse_xml($exec_return, '<StateOrProvince xsi:type="xs:string">', '</StateOrProvince>');
-      $edit->zip = paypalpro_parse_xml($exec_return, '<PostalCode xsi:type="xs:string">', '</PostalCode>');
-      $edit->country = paypalpro_parse_xml($exec_return, '<Country xsi:type="ebl:CountryCodeType">', '</Country>');
-      $edit->mail = paypalpro_parse_xml($exec_return, '<Payer xsi:type="ebl:EmailAddressType">', '</Payer>');
-      //$business = paypalpro_parse_xml($exec_return, '<PayerBusiness xsi:type="xs:string">', '</PayerBusiness>');
-      //$address_type = paypalpro_parse_xml($exec_return, '<Address xsi:type="ebl:AddressType"><Name xsi:type="xs:string">', '</Name');
-      //$status = paypalpro_parse_xml($exec_return, '<PayerStatus xsi:type="ebl:PayPalUserStatusCodeType">', '</PayerStatus>');
-      //$countryCode = paypalpro_parse_xml($exec_return, '<Country xsi:type="ebl:CountryCodeType">', '</Country>');
-
-      $edit->paypalpro_token = paypalpro_parse_xml($exec_return, '<Token xsi:type="ebl:ExpressCheckoutTokenType">', '</Token');
-      $edit->paypalpro_payerid = paypalpro_parse_xml($exec_return, '<PayerID xsi:type="ebl:UserIDType">', '</PayerID>');
-      $payeremail = paypalpro_parse_xml($exec_return, '<Payer xsi:type="ebl:EmailAddressType">', '</Payer');
-      $account = paypalpro_parse_xml($exec_return, '<Name xsi:type="xs:string">', '</Name>');
-
-      $edit->screen+= 2;
-      ec_checkout_hide_data($edit);
-      drupal_goto('cart/checkout', 'op=next');
-    }
-  }
-  drupal_goto('cart/checkout');
-}
-
-/**
- * This is the final step in Express Checkout where we actually bill the 
- * user's PayPal account.
- *
- * @param $txn   The current transaction object.
- */
-function paypalpro_express_checkout_process($txn) {
-  global $user, $base_url;
-  
-  // submit a SOAP doexpresscheckoutpayment message with curl
-  $SOAPrequest = paypalpro_doExpressCheckoutPayment($txn);
-
-  $ch = curl_init();
-  curl_setopt($ch, CURLOPT_URL, variable_get('paypalpro_url', 'https://api.sandbox.paypal.com/2.0/'));
-  curl_setopt($ch, CURLOPT_SSLCERT, variable_get('paypalpro_sslcert', ''));
-  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
-  curl_setopt($ch, CURLOPT_POSTFIELDS, $SOAPrequest);
-  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-  $exec_return = curl_exec($ch);
-  if ($errno = curl_errno($ch)) {
-    $error = curl_error($ch);
-  }
-  curl_close($ch);
-
-  if (strpos($exec_return, 'Success')) {
-    $txn->proid = paypalpro_parse_xml($exec_return, '<TransactionID>', '</TransactionID>');
-    // TODO: fix this when non-USD are supported
-    $txn->amount = paypalpro_parse_xml($exec_return, '<GrossAmount xsi:type="cc:BasicAmountType" currencyID="USD">', '</GrossAmount>');
-
-    // set e-commerce API transaction payment status to 'completed'.
-    $txn->payment_status = payment_get_status_id('completed');
-    // transaction handled by paypalpro module
-    $txn->payment_method = 'paypalpro';
-
-    $is_new = (db_result(db_query('SELECT COUNT(txnid) FROM {ec_paypalpro} WHERE txnid = %d', $txn->txnid))) ? FALSE : TRUE;
-    $txnid = store_transaction_save($txn);
-
-    if ($is_new && $txnid) {
-      // compose and send confirmation email to the user
-      store_send_invoice_email($txnid);
-    }
-
-    // transaction complete, return to http://
-    $goto = str_replace('https://', 'http://', $base_url);
-    header("Location: $goto/" . url(strtr(variable_get('paypalpro_success_url', 'store/transaction/view/%txnid'), array('%txnid' => $txnid))));
-    exit();
-  }
-  else {
-    if (strpos($exec_return, 'Failure')) {
-      $errors = paypalpro_get_errors($exec_return);
-      foreach ($errors as $error) {
-        paypalpro_set_error($error);
-      }
-    }
-    else {
-      $output .= drupal_set_message('<b>Communication error.</b>  Failed to connect to the authentication server.  Please try again later.', 'error');
-    }
-  }
-}
-
-
-/**
  * Save or update a transaction, called from paypalpro_paymentapi.
  *
  * @param $txn    The transaction object.
  */
 function paypalpro_save($txn) {
   if (is_numeric($txn->txnid) && $txn->proid) {
-    db_query("UPDATE {ec_paypalpro} SET proid = '%s', amount = '%f' WHERE txnid = %d", $txn->proid, $txn->amount, $txn->txnid);
+    db_query('UPDATE {ec_paypalpro} SET proid = \'%s\', amount = %f WHERE txnid = %d', $txn->proid, $txn->amount, $txn->txnid);
     if (!db_affected_rows()) {
-      db_query("INSERT INTO {ec_paypalpro} (txnid, proid, amount) VALUES(%d, '%s', '%f')", $txn->txnid, $txn->proid, $txn->amount);
+      db_query('INSERT INTO {ec_paypalpro} (txnid, proid, amount) VALUES(%d, \'%s\', %f)', $txn->txnid, $txn->proid, $txn->amount);
     }
   }
 }
@@ -629,23 +517,14 @@ function paypalpro_delete($txn) {
  */
 function paypalpro_goto($txn) {
   global $base_url;
-  $txn = (object)$txn;
 
+  $base = '';
   // if configured for secure connections, rewrite http:// a http://
   if (variable_get('paypalpro_secure', 1)) {
-    $base = str_replace('http://', 'https://', $base_url);
-  }
-  else {
-    $base = $base_url;
+    $base = str_replace('http://', 'https://', $base_url) . '/';
   }
 
-  // Save the transaction object in the current session to be restored when
-  // displaying the form.  This way the user doesn't have to re-enter
-  // all their credit card information.
-  $_SESSION['edit'] = (array)$txn;
-
-  header("Location: $base". '/'. url('paypalpro/form/'. $txn->txnid));
-  exit();
+  drupal_goto($base . 'paypalpro/form/'. $txn->txnid);
 }
 
 function paypalpro_set_error($error) {
@@ -658,319 +537,108 @@ function paypalpro_set_error($error) {
       form_set_error('cc_number', '<b>'. $error['short'] .'</b> '. t('Please re-enter your credit card number.'));
       break;
     default:
-      drupal_set_message('<b>'. $error['short'] .'</b> '.  $error['long'] .'.', 'error');
+      watchdog('paypalpro', '<b>'. $error['short'] .'</b> '.  $error['long'] .'.', WATCHDOG_ERROR);
+      drupal_set_message(t('There was an error processing your transaction with PayPal.  A message has been logged.  We are sorry for the inconvenience'));
       break;
   }
 }
 
-
 /**
- * Process a credit card transaction.  Makes a curl connection to PayPal's API
- * server to validate the credit card.  We manually process the returned SOAP
- * string rather than using PEAR and PayPal's PHP API.  After a succesful
- * transaction, the transaction information is stored in the local database.
- * After a failed transaction, the user is redirected back to the credit card
- * form and provided a helpful error to explain what is wrong.
+ * A simple xml parsing function.
  *
- * @param $edit   The $edit array.
- */
-function paypalpro_process($edit) {
-  global $user, $base_url;
-
-  $t = store_transaction_load($edit['txnid']);
-
-  // make sure the current users owns this transaction (or is the site admin)
-  if ($user->uid != $t->uid && $user->uid != 1) {
-    drupal_access_denied();
-  }
-
-  // if configured, require https://
-  if (variable_get('paypalpro_secure', 1) && !$_SERVER['HTTPS']) {
-    drupal_access_denied();
-  }
-
-  // TODO: validate url, cert, etc...
-
-  // submit a SOAP paymentrequest message with curl
-  $SOAPrequest = paypalpro_paymentrequest($t, $edit);
-  $ch = curl_init();
-  curl_setopt($ch, CURLOPT_URL, variable_get('paypalpro_url', 'https://api.sandbox.paypal.com/2.0/'));
-  curl_setopt($ch, CURLOPT_SSLCERT, variable_get('paypalpro_sslcert', ''));
-  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
-  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
-  curl_setopt($ch, CURLOPT_POSTFIELDS, $SOAPrequest);
-  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-  $exec_return = curl_exec($ch);
-  if ($errno = curl_errno($ch)) {
-    $error = curl_error($ch);
-  }
-  curl_close($ch);
-
-  // an error here indicates that the module isn't properly configured
-  if ($errno) {
-    drupal_set_message(t('PayPalPro configuration error: '). $error, 'error');
-  }
-
-  // parse the paymentreply SOAP message, look for 'Success' or 'Failure'
-  if (strpos($exec_return, 'Success')) {
-    drupal_set_message(t('Transaction approved.  Thank you for your order!'));
-    $edit['proid'] = paypalpro_parse_xml($exec_return, '<TransactionID>', '</TransactionID>');
-    // TODO: fix this when non-USD are supported
-    $edit['amount'] = paypalpro_parse_xml($exec_return, '<Amount xsi:type="cc:BasicAmountType" currencyID="USD">', '</Amount>');
-
-    // set e-commerce API transaction payment status to 'completed'.
-    $edit['payment_status'] = payment_get_status_id('completed');
-    // transaction handled by paypalpro module
-    $edit['payment_method'] = 'paypalpro';
-
-    $is_new = (db_result(db_query('SELECT COUNT(txnid) FROM {ec_paypalpro} WHERE txnid = %d', $edit['txnid']))) ? FALSE : TRUE;
-    $txnid = store_transaction_save($edit);
-
-    if ($is_new && $txnid) {
-      // compose and send confirmation email to the user
-      store_send_invoice_email($txnid);
-    }
+ * @param  $xml        A text string that contains the xml to be parsed.
+ * @param  $open_tag   The opening xml tag to search for.
+ * @param  $close_tag  The closing xml tag to search for.
+ * @return string      The string between $open_tag and $close_tag
+ */
+function paypalpro_parse_xml($xml, $open_tag, $close_tag) {
+  $pos1 = strpos($xml, $open_tag);
+  $pos2 = strpos($xml, $close_tag);
+  return substr($xml, $pos1 + strlen($open_tag), $pos2 - ($pos1 + strlen($open_tag)));
+}
 
-    // transaction complete, return to http://
-    $goto = str_replace('https://', 'http://', $base_url);
-    header("Location: $goto/" . url(strtr(variable_get('paypalpro_success_url', 'store/transaction/view/%txnid'), array('%txnid' => $txnid))));
-    exit();
-  }
-  else {
-    // transaction failed
-    if (strpos($exec_return, 'Failure')) {
-      $errors = paypalpro_get_errors($exec_return);
-      foreach ($errors as $error) {
-        paypalpro_set_error($error);
+/**
+ * A recursive xml parsing function for obtaining multiple error messages.
+ * @param  $xml        A text string that contains the xml to be parsed.
+ * @return array       All error messages found in the xml.
+ */
+function paypalpro_get_errors($xml) {
+  // errors in returned xml are comprised of short and long messages
+  define(SHORT_OPEN, '<ShortMessage xsi:type="xs:string">');
+  define(SHORT_CLOSE, '</ShortMessage>');
+  define(LONG_OPEN, '<LongMessage xsi:type="xs:string">');
+  define(LONG_CLOSE, '</LongMessage>');
+  $errors = array();
+  // loop through xml to find all error messages
+  $loop = TRUE;
+  while ($loop) {
+    // test if there are any more errors by looking for the SHORT_OPEN string
+    if (strpos($xml, SHORT_OPEN)) {
+      $error['short'] = paypalpro_parse_xml($xml, SHORT_OPEN, SHORT_CLOSE);
+      // there's no need to report the generic "Internal Error"
+      if ($error['short'] != 'Internal Error') {
+        $error['long'] = paypalpro_parse_xml($xml, LONG_OPEN, LONG_CLOSE);
+        $errors[] = $error;
       }
+      $xml = substr($xml, strpos($xml, LONG_CLOSE) + strlen(LONG_CLOSE));
     }
     else {
-      $output .= drupal_set_message('<b>Communication error.</b>  Failed to connect to the authentication server.  Please try again later.', 'error');
+      // no more error messages found
+      $loop = FALSE;
     }
-    paypalpro_goto($edit);
   }
+  return $errors;
 }
 
 /**
- * Manaually generate a SOAP request (this is perhaps ugly, but it greatly
- * simplifies the installation process as we don't have to require PEAR or
- * the PayPal PHP API). 
- *
- * This SOAP request begins the Express Checkout process.
+ * Look for an adress in the user's address book that exactly matches the
+ * address returned by PayPal.  If found, return the aid.
  *
- * @param  $total  The order total, including shipping/handling/tax.
- * @return SOAP    The SOAP expresscheckoutrequest message.
+ * @param  $edit  The $edit array, with address info returned from PayPal.
+ * @return $aid   The id of the address entry in the database, if match found.
  */
-function paypalpro_setExpressCheckoutRequest($total) {
-  global $base_url;
-
-  $username = variable_get('paypalpro_username', '');
-  $password = variable_get('paypalpro_password', '');
-  $OrderDescription = '';  // TODO: description?
-  $OrderTotal = $total;
-
-  // if configured for secure connections, rewrite http:// a http://
-  if (variable_get('paypalpro_secure', 1)) {
-    $base = str_replace('http://', 'https://', $base_url);
-  }
-  else {
-    $base = $base_url;
-  }
-
-  // succesful payment at PayPal's website
-  $ReturnURL = $base .'/'. url('paypalpro/express');
-  // cancelled or otherwise unsuccessful payment at Paypal's website, 
-  // return to checkout start
-  $CancelURL = $base .'/'. url('cart/checkout');
-
-
-$SOAPrequest = <<< End_Of_Quote
-<?xml version="1.0" encoding="UTF-8"?>
-<SOAP-ENV:Envelope
-	xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
-	xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
-	xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
-	xmlns:xsd="http://www.w3.org/1999/XMLSchema"
-	SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-	<SOAP-ENV:Header>
-		<RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" SOAP-ENV:mustUnderstand="1">
-			<Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
-				<Username>$username</Username>
-				<Password>$password</Password>
-			</Credentials>
-		</RequesterCredentials>
-	</SOAP-ENV:Header>
-	<SOAP-ENV:Body>
-		<SetExpressCheckoutReq xmlns="urn:ebay:api:PayPalAPI">
-			<SetExpressCheckoutRequest>
-				<Version xmlns="urn:ebay:apis:eBLBaseComponents">1.0</Version>
-				<SetExpressCheckoutRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">
-					<OrderTotal currencyID="USD" xsl:type="cc:BasicAmountType">$OrderTotal</OrderTotal>
-					<OrderDescription>$OrderDescription</OrderDescription>
-					<ReturnURL>$ReturnURL</ReturnURL>
-					<CancelURL>$CancelURL</CancelURL>
-				</SetExpressCheckoutRequestDetails>
-			</SetExpressCheckoutRequest>
-		</SetExpressCheckoutReq>
-	</SOAP-ENV:Body>
-</SOAP-ENV:Envelope>
-End_Of_Quote;
-
-  return $SOAPrequest;
+function paypalpro_address_get_aid($edit) {
+  $address = db_fetch_object(db_query("SELECT aid FROM {ec_address} WHERE uid = %d AND firstname = '%s' AND lastname = '%s' AND street1 = '%s' AND street2 = '%s' AND zip = '%s' AND city = '%s' AND state = '%s' AND country = '%s'", $edit->uid, $edit->firstname, $edit->lastname, $edit->street1, $edit->street2, $edit->zip, $edit->city, $edit->state, $edit->country));
+  return $address->aid;
 }
 
 /**
- * Manaually generate a SOAP request (this is perhaps ugly, but it greatly
- * simplifies the installation process as we don't have to require PEAR or
- * the PayPal PHP API). 
- *
- * This SOAP request gets the user's details from PayPal.
+ * Save the address returned from PayPal in the local address book, then return
+ * the aid.  If the address already exists, simply return the aid.
  *
- * @param  $token  A token provided by PayPal, used to track the order to completion.
- * @return SOAP    The SOAP expresscheckoutrequest message.
+ * @param  $edit  The $edit array with address info returned from PayPal.
+ * @return @aid   The id of the address entry in the database.
  */
-function paypalpro_getExpressCheckoutDetails($token) {
-  $username = variable_get('paypalpro_username', '');
-  $password = variable_get('paypalpro_password', '');
-
-$SOAPrequest = <<< End_Of_Quote
-<?xml version="1.0" encoding="UTF-8"?>
-<SOAP-ENV:Envelope
-	xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
-	xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
-	xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
-	xmlns:xsd="http://www.w3.org/1999/XMLSchema"
-	SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-	<SOAP-ENV:Header>
-		<RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"
-SOAP-ENV:mustUnderstand="1">
-			<Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
-				<Username>$username</Username>
-				<Password>$password</Password>
-			</Credentials>
-		</RequesterCredentials>
-	</SOAP-ENV:Header>
-	<SOAP-ENV:Body>
-		<GetExpressCheckoutDetailsReq xmlns="urn:ebay:api:PayPalAPI">
-			<GetExpressCheckoutDetailsRequest>
-				<Version xmlns="urn:ebay:apis:eBLBaseComponents">1.00</Version>
-				<Token>$token</Token>
-			</GetExpressCheckoutDetailsRequest>
-		</GetExpressCheckoutDetailsReq>
-	</SOAP-ENV:Body>
-</SOAP-ENV:Envelope>
-End_Of_Quote;
-
-  return $SOAPrequest;
+function paypalpro_address_save($edit) {
+  $aid = paypalpro_address_get_aid($edit);
+  if (!$aid) {
+    db_query("INSERT INTO {ec_address} (uid, firstname, lastname, street1, street2, zip, city, state, country) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $edit->uid, $edit->firstname, $edit->lastname, $edit->street1, $edit->street2, $edit->zip, $edit->city, $edit->state, $edit->country);
+    $aid = paypalpro_address_get_aid($edit);
+  }
+  return $aid;
 }
 
 /**
- * Manaually generate a SOAP request (this is perhaps ugly, but it greatly
- * simplifies the installation process as we don't have to require PEAR or
- * the PayPal PHP API). 
- *
- * This SOAP request actually submits the order and withdrawls the money
- * from the user's PayPal account.
- *
- * @param  $txn  The current transaction object.
+ * 
+ * @param $SOAPrequest string SOAP request ro post to PayPal
+ * @param $errno int option return variable to get errno from curl
+ * @param $error string option return variable to get error from curl
  */
-function paypalpro_doExpressCheckoutPayment($txn) {
-  $username = variable_get('paypalpro_username', '');
-  $password = variable_get('paypalpro_password', '');
-
-  $token = $txn->paypalpro_token;
-  $payerid = $txn->paypalpro_payerid;
-
-  $OrderTotal = $txn->gross;
-  $ItemTotal = $txn->subtotal;
-  $ShippingTotal = $txn->shipping_cost;
-  $HandlingTotal = 0; // not currently used
-  $TaxTotal = 0; // not currently used
-  $ShipName = $txn->shipping_firstname .' '. $txn->shipping_lastname;
-  $Street1 = $txn->shipping_street1;
-  $Street2 = $txn->shipping_street2;
-  $CityName = $txn->shipping_city;
-  $StateOrProvince = $txn->shipping_state;
-  $Country = strtoupper($txn->shipping_country);
-  $PostalCode = $txn->shipping_zip;
-
-$SOAPrequest = <<< End_Of_Quote
-<?xml version="1.0" encoding="UTF-8"?>
-<SOAP-ENV:Envelope
-	xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
-	xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
-	xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
-	xmlns:xsd="http://www.w3.org/1999/XMLSchema"
-	SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-	<SOAP-ENV:Header>
-		<RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"
-SOAP-ENV:mustUnderstand="1">
-			<Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
-				<Username>$username</Username>
-				<Password>$password</Password>
-			</Credentials>
-		</RequesterCredentials>
-	</SOAP-ENV:Header>
-
-	<SOAP-ENV:Body>
-		<DoExpressCheckoutPaymentReq xmlns="urn:ebay:api:PayPalAPI">
-			<DoExpressCheckoutPaymentRequest>
-				<Version xmlns="urn:ebay:apis:eBLBaseComponents">1.0</Version>
-					<DoExpressCheckoutPaymentRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">
-						<PaymentAction>Sale</PaymentAction>
-						<Token>$token</Token>
-						<PayerID>$payerid</PayerID>
-						<PaymentDetails>
-							<OrderTotal currencyID="USD">$OrderTotal</OrderTotal>
-							<ItemTotal currencyID="USD">$ItemTotal</ItemTotal>
-							<ShippingTotal currencyID="USD">$ShippingTotal</ShippingTotal>
-							<HandlingTotal currencyID="USD">$HandlingTotal</HandlingTotal>
-							<TaxTotal currencyID="USD">$TaxTotal</TaxTotal>
-
-							<ShipToAddress>
-								<Name>$ShipName</Name>
-								<Street1>$Street1</Street1>
-								<Street2>$Street2</Street2>
-								<CityName>$CityName</CityName>
-								<StateOrProvince>$StateOrProvince</StateOrProvince>
-								<Country>$Country</Country>
-								<PostalCode>$PostalCode</PostalCode>
-							</ShipToAddress>
-End_Of_Quote;
-
-  foreach ($txn->items as $item) {
-    if ($item->title) {
-      $ItemName = $item->title;
-    }
-    elseif ($item->sku) {
-      $ItemName = $item->sku;
-    }
-    else {
-      $ItemName = $item->nid;
-    }
-    $ItemNumber = $item->nid;
-    $ItemQty = $item->qty;
-    $ItemPrice = $item->price;
-    $SOAPrequest .= <<< End_Of_Quote
-							<PaymentItem>
-								<Name>$ItemName</Name>
-								<Number>$ItemNumber</Number>
-								<Quantity>$ItemQty</Quantity>
-								<Amount currencyID="USD">$ItemPrice</Amount>
-							</PaymentItem>
-End_Of_Quote;
+function paypalpro_make_SOAP_request($SOAPrequest, &$errno, &$error) {
+  $ch = curl_init();
+  curl_setopt($ch, CURLOPT_URL, variable_get('paypalpro_url', 'https://api.sandbox.paypal.com/2.0/'));
+  curl_setopt($ch, CURLOPT_SSLCERT, variable_get('paypalpro_sslcert', ''));
+  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
+  curl_setopt($ch, CURLOPT_POSTFIELDS, $SOAPrequest);
+  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+  $exec_return = curl_exec($ch);
+  if ($errno = curl_errno($ch)) {
+    $error = curl_error($ch);
   }
-
-  $SOAPrequest .= <<< End_Of_Quote
-					</PaymentDetails>
-				</DoExpressCheckoutPaymentRequestDetails>
-			</DoExpressCheckoutPaymentRequest>
-		</DoExpressCheckoutPaymentReq>
-	</SOAP-ENV:Body>
-</SOAP-ENV:Envelope>
-End_Of_Quote;
-
-  return $SOAPrequest;
+  curl_close($ch);
+  
+  return $exec_return;
 }
 
 /**
@@ -1006,36 +674,68 @@ function paypalpro_paymentrequest($t, $e
   $Custom = '';       // TODO: what is this?
   $InvoiceID = $edit->txnid;
 
-  $ShipToStreet1 = $t->shipping_street1;
-  $ShipToStreet2 = $t->shipping_street2;
-  $ShipToCityName = $t->shipping_city;
-  $ShipToStateOrProvince = $t->shipping_state;
-  $ShipToCountry = strtoupper($t->shipping_country);  // TODO
+  $ShipToStreet1 = $t->address['shipping']->street1;
+  $ShipToStreet2 = $t->address['shipping']->street2;
+  $ShipToCityName = $t->address['shipping']->city;
+  $ShipToStateOrProvince = $t->address['shipping']->state;
+  $ShipToCountry = strtoupper($t->address['shipping']->country);
   $ShipToPhone = '';  // TODO: where do we find the phone number
-  $ShipToPostalCode = $t->shipping_zip;
+  $ShipToPostalCode = $t->address['shipping']->zip;
+
+  $CreditCardType = $paypalpro_cc_types[$edit['cc_type']];
+  $CreditCardNumber = $edit['cc_number'];
+  $ExpMonth = $edit['cc_month'];
+  $ExpYear = $edit['cc_year'];
+  $CVV2 = $edit['cvv2'];
+
+  $Salutation = '';
+  $FirstName = $edit['cc_first_name'];
+  $MiddleName = $edit['cc_middle_name'];
+  $LastName = $edit['cc_last_name'];
+  $Suffix = '';
+
+  $Name = $t->address['billing']->firstname .' '. $t->address['billing']->lastname;
+  $Street1 = $t->address['billing']->street1;
+  $Street2 = $t->address['billing']->street2;
+  $CityName = $t->address['billing']->city;
+  $StateOrProvince = $t->address['billing']->state;
+  $Country = strtoupper($t->address['billing']->country);
+  $CountryName = store_get_country($t->address['billing']->country);
+  $Phone = ''; // TODO update phone handling
+  $PostalCode = $t->address['billing']->zip;
+
+  $AddressID = '';
+  $AddressOwner = '';
+  $ExternalAddressID = '';
+  $InternationalName = '';
+  $InternationalStateAndCity = '';
+  $InternationalStreet = '';
+  $AddressStatus = '';
+
+  $IPAddress = $_SERVER['REMOTE_ADDR'];
 
   $SOAPrequest = <<< End_Of_Quote
 <?xml version="1.0" encoding="UTF-8"?>
 <SOAP-ENV:Envelope
-	xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
-	xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
-	xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
-	xmlns:xsd="http://www.w3.org/1999/XMLSchema"
-	SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-	<SOAP-ENV:Header>
-		<RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"
+  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
+  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
+  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+  <SOAP-ENV:Header>
+    <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"
 SOAP-ENV:mustUnderstand="1">
-			<Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
-				<Username>$username</Username>
-				<Password>$password</Password>
-			</Credentials>
-		</RequesterCredentials>
-	</SOAP-ENV:Header>
-	<SOAP-ENV:Body>
-		<DoDirectPaymentReq xmlns="urn:ebay:api:PayPalAPI">
-			<DoDirectPaymentRequest xmlns="urn:ebay:api:PayPalAPI">
-				<Version xmlns="urn:ebay:apis:eBLBaseComponents">1.0</Version>
-					<DoDirectPaymentRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">
+      <Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
+        <Username>$username</Username>
+        <Password>$password</Password>
+      </Credentials>
+    </RequesterCredentials>
+  </SOAP-ENV:Header>
+  <SOAP-ENV:Body>
+    <DoDirectPaymentReq xmlns="urn:ebay:api:PayPalAPI">
+      <DoDirectPaymentRequest xmlns="urn:ebay:api:PayPalAPI">
+        <Version xmlns="urn:ebay:apis:eBLBaseComponents">1.0</Version>
+          <DoDirectPaymentRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">
 
     <PaymentAction>Sale</PaymentAction>
     <PaymentDetails>
@@ -1076,38 +776,6 @@ function paypalpro_paymentrequest($t, $e
 End_Of_Quote;
   }
 
-  $CreditCardType = $paypalpro_cc_types[$edit['cc_type']];
-  $CreditCardNumber = $edit['cc_number'];
-  $ExpMonth = $edit['cc_month'];
-  $ExpYear = $edit['cc_year'];
-  $CVV2 = $edit['cvv2'];
-
-  $Salutation = '';
-  $FirstName = $edit['cc_first_name'];
-  $MiddleName = $edit['cc_middle_name'];
-  $LastName = $edit['cc_last_name'];
-  $Suffix = '';
-
-  $Name = $t->billing_firstname .' '. $t->billing_lastname;
-  $Street1 = $t->billing_street1;
-  $Street2 = $t->billing_street2;
-  $CityName = $t->billing_city;
-  $StateOrProvince = $t->billing_state;
-  $Country = strtoupper($t->billing_country);  // TODO
-  $CountryName = store_get_country($t->billing_country);
-  $Phone = ''; // TODO
-  $PostalCode = $t->billing_zip;
-
-  $AddressID = '';
-  $AddressOwner = '';
-  $ExternalAddressID = '';
-  $InternationalName = '';
-  $InternationalStateAndCity = '';
-  $InternationalStreet = '';
-  $AddressStatus = '';
-
-  $IPAddress = $_SERVER['REMOTE_ADDR'];
-
   $SOAPrequest .= <<< End_Of_Quote
     </PaymentDetails>
     <CreditCard>
@@ -1152,91 +820,13 @@ function paypalpro_paymentrequest($t, $e
     <IPAddress>$IPAddress</IPAddress>
     <MerchantSessionId>$MerchantSessionId</MerchantSessionId>
 
-				</DoDirectPaymentRequestDetails>
-			</DoDirectPaymentRequest>
-		</DoDirectPaymentReq>
+        </DoDirectPaymentRequestDetails>
+      </DoDirectPaymentRequest>
+    </DoDirectPaymentReq>
 
-	</SOAP-ENV:Body>
+  </SOAP-ENV:Body>
 </SOAP-ENV:Envelope>
 End_Of_Quote;
 
   return $SOAPrequest;
 }
-
-/**
- * A simple xml parsing function.
- *
- * @param  $xml        A text string that contains the xml to be parsed.
- * @param  $open_tag   The opening xml tag to search for.
- * @param  $close_tag  The closing xml tag to search for.
- * @return string      The string between $open_tag and $close_tag
- */
-function paypalpro_parse_xml($xml, $open_tag, $close_tag) {
-  $pos1 = strpos($xml, $open_tag);
-  $pos2 = strpos($xml, $close_tag);
-  return substr($xml, $pos1 + strlen($open_tag), $pos2 - ($pos1 + strlen($open_tag)));
-}
-
-/**
- * A recursive xml parsing function for obtaining multiple error messages.
- *
- * @param  $xml        A text string that contains the xml to be parsed.
- * @param  $open_tag   The opening xml tag to search for.
- * @param  $close_tag  The closing xml tag to search for.
- * @return array       All error messages found in the xml.
- */
-function paypalpro_get_errors($xml) {
-  // errors in returned xml are comprised of short and long messages
-  define(SHORT_OPEN, '<ShortMessage xsi:type="xs:string">');
-  define(SHORT_CLOSE, '</ShortMessage>');
-  define(LONG_OPEN, '<LongMessage xsi:type="xs:string">');
-  define(LONG_CLOSE, '</LongMessage>');
-  $errors = array();
-  // loop through xml to find all error messages
-  $loop = TRUE;
-  while ($loop) {
-    // test if there are any more errors by looking for the SHORT_OPEN string
-    if (strpos($xml, SHORT_OPEN)) {
-      $error['short'] = paypalpro_parse_xml($xml, SHORT_OPEN, SHORT_CLOSE);
-      // there's no need to report the generic "Internal Error"
-      if ($error['short'] != 'Internal Error') {
-        $error['long'] = paypalpro_parse_xml($xml, LONG_OPEN, LONG_CLOSE);
-        $errors[] = $error;
-      }
-      $xml = substr($xml, strpos($xml, LONG_CLOSE) + strlen(LONG_CLOSE));
-    }
-    else {
-      // no more error messages found
-      $loop = FALSE;
-    }
-  }
-  return $errors;
-}
-
-/**
- * Look for an adress in the user's address book that exactly matches the
- * address returned by PayPal.  If found, return the aid.
- *
- * @param  $edit  The $edit array, with address info returned from PayPal.
- * @return $aid   The id of the address entry in the database, if match found.
- */
-function paypalpro_address_get_aid($edit) {
-  $address = db_fetch_object(db_query("SELECT aid FROM {ec_address} WHERE uid = %d AND firstname = '%s' AND lastname = '%s' AND street1 = '%s' AND street2 = '%s' AND zip = '%s' AND city = '%s' AND state = '%s' AND country = '%s'", $edit->uid, $edit->firstname, $edit->lastname, $edit->street1, $edit->street2, $edit->zip, $edit->city, $edit->state, $edit->country));
-  return $address->aid;
-}
-
-/**
- * Save the address returned from PayPal in the local address book, then return
- * the aid.  If the address already exists, simply return the aid.
- *
- * @param  $edit  The $edit array with address info returned from PayPal.
- * @return @aid   The id of the address entry in the database.
- */
-function paypalpro_address_save($edit) {
-  $aid = paypalpro_address_get_aid($edit);
-  if (!$aid) {
-    db_query("INSERT INTO {ec_address} (uid, firstname, lastname, street1, street2, zip, city, state, country) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $edit->uid, $edit->firstname, $edit->lastname, $edit->street1, $edit->street2, $edit->zip, $edit->city, $edit->state, $edit->country);
-    $aid = paypalpro_address_get_aid($edit);
-  }
-  return $aid;
-}
--- ecommerce/contrib/paypalpro/paypalpro_express.module	2006/03/13 00:36:16
+++ ecommerce/contrib/paypalpro/paypalpro_express.module	2006/07/11 13:11:42
@@ -13,6 +13,29 @@ function paypalpro_express_help($section
 }
 
 /**
+ * Implementation of Drupal _menu() hook.
+ *
+ * @param  $may_cache  Flag to indicate if menu items can be cached.
+ * @return array       Menu items.
+ */
+function paypalpro_express_menu($may_cache) {
+  $items = array();
+
+  if ($may_cache) {
+    $items[] = array(
+      'path' => 'paypalpro/redirect', 'title' => t('Express checkout redirect'),
+      'callback' => 'paypalpro_express_checkout_redirect', 'access' => TRUE, 
+      'type' => MENU_CALLBACK);
+    $items[] = array(
+      'path' => 'paypalpro/express', 'title' => t('PayPal Express Checkout'),
+      'callback' => 'paypalpro_express_return', 'access' => TRUE, 
+      'type' => MENU_CALLBACK);
+  }
+
+  return $items;
+}
+
+/**
  * Implementation of hook_paymentapi()
  */
 function paypalpro_express_paymentapi(&$txn, $op) {
@@ -24,7 +47,7 @@ function paypalpro_express_paymentapi(&$
     case 'form':
       $form[] = array(
         '#type' => 'markup',
-        '#value' => '<img src="https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif" align="left" style="margin-right:7px;">',
+        '#value' => '<img src="https://www.paypal.com/en_US/i/logo/PayPal_mark_37x23.gif" align="left" style="margin-right:7px; margin-top: 0.5em;"><span style="font-size:11px; font-family: Arial, Verdana;">Save time.  Checkout securely.  Pay without sharing your financial information.</span>'
       );
       return $form;
       break;
@@ -52,3 +75,396 @@ function paypalpro_express_checkoutapi(&
       break;
   }
 }
+
+
+/**
+ * Redirects the user to PayPal's secure site.  The goal is to have them log 
+ * in, select an address, and to obtain a token from PayPal used to actually 
+ * charge the user's account.  There is no return from this function, we 
+ * either redirect to PayPal's website, or on an error we go back to the 
+ * shopping cart.
+ * 
+ * TODO on error we should probably go back to payment select.
+ */
+function paypalpro_express_checkout_redirect() {
+  $txn = ec_checkout_get_data();
+
+  // submit a SOAP SetExpressCheckoutRequest message with curl
+  $SOAPrequest = paypalpro_setExpressCheckoutRequest(store_transaction_calc_gross($txn));
+  $errno = $error = 0;
+  $exec_return = paypalpro_make_SOAP_request($SOAPrequest, $errno, $error);
+
+  if (strpos($exec_return, 'Success')) {
+    $token = paypalpro_parse_xml($exec_return, '<Token xsi:type="ebl:ExpressCheckoutTokenType">', '</Token');
+    
+    $query = "cmd=_express-checkout&token=$token";
+    drupal_goto(variable_get('paypalpro_express_url', 'https://www.sandbox.paypal.com/cgi-bin/webscr'), $query);
+  }
+  elseif (strpos($exec_return, 'Failure')) {
+    $errors = paypalpro_get_errors($exec_return);
+    foreach ($errors as $error) {
+      paypalpro_set_error($error);
+    }
+  }
+  else {
+    drupal_set_message('<b>Communication error.</b>  Failed to connect to the authentication server.  Please try again later.', 'error');
+    if ($error) {
+      // Curl errors: http://curl.haxx.se/libcurl/c/libcurl-errors.html
+      drupal_set_message("(libcurl error #$errno: $error)", 'error');
+    }
+  }
+  drupal_goto('cart/checkout');
+}
+
+/**
+ * This is the return entry point for PayPal Express.  It handles the
+ * information given by PayPal and returns the user to the correct location in
+ * the checkout process.
+ * 
+ * @param $arg drupal path arg to let PayPal cancel using paypal express.
+ */
+function paypalpro_express_return($arg = NULL) {
+  global $user;
+
+  $token = array_key_exists('token', $_GET) ? $_GET['token'] : '';
+  $txn = ec_checkout_get_data();
+
+  if ($token && $arg != 'cancel') {
+    // submit a SOAP GetExpressCheckoutDetails message with curl
+    $SOAPrequest = paypalpro_getExpressCheckoutDetails($token);
+    $errno = $error = 0;
+    $exec_return = paypalpro_make_SOAP_request($SOAPrequest, $errno, $error);
+
+    if (strpos($exec_return, 'Success')) {
+      $shippable = FALSE;
+      foreach ($txn->items as $item) {
+        if (product_is_shippable($item->nid)) {
+          $shippable = TRUE;
+          break;
+        }
+      }
+
+      // If the address module is enabled we need to skip that screen
+      if ($address_key = array_search('address', $txn->screens)) {
+        unset($txn->screens[$address_key]);
+      }
+
+      // Prepare shipping/billing fields for storage
+      $txn->address['shipping']->firstname  = paypalpro_parse_xml($exec_return, '<FirstName xmlns="urn:ebay:apis:eBLBaseComponents">', '</FirstName>');
+      $txn->address['shipping']->lastname   = paypalpro_parse_xml($exec_return, '<LastName xmlns="urn:ebay:apis:eBLBaseComponents">', '</LastName>');
+      $txn->address['shipping']->street1    = paypalpro_parse_xml($exec_return, '<Street1 xsi:type="xs:string">', '</Street1>');
+      $txn->address['shipping']->street2    = paypalpro_parse_xml($exec_return, '<Street2 xsi:type="xs:string">', '</Street2>');
+      $txn->address['shipping']->city       = paypalpro_parse_xml($exec_return, '<CityName xsi:type="xs:string">', '</CityName>');
+      $txn->address['shipping']->state      = paypalpro_parse_xml($exec_return, '<StateOrProvince xsi:type="xs:string">', '</StateOrProvince>');
+      $txn->address['shipping']->zip        = paypalpro_parse_xml($exec_return, '<PostalCode xsi:type="xs:string">', '</PostalCode>');
+      $txn->address['shipping']->country    = strtolower(paypalpro_parse_xml($exec_return, '<Country xsi:type="ebl:CountryCodeType">', '</Country>'));
+      
+      //paypalpro_address_save();
+      // We don't have a billing address as that is stored with paypal.  Just use
+      // shipping for now.  We could add the paypal id in the future to be helpful.
+      $txn->address['billing']->firstname = $txn->address['shipping']->firstname;
+      $txn->address['billing']->lastname  = $txn->address['shipping']->lastname;
+      $txn->address['billing']->street1   = $txn->address['shipping']->street1;
+      $txn->address['billing']->street2   = $txn->address['shipping']->street2;
+      $txn->address['billing']->city      = $txn->address['shipping']->city;
+      $txn->address['billing']->state     = $txn->address['shipping']->state;
+      $txn->address['billing']->zip       = $txn->address['shipping']->zip;
+      $txn->address['billing']->country   = $txn->address['shipping']->country;
+      
+      // TODO should we overwrite the payment email with the one we get from paypal?
+      $txn->mail = paypalpro_parse_xml($exec_return, '<Payer xsi:type="ebl:EmailAddressType">', '</Payer>');
+
+      // Other Paypal info we might use
+      // TODO add support for address_status and spp http://www.paypal.com/spp 
+      //$address_status = paypalpro_parse_xml($exec_return, '<AddressStatus xsi:type="ebl:AddressStatusCodeType">', '</AddressStatus>');
+      //$account = paypalpro_parse_xml($exec_return, '<Name xsi:type="xs:string">', '</Name>');
+      //$business = paypalpro_parse_xml($exec_return, '<PayerBusiness xsi:type="xs:string">', '</PayerBusiness>');
+      //$address_type = paypalpro_parse_xml($exec_return, '<Address xsi:type="ebl:AddressType"><Name xsi:type="xs:string">', '</Name');
+      //$status = paypalpro_parse_xml($exec_return, '<PayerStatus xsi:type="ebl:PayPalUserStatusCodeType">', '</PayerStatus>');
+      //$countryCode = paypalpro_parse_xml($exec_return, '<Country xsi:type="ebl:CountryCodeType">', '</Country>');
+
+      // Store PPP tokens for later
+      $txn->paypalpro_token = paypalpro_parse_xml($exec_return, '<Token xsi:type="ebl:ExpressCheckoutTokenType">', '</Token');
+      $txn->paypalpro_payerid = paypalpro_parse_xml($exec_return, '<PayerID xsi:type="ebl:UserIDType">', '</PayerID>');
+
+      // Cleanup and move on
+      $txn->screen++;  // go to next screen
+      ec_checkout_hide_data($txn);
+      drupal_goto('cart/checkout', 'op=next');
+    }
+  }
+
+  // Try to get back to payment selection on failure.
+  $txn->screen--;
+  ec_checkout_hide_data($txn);
+  drupal_goto('cart/checkout', 'op=next');
+}
+
+/**
+ * This is the final step in Express Checkout where we actually bill the 
+ * user's PayPal account.
+ *
+ * @param $txn   The current transaction object.
+ */
+function paypalpro_express_checkout_process($txn) {
+  global $user, $base_url;
+  
+  // submit a SOAP DoExpressCheckoutPayment message with curl
+  $SOAPrequest = paypalpro_doExpressCheckoutPayment($txn);
+  $errno = $error = 0;
+  $exec_return = paypalpro_make_SOAP_request($SOAPrequest, $errno, $error);
+
+  if (strpos($exec_return, 'Success')) {
+    $txn->proid = paypalpro_parse_xml($exec_return, '<TransactionID>', '</TransactionID>');
+    // TODO: fix this when non-USD are supported
+    $txn->amount = paypalpro_parse_xml($exec_return, '<GrossAmount xsi:type="cc:BasicAmountType" currencyID="USD">', '</GrossAmount>');
+
+    // set e-commerce API transaction payment status to 'completed'.
+    $txn->payment_status = payment_get_status_id('completed');
+    // transaction handled by paypalpro module
+    $txn->payment_method = 'paypalpro';
+
+    $is_new = (db_result(db_query('SELECT COUNT(txnid) FROM {ec_paypalpro} WHERE txnid = %d', $txn->txnid))) ? FALSE : TRUE;
+    $txnid = store_transaction_save($txn);
+
+    if ($is_new && $txnid) {
+      // compose and send confirmation email to the user
+      store_send_invoice_email($txnid);
+    }
+
+    // transaction complete, make sure we return to http://
+    $goto = str_replace('https://', 'http://', $base_url);
+    drupal_goto("$goto/" . url(strtr(variable_get('paypalpro_success_url', 'store/transaction/view/%txnid'), array('%txnid' => $txnid))));
+  }
+  elseif (strpos($exec_return, 'Failure')) {
+    $errors = paypalpro_get_errors($exec_return);
+    foreach ($errors as $error) {
+      paypalpro_set_error($error);
+    }
+  }
+  else {
+    drupal_set_message('<b>Communication error.</b>  Failed to connect to the authentication server.  Please try again later.', 'error');
+    if ($error) {
+      // Curl errors: http://curl.haxx.se/libcurl/c/libcurl-errors.html
+      drupal_set_message("(libcurl error #$errno: $error)", 'error');
+    }
+  }
+}
+
+/**
+ * Manaually generate a SOAP request (this is perhaps ugly, but it greatly
+ * simplifies the installation process as we don't have to require PEAR or
+ * the PayPal PHP API). 
+ *
+ * This SOAP request begins the Express Checkout process.
+ *
+ * @param  $total  The order total, including shipping/handling/tax.
+ * @return SOAP    The SOAP expresscheckoutrequest message.
+ */
+function paypalpro_setExpressCheckoutRequest($total) {
+  $username = variable_get('paypalpro_username', '');
+  $password = variable_get('paypalpro_password', '');
+  $OrderDescription = '';  // TODO: description?
+  $OrderTotal = $total;
+
+  // succesful payment at PayPal's website
+  $ReturnURL = url('paypalpro/express', NULL, NULL, TRUE);
+  // cancelled or otherwise unsuccessful payment at Paypal's website, 
+  // return to checkout start
+  $CancelURL = url('paypalpro/express/cancel', NULL, NULL, TRUE);
+  //$CancelURL = url('cart/checkout', 'op=next', NULL, TRUE);
+
+  // if configured for secure connections, rewrite http:// a http://
+  if (variable_get('paypalpro_secure', 1)) {
+    $ReturnURL = str_replace('http://', 'https://', $ReturnURL);
+    $CancelURL = str_replace('http://', 'https://', $CancelURL);
+  }
+
+$SOAPrequest = <<< End_Of_Quote
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope
+  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
+  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
+  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+  <SOAP-ENV:Header>
+    <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" SOAP-ENV:mustUnderstand="1">
+      <Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
+        <Username>$username</Username>
+        <Password>$password</Password>
+      </Credentials>
+    </RequesterCredentials>
+  </SOAP-ENV:Header>
+  <SOAP-ENV:Body>
+    <SetExpressCheckoutReq xmlns="urn:ebay:api:PayPalAPI">
+      <SetExpressCheckoutRequest>
+        <Version xmlns="urn:ebay:apis:eBLBaseComponents">1.0</Version>
+        <SetExpressCheckoutRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">
+          <OrderTotal currencyID="USD" xsl:type="cc:BasicAmountType">$OrderTotal</OrderTotal>
+          <OrderDescription>$OrderDescription</OrderDescription>
+          <ReturnURL>$ReturnURL</ReturnURL>
+          <CancelURL>$CancelURL</CancelURL>
+        </SetExpressCheckoutRequestDetails>
+      </SetExpressCheckoutRequest>
+    </SetExpressCheckoutReq>
+  </SOAP-ENV:Body>
+</SOAP-ENV:Envelope>
+End_Of_Quote;
+
+  return $SOAPrequest;
+}
+
+/**
+ * Manaually generate a SOAP request (this is perhaps ugly, but it greatly
+ * simplifies the installation process as we don't have to require PEAR or
+ * the PayPal PHP API). 
+ *
+ * This SOAP request gets the user's details from PayPal.
+ *
+ * @param  $token  A token provided by PayPal, used to track the order to completion.
+ * @return SOAP    The SOAP expresscheckoutrequest message.
+ */
+function paypalpro_getExpressCheckoutDetails($token) {
+  $username = variable_get('paypalpro_username', '');
+  $password = variable_get('paypalpro_password', '');
+
+$SOAPrequest = <<< End_Of_Quote
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope
+  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
+  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
+  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+  <SOAP-ENV:Header>
+    <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"
+SOAP-ENV:mustUnderstand="1">
+      <Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
+        <Username>$username</Username>
+        <Password>$password</Password>
+      </Credentials>
+    </RequesterCredentials>
+  </SOAP-ENV:Header>
+  <SOAP-ENV:Body>
+    <GetExpressCheckoutDetailsReq xmlns="urn:ebay:api:PayPalAPI">
+      <GetExpressCheckoutDetailsRequest>
+        <Version xmlns="urn:ebay:apis:eBLBaseComponents">1.00</Version>
+        <Token>$token</Token>
+      </GetExpressCheckoutDetailsRequest>
+    </GetExpressCheckoutDetailsReq>
+  </SOAP-ENV:Body>
+</SOAP-ENV:Envelope>
+End_Of_Quote;
+
+  return $SOAPrequest;
+}
+
+/**
+ * Manaually generate a SOAP request (this is perhaps ugly, but it greatly
+ * simplifies the installation process as we don't have to require PEAR or
+ * the PayPal PHP API). 
+ *
+ * This SOAP request actually submits the order and withdrawls the money
+ * from the user's PayPal account.
+ *
+ * @param  $txn  The current transaction object.
+ */
+function paypalpro_doExpressCheckoutPayment($txn) {
+  $username = variable_get('paypalpro_username', '');
+  $password = variable_get('paypalpro_password', '');
+
+  $token = $txn->paypalpro_token;
+  $payerid = $txn->paypalpro_payerid;
+
+  $OrderTotal = $txn->gross;
+  $ItemTotal = $txn->subtotal;
+  $ShippingTotal = $txn->shipping_cost;
+  $HandlingTotal = 0; // not currently used
+  $TaxTotal = 0; // not currently used
+  $ShipName = $txn->shipping_firstname .' '. $txn->shipping_lastname;
+  $Street1 = $txn->shipping_street1;
+  $Street2 = $txn->shipping_street2;
+  $CityName = $txn->shipping_city;
+  $StateOrProvince = $txn->shipping_state;
+  $Country = strtoupper($txn->shipping_country);
+  $PostalCode = $txn->shipping_zip;
+
+$SOAPrequest = <<< End_Of_Quote
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope
+  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
+  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
+  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+  <SOAP-ENV:Header>
+    <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"
+SOAP-ENV:mustUnderstand="1">
+      <Credentials xmlns="urn:ebay:apis:eBLBaseComponents">
+        <Username>$username</Username>
+        <Password>$password</Password>
+      </Credentials>
+    </RequesterCredentials>
+  </SOAP-ENV:Header>
+
+  <SOAP-ENV:Body>
+    <DoExpressCheckoutPaymentReq xmlns="urn:ebay:api:PayPalAPI">
+      <DoExpressCheckoutPaymentRequest>
+        <Version xmlns="urn:ebay:apis:eBLBaseComponents">1.0</Version>
+          <DoExpressCheckoutPaymentRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">
+            <PaymentAction>Sale</PaymentAction>
+            <Token>$token</Token>
+            <PayerID>$payerid</PayerID>
+            <PaymentDetails>
+              <OrderTotal currencyID="USD">$OrderTotal</OrderTotal>
+              <ItemTotal currencyID="USD">$ItemTotal</ItemTotal>
+              <ShippingTotal currencyID="USD">$ShippingTotal</ShippingTotal>
+              <HandlingTotal currencyID="USD">$HandlingTotal</HandlingTotal>
+              <TaxTotal currencyID="USD">$TaxTotal</TaxTotal>
+
+              <ShipToAddress>
+                <Name>$ShipName</Name>
+                <Street1>$Street1</Street1>
+                <Street2>$Street2</Street2>
+                <CityName>$CityName</CityName>
+                <StateOrProvince>$StateOrProvince</StateOrProvince>
+                <Country>$Country</Country>
+                <PostalCode>$PostalCode</PostalCode>
+              </ShipToAddress>
+End_Of_Quote;
+
+  foreach ($txn->items as $item) {
+    if ($item->title) {
+      $ItemName = $item->title;
+    }
+    elseif ($item->sku) {
+      $ItemName = $item->sku;
+    }
+    else {
+      $ItemName = $item->nid;
+    }
+    $ItemNumber = $item->nid;
+    $ItemQty = $item->qty;
+    $ItemPrice = $item->price;
+    $SOAPrequest .= <<< End_Of_Quote
+              <PaymentItem>
+                <Name>$ItemName</Name>
+                <Number>$ItemNumber</Number>
+                <Quantity>$ItemQty</Quantity>
+                <Amount currencyID="USD">$ItemPrice</Amount>
+              </PaymentItem>
+End_Of_Quote;
+  }
+
+  $SOAPrequest .= <<< End_Of_Quote
+          </PaymentDetails>
+        </DoExpressCheckoutPaymentRequestDetails>
+      </DoExpressCheckoutPaymentRequest>
+    </DoExpressCheckoutPaymentReq>
+  </SOAP-ENV:Body>
+</SOAP-ENV:Envelope>
+End_Of_Quote;
+
+  return $SOAPrequest;
+}
