commerce_braintree_dropin.module in Commerce Braintree 7.3
Same filename and directory in other branches
Provides integration with Braintree Drop-in UI.
File
modules/commerce_braintree_dropin/commerce_braintree_dropin.moduleView source
<?php
/**
* @file
* Provides integration with Braintree Drop-in UI.
*/
/**
* Implements hook_library().
*/
function commerce_braintree_dropin_library() {
$path = drupal_get_path('module', 'commerce_braintree_dropin');
$libraries['braintree.dropin'] = array(
'title' => 'Braintree DropIn',
'website' => 'https://braintreepayments.com',
'version' => '1.7.0',
'js' => array(
'https://js.braintreegateway.com/web/dropin/1.7.0/js/dropin.min.js' => array(
'type' => 'external',
),
$path . '/js/commerce_braintree_dropin.js' => array(),
),
);
return $libraries;
}
/**
* Implements hook_commerce_payment_method_info().
*/
function commerce_braintree_dropin_commerce_payment_method_info() {
$payment_methods = array();
$payment_methods['braintree_dropin'] = array(
'base' => 'commerce_braintree_dropin',
'title' => t('Braintree Drop-in UI'),
'short_title' => t('Braintree Drop-in UI'),
'display_title' => t('Credit card'),
'description' => t('Integrates with Braintree Drop-in for secure on-site credit card payment.'),
'terminal' => TRUE,
'offsite' => FALSE,
'callbacks' => array(
'submit_form_validate' => 'commerce_braintree_js_form_validate',
'submit_form_submit' => 'commerce_braintree_js_form_submit',
),
'cardonfile' => array(
'create form callback' => 'commerce_braintree_js_cardonfile_form',
'update form callback' => 'commerce_braintree_js_cardonfile_form',
'create callback' => 'commerce_braintree_js_cardonfile_form_submit',
'update callback' => 'commerce_braintree_js_cardonfile_form_submit',
'delete callback' => 'commerce_braintree_cardonfile_update_delete',
'charge callback' => 'commerce_braintree_cardonfile_charge',
),
);
return $payment_methods;
}
/**
* Returns the default settings for Braintree Drop-in UI.
*/
function commerce_braintree_dropin_default_settings() {
return array(
'merchant_id' => '',
'public_key' => '',
'private_key' => '',
'merchant_account_id' => '',
'environment' => 'sandbox',
'paypal_flow' => 0,
'cardonfile' => FALSE,
'submit_for_settlement' => TRUE,
);
}
/**
* Payment method callback: Braintree Web settings form.
*
* @see CALLBACK_commerce_payment_method_settings_form()
*/
function commerce_braintree_dropin_settings_form($settings = array()) {
$settings = $settings + commerce_braintree_dropin_default_settings();
// Reuse the transparent redirect settings form.
$form = commerce_braintree_settings_form($settings);
// Add option to enable the Braintree PayPal button.
$form['paypal_flow'] = array(
'#type' => 'radios',
'#title' => t('PayPal Flow'),
'#description' => t('Enables PayPal payments. Vault will store the payment information in the Braintree vault. Checkout will not store the payment information. <strong>Must also be configured in the Braintree dashboard.</strong>'),
'#options' => array(
0 => t('Disabled'),
'vault' => t('Vault'),
'checkout' => t('Checkout'),
),
'#default_value' => $settings['paypal_flow'],
);
return $form;
}
/**
* Implements hook_form_alter().
*/
function commerce_braintree_dropin_form_alter(&$form, &$form_state, $form_id) {
// Check to see if this is a checkout form and there is payment form element.
if (strstr($form_id, 'commerce_checkout_form') && !empty($form['commerce_payment']) && !empty($form_state['order']->payment_methods)) {
// Determine if Braintree Drop-in is available for this order.
$payment_methods = $form_state['order']->payment_methods;
foreach ($payment_methods as $payment_method) {
if ($payment_method['method_id'] == 'braintree_dropin') {
$drop_in = TRUE;
}
}
if (!empty($drop_in)) {
// When there is more than 1 payment method available, replace the
// ajax handler for the payment method selector so that the entire
// form is reloaded. Otherwise you cannot switch between different
// payment methods during checkout. This is because Braintree
// Drop-in hijacks the form event handlers.
if (count($payment_methods) > 1) {
$form['#prefix'] = '<div id="commerce-braintree-dropin-checkout-wrapper">';
$form['#suffix'] = '</div>';
$form['commerce_payment']['payment_method']['#ajax'] = array(
'callback' => 'commerce_braintree_dropin_checkout_ajax',
'wrapper' => 'commerce-braintree-dropin-checkout-wrapper',
);
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Alters the admin order payment form to add support for Braintree Drop-in UI.
*/
function commerce_braintree_dropin_form_commerce_payment_order_transaction_add_form_alter(&$form, &$form_state) {
// Include the Braintree Drop-in UI when it is selected as the payment method.
if (!empty($form['payment_terminal']) && $form_state['payment_method']['method_id'] == 'braintree_dropin') {
$arguments = array();
if (!empty($form_state['order']->uid)) {
$account = user_load($form_state['order']->uid);
if (!empty($account->data['braintree_vault']['id'])) {
$arguments['customerId'] = $account->data['braintree_vault']['id'];
}
}
$form['payment_terminal']['payment_details'] += (array) commerce_braintree_dropin_submit_form_elements($form_state['payment_method'], $arguments, $form_state['order']);
}
}
/**
* Implements hook_commerce_cardonfile_payment_terminal_form_alter().
*/
function commerce_braintree_commerce_cardonfile_payment_terminal_form_alter(&$form, &$form_state) {
// Drop-in UI has it's own interface for card on file, so we disable
// access to the cardonfile form elements when drop-in is selected.
if (!empty($form['payment_terminal']['payment_details']['cardonfile']) && !empty($form_state['payment_method']['base']) && $form_state['payment_method']['base'] == 'commerce_braintree_dropin') {
$form['payment_terminal']['payment_details']['cardonfile']['#access'] = FALSE;
}
}
/**
* Override of commerce_payment_pane_checkout_form_details_refresh().
*
* Replaces the entire form so that Braintree Drop-in events can be
* added or removed properly when changing payment methods during
* checkout.
*/
function commerce_braintree_dropin_checkout_ajax(&$form, &$form_state) {
return $form;
}
/**
* Form callback for Braintree Drop-in payment method.
*
* @see CALLBACK_commerce_payment_method_submit_form().
*/
function commerce_braintree_dropin_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
$form = array();
$arguments = array();
if (!empty($order->uid)) {
$account = user_load($order->uid);
if (!empty($account->data['braintree_vault']['id'])) {
$arguments['customerId'] = $account->data['braintree_vault']['id'];
}
}
// Append common the drop-in ui form elements to the form array.
$form += (array) commerce_braintree_dropin_submit_form_elements($payment_method, $arguments, $order);
return $form;
}
/**
* Returns the common FAPI form elements for all Drop-in UI implementations.
*
* @param $payment_method
* The payment method being used.
* @param array $arguments
* An array of arguments to be passed Braintree token method.
* @param mixed $order
* An optional order object.
*
* @return array
* An array of form elements for the drop-in ui.
*/
function commerce_braintree_dropin_submit_form_elements($payment_method, $arguments = array(), $order = FALSE) {
global $user;
$form = array();
// Initialize Braintree and create a token.
commerce_braintree_initialize($payment_method);
$js_settings = array(
'environment' => $payment_method['settings']['environment'],
'options' => array(
'authorization' => Braintree_ClientToken::generate($arguments),
'container' => '#commerce-braintree-dropin-container',
),
'submitSelector' => '.checkout-continue',
'nonceInput' => 'commerce_payment[payment_details][nonce]',
);
// Add PayPal payment option if enabled.
if (!empty($payment_method['settings']['paypal_flow'])) {
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$amount = $order_wrapper->commerce_order_total
->value();
$js_settings['options']['paypal']['flow'] = $payment_method['settings']['paypal_flow'];
$js_settings['options']['paypal']['amount'] = commerce_currency_amount_to_decimal($amount['amount'], $amount['currency_code']);
$js_settings['options']['paypal']['currency'] = $amount['currency_code'];
}
// Allow other modules to alter the JS settings.
drupal_alter('commerce_braintree_dropin_js', $js_settings, $payment_method);
// The custom token is required to generate the Drop-in payment form.
$form['#attached']['js'][] = array(
'data' => array(
'commerceBraintreeDropin' => $js_settings,
),
'type' => 'setting',
);
$form['#attached']['library'][] = array(
'commerce_braintree_dropin',
'braintree.dropin',
);
// Include a container div for the Drop-in form to attach to.
$form['braintree_dropin'] = array(
'#markup' => '<div id="commerce-braintree-dropin-container"></div>',
);
// Add a field to store the nonce.
$form['nonce'] = array(
'#type' => 'hidden',
);
// Add option to save card on file for authenticated users.
if (!empty($payment_method['settings']['cardonfile']) && !empty($user->uid)) {
$storage = variable_get('commerce_cardonfile_storage', 'opt-in');
if ($storage !== 'required') {
$form['cardonfile'] = array(
'#type' => 'checkbox',
'#title' => t('Securely save this payment method for next time.'),
'#default_value' => $storage == 'opt-in' ? FALSE : TRUE,
);
}
else {
$form['cardonfile'] = array(
'#type' => 'value',
'#value' => TRUE,
);
}
}
return $form;
}
/**
* Form callback for commerce_cardonfile entities.
*/
function commerce_braintree_dropin_cardonfile_form($form, &$form_state, $op, $card) {
$form_state['card'] = $card;
$account = user_load($card->uid);
$arguments = array();
$payment_instance = commerce_payment_method_instance_load($card->instance_id);
$form_state['payment_instance'] = $payment_instance;
commerce_braintree_initialize($payment_instance);
if (!empty($card->remote_id)) {
// Query Braintree for the payment method matching this card on file.
try {
$payment_method = \Braintree_PaymentMethod::find($card->remote_id);
} catch (Exception $ex) {
// If Braintree doesn't return the payment method, we cannot proceed.
drupal_set_message(t('We are unable to locate your stored payment method'), 'error');
watchdog('commerce_braintree_dropin', 'Unable to fetch Braintree payment method due to @error', array(
'@error' => $ex
->getMessage(),
), WATCHDOG_ERROR);
return array();
}
}
// Determine the Braintree customer id and append it to the API request.
if (!empty($account->data) && !empty($account->data['braintree_vault'])) {
// Set the Braintree Customer ID if stored on the user object.
$arguments['customerId'] = $account->data['braintree_vault']['id'];
}
else {
if (!empty($payment_method->customerId)) {
// Set the Braintree Customer ID from the loaded payment method.
$arguments['customerId'] = $payment_method->customerId;
}
}
// Append common the drop-in ui form elements to the form array.
$form += (array) commerce_braintree_dropin_submit_form_elements($payment_instance, $arguments);
// Remove the card on file options since we're always saving these.
unset($form['cardonfile']);
$form['customer_id'] = array(
'#type' => 'value',
'#value' => !empty($arguments['customerId']) ? $arguments['customerId'] : FALSE,
);
$form['actions'] = array(
'#type' => 'container',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save Payment Method'),
);
return $form;
}
/**
* Submit handler for commerce_cardonfile form callbacks.
*/
function commerce_braintree_dropin_cardonfile_form_submit(&$form, &$form_state) {
$account = user_load($form_state['card']->uid);
$cards = commerce_cardonfile_load_multiple_by_uid($form_state['card']->uid, $form_state['payment_instance']['instance_id']);
commerce_braintree_initialize($form_state['payment_instance']);
$nonce = commerce_braintree_js_get_nonce();
// Populate the customer variable with data from Braintree.
if (!empty($form_state['values']['customer_id'])) {
// Query for the customer record from the customer id in form values.
try {
$customer = Braintree_Customer::find($form_state['values']['customer_id']);
} catch (Exception $ex) {
watchdog('commerce_braintree_dropin', 'Unable to fetch Braintree customer account due to @error', array(
'@error' => $ex
->getMessage(),
), WATCHDOG_ERROR);
return;
}
// Create or determine the payment method that was selected
// during update.
try {
$payment_method = Braintree_PaymentMethod::create(array(
'customerId' => $form_state['values']['customer_id'],
'paymentMethodNonce' => $nonce,
))->paymentMethod;
} catch (Exception $ex) {
watchdog('commerce_braintree_dropin', 'Unable to load/create a payment method due to @error', array(
'@error' => $ex
->getMessage(),
), WATCHDOG_ERROR);
return;
}
}
else {
// Create a new customer and payment method at once.
try {
$customer = Braintree_Customer::create(array(
'email' => $account->mail,
'paymentMethodNonce' => $nonce,
))->customer;
$payment_method = reset($customer->creditCards);
} catch (Exception $ex) {
watchdog('commerce_braintree_dropin', 'Unable to fetch Braintree customer account due to @error', array(
'@error' => $ex
->getMessage(),
), WATCHDOG_ERROR);
return;
}
}
try {
if (!empty($payment_method->token)) {
// Set this payment method as the default.
Braintree_PaymentMethod::update($payment_method->token, array(
'options' => array(
'makeDefault' => TRUE,
),
));
$instance_default = $payment_method->token;
}
} catch (Exception $ex) {
watchdog('commerce_braintree_dropin', 'Unable to set default payment method due to @error', array(
'@error' => $ex
->getMessage(),
), WATCHDOG_ERROR);
}
// Loop over each of the Braintree payment methods and make sure
// a matching card on file entity exits.
foreach ($customer->creditCards as $vault_profile) {
$card = commerce_cardonfile_load_multiple(array(), array(
'remote_id' => $vault_profile->token,
));
// Create a new card on file entity if we were unable to load one
// for this vault profile.
if (empty($card)) {
$card = commerce_cardonfile_new();
$card->remote_id = $vault_profile->token;
$card->status = TRUE;
$card->uid = $form_state['card']->uid;
}
else {
$card = reset($card);
}
// Update the values returned from Braintree.
$card->card_type = $vault_profile->cardType;
$card->card_number = $vault_profile->last4;
$card->card_exp_month = $vault_profile->expirationMonth;
$card->card_exp_year = $vault_profile->expirationYear;
$card->instance_default = $vault_profile->default;
$card->payment_method = $form_state['payment_instance']['method_id'];
$card->instance_id = $form_state['payment_instance']['instance_id'];
$card->instance_default = !empty($instance_default) ? $instance_default == $vault_profile->token : $vault_profile->default;
commerce_cardonfile_save($card);
// Pop this card off the cards on file array.
unset($cards[$card->card_id]);
}
// Loop over any cards that were't returned from Braintree
// and make sure they're deleted.
foreach ($cards as $card) {
commerce_cardonfile_delete($card->card_id);
}
// Store the Braintree customer information on the Drupal user.
$account->data['braintree_vault']['id'] = $customer->id;
user_save($account);
drupal_set_message(t('Payment information updated successfully'));
$form_state['redirect'] = 'user/' . $form_state['card']->uid . '/cards';
}
/**
* Implements hook_commerce_cardonfile_checkout_pane_form_alter().
*/
function commerce_braintree_dropin_commerce_cardonfile_checkout_pane_form_alter(&$pane, $form, $form_state) {
// Drop-in UI has it's own card on file UI.
// Remove what commerce_cardonfile provides.
if (strpos($form['commerce_payment']['payment_method']['#default_value'], 'braintree_dropin') === 0) {
$pane['cardonfile']['#access'] = FALSE;
}
}
/**
* Delete callback for commerce_cardonfile.
*/
function commerce_braintree_dropin_cardonfile_delete($form, &$form_state, $payment_method, $card) {
commerce_braintree_initialize($payment_method);
try {
Braintree_PaymentMethod::delete($card->remote_id);
} catch (Exception $ex) {
watchdog('commerce_braintree_dropin', 'Unable to delete payment method due to @error', array(
'@error' => $ex
->getMessage(),
), WATCHDOG_ERROR);
drupal_set_message(t('Unable to delete the payment method at this time'), 'error');
}
// If the card being deleted is the instance default, attempt to update
// another card owned by the the same user as the instance default.
if ($card->instance_default) {
$cards = commerce_cardonfile_load_multiple_by_uid($card->uid, $payment_method);
unset($cards[$card->card_id]);
if (!empty($cards)) {
$new_default = reset($cards);
$new_default->instance_default = TRUE;
commerce_cardonfile_save($new_default);
}
}
commerce_cardonfile_delete($card->card_id);
return TRUE;
}