You are here

commerce_braintree.module in Commerce Braintree 7

Same filename and directory in other branches
  1. 7.3 commerce_braintree.module
  2. 7.2 commerce_braintree.module

Implementations of the Braintree payment gateway (http://braintreepayments.com) for drupal commerce.

File

commerce_braintree.module
View source
<?php

/**
 * @file
 * Implementations of the Braintree payment gateway
 * (http://braintreepayments.com) for drupal commerce.
 */

/**
 * Implements hook_menu().
 */
function commerce_braintree_menu() {

  // Define an always accessible path to receive IPNs.
  $items['user/commerce_braintree/update_card'] = array(
    'page callback' => 'commerce_braintree_update_card',
    'page arguments' => array(),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_braintree_commerce_payment_method_info() {
  $payment_methods['braintree'] = array(
    'base' => 'commerce_braintree',
    'file' => 'commerce_braintree.commerce_braintree.inc',
    'title' => t('Braintree'),
    'short_title' => t('Braintree'),
    'display_title' => t('Credit Card'),
    'description' => t('Provide integration with Braintree.'),
    'terminal' => FALSE,
    'offsite' => TRUE,
    'offsite_autoredirect' => FALSE,
  );
  $payment_methods['braintree_cof'] = array(
    'base' => 'commerce_braintree_cof',
    'file' => 'commerce_braintree.commerce_braintree_cof.inc',
    'title' => t('Braintree - Card On File'),
    'short_title' => t('Braintree Cardonfile'),
    'display_title' => t('Credit Card'),
    'description' => t('Provide integration with Braintree (integrated with Commerce Cardonfile.'),
    'terminal' => FALSE,
    'offsite' => TRUE,
    'offsite_autoredirect' => FALSE,
    'cardonfile' => array(
      'update callback' => 'commerce_braintree_cardonfile_update_delete',
      'delete callback' => 'commerce_braintree_cardonfile_update_delete',
    ),
  );
  return $payment_methods;
}

/**
 * Menu callback. Get the query from Braintree when updating a credit card.
 */
function commerce_braintree_update_card() {
  global $user;
  $feedback = commerce_braintree_get_feedback();
  if ($feedback) {
    $payment_method = commerce_payment_method_instance_load('braintree_cof|commerce_payment_braintree_cof');
    _commerce_braintree_init_credentials($payment_method);
    $result = Braintree_TransparentRedirect::confirm($feedback);
    $token = $result->creditCard->_attributes['token'];
    $cardholderName = $result->creditCard->_attributes['cardholderName'];
    $expirationMonth = $result->creditCard->_attributes['expirationMonth'];
    $expirationYear = $result->creditCard->_attributes['expirationYear'];
    $last4 = $result->creditCard->_attributes['last4'];
    $card_stored = db_select('commerce_card_data', 'ccd')
      ->fields('ccd')
      ->condition('ccd.remote_id', $token)
      ->execute()
      ->fetchAssoc();
    $card_stored['card_name'] = $cardholderName;
    $card_stored['card_exp_month'] = $expirationMonth;
    $card_stored['card_exp_year'] = $expirationYear;
    $card_stored['card_number'] = $last4;
    commerce_cardonfile_data_save($card_stored);
  }
  drupal_goto('user/' . $user->uid . '/stored-payment-methods');
}

/**
 * Callback for card on file update or delete.
 */
function commerce_braintree_cardonfile_update_delete($form, &$form_state, $payment_method, $card_data) {
  if ($form['#id'] == 'commerce-cardonfile-delete-form') {
    _commerce_braintree_init_credentials($payment_method);
    $result = Braintree_CreditCard::delete($card_data['remote_id']);
    if ($result->success) {
      db_delete('commerce_card_data')
        ->condition('card_id', $card_data['card_id'])
        ->execute();
    }
    return TRUE;
  }
}

/**
 * Returns the default settings for the Braintree payment method.
 */
function commerce_braintree_default_settings_form($settings = NULL) {
  $default_currency = commerce_default_currency();

  // Merge default settings into the stored settings array.
  // Demonstration or production ?
  $form['commerce_braintree_mode'] = array(
    '#type' => 'select',
    '#title' => t('Mode'),
    '#options' => array(
      'sandbox' => t('Sandbox'),
      'production' => t('Production'),
    ),
    '#default_value' => isset($settings['commerce_braintree_mode']) ? $settings['commerce_braintree_mode'] : NULL,
  );
  $form['commerce_braintree_merchant_id'] = array(
    '#type' => 'textfield',
    '#title' => t('Merchant ID'),
    '#description' => t("The merchantID."),
    '#size' => 30,
    '#maxlength' => 128,
    '#default_value' => isset($settings['commerce_braintree_merchant_id']) ? $settings['commerce_braintree_merchant_id'] : NULL,
    '#required' => TRUE,
  );
  $form['commerce_braintree_public_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Public Key'),
    '#description' => t("The publicKey."),
    '#size' => 30,
    '#maxlength' => 128,
    '#default_value' => isset($settings['commerce_braintree_public_key']) ? $settings['commerce_braintree_public_key'] : NULL,
    '#required' => TRUE,
  );
  $form['commerce_braintree_private_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Private Key'),
    '#description' => t("The privateKey."),
    '#size' => 40,
    '#maxlength' => 128,
    '#default_value' => isset($settings['commerce_braintree_private_key']) ? $settings['commerce_braintree_private_key'] : NULL,
    '#required' => TRUE,
  );
  return $form;
}

/**
 * Implements hook_commerce_checkout_page_info_alter().
 */
function commerce_braintree_commerce_checkout_page_info_alter(&$checkout_pages) {

  // Find a better way to remove/rewrite the help.
  // unset($checkout_pages['payment']['help']);
}

/**
 * Implements hook_form_alter().
 */
function commerce_braintree_form_alter(&$form, &$form_state, $form_id) {

  // If the current form ID is for a checkout form...
  if (strpos($form_id, 'commerce_checkout_form_') === 0 && $form_id != 'commerce_checkout_form_payment') {

    // And it specifies a valid checkout page...
    if (commerce_checkout_page_load(substr($form_id, 23))) {

      // And the current page's form includes the payment checkout pane...
      if (!empty($form['commerce_payment'])) {

        // Check to see if the currently selected payment method is Card on File
        // enabled (via the cardonfile boolean in its info array).
        $payment_method = commerce_payment_method_instance_load($form['commerce_payment']['payment_method']['#default_value']);
        if (!empty($payment_method['cardonfile'])) {

          // Load any existing card data for the current payment method instance
          // and user.
          $stored_cards = commerce_cardonfile_data_load_multiple($form_state['account']->uid, $payment_method['instance_id']);

          // Filter out expired cards.
          foreach ($stored_cards as $card_id => $card_data) {
            if ($card_data['card_exp_year'] < date('Y') || $card_data['card_exp_year'] == date('Y') && $card_data['card_exp_month'] < date('m')) {
              unset($stored_cards[$card_id]);
            }
          }

          // If we found any stored cards, show the options in the form.
          if (!empty($stored_cards)) {
            $element = variable_get('commerce_cardonfile_selector', 'radios');
            $options = commerce_cardonfile_options_list($stored_cards, $element);
            $form['commerce_payment']['payment_details']['cardonfile'] = array(
              '#type' => $element,
              '#title' => t('Select a stored credit card'),
              '#options' => $options,
              '#default_value' => key($options),
              '#weight' => -10,
            );

            // Be sure that the credit card form is not displayed.
            $form['commerce_payment']['payment_details']['credit_card']['#access'] = FALSE;

            // Add the CSS to hide a sole credit card icon if specified.
            if (variable_get('commerce_cardonfile_hide_cc_radio_button', TRUE)) {
              if (count($form['commerce_payment']['payment_method']['#options']) == 1) {
                $form['commerce_payment']['payment_method']['#attached']['css'][] = drupal_get_path('module', 'commerce_cardonfile') . '/theme/commerce_cardonfile.checkout.css';
              }
            }
          }
        }
      }
    }
  }
}

/**
 * Implements hook_commerce_checkout_pane_info_alter().
 */
function commerce_braintree_commerce_checkout_pane_info_alter(&$checkout_panes) {

  // Add custom validation to be able to save the values in session.
  $checkout_panes['commerce_payment']['callbacks']['checkout_form_validate'] = '_commerce_braintree_commerce_payment_checkout_custom_validation';
}

/**
 * Callback. Custom checkout payement validation.
 *
 * @param $form
 * @param $form_state
 * @param $checkout_pane
 * @param $order
 * @return bool
 */
function _commerce_braintree_commerce_payment_checkout_custom_validation($form, &$form_state, $checkout_pane, $order) {
  if (isset($form_state['values']['commerce_payment']['payment_details']['cardonfile'])) {
    commerce_braintree_payment_session_save($order->order_id, $form_state['values']['commerce_payment']['payment_details']['cardonfile']);
    return TRUE;
  }

  // Do nothing, but still return TRUE.
  return TRUE;
}

/**
 * Saves the "card on file" choice (reuse a card, which card or new card).
 *
 * @param $order_id
 * @param $choice
 */
function commerce_braintree_payment_session_save($order_id, $choice) {
  $_SESSION['order_' . $order_id] = $choice;
}

/**
 * Get the "card on file" choice (reuse a card, which card or new card) in a
 * session variable.
 *
 * @param $order_id
 * @param $choice
 */
function commerce_braintree_payment_session_load($order_id) {
  return isset($_SESSION['order_' . $order_id]) ? $_SESSION['order_' . $order_id] : NULL;
}

/**
 * Delete the "card on file" choice.
 */
function commerce_braintree_payement_session_delete($order_id = NULL) {
  if ($order_id) {
    unset($_SESSION['order_' . $order_id]);
  }
}

/**
 * Include necessary API files from braintree and init the credentials.
 *
 * @param array $payment_method
 *   The payement method array provided by Commerce. Credentials will be taken
 *   from that.
 */
function _commerce_braintree_init_credentials($payment_method = array()) {
  $credentials = array();
  $credentials['commerce_braintree_mode'] = $payment_method['settings']['commerce_braintree_mode'];
  $credentials['commerce_braintree_merchant_id'] = $payment_method['settings']['commerce_braintree_merchant_id'];
  $credentials['commerce_braintree_public_key'] = $payment_method['settings']['commerce_braintree_public_key'];
  $credentials['commerce_braintree_private_key'] = $payment_method['settings']['commerce_braintree_private_key'];

  // Include Braintree API.
  require_once drupal_get_path('module', 'commerce_braintree') . '/braintree_php/lib/Braintree.php';
  if (!empty($credentials)) {
    Braintree_Configuration::environment($credentials['commerce_braintree_mode']);
    Braintree_Configuration::merchantId($credentials['commerce_braintree_merchant_id']);
    Braintree_Configuration::publicKey($credentials['commerce_braintree_public_key']);
    Braintree_Configuration::privateKey($credentials['commerce_braintree_private_key']);
  }
}

/**
 * Helper function. Build the credit card form.
 */
function commerce_braintree_credit_card_form($payment_method) {

  // Merge default values into the default array.
  $default = array(
    'start_month' => '',
    'start_year' => date('Y') - 5,
    'exp_month' => date('m'),
    'exp_year' => date('Y'),
  );
  $form['commerce_payment']['payment_method']['#default_value'] = $payment_method['instance_id'];
  $current_year_2 = date('y');
  $current_year_4 = date('Y');
  $form['commerce_payment']['payment_details']['credit_card'] = array(
    '#tree' => TRUE,
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.theme.css',
      ),
    ),
  );

  // Add a field for the credit card number.
  $form['commerce_payment']['payment_details']['credit_card']['number'] = array(
    '#type' => 'textfield',
    '#title' => t('Card number'),
    '#default_value' => '',
    '#attributes' => array(
      'autocomplete' => 'off',
    ),
    '#required' => TRUE,
    '#maxlength' => 19,
    '#size' => 20,
    '#name' => 'transaction[credit_card][number]',
  );

  // Add a field for the credit card number.
  $form['commerce_payment']['payment_details']['credit_card']['owner'] = array(
    '#type' => 'textfield',
    '#title' => t('Card owner'),
    '#default_value' => '',
    '#attributes' => array(
      'autocomplete' => 'off',
    ),
    '#required' => TRUE,
    '#size' => 50,
    '#name' => 'transaction[credit_card][cardholder_name]',
  );

  // Add fields for the credit card expiration date.
  $form['commerce_payment']['payment_details']['credit_card']['exp_month'] = array(
    '#type' => 'select',
    '#title' => t('Expiration'),
    '#options' => drupal_map_assoc(array_keys(commerce_months())),
    '#default_value' => strlen($default['exp_month']) == 1 ? '0' . $default['exp_month'] : $default['exp_month'],
    '#required' => TRUE,
    '#prefix' => '<div class="commerce-credit-card-expiration">',
    '#suffix' => '<span class="commerce-month-year-divider">/</span>',
    '#name' => 'transaction[credit_card][expiration_month]',
  );

  // Build a year select list that uses a 4 digit key with a 2 digit value.
  $options = array();
  for ($i = 0; $i < 20; $i++) {
    $options[$current_year_4 + $i] = str_pad($current_year_2 + $i, 2, '0', STR_PAD_LEFT);
  }
  $form['commerce_payment']['payment_details']['credit_card']['exp_year'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => $default['exp_year'],
    '#suffix' => '</div>',
    '#name' => 'transaction[credit_card][expiration_year]',
  );
  $form['commerce_payment']['payment_details']['credit_card']['code'] = array(
    '#type' => 'textfield',
    '#title' => t('Security code'),
    '#default_value' => '',
    '#attributes' => array(
      'autocomplete' => 'off',
    ),
    '#required' => TRUE,
    '#maxlength' => 4,
    '#size' => 4,
    '#name' => 'transaction[credit_card][cvv]',
  );
  return $form;
}

/**
 * Return the query string that contains Braintree response, after a request.
 */
function commerce_braintree_get_feedback() {
  $feedback = FALSE;
  if (isset($_SERVER['QUERY_STRING'])) {
    $feedback = $_SERVER['QUERY_STRING'];
  }
  return $feedback;
}

/**
 * Get transaction with a specific Braintree ID.
 */
function commerce_braintree_get_payment_transaction($feedback_remote_id) {
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'commerce_payment_transaction')
    ->propertyCondition('payment_method', 'braintree')
    ->propertyCondition('remote_id', $feedback_remote_id)
    ->execute();
  if (isset($result['commerce_payment_transaction']) && count($result['commerce_payment_transaction']) > 0) {
    $transaction = array_pop($result['commerce_payment_transaction']);
    return $transaction->transaction_id;
  }
  return FALSE;
}

/**
 * Implements hook_module_implements_alter().
 *
 * The hook_form_alter used in commerce_cardonfile is not adapted to a token
 * payment gateway. We need to remove the implementations of cardonfile
 * form_alter and use our own alter.
 *
 * @param $implementations
 * @param $hook
 */
function commerce_braintree_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'form_alter' && isset($implementations['commerce_cardonfile'])) {
    $group = $implementations['commerce_cardonfile'];
    unset($implementations['commerce_cardonfile']);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function commerce_braintree_form_commerce_cardonfile_update_form_alter(&$form, &$form_state) {
  global $user;
  $payment_method = commerce_payment_method_instance_load('braintree_cof|commerce_payment_braintree_cof');
  _commerce_braintree_init_credentials($payment_method);
  $form['credit_card']['owner']['#name'] = 'credit_card[cardholder_name]';
  $form['credit_card']['number']['#name'] = 'credit_card[number]';
  $form['credit_card']['exp_month']['#name'] = 'transaction[credit_card][expiration_month]';
  $form['credit_card']['exp_year']['#name'] = 'transaction[credit_card][expiration_year]';
  $trData = Braintree_TransparentRedirect::updateCreditCardData(array(
    'redirectUrl' => url('user/commerce_braintree/update_card', array(
      'absolute' => TRUE,
    )),
    'paymentMethodToken' => $form['card_data']['#value']['remote_id'],
  ));
  $form['tr_data']['#type'] = 'hidden';
  $form['tr_data']['#name'] = 'tr_data';
  $form['tr_data']['#default_value'] = $trData;
  $form['#action'] = Braintree_TransparentRedirect::url();
}

/**
 * Provide the cancel and return url used when calling the payment provider.
 */
function _commerce_braintree_set_feedback_url($order, $payment_method) {

  // Set feedback URLs.
  $settings = array(
    // Return to the previous page when payment is canceled.
    'cancel_return' => url('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key'], array(
      'absolute' => TRUE,
    )),
    // Return to the payment redirect page for processing successful payments.
    'return' => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array(
      'absolute' => TRUE,
    )),
    // Specify the current payment method instance ID in the notify_url
    'payment_method' => $payment_method['instance_id'],
  );
  return $settings;
}

/**
 * Prepare data that will be sent during a Braintree transaction.
 *
 * @param $order
 * @return array
 */
function _commerce_braintree_get_transaction_informations($order) {
  $wrapper = entity_metadata_wrapper('commerce_order', $order);

  // Get financial info.
  $currency_code = $wrapper->commerce_order_total->currency_code
    ->value();
  $amount = $wrapper->commerce_order_total->amount
    ->value();

  // Customer data.
  $customer_name = $wrapper->commerce_customer_billing->commerce_customer_address->name_line
    ->value();
  $country = $wrapper->commerce_customer_billing->commerce_customer_address->country
    ->value();
  $thoroughfare = $wrapper->commerce_customer_billing->commerce_customer_address->thoroughfare
    ->value();
  $locality = $wrapper->commerce_customer_billing->commerce_customer_address->locality
    ->value();
  $postal_code = $wrapper->commerce_customer_billing->commerce_customer_address->postal_code
    ->value();
  $administrative_area = $wrapper->commerce_customer_billing->commerce_customer_address->administrative_area
    ->value();
  $wrapper2 = entity_metadata_wrapper('user', $order->uid);
  $customer_mail = $wrapper2->mail
    ->value();
  return array(
    $amount,
    $customer_name,
    $country,
    $thoroughfare,
    $locality,
    $postal_code,
    $administrative_area,
    $customer_mail,
  );
}

/**
 * Process the actual Braintree transaction.
 *
 * @param $result
 * @param $order
 * @param array $payment_method
 * @param $redirect
 */
function _commerce_braintree_default_process_transaction($result, $order, $payment_method, $redirect) {

  // Get the braintree transaction object.
  $transaction = $result->transaction;

  // Check if we already have this transaction stores in Commerce.
  $transaction_id = isset($transaction->id) ? commerce_braintree_get_payment_transaction($transaction->id) : NULL;
  if (!$transaction_id) {
    $commerce_transaction = commerce_payment_transaction_new('braintree', $order->order_id);
  }
  else {
    $commerce_transaction = commerce_payment_transaction_load($transaction_id);
  }
  $message = NULL;
  if ($result->success) {
    $processorResponseCode = $result->transaction->processorResponseCode;
    $processorResponseText = $result->transaction->processorResponseText;
    $message .= $processorResponseText;
  }
  else {
    $message = $result->message;
    drupal_set_message(t('There was an error: %error.', array(
      '%error' => $result->message,
    )), 'error');
  }

  // Prepare the data to be recorded in Commerce.
  $commerce_transaction->instance_id = $payment_method['instance_id'];
  $commerce_transaction->message = $message;
  if ($redirect) {
    if (!$result->success) {
      $commerce_transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
      commerce_payment_transaction_save($commerce_transaction);

      // There was an error, go back to the previous pane.
      commerce_payment_redirect_pane_previous_page($order);
    }
    else {
      $commerce_transaction->remote_id = $transaction->id;
      $commerce_transaction->remote_status = $transaction->status;

      // Commerce don't store amount in decimal, convert it.
      $commerce_transaction->amount = commerce_currency_decimal_to_amount($transaction->amount, $transaction->currencyIsoCode);
      $commerce_transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
      commerce_payment_transaction_save($commerce_transaction);

      // Transaction succeded. Go to next pane.
      commerce_payment_redirect_pane_next_page($order);
    }
  }
}

Functions

Namesort descending Description
commerce_braintree_cardonfile_update_delete Callback for card on file update or delete.
commerce_braintree_commerce_checkout_page_info_alter Implements hook_commerce_checkout_page_info_alter().
commerce_braintree_commerce_checkout_pane_info_alter Implements hook_commerce_checkout_pane_info_alter().
commerce_braintree_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_braintree_credit_card_form Helper function. Build the credit card form.
commerce_braintree_default_settings_form Returns the default settings for the Braintree payment method.
commerce_braintree_form_alter Implements hook_form_alter().
commerce_braintree_form_commerce_cardonfile_update_form_alter Implements hook_form_FORM_ID_alter().
commerce_braintree_get_feedback Return the query string that contains Braintree response, after a request.
commerce_braintree_get_payment_transaction Get transaction with a specific Braintree ID.
commerce_braintree_menu Implements hook_menu().
commerce_braintree_module_implements_alter Implements hook_module_implements_alter().
commerce_braintree_payement_session_delete Delete the "card on file" choice.
commerce_braintree_payment_session_load Get the "card on file" choice (reuse a card, which card or new card) in a session variable.
commerce_braintree_payment_session_save Saves the "card on file" choice (reuse a card, which card or new card).
commerce_braintree_update_card Menu callback. Get the query from Braintree when updating a credit card.
_commerce_braintree_commerce_payment_checkout_custom_validation Callback. Custom checkout payement validation.
_commerce_braintree_default_process_transaction Process the actual Braintree transaction.
_commerce_braintree_get_transaction_informations Prepare data that will be sent during a Braintree transaction.
_commerce_braintree_init_credentials Include necessary API files from braintree and init the credentials.
_commerce_braintree_set_feedback_url Provide the cancel and return url used when calling the payment provider.