commerce_braintree.module in Commerce Braintree 7
Same filename and directory in other branches
Implementations of the Braintree payment gateway (http://braintreepayments.com) for drupal commerce.
File
commerce_braintree.moduleView 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);
}
}
}