From 6545023eca5ac9dfcf11cba073a07274dc2eab55 Mon Sep 17 00:00:00 2001
From: Lapith <brian.smith@opensesame.com>
Date: Thu, 12 May 2011 12:29:22 -0700
Subject: [PATCH 2/2] Added module for WPP.

---
 modules/wpp/commerce_paypal_wpp.info   |   13 +
 modules/wpp/commerce_paypal_wpp.module |  536 ++++++++++++++++++++++++++++++++
 2 files changed, 549 insertions(+), 0 deletions(-)
 create mode 100644 modules/wpp/commerce_paypal_wpp.info
 create mode 100644 modules/wpp/commerce_paypal_wpp.module

diff --git a/modules/wpp/commerce_paypal_wpp.info b/modules/wpp/commerce_paypal_wpp.info
new file mode 100644
index 0000000..139c2a1
--- /dev/null
+++ b/modules/wpp/commerce_paypal_wpp.info
@@ -0,0 +1,13 @@
+name = PayPal WPP
+description = Implements PayPal Website Payments Pro in Drupal Commerce checkout.
+package = Commerce (PayPal)
+dependencies[] = commerce
+dependencies[] = commerce_ui
+dependencies[] = commerce_payment
+dependencies[] = commerce_order
+dependencies[] = commerce_paypal
+core = 7.x
+
+; Base module files
+files[] = commerce_paypal_wpp.module
+
diff --git a/modules/wpp/commerce_paypal_wpp.module b/modules/wpp/commerce_paypal_wpp.module
new file mode 100644
index 0000000..9abb78a
--- /dev/null
+++ b/modules/wpp/commerce_paypal_wpp.module
@@ -0,0 +1,536 @@
+<?php
+
+/**
+ * @file
+ * Implements PayPal Website Payments Standard in Drupal Commerce checkout.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function commerce_paypal_wpp_menu() {
+  $items = array();
+
+  $items['paypal-test/wpp/%commerce_order'] = array(
+    'title' => 'PayPal WPP test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('commerce_paypal_wpp_form', 2),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_commerce_payment_method_info().
+ */
+function commerce_paypal_wpp_commerce_payment_method_info() {
+  $payment_methods = array();
+
+  $payment_methods['paypal_wpp'] = array(
+    'base' => 'commerce_paypal_wpp',
+    'title' => t('PayPal WPP'),
+    'short_title' => t('PayPal'),
+    'description' => t('PayPal Website Payments Standard'),
+    'terminal' => FALSE,
+    'offsite' => FALSE,
+  );
+
+  return $payment_methods;
+}
+
+/**
+ * Payment method callback: settings form.
+ */
+function commerce_paypal_wpp_settings_form($settings = NULL) {
+  $form = array();
+
+  // Merge default settings into the stored settings array.
+  $default_currency = variable_get('commerce_default_currency', 'USD');
+
+  $settings = (array) $settings + array(
+    'apiUserName' => '',
+    'apiPassword' => '',
+    'signature' => '',
+    'server' => 'sandbox',
+    'payment_action' => 'sale',
+  );
+
+  $form['apiUserName'] = array(
+    '#type' => 'textfield',
+    '#title' => t('API UserName:'),
+    '#description' => t('your paypal api user name.'),
+    '#default_value' => $settings['apiUserName'],
+    '#required' => TRUE,
+  );
+  $form['apiPassword'] = array(
+    '#type' => 'textfield',
+    '#title' => t('PayPal account password'),
+    '#default_value' => $settings['apiPassword'],
+    '#required' => TRUE,
+  );
+  $form['signature'] = array(
+    '#type' => 'textfield',
+    '#title' => t('PayPal account signature'),
+    '#default_value' => $settings['signature'],
+    '#required' => TRUE,
+  );
+  $form['server'] = array(
+    '#type' => 'radios',
+    '#title' => t('PayPal server'),
+    '#options' => array(
+      'sandbox' => ('Sandbox - use for testing, requires a PayPal Sandbox account'),
+      'live' => ('Live - use for processing real transactions'),
+    ),
+    '#default_value' => $settings['server'],
+  );
+  $form['payment_action'] = array(
+    '#type' => 'radios',
+    '#title' => t('Payment action'),
+    '#options' => array(
+      'sale' => t('Sale - authorize and capture the funds at the time the payment is processed'),
+      'authorization' => t('Authorization - reserve funds on the card to be captured later through your PayPal account'),
+    ),
+    '#default_value' => $settings['payment_action'],
+  );
+
+  return $form;
+}
+
+function commerce_paypal_wpp_submit_form($payment_method, $pane_values, $checkout_pane, $order)
+{
+  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
+  return commerce_payment_credit_card_form(array('code' => 'CVV:'), array('code' => ''));
+}
+
+function commerce_paypal_wpp_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array())
+{
+  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
+
+  // Validate the credit card fields.
+  $settings = array(
+    'form_parents' => array_merge($form_parents, array('credit_card')),
+  );
+
+  if (!commerce_payment_credit_card_validate($pane_values['credit_card'], $settings)) {
+    return FALSE;
+  }
+}
+
+function commerce_paypal_wpp_paypal_ipn_process($order, $payment_method, &$ipn)
+{
+  $prior_ipn = commerce_paypal_ipn_load($ipn['txn_id']);
+  
+  if($prior_ipn === NULL)
+  {
+    return FALSE;
+  }
+  
+  $transaction = commerce_payment_transaction_load($prior_ipn['transaction_id']);
+  
+  $ipn['transaction_id'] = $transaction->transaction_id;
+  
+  switch ($ipn['payment_status']) {
+      case 'Canceled_Reversal':
+        $transaction->message = 'PayPal has cancelled the reversal and returned @amount @currency to your account.';
+        $transaction->message_variables = array('@amount' => $ipn['mc_gross'], '@currency' => $ipn['mc_currency']);
+        $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+        break;
+
+      case 'Completed':
+        $transaction->message = 'PayPal IPN reported a payment of @amount @currency.';
+        $transaction->message_variables = array('@amount' => $ipn['mc_gross'], '@currency' => $ipn['mc_currency']);
+        $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+        break;
+
+      case 'Denied':
+        $transaction->message = "You have denied the customer's payment.";
+        $transaction->message_variables = array();
+        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        break;
+
+      case 'Expired':
+        $transaction->message = "The authorization has failed due to expiration and cannot be captured.";
+        $transaction->message_variables = array();
+        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        break;
+
+      case 'Failed':
+        $transaction->message = "The customer's attempted payment from a bank account failed.";
+        $transaction->message_variables = array();
+        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        break;
+
+      case 'Pending':
+        $transaction->message = "Payment is pending at PayPal: @reason";
+        $transaction->message_variables = array('@reason' => _commerce_paypal_wpp_pending_message(check_plain($ipn['pending_reason'])));
+        $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
+        break;
+
+      case 'Refunded':
+        $transaction->message = 'PayPal with transaction ID: @txn_id has been refunded.';
+        $transaction->message_variables = array('@txn_id' => $ipn['txn_id']);
+        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        break;
+
+      case 'Reversed':
+        $transaction->message = 'Payment has been reversed by PayPal: @reason';
+        $transaction->message_variables = array('@reason' => _commerce_paypal_wpp_reversal_message(check_plain($ipn['reason_code'])));
+        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        break;
+
+      case 'Processed':
+        $transaction->message = 'Payment accepted.';
+        $transaction->message_variables = array();
+        $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+        break;
+
+      case 'Voided':
+        $transaction->message = 'Payment has been voided by PayPal.';
+        $transaction->message_variables = array();
+        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+        break;
+    }
+    
+  $transaction->revision = TRUE;
+  commerce_payment_transaction_save($transaction);
+}
+
+function commerce_paypal_wpp_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge)
+{
+  global $user;
+
+  $profile = commerce_customer_profile_load($order->commerce_customer_billing['und']['0']['profile_id']);
+  
+  $transaction = _commerce_paypal_wpp_add_transaction($order, $payment_method, $charge);
+  
+  $time = time();
+  
+  $nvp_request = array(
+      'METHOD' => 'DoDirectPayment',
+      'PAYMENTACTION' => $payment_method['settings']['payment_action'],
+      'IPADDRESS' => ip_address(),
+      'AMT' => $charge['amount'],
+      'CREDITCARDTYPE' => commerce_paypal_wpp_card_type($pane_values['credit_card']['number']),
+      'ACCT' =>  $pane_values['credit_card']['number'],
+      'EXPDATE' => $pane_values['credit_card']['exp_month'] . $pane_values['credit_card']['exp_year'],
+      'CVV2' => $pane_values['credit_card']['code'],
+      'FIRSTNAME' => substr($user->data['profile_first_name'], 0, 25),//TODO
+      'LASTNAME' => substr($user->data['profile_last_name'], 0, 25),//TODO
+      'STREET' => substr($profile->commerce_customer_address['und']['0']['thoroughfare'], 0, 100),
+      'STREET2' => substr($profile->commerce_customer_address['und']['0']['sub_premise'], 0, 100),
+      'CITY' => substr($profile->commerce_customer_address['und']['0']['locality'], 0, 40),
+      'STATE' => $profile->commerce_customer_address['und']['0']['administrative_area'],
+      'ZIP' => $profile->commerce_customer_address['und']['0']['postal_code'],
+      'COUNTRYCODE' => $profile->commerce_customer_address['und']['0']['country'],
+      'CURRENCYCODE' => $charge['currency_code'],
+      'DESC' => "",
+      'INVNUM' => commerce_paypal_ipn_invoice($order),
+      'BUTTONSOURCE' => 'Drupal_Commerce_DP_US',
+      'NOTIFYURL' => commerce_paypal_ipn_url($settings['payment_method']),
+      'EMAIL' => substr($user->mail, 0, 127),
+      //'PHONENUM' => substr($order->billing_phone, 0, 20),//TODO
+    );
+  
+  $nvp_response = commerce_paypal_wpp_api_request($nvp_request, GetServer($payment_method['settings']['server']), $payment_method['settings']['apiUserName'], $payment_method['settings']['apiPassword'], $payment_method['settings']['signature']);
+  
+  $ipn = array(
+    'order_id' => $order->order_id,
+    'txn_id' => $nvp_response['TRANSACTIONID'],
+    'txn_type' => $nvp_request['PAYMENTACTION'],
+    'receiver_email' => '',
+    'payer_email' => $user->mail,
+    'transaction_id' => $transaction->transaction_id,
+    'mc_gross' => $charge['amount'],
+    'mc_currency' => $charge['currency_code'],
+    'payment_status' => 'Completed',
+    'payment_type' => $nvp_request['CREDITCARDTYPE']
+  );
+  
+  $transaction->remote_status = $nvp_response['ACK'];
+  $transaction->remote_id = $nvp_response['TRANSACTIONID'];
+  
+  switch ($nvp_response['ACK']) {
+    case 'SuccessWithWarning':
+      $warningMessage = _commerce_paypal_wpp_build_error_messages($nvp_response);
+
+      $transaction->message = $warningMessage;
+      $transaction->message_variables = array();
+      $ipn['payment_status'] = 'Complete with Warning';
+      
+      // fall through
+    case 'Success':
+      if($nvp_response['ACK'] != 'SuccessWithWarning') // make sure not to overwrite the warning
+      {
+        $transaction->message = "Success";
+        $transaction->message_variables = array();
+      }
+      _addPaypalMatchInfo($transaction, $nvp_response);
+      
+      if ($nvp_response['txn_type'] != COMMERCE_CREDIT_AUTH_ONLY) {
+        $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+      }
+      break;
+    case 'FailureWithWarning':
+      // fall through
+    case 'Failure':  
+      $transaction->message = _commerce_paypal_wpp_build_error_messages($nvp_response);
+      $transaction->message_variables = array();
+      _addPaypalMatchInfo($transaction, $nvp_response);
+      
+      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+      $ipn['payment_status'] = 'Failure';
+      break;
+    default:
+      $transaction->message = 'Unexpected acknowledgement status: @status';
+      $transaction->message_variables = array('@status' => $nvp_response['ACK']);
+      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+      $ipn['payment_status'] = 'Unknown';
+      break;
+  }
+  
+  commerce_paypal_ipn_save($ipn);
+  $transaction->revision = TRUE;
+  commerce_payment_transaction_save($transaction);
+  
+  if($transaction->status == COMMERCE_PAYMENT_STATUS_FAILURE)
+  {
+    drupal_set_message(t('We received the following error processing your card. Please enter you information again or try a different card.'));
+    drupal_set_message(t($transaction->message, $transaction->message_variables));
+    return FALSE;
+  }
+  
+  return TRUE;
+}
+
+function _addPaypalMatchInfo($transaction, $nvp_response)
+{
+  if ($nvp_response['txn_type'] != COMMERCE_CREDIT_PRIOR_AUTH_CAPTURE) {
+    $transaction->message .= ': Address: @addressMatch CVV: @cvvMatch';
+    $transaction->message_variables = array('@addressMatch' => _commerce_paypal_wpp_avscode_message($nvp_response['AVSCODE']),
+                                            '@cvvMatch' => _commerce_paypal_wpp_cvvmatch_message($nvp_response['CVV2MATCH']));
+  }
+}
+
+function _commerce_paypal_wpp_add_transaction($order, $paymentMethod, $charge)
+{
+  $transaction = commerce_payment_transaction_new('commerce_paypal_wpp', $order->order_id);
+  $transaction->instance_id = $payment_method['instance_id'];
+  $transaction->amount = $charge['amount'];
+  $transaction->currency_code = $charge['currency_code'];
+  $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
+  $transaction->message = 'Transaction being sent to: @destination';
+  $transaction->message_variables = array('@destination' => $payment_method['settings']['server']);
+  $transaction->remote_status = 'Sent to Paypal';
+  return commerce_payment_transaction_save($transaction);
+}
+
+// Return a message for the reason code of a PayPal reversal.
+function _commerce_paypal_wpp_reversal_message($reason) {
+  switch ($reason) {
+    case 'chargeback':
+      return t('The customer has initiated a chargeback.');
+    case 'guarantee':
+      return t('The customer triggered a money-back guarantee.');
+    case 'buyer-complaint':
+      return t('The customer filed a complaint about the transaction.');
+    case 'refund':
+      return t('You gave the customer a refund.');
+    case 'other':
+    default:
+      return t('Reason unknown; contact PayPal Customer Service for more information.');
+  }
+}
+
+// Return a message for the pending reason of a PayPal payment.
+function _commerce_paypal_wpp_pending_message($reason) {
+  switch ($reason) {
+    case 'address':
+      return t('Customer did not include a confirmed shipping address per your address settings.');
+    case 'authorization':
+      return t('Waiting on you to capture the funds per your authorization settings.');
+    case 'echeck':
+      return t('eCheck has not yet cleared.');
+    case 'intl':
+      return t('You must manually accept or deny this international payment from your Account Overview.');
+    case 'multi-currency':
+      return t('You must manually accept or deny a payment of this currency from your Account Overview.');
+    case 'unilateral':
+      return t('Your e-mail address is not yet registered or confirmed.');
+    case 'upgrade':
+      return t('You must upgrade your account to Business or Premier status to receive credit card payments.');
+    case 'verify':
+      return t('You must verify your account before you can accept this payment.');
+    case 'other':
+    default:
+      return t('Reason unknown; contact PayPal Customer Service for more information.');
+  }
+}
+
+// Returns a human readable message for the CVV2 match code.
+function _commerce_paypal_wpp_cvvmatch_message($code) {
+  if (is_numeric($code)) {
+    switch ($code) {
+      case '0':
+        return t('Matched');
+      case '1':
+        return t('No match');
+      case '2':
+        return t('The merchant has not implemented CVV2 code handling.');
+      case '3':
+        return t('Merchant has indicated that CVV2 is not present on card.');
+      case '4':
+        return t('Service not available');
+      default:
+        return t('Unkown error');
+    }
+  }
+
+  switch ($code) {
+    case 'M':
+      return t('Match');
+    case 'N':
+      return t('No match');
+    case 'P':
+      return t('Not processed');
+    case 'S':
+      return t('Service not supported');
+    case 'U':
+      return t('Service not available');
+    case 'X':
+      return t('No response');
+    default:
+      return t('Not checked');
+  }
+}
+
+// Returns a human readable message for the AVS code.
+function _commerce_paypal_wpp_avscode_message($code) {
+  if (is_numeric($code)) {
+    switch ($code) {
+      case '0':
+        return t('All the address information matched.');
+      case '1':
+        return t('None of the address information matched; transaction declined.');
+      case '2':
+        return t('Part of the address information matched.');
+      case '3':
+        return t('The merchant did not provide AVS information. Not processed.');
+      case '4':
+        return t('Address not checked, or acquirer had no response. Service not available.');
+      case 'Null':
+      default:
+        return t('No AVS response was obtained.');
+    }
+  }
+
+  switch ($code) {
+    case 'A':
+    case 'B':
+      return t('Address matched; postal code did not');
+    case 'C':
+    case 'N':
+      return t('Nothing matched; transaction declined');
+    case 'D':
+    case 'F':
+    case 'X':
+    case 'Y':
+      return t('Address and postal code matched');
+    case 'E':
+      return t('Not allowed for MOTO transactions; transaction declined');
+    case 'G':
+      return t('Global unavailable');
+    case 'I':
+      return t('International unavailable');
+    case 'P':
+    case 'W':
+    case 'Z':
+      return t('Postal code matched; address did not');
+    case 'R':
+      return t('Retry for validation');
+    case 'S':
+      return t('Service not supported');
+    case 'U':
+      return t('Unavailable');
+    default:
+      return t('An unknown error occurred.');
+  }
+}
+
+/**
+ * Build error message(s) from PayPal failure responses.
+ */
+function _commerce_paypal_wpp_build_error_messages($nvp_response) {
+  $code = 0;
+  $message = '';
+  while (array_key_exists('L_SEVERITYCODE'. $code, $nvp_response)) {
+    $message .= check_plain($nvp_response['L_SEVERITYCODE'. $code]) . check_plain($nvp_response['L_ERRORCODE'. $code]) .': '. check_plain($nvp_response['L_LONGMESSAGE'. $code]);
+    $code++;
+  }
+  return $message;
+}
+
+function GetServer($serverSetting)
+{
+  if($serverSetting == "live")
+  {
+    return 'https://www.paypal.com/cgi-bin/webscr';
+  }
+  return 'https://api-3t.sandbox.paypal.com/nvp';
+}
+
+function commerce_paypal_wpp_api_request($request, $server, $user, $password, $signature) {
+  $request['USER'] = $user;
+  $request['PWD'] = $password;
+  $request['VERSION'] = '3.0';
+  $request['SIGNATURE'] = $signature;
+
+  $data = '';
+  foreach ($request as $key => $value) {
+    $data .= $key .'='. urlencode(ereg_replace(',', '', $value)) .'&';
+  }
+  $data = substr($data, 0, -1);
+
+  $ch = curl_init();
+  curl_setopt($ch, CURLOPT_URL, $server);
+  curl_setopt($ch, CURLOPT_VERBOSE, 0);
+  curl_setopt($ch, CURLOPT_POST, 1);
+  curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
+  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+  $response = curl_exec($ch);
+  if ($error = curl_error($ch)) {
+    watchdog('commerce_paypal_wpp', '!error', array('!error' => $error), WATCHDOG_ERROR);
+  }
+  curl_close($ch);
+
+  return commerce_paypal_wpp_nvp_to_array($response);
+}
+
+// Turns PayPal's NVP response to an API call into an associative array.
+function commerce_paypal_wpp_nvp_to_array($nvpstr) {
+  foreach (explode('&', $nvpstr) as $nvp) {
+    list($key, $value) = explode('=', $nvp);
+    $nvp_array[urldecode($key)] = urldecode($value);
+  }
+
+  return $nvp_array;
+}
+
+function commerce_paypal_wpp_card_type($cc_number) {
+  switch (substr(strval($cc_number), 0, 1)) {
+    case '3':
+      return 'Amex';
+    case '4':
+      return 'Visa';
+    case '5':
+      return 'MasterCard';
+    case '6':
+      return 'Discover';
+  }
+
+  return FALSE;
+}
\ No newline at end of file
-- 
1.7.4.msysgit.0

