You are here

commerce_payleap.module in Commerce Payleap 7

Implements PayLeap payment services for use in Drupal Commerce.

File

commerce_payleap.module
View source
<?php

/**
 * @file
 * Implements PayLeap payment services for use in Drupal Commerce.
 */

// PayLeap transaction mode definitions:
define('PAYLEAP_TXN_MODE_PRODUCTION', 'production');
define('PAYLEAP_TXN_MODE_TEST', 'test');

// PayLeap request type definitions:
define('PAYLEAP_TXN_TYPE_DIRECT_CAPTURE', 'direct_capture');
define('PAYLEAP_TXN_TYPE_DELAYED_CAPTURE', 'delayed_capture');
define('PAYLEAP_TXN_TYPE_ADDRECURRINGCREDITCARD', 'add_recurring_credit_card');
define('PAYLEAP_TXN_TYPE_RECURRING_CAPTURE', 'recurring_capture');
define('PAYLEAP_TXN_TYPE_MANAGECREDITCARDINFO', 'manage_credit_card_info');
define('PAYLEAP_TXN_TYPE_MANAGECUSTOMER', 'manage_customer');
define('PAYLEAP_TXN_TYPE_MANAGECONTRACT', 'manage_contract');
define('PAYLEAP_TXN_TYPE_VOID', 'void');
define('PAYLEAP_TXN_TYPE_FORCE', 'force');
define('PAYLEAP_PAYMENT_STATUS_CANCELED', 'canceled');

// Tracking code provided by PayLeap for Drupal Commerce.
define('PAYLEAP_COMMERCE_PARTNER_ID', 'commerceguys298');

/**
 * Implements hook_commerce_payment_transaction_status_info().
 */
function commerce_payleap_commerce_payment_transaction_status_info() {
  $statuses = array();
  $statuses[PAYLEAP_PAYMENT_STATUS_CANCELED] = array(
    'status' => PAYLEAP_PAYMENT_STATUS_CANCELED,
    'title' => t('Canceled'),
    'total' => FALSE,
  );
  return $statuses;
}

/**
 * Implements hook_menu().
 */
function commerce_payleap_menu() {
  $items = array();

  // Add a menu item for capturing authorizations.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/payleap-capture'] = array(
    'title' => 'Capture',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_payleap_capture_form',
      3,
      5,
    ),
    'access callback' => 'commerce_payleap_capture_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
    'file' => 'includes/commerce_payleap.admin.inc',
  );
  return $items;
}
function commerce_payleap_capture_access($order, $transaction) {

  // Return FALSE if the transaction isn't for PayLeap or isn't awaiting capture.
  if ($transaction->payment_method != PAYLEAP_TXN_TYPE_DELAYED_CAPTURE || $transaction->remote_status == 'Fail' || $transaction->status != COMMERCE_PAYMENT_STATUS_PENDING) {
    return FALSE;
  }

  // TODO: Check if this is relevant to PayLeap.
  // Return FALSE if it is more than 30 days past the original authorization.
  if (time() - $transaction->created > 86400 * 30) {
    return FALSE;
  }

  // Allow access if the user can update payments on this transaction.
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_payleap_commerce_payment_method_info() {
  $payment_methods = array();
  $payment_methods['payleap_direct'] = array(
    'base' => 'commerce_payleap_direct',
    'title' => t('PayLeap direct processing'),
    'short_title' => t('PayLeap direct processing'),
    'display_title' => t('Credit card'),
    'description' => t('Integrates PayLeap direct processing payment, direct or delayed capture '),
  );
  $payment_methods['payleap_cof'] = array(
    'base' => 'commerce_payleap_cof',
    'title' => t('PayLeap - Card On File'),
    'short_title' => t('PayLeap Cardonfile'),
    'display_tiwtle' => t('Card on File'),
    'description' => t('PayLeap - Can be used for regular transaction with option to keep the credit card saved for re-use - requires the Card on File module '),
    'cardonfile' => array(
      'update callback' => 'commerce_payleap_cof_cardonfile_update_delete',
      'delete callback' => 'commerce_payleap_cof_cardonfile_update_delete',
    ),
  );
  return $payment_methods;
}

/**
 * Returns the default settings for the PayLeap AIM payment method.
 */
function commerce_payleap_default_settings() {
  return array(
    'login' => '',
    'tran_key' => '',
    'txn_mode' => PAYLEAP_TXN_MODE_TEST,
    'txn_type' => COMMERCE_CREDIT_AUTH_CAPTURE,
    'vendor_number' => '',
    'log' => array(
      'request' => '0',
      'response' => '0',
    ),
    'failure_interval' => '',
    'max_failures' => '',
  );
}

/**
 * Payment method callback: settings form.
 */
function commerce_payleap_cof_settings_form($settings = NULL) {

  // Merge default settings into the stored settings array.
  $settings = (array) $settings + commerce_payleap_default_settings();
  $form = commerce_payleap_base_settings_form($settings);

  // COF support in conjunction with AIM requires the Card on File module.
  $form['vendor_number'] = array(
    '#type' => 'textfield',
    '#title' => t('Vendor number'),
    '#description' => t('Required for Card on File and recurring payments functionality'),
    '#default_value' => $settings['vendor_number'],
    '#required' => TRUE,
    '#weight' => -2,
  );
  return $form;
}

/**
 * Payment method callback: settings form.
 */
function commerce_payleap_direct_settings_form($settings = NULL) {

  // Merge default settings into the stored settings array.
  $settings = (array) $settings + commerce_payleap_default_settings();
  $form = commerce_payleap_base_settings_form($settings);
  $form['txn_type'] = array(
    '#type' => 'radios',
    '#title' => t('Default credit card transaction type'),
    '#description' => t('The default will be used to process transactions during checkout.'),
    '#options' => array(
      COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'),
      COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual or automated capture after checkout)'),
    ),
    '#default_value' => $settings['txn_type'],
  );
  return $form;
}

/**
 * Build common form for both payment method.
 */
function commerce_payleap_base_settings_form($settings) {

  // Merge default settings into the stored settings array.
  $form = array();
  $form['login'] = array(
    '#type' => 'textfield',
    '#title' => t('PayLeap API login ID'),
    '#description' => t('Your API Login ID'),
    '#default_value' => $settings['login'],
    '#required' => TRUE,
    '#weight' => -5,
  );
  $form['tran_key'] = array(
    '#type' => 'textfield',
    '#title' => t('PayLeap API transaction key'),
    '#description' => t('Transaction Key are unique pieces of information specifically associated with your payment gateway account'),
    '#default_value' => $settings['tran_key'],
    '#required' => TRUE,
    '#weight' => -4,
  );
  $form['txn_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Transaction mode'),
    '#description' => t('Adjust to live transactions when you are ready to start processing real payments.'),
    '#options' => array(
      PAYLEAP_TXN_MODE_PRODUCTION => t('Live transactions in a production account'),
      PAYLEAP_TXN_MODE_TEST => t('Test transactions with your account'),
    ),
    '#default_value' => $settings['txn_mode'],
  );
  $form['log'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Log the following messages for debugging'),
    '#options' => array(
      'request' => t('API request messages'),
      'response' => t('API response messages'),
    ),
    '#default_value' => $settings['log'],
  );
  return $form;
}

/**
 * Payment method callback: checkout form - Direct.
 */
function commerce_payleap_direct_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' => '',
  ));
}

/**
 * Payment method callback: checkout form - Card on File.
 */
function commerce_payleap_cof_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' => '',
  ));
}

/**
 * Payment method callback: checkout form validation - Direct.
 */
function commerce_payleap_direct_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;
  }
}

/**
 * Payment method callback: checkout form validation - Card on File.
 */
function commerce_payleap_cof_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) {

  // If the customer specified a card on file, skip the normal validation.
  if (module_exists('commerce_cardonfile') && ($pane_values['credit_card']['cardonfile_store'] || !empty($pane_form['cardonfile']))) {
    return;
  }
  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;
  }
}

/**
 *  Payment method callback: checkout form submission - Card on file.
 *
 * @see MerchantServices.svc/ProcessCreditCard – Recurring Billing (SCM API Guide)
 */
function commerce_payleap_cof_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
  $info = array();
  $payment_method['settings']['txn_payleap_type'] = PAYLEAP_TXN_TYPE_RECURRING_CAPTURE;
  $billing_data = commerce_payleap_get_billing_info($order);

  // If Card on File storage is enabled and the form says to store data.
  if (module_exists('commerce_cardonfile')) {

    // If the customer specified payment using a card on file, attempt that now
    // and simply return the result.
    if (!empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') {
      $card_data = commerce_cardonfile_data_load($pane_values['cardonfile']);
      $ids = explode('|', $card_data['remote_id']);
      $info['CustomerKey'] = $ids[0];
      $info['CcInfoKey'] = $ids[1];
    }
    else {
      if (!empty($pane_values['credit_card']['cardonfile_store']) && $pane_values['credit_card']['cardonfile_store']) {

        // First look to see if we already have cards on file for the user.
        $stored_cards = commerce_cardonfile_data_load_multiple($order->uid, $payment_method['instance_id']);

        // Prepare card to save.
        $info['CustomerKey'] = '';
        $new_card_data = array(
          'uid' => $order->uid,
          'payment_method' => $payment_method['method_id'],
          'instance_id' => $payment_method['instance_id'],
          'card_type' => 'card',
          'card_name' => $billing_data['name_on_card'],
          'card_number' => substr($pane_values['credit_card']['number'], -4),
          'card_exp_month' => $pane_values['credit_card']['exp_month'],
          'card_exp_year' => $pane_values['credit_card']['exp_year'],
          'status' => 1,
        );

        // If we didn't find any card on file, attempt to make a new Customer Profile now.
        if (empty($stored_cards)) {

          // Submit a request to create the Customer Profile.
          if ($response = commerce_payleap_customer_profile_request($payment_method, $order, $info, 'Add')) {

            // If the Customer Profile creation was a success, store the new card.
            if ($response['status']) {

              // Get the remote ID.
              $info['CustomerKey'] = (string) $response['xml']->CustomerKey;

              // Save and log the creation of the Customer Profile.
              watchdog('commerce_payleap', 'Customer Profile @profile_id created and saved to user @uid.', array(
                '@profile_id' => $info['CustomerKey'],
                '@uid' => $order->uid,
              ));
            }
            else {

              // Could not save the a Customer Profile.
              watchdog('commerce_payleap', 'Customer Profile save error. Unable to save profile for user @uid. @msg', array(
                '@uid' => $order->uid,
                '@msg' => $response['msg'],
              ));
            }
          }
        }
        else {

          // Extract the user's Customer Profile ID from the first card's remote ID.
          $card_data = reset($stored_cards);
          $ids = explode('|', $card_data['remote_id']);
          $info['CustomerKey'] = $ids[0];
        }

        // Could not get a Customer Profile.
        if (empty($info['CustomerKey'])) {
          $msg = t('Unable to save credit card profile.');
          if (isset($response['msg'])) {
            $msg = t('Unable to save credit card profile - @msg.', array(
              '@msg' => $response['msg'],
            ));
          }
          drupal_set_message($msg, 'error');
          return FALSE;
        }
        else {
          $info += array(
            'CardNum' => $pane_values['credit_card']['number'],
            'ExpDate' => $pane_values['credit_card']['exp_month'] . substr($pane_values['credit_card']['exp_year'], 2, 2),
            'NameOnCard' => $billing_data['name_on_card'],
            'Street' => $billing_data['street'],
            'Zip' => $billing_data['zip'],
            'ExtData' => $billing_data['ext_data'],
          );
          $response = commerce_payleap_card_profile_request($payment_method, $info, 'Add');

          // If the CreditCardInfo creation was a success, store the new card on
          // file data locally.
          if ($response['status']) {

            // Build a remote ID that includes the Customer Profile ID and the new
            // CreditCardInfo ID.
            $info['CcInfoKey'] = (string) $response['xml']->CcInfoKey;
            $new_card_data['remote_id'] = $info['CustomerKey'] . '|' . $info['CcInfoKey'];

            // Save and log the creation of the new card on file.
            commerce_cardonfile_data_save($new_card_data);
            watchdog('commerce_payleap', 'Credit Card @card_id added to Customer Profile @profile_id for user @uid.', array(
              '@card_id' => $info['CcInfoKey'],
              '@profile_id' => $info['CustomerKey'],
              '@uid' => $order->uid,
            ));
          }
          else {

            // But if we could not find a Customer Profile, assume the existing
            // Customer Profile ID we had is no longer valid and deactivate the card
            // data that resulted in the error.
            $card_data['status'] = 0;
            commerce_cardonfile_data_save($card_data);
            watchdog('commerce_payleap', 'Credit Card save error. Unable to save credit card for user @uid. @msg', array(
              '@uid' => $order->uid,
              '@msg' => $response['msg'],
            ));
            return FALSE;
          }
        }
      }
    }
  }
  if (empty($info['CustomerKey']) || empty($info['CcInfoKey'])) {

    // Fallback on Direct Transaction.
    $payment_method['settings']['txn_payleap_type'] = PAYLEAP_TXN_TYPE_DIRECT_CAPTURE;
    return commerce_payleap_direct_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge);
  }

  // Send the tracking code.
  $info['ExtData'] = '<CertifiedVendorId>' . PAYLEAP_COMMERCE_PARTNER_ID . '</CertifiedVendorId>';
  $info += array(
    'Amount' => commerce_currency_amount_to_decimal($charge['amount'], $charge['currency_code']),
  );
  return commerce_payleap_transaction_process($payment_method, $info, $order, $charge);
}

/**
 * Payment method callback: checkout form submission - Direct.
 *
 * @see TransactServices.svc/ProcessCreditCard - Sale (Transaction API Guide)
 */
function commerce_payleap_direct_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
  $billing_data = commerce_payleap_get_billing_info($order);
  $info = array(
    'CardNum' => $pane_values['credit_card']['number'],
    'ExpDate' => $pane_values['credit_card']['exp_month'] . substr($pane_values['credit_card']['exp_year'], 2, 2),
  );

  // Define correct transaction type delayed or direct.
  switch ($payment_method['settings']['txn_type']) {
    case COMMERCE_CREDIT_AUTH_CAPTURE:
      $payment_method['settings']['txn_payleap_type'] = PAYLEAP_TXN_TYPE_DIRECT_CAPTURE;
      $info['TransType'] = 'Sale';
      break;
    case COMMERCE_CREDIT_AUTH_ONLY:
      $payment_method['settings']['txn_payleap_type'] = PAYLEAP_TXN_TYPE_DELAYED_CAPTURE;
      $info['TransType'] = 'Auth';
      break;
  }
  if (isset($pane_values['credit_card']['code'])) {
    $info['CVNum'] = $pane_values['credit_card']['code'];
  }

  // Build a name-value pair array for this transaction.
  $info += array(
    'Amount' => commerce_currency_amount_to_decimal($charge['amount'], $charge['currency_code']),
    'MagData' => '',
  );

  // Add additional transaction information to the request array.
  $info += array(
    'InvNum' => $order->order_number,
  );
  $info += array(
    'NameOnCard' => $billing_data['name_on_card'],
    'Street' => $billing_data['street'],
    'Zip' => $billing_data['zip'],
    'ExtData' => $billing_data['ext_data'],
  );

  // Send the tracking code.
  $info['ExtData'] = '<CertifiedVendorId>' . PAYLEAP_COMMERCE_PARTNER_ID . '</CertifiedVendorId>';
  return commerce_payleap_transaction_process($payment_method, $info, $order, $charge);
}

/**
 * Proceed to the payment and record a transaction.
 */
function commerce_payleap_transaction_process($payment_method, $info, $order, $charge) {

  // Submit the request to PayLeap.
  $response = commerce_payleap_request($payment_method, $info);

  // Prepare a transaction object to log the API response.
  $transaction = commerce_payment_transaction_new($payment_method['settings']['txn_payleap_type'], $order->order_id);
  $transaction->instance_id = $payment_method['instance_id'];
  $transaction->remote_id = isset($response['xml']->PNRef) ? (string) $response['xml']->PNRef : '';
  $transaction->amount = $charge['amount'];
  $transaction->currency_code = $charge['currency_code'];
  $transaction->payload[REQUEST_TIME] = isset($response['xml']) ? $response['xml']
    ->asXML() : '';

  // Store the Message of transaction in the remote status.
  $transaction->remote_status = $response['status'];
  $transaction->message = implode('<br />', commerce_payleap_get_log_message($response, $payment_method['settings']['txn_payleap_type']));

  // Set the transaction status based on the type of transaction this was.
  if ($payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_DIRECT_CAPTURE && $payment_method['settings']['txn_type'] == COMMERCE_CREDIT_AUTH_ONLY) {
    $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
  }
  else {
    if ($response['status']) {
      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
    }
    else {
      $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
    }
  }

  // If we didn't get an approval response code.
  // Create a failed transaction with the error message.
  // Save auth code.
  $transaction->data['auth_code'][] = isset($response['xml']) && isset($response['xml']->AuthCode) ? (string) $response['xml']->AuthCode : '';

  // Save the transaction information.
  commerce_payment_transaction_save($transaction);

  // If the payment failed, display an error and rebuild the form.
  if (!$response['status']) {
    drupal_set_message(t('We received the following error processing your card. Please enter you information again or try a different card.'), 'error');
    if (!empty($response['msg'])) {
      drupal_set_message(check_plain($response['msg']), 'error');
    }
    return FALSE;
  }
  return TRUE;
}

/**
 * Build log message.
 */
function commerce_payleap_get_log_message($response, $type) {

  // Build a meaningful response message.
  $status = !$response['status'] ? t('@type : REJECTED', array(
    '@type' => $type,
  )) : t('@type : ACCEPTED', array(
    '@type' => $type,
  )) . ': ' . check_plain($response['msg']);
  $avs = !empty($response['xml']->GetAVSResult) ? (string) $response['xml']->GetAVSResult : FALSE;
  $cvv = !empty($response['xml']->GetCVResult) ? (string) $response['xml']->GetCVResult : FALSE;
  $message = array(
    $status,
    $avs ? t('AVS response: @avs', array(
      '@avs' => commerce_payleap_avs_response($avs),
    )) : '',
    $cvv ? t('CVV match: @cvv', array(
      '@cvv' => commerce_payleap_cvv_response($cvv),
    )) : '',
  );
  return $message;
}

/**
 * Prepare ExtData XML element.
 */
function commerce_payleap_get_billing_info($order) {
  $billing_data = array(
    'ext_data' => '',
    'street' => '',
    'street2' => '',
    'city' => '',
    'state' => '',
    'zip' => '',
    'country' => '',
    'name_on_card' => '',
    'first_name' => '',
    'last_name' => '',
  );
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  if ($order_wrapper->commerce_customer_billing
    ->value()) {
    $ext_data = '';
    $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address
      ->value();
    if (empty($billing_address['first_name'])) {
      $name_parts = explode(' ', $billing_address['name_line']);
      $billing_address['first_name'] = array_shift($name_parts);
      $billing_address['last_name'] = implode(' ', $name_parts);
    }

    // Build and populate the API request SimpleXML element.
    $ext_data .= '<CustomerID>' . substr($order->uid, 0, 20) . '</CustomerID>';

    // Customer Billing Address.
    $ext_data .= '<Invoice><BillTo>';
    $name_on_card = substr($billing_address['first_name'], 0, 50) . ' ' . substr($billing_address['last_name'], 0, 50);

    // Use company name as billing name when available.
    if (!empty($billing_address['organisation_name'])) {
      $ext_data .= '<Name>' . substr($billing_address['organisation_name'], 0, 50) . '</Name>';
    }
    else {
      $ext_data .= '<Name>' . $name_on_card . '</Name>';
    }
    $ext_data .= '<Email>' . substr($order->mail, 0, 255) . '</Email>';
    $ext_data .= '<Address>';
    $ext_data .= '<Street>' . substr($billing_address['thoroughfare'], 0, 60) . '</Street>';
    $ext_data .= '<City>' . substr($billing_address['locality'], 0, 40) . '</City>';
    $ext_data .= '<State>' . substr($billing_address['administrative_area'], 0, 40) . '</State>';
    $ext_data .= '<Zip>' . substr($billing_address['postal_code'], 0, 20) . '</Zip>';
    $ext_data .= '<Country>' . $billing_address['country'] . '</Country>';
    $ext_data .= '</Address>';
    $ext_data .= '</BillTo></Invoice>';
    $billing_data['ext_data'] = $ext_data;
    $billing_data['street'] = substr($billing_address['thoroughfare'], 0, 60);
    $billing_data['street2'] = substr($billing_address['premise'], 0, 60);
    $billing_data['city'] = substr($billing_address['locality'], 0, 40);
    $billing_data['state'] = substr($billing_address['administrative_area'], 0, 40);
    $billing_data['zip'] = substr($billing_address['postal_code'], 0, 20);
    $billing_data['country'] = $billing_address['country'];
    $billing_data['name_on_card'] = $name_on_card;
    $billing_data['first_name'] = $billing_address['first_name'];
    $billing_data['last_name'] = $billing_address['last_name'];
  }
  return $billing_data;
}

/**
 * Returns the URL to the PayLeap server determined by transaction mode.
 *
 * @param $txn_mode
 *   The transaction mode that relates to the production or test server.
 * @param $txn_payleap_type
 *   The transaction type that relate to transaction steps or results.
 * @return string The URL to use to submit requests to the PayLeap server.
 */
function commerce_payleap_server_url($txn_mode, $txn_payleap_type) {
  switch ($txn_payleap_type) {
    case PAYLEAP_TXN_TYPE_DIRECT_CAPTURE:
    case PAYLEAP_TXN_TYPE_DELAYED_CAPTURE:
    case PAYLEAP_TXN_TYPE_VOID:
    case PAYLEAP_TXN_TYPE_FORCE:
      $service = 'TransactServices.svc/ProcessCreditCard';
      break;
    case PAYLEAP_TXN_TYPE_RECURRING_CAPTURE:
      $service = 'MerchantServices.svc/ProcessCreditCard';
      break;
    case PAYLEAP_TXN_TYPE_MANAGECREDITCARDINFO:
      $service = 'MerchantServices.svc/ManageCreditCardInfo';
      break;
    case PAYLEAP_TXN_TYPE_ADDRECURRINGCREDITCARD:
      $service = 'MerchantServices.svc/AddRecurringCreditCard';
      break;
    case PAYLEAP_TXN_TYPE_MANAGECUSTOMER:
      $service = 'MerchantServices.svc/ManageCustomer';
      break;
    case PAYLEAP_TXN_TYPE_MANAGECONTRACT:
      $service = 'MerchantServices.svc/ManageContract';
      break;
    default:
      $service = '';
      break;
  }
  switch ($txn_mode) {
    case PAYLEAP_TXN_MODE_PRODUCTION:
      return 'https://secure1.payleap.com/' . $service;
    case PAYLEAP_TXN_MODE_TEST:
      return 'https://uat.payleap.com/' . $service;
  }
}

/**
 * Submits a request to PayLeap.
 *
 * @param $payment_method
 *   The payment method instance array associated with this API request.
 * @param array $info
 *   The payment method extented info.
 * @return array
 *    Response.
 */
function commerce_payleap_request($payment_method, $info = array()) {

  // Get the API endpoint URL for the method's transaction mode and type.
  $url = commerce_payleap_server_url($payment_method['settings']['txn_mode'], $payment_method['settings']['txn_payleap_type']);

  // Add the default name-value pairs to the array.
  $info += array(
    // API credentials
    'Username' => $payment_method['settings']['login'],
    'Password' => $payment_method['settings']['tran_key'],
  );
  $optional_settings = array(
    'vendor_number' => 'Vendor',
  );
  foreach ($optional_settings as $setting_name => $query_name) {
    if (!empty($payment_method['settings'][$setting_name])) {
      $info[$query_name] = $payment_method['settings'][$setting_name];
    }
  }

  // Allow modules to alter parameters of the API request.
  drupal_alter('commerce_payleap_direct_request', $info);

  // Log the request if specified.
  if ($payment_method['settings']['log']['request'] == 'request') {

    // Mask the credit card number and CVV.
    $log_nvp = $info;
    $log_nvp['Username'] = str_repeat('X', strlen($log_nvp['Username']));
    $log_nvp['Password'] = str_repeat('X', strlen($log_nvp['Password']));
    if (!empty($log_nvp['CardNum'])) {
      $log_nvp['CardNum'] = str_repeat('X', strlen($log_nvp['CardNum']) - 4) . substr($log_nvp['CardNum'], -4);
    }
    if (!empty($log_nvp['CcAccountNum'])) {
      $log_nvp['CcAccountNum'] = str_repeat('X', strlen($log_nvp['CcAccountNum']) - 4) . substr($log_nvp['CcAccountNum'], -4);
    }
    if (!empty($log_nvp['CVNum'])) {
      $log_nvp['CVNum'] = str_repeat('X', strlen($log_nvp['CVNum']));
    }
    watchdog('commerce_payleap', 'PayLeap request to @url: !param', array(
      '@url' => $url,
      '!param' => '<pre>' . check_plain(print_r($log_nvp, TRUE)) . '</pre>',
    ), WATCHDOG_DEBUG);
  }

  // Prepare the name-value pair array to be sent as a string.
  $pairs = array();
  foreach ($info as $key => $value) {
    $pairs[] = $key . '=' . urlencode($value);
  }

  // Setup the cURL request.
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_VERBOSE, 0);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $pairs));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
  $result = curl_exec($ch);

  // Log any errors to the watchdog.
  if ($error = curl_error($ch)) {
    watchdog('commerce_payleap', 'cURL error: @error', array(
      '@error' => $error,
    ), WATCHDOG_ERROR);
    $response['status'] = FALSE;
    $response['msg'] = $error;
    return $response;
  }
  curl_close($ch);

  // If we received data back from the server.
  if (empty($result)) {
    watchdog('commerce_payleap', 'cURL error empty result returned.', array(), WATCHDOG_ERROR);
    $response['status'] = FALSE;
    $response['msg'] = t('No answer from server');
  }
  else {

    // Remove non-absolute XML namespaces to prevent SimpleXML warnings.
    $result = str_replace(' xmlns="http://www.payleap.com/payments"', '', $result);

    // Extract the result into an XML response object.
    $xml = new SimpleXMLElement($result);
    $response = array();

    // Log the API response if specified.
    if ($payment_method['settings']['log']['response'] == 'response') {
      watchdog('commerce_payleap', 'API response received:<pre>@xml</pre>', array(
        '@xml' => $xml
          ->asXML(),
      ));
    }
    if ($payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_DIRECT_CAPTURE || $payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_DELAYED_CAPTURE || $payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_FORCE || $payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_VOID) {

      // 0 - mean OK.
      $response['status'] = (string) $xml->Result === '0' ? TRUE : FALSE;
      $response['msg'] = (string) $xml->RespMSG;
    }
    elseif ($payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_RECURRING_CAPTURE) {

      // 0 - mean OK.
      $response['status'] = (string) $xml->Result === '0' ? TRUE : FALSE;
      $response['msg'] = (string) $xml->Message;
    }
    elseif ($payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_ADDRECURRINGCREDITCARD || $payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_MANAGECONTRACT || $payment_method['settings']['txn_payleap_type'] == PAYLEAP_TXN_TYPE_MANAGECREDITCARDINFO) {
      $response['status'] = (string) $xml->Code == 'Ok' ? TRUE : FALSE;
      $response['msg'] = (string) $xml->Error;
    }
    else {
      $response['status'] = (string) $xml->Code == 'Fail' ? FALSE : TRUE;
      $response['msg'] = (string) $xml->Error;
    }

    // Request approved, Save original xml responce with all data.
    $response['xml'] = $xml;
  }
  return $response;
}

/**
 * Process a Void or Force transaction.
 *
 * @param $transaction
 *  The Transaction entity.
 * @param $payment_method
 *   The payment method instance array associated with this API request.
 * @param $amount
 *   Current transaction amount.
 * @param $action
 *   Transaction type, 'Void' or 'Force' supported.
 * @return bool
 *  Return TRUE on success.
 * @see TransactServices.svc/ProcessCreditCard (Transaction API Guide)
 */
function commerce_payleap_transaction_request($transaction, $payment_method, $amount, $action) {

  // Action can be Void, Capture.
  // Build the base profile request data.
  $api_request_data = array(
    'TransType' => $action,
    // The PNRef number of the original sale transaction.
    'PNRef' => $transaction->remote_id,
    'Amount' => $amount,
    'ExtData' => '<AuthCode>' . $transaction->data['auth_code']['0'] . '</AuthCode>',
    'CardNum' => '',
    'NameOnCard' => '',
    'ExpDate' => '',
    'Street' => '',
    'Zip' => '',
    'MagData' => '',
    'InvNum' => '',
    'CVNum' => '',
  );
  $payment_method['settings']['txn_payleap_type'] = $action == 'Void' ? PAYLEAP_TXN_TYPE_VOID : PAYLEAP_TXN_TYPE_FORCE;
  $response = commerce_payleap_request($payment_method, $api_request_data);
  $result = TRUE;
  $transaction->payload[REQUEST_TIME] = isset($response['xml']) ? $response['xml']
    ->asXML() : '';

  // If we didn't get an approval response code...
  if (!$response['status']) {

    // Display an error message but leave the transaction pending.
    drupal_set_message(t('PayLeap request failed'), 'error');
    drupal_set_message(check_plain($response['msg']), 'error');
    $result = FALSE;
  }
  else {
    drupal_set_message(t('@action request successfully.', array(
      '@action' => $action,
    )));

    // Update to new remote ID.
    $transaction->remote_id = isset($response['xml']->PNRef) ? (string) $response['xml']->PNRef : '';

    // Update the transaction amount to the actual capture amount.
    $transaction->data['auth_code'][] = isset($response['xml']) && isset($response['xml']->AuthCode) ? (string) $response['xml']->AuthCode : '';
    if ($response['status']) {

      // Set the remote and local status accordingly.
      switch ($action) {
        case 'Void':
          $transaction->status = PAYLEAP_PAYMENT_STATUS_CANCELED;
          break;
        case 'Force':
          $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
          break;
      }
      $transaction->amount = commerce_currency_decimal_to_amount($amount, $transaction->currency_code);
    }
    $transaction->remote_status = $response['status'];
    $transaction->payload[REQUEST_TIME] = isset($response['xml']) ? $response['xml']
      ->asXML() : '';

    // Append a capture indication to the result message.
    $transaction->message .= implode('<br />', commerce_payleap_get_log_message($response, $payment_method['settings']['txn_payleap_type']));
  }
  commerce_payment_transaction_save($transaction);
  return $result;
}

/**
 * Submits a ManageCreditCardInfo request PayLeap.
 * This function will perform a Add/Update/Delete of a credit card profile.
 *
 * @param $payment_method
 *   The payment method instance array containing the API credentials for a
 *   Payleap account.
 * @param $info
 * @param string $action
 *   The action wanted: Add/Update/Delete
 * @return array A SimpleXMLElement containing the API response.@see MerchantServices.svc/ManageCreditCardInfo (SCM API Guide)
 */
function commerce_payleap_card_profile_request($payment_method, $info, $action = 'Add') {

  // Build the base profile request data.
  $api_request_data = array(
    'TransType' => $action,
    // Unique numerical identifier for a customer.
    // Found in the response values of operations for managing customer
    // information and adding recurring payments.
    'CcNameonCard' => $info['NameOnCard'],
    'CustomerKey' => $info['CustomerKey'],
    'CcAccountNum' => $info['CardNum'],
    'CcExpDate' => $info['ExpDate'],
    'CcStreet' => $info['Street'],
    'CcZip' => $info['Zip'],
    'ExtData' => '',
  );

  // Update and Delete required fields.
  if ($action == 'Delete' || $action == 'Update') {
    $api_request_data += array(
      // Unique numerical identifier for credit card. Found in the response
      // values for AddRecurringCreditCard as the CcInfoKey.
      'CardInfoKey' => $info['CardInfoKey'],
    );
  }
  $payment_method['settings']['txn_payleap_type'] = PAYLEAP_TXN_TYPE_MANAGECREDITCARDINFO;
  return commerce_payleap_request($payment_method, $api_request_data);
}

/**
 * Callback for card on file update or delete.
 *
 * @see MerchantServices.svc/ManageCreditCardInfo (SCM API Guide)
 */
function commerce_payleap_cof_cardonfile_update_delete($form, &$form_state, $payment_method, $card_data) {
  $info = array();
  $ids = explode('|', $card_data['remote_id']);

  // Update method expect the the full credit card number.
  // Delete can be triggered without it.
  $card_number = '';
  if ($form['#id'] == 'commerce-cardonfile-update-form') {
    $action = 'Update';
    if ($form_state['values']['credit_card']['number'] != $form['credit_card']['number']['#default_value']) {
      $card_number = $form_state['values']['credit_card']['number'];
    }
    else {
      form_set_error('credit_card][number', t('The credit card number you entered is invalid.'));
      return FALSE;
    }
  }
  else {
    $action = 'Delete';
  }
  $info += array(
    'CustomerKey' => $ids[0],
    'CardInfoKey' => $ids[1],
    'NameOnCard' => $card_data['card_name'],
    'CardNum' => $card_number,
    'ExpDate' => $card_data['card_exp_month'] . substr($card_data['card_exp_year'], 2, 2),
    'Street' => '',
    'Zip' => '',
  );

  // Fetch the response from the API server and let Card on File know if the
  // update was successful.
  $result = commerce_payleap_card_profile_request($payment_method, $info, $action);
  return (bool) $result['status'];
}

/**
 * Implements hook_form_FORM_ID_alte().
 */
function commerce_payleap_form_commerce_cardonfile_update_form_alter(&$form, &$form_state, $form_id) {

  // Replace the credit card number field description.
  $form['credit_card']['number']['#description'] = t('You must replace the default value with actual or new credit card number.');
}

/**
 * Submits a ManageCustomer request PayLeap.
 * This function will perform a Add/Update/Delete of a Customer Profile
 *
 * @param $payment_method
 *   The payment method instance array containing the API credentials for a
 *   Payleap account.
 * @param $order
 *   The order object containing the billing address and e-mail to use for the
 *   customer profile.
 * @param $info
 * @param string $action
 *   The action wanted: Add/Update/Delete
 * @return array A SimpleXMLElement containing the API response.@see MerchantServices.svc/ManageCustomer (SCM API Guide)
 */
function commerce_payleap_customer_profile_request($payment_method, $order, $info, $action = 'Add') {
  $api_request_data = array();
  $billto = commerce_payleap_get_billing_info($order);

  // Update and Add required fields.
  if ($action == 'Add' || $action == 'Update') {
    $api_request_data += array(
      // Unique, merchant-supplied identifier for a customer.
      'CustomerID' => $order->uid,
      'Title' => $billto['name_on_card'],
      'CustomerName' => $billto['first_name'] . ' ' . $billto['last_name'],
      'FirstName' => $billto['first_name'],
      'LastName' => $billto['last_name'],
      'Email' => $order->mail,
      'Street1' => $billto['street'],
      'Street2' => $billto['street2'],
      'Zip' => $billto['zip'],
      'CountryID' => $billto['country'],
      'StateID' => $billto['state'],
      'City' => $billto['city'],
    );
  }

  // Update and Delete required fields.
  if ($action == 'Delete' || $action == 'Update') {
    $api_request_data += array(
      // Unique numerical identifier for a customer.
      // Found in the response values of operations for managing customer
      // information and adding recurring payments.
      'CustomerKey' => $info['customer_key'],
    );
  }
  $payment_method['settings']['txn_payleap_type'] = PAYLEAP_TXN_TYPE_MANAGECUSTOMER;

  // Build the base profile request data.
  $api_request_data += array(
    'TransType' => $action,
    'CustomerKey' => '',
    'CustomerID' => '',
    'FirstName' => '',
    'LastName' => '',
    'Title' => '',
    'Department' => '',
    'Street1' => '',
    'Street2' => '',
    'Street3' => '',
    'City' => '',
    'StateID' => '',
    'Province' => '',
    'Zip' => '',
    'CountryID' => '',
    'DayPhone' => '',
    'NightPhone' => '',
    'Fax' => '',
    'Mobile' => '',
    'Email' => '',
    'ExtData' => '',
  );
  return commerce_payleap_request($payment_method, $api_request_data);
}

/**
 * Returns the message text for an AVS response code.
 *
 * @see AVS Response Codes (Transaction API Guide)
 */
function commerce_payleap_avs_response($code) {
  switch ($code) {
    case 'A':
      return t('Address: Address matches, Zip does not');
    case 'B':
      return t('Street Match: Street addresses match for international transaction, but postal code doesn’t');
    case 'C':
      return t('Street Address: Street addresses and postal code not verified for international transaction');
    case 'D':
      return t('Match: Street addresses and postal codes match for international transaction');
    case 'E':
      return t('Error: Transaction unintelligible for AVS or edit error found in the message that prevents AVS from being performed');
    case 'G':
      return t('Unavailable: Address information not available for international transaction');
    case 'I':
      return t('Not Verified: Address Information not verified for International transaction');
    case 'M':
      return t('Match: Street addresses and postal codes match for international transaction');
    case 'N':
      return t('No: Neither address nor Zip matches');
    case 'P':
      return t('Postal Match: Postal codes match for international transaction, but street address doesn’t');
    case 'R':
      return t('Retry: System unavailable or time-out');
    case 'S':
      return t('Not Supported: Issuer doesn’t support AVS service');
    case 'U':
      return t('Unavailable: Address information not available');
    case 'W':
      return t('Whole Zip: 9-digit Zip matches, address doesn’t');
    case 'X':
      return t('Exact: Address and nine-digit Zip match');
    case 'Y':
      return t('Yes: Address and five-digit Zip match');
    case 'Z':
      return t('Whole Zip: 9-digit Zip matches, address doesn’t');
    case '0':
      return t('No response sent');
    case '5':
      return t('Invalid AVS response');
  }
  return '-';
}

/**
 * Returns the message text for a CVV match.
 *
 * @see CVV Response Codes (Transaction API Guide)
 */
function commerce_payleap_cvv_response($code) {
  switch ($code) {
    case 'M':
      return t('CVV2/CVC2/CID Match');
    case 'N':
      return t('CVV2/CVC2/CID No Match');
    case 'P':
      return t('Not Processed');
    case 'S':
      return t('Issuer indicates that the CV data should be present on the card, but the merchant has indicated that the CV data is not present on the card.');
    case 'U':
      return t('Unknown / Issuer has not certified for CV or issuer has not provided Visa/MasterCard with the CV encryption keys.');
    case 'X':
      return t('Server Provider did not respond');
  }
  return '-';
}

Functions

Namesort descending Description
commerce_payleap_avs_response Returns the message text for an AVS response code.
commerce_payleap_base_settings_form Build common form for both payment method.
commerce_payleap_capture_access
commerce_payleap_card_profile_request Submits a ManageCreditCardInfo request PayLeap. This function will perform a Add/Update/Delete of a credit card profile.
commerce_payleap_cof_cardonfile_update_delete Callback for card on file update or delete.
commerce_payleap_cof_settings_form Payment method callback: settings form.
commerce_payleap_cof_submit_form Payment method callback: checkout form - Card on File.
commerce_payleap_cof_submit_form_submit Payment method callback: checkout form submission - Card on file.
commerce_payleap_cof_submit_form_validate Payment method callback: checkout form validation - Card on File.
commerce_payleap_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_payleap_commerce_payment_transaction_status_info Implements hook_commerce_payment_transaction_status_info().
commerce_payleap_customer_profile_request Submits a ManageCustomer request PayLeap. This function will perform a Add/Update/Delete of a Customer Profile
commerce_payleap_cvv_response Returns the message text for a CVV match.
commerce_payleap_default_settings Returns the default settings for the PayLeap AIM payment method.
commerce_payleap_direct_settings_form Payment method callback: settings form.
commerce_payleap_direct_submit_form Payment method callback: checkout form - Direct.
commerce_payleap_direct_submit_form_submit Payment method callback: checkout form submission - Direct.
commerce_payleap_direct_submit_form_validate Payment method callback: checkout form validation - Direct.
commerce_payleap_form_commerce_cardonfile_update_form_alter Implements hook_form_FORM_ID_alte().
commerce_payleap_get_billing_info Prepare ExtData XML element.
commerce_payleap_get_log_message Build log message.
commerce_payleap_menu Implements hook_menu().
commerce_payleap_request Submits a request to PayLeap.
commerce_payleap_server_url Returns the URL to the PayLeap server determined by transaction mode.
commerce_payleap_transaction_process Proceed to the payment and record a transaction.
commerce_payleap_transaction_request Process a Void or Force transaction.

Constants