commerce_braintree_express_checkout.module in Commerce Braintree 7.3
Provides integration PayPal Express Checkout for Braintree.
File
modules/commerce_braintree_express_checkout/commerce_braintree_express_checkout.moduleView source
<?php
/**
* @file
* Provides integration PayPal Express Checkout for Braintree.
*/
define('COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID', 'braintree_express_checkout|commerce_payment_braintree_express_checkout');
/**
* Implements hook_library().
*/
function commerce_braintree_express_checkout_library() {
$path = drupal_get_path('module', 'commerce_braintree_express_checkout');
$libraries['braintree.expresscheckout'] = array(
'title' => 'Braintree Express Checkout',
'website' => 'https://developer.paypal.com/docs/accept-payments/express-checkout/ec-braintree-sdk/client-side/javascript/v3/',
'version' => '3.22.2',
'js' => array(
'https://js.braintreegateway.com/web/3.22.2/js/client.min.js' => array(
'type' => 'external',
),
'https://js.braintreegateway.com/web/3.22.2/js/paypal-checkout.min.js' => array(
'type' => 'external',
),
'https://www.paypalobjects.com/api/checkout.js' => array(
'type' => 'external',
),
$path . '/js/commerce_braintree_express_checkout.js' => array(),
),
);
return $libraries;
}
/**
* Implements hook_commerce_payment_method_info().
*/
function commerce_braintree_express_checkout_commerce_payment_method_info() {
$payment_methods = array();
$payment_methods['braintree_express_checkout'] = array(
'base' => 'commerce_braintree_express_checkout',
'title' => t('Braintree Express Checkout'),
'short_title' => t('Braintree Express Checkout'),
'display_title' => t('PayPal Express Checkout'),
'description' => t('Integrates with PayPal Express Checkout for secure on-site credit card payment through Braintree.'),
'callbacks' => array(
'submit_form_validate' => 'commerce_braintree_js_form_validate',
'submit_form_submit' => 'commerce_braintree_js_form_submit',
),
);
return $payment_methods;
}
/**
* Returns the default settings for Express Checkout.
*/
function commerce_braintree_express_checkout_default_settings() {
return array(
'merchant_id' => '',
'public_key' => '',
'private_key' => '',
'merchant_account_id' => '',
'descriptor_name' => '',
'environment' => 'sandbox',
'submit_for_settlement' => TRUE,
'show_on_cart' => TRUE,
'update_billing_profiles' => TRUE,
'update_shipping_profiles' => TRUE,
);
}
/**
* Payment method callback: Braintree Express Checkout settings.
*
* @see CALLBACK_commerce_payment_method_settings_form()
*/
function commerce_braintree_express_checkout_settings_form($settings = array()) {
$settings = $settings + commerce_braintree_express_checkout_default_settings();
// Reuse the transparent redirect settings form.
$form = commerce_braintree_settings_form($settings);
$form['show_on_cart'] = array(
'#type' => 'checkbox',
'#title' => t('Enable Express Checkout on cart page'),
'#description' => t('Show PayPal Express Checkout button on the shopping cart page (/cart)'),
'#default_value' => $settings['show_on_cart'],
);
$form['update_billing_profiles'] = array(
'#type' => 'checkbox',
'#title' => t('Update billing customer profiles with address information the customer enters at PayPal.'),
'#default_value' => $settings['update_billing_profiles'],
);
if (module_exists('commerce_shipping')) {
$form['update_shipping_profiles'] = array(
'#type' => 'checkbox',
'#title' => t('Update shipping customer profiles with address information the customer enters at PayPal.'),
'#default_value' => $settings['update_shipping_profiles'],
);
}
return $form;
}
/**
* Implements hook_form_alter().
*/
function commerce_braintree_express_checkout_form_alter(&$form, &$form_state, $form_id) {
if (strpos($form_id, 'commerce_checkout_form') === 0 && !empty($form['commerce_payment']) && !empty($form['buttons'])) {
$order = $form_state['order'];
// Only alter the form if express checkout is enabled and
// available to this order.
if (!_commerce_braintree_express_checkout_enabled($order)) {
return;
}
$ec_payment_method = commerce_payment_method_instance_load(COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID);
if (!empty($order->data['commerce_braintree_express_checkout']['nonce'])) {
$nonce = $order->data['commerce_braintree_express_checkout']['nonce'];
// If the order contains a valid nonce alter the payment form
// to set Express Checkout as the payment option.
if (commerce_braintree_express_checkout_validate_nonce($nonce)) {
// Update the payment method label to show the PayPal account.
$label = t('Pay with your PayPal account (@mail).', array(
'@mail' => $order->data['commerce_braintree_express_checkout']['details']['email'],
));
// Remove other payment options since the customer has already
// selected to use Express Checkout payments.
$form['commerce_payment']['payment_method']['#options'] = array(
COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID => $label,
);
}
}
// Add the Express Checkout button to all payment check pages.
$form['buttons'] += commerce_braintree_express_checkout_button_form($order, $ec_payment_method, array(
'form_id' => $form_id,
));
}
}
/**
* Form callback for Braintree Express Checkout payment method.
*
* @see CALLBACK_commerce_payment_method_submit_form().
*/
function commerce_braintree_express_checkout_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
$nonce = FALSE;
if (!empty($order->data['commerce_braintree_express_checkout']['nonce'])) {
// Unset the nonce if it's no longer valid.
if (commerce_braintree_express_checkout_validate_nonce($order->data['commerce_braintree_express_checkout']['nonce'])) {
$nonce = $order->data['commerce_braintree_express_checkout']['nonce'];
$form['cancel'] = array(
'#type' => 'submit',
'#value' => t('Use another payment method'),
'#element_validate' => array(),
'#submit' => array(
'commerce_braintree_express_checkout_cancel',
),
);
}
}
$form['nonce'] = array(
'#type' => 'hidden',
'#default_value' => $nonce,
);
return $form;
}
/**
* Submit callback to cancel Express Checkout.
*/
function commerce_braintree_express_checkout_cancel($form, &$form_state) {
if (!empty($form_state['order'])) {
$order = $form_state['order'];
}
else {
global $user;
$order = commerce_cart_order_load($user->uid);
}
// Remove the Express Checkout data from the order and save it.
if (!empty($order->data['commerce_braintree_express_checkout'])) {
unset($order->data['commerce_braintree_express_checkout']);
commerce_order_save($order);
drupal_set_message(t('The PayPal account associated with this order will no longer be used.'));
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function commerce_braintree_express_checkout_form_views_form_commerce_cart_form_default_alter(&$form, &$form_state, $form_id) {
if (!empty($form_state['build_info']['args'][0]->result)) {
// This hook executes before commerce_line_item adds the order
// object to form state, so we'll load it the same way.
$view = $form_state['build_info']['args'][0];
$order = commerce_order_load($view->argument['order_id']->value[0]);
// Verify that Express Checkout is available for this order.
if (_commerce_braintree_express_checkout_enabled($order)) {
$payment_method = commerce_payment_method_instance_load(COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID);
// Determine if we should show Express Checkout on /cart.
if (!empty($payment_method['settings']['show_on_cart'])) {
// Add the Express Checkout to the cart form.
$form['actions'] += commerce_braintree_express_checkout_button_form($order, $payment_method, array(
'form_id' => $form_id,
));
// Append our submit handler to form.
$form['#submit'][] = 'commerce_braintree_express_checkout_submit';
}
}
}
}
/**
* Validates that a nonce is still active and has not been consumed.
*
* @param $nonce
* The Braintree nonce.
* @return bool
* TRUE if the nonce is still valid.
*/
function commerce_braintree_express_checkout_validate_nonce($nonce) {
// Utilize static caching to prevent extraneous API calls.
$valid =& drupal_static(__FUNCTION__);
if (!isset($valid)) {
$payment_method = commerce_payment_method_instance_load(COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID);
commerce_braintree_initialize($payment_method);
$result = Braintree_PaymentMethodNonce::find($nonce);
// The nonce is valid if a response is returned and it has not been
// previously consumed.
$valid = !empty($result) && isset($result->consumed) && $result->consumed == FALSE;
}
return $valid;
}
/**
* Builds the Express Checkout form elements.
*
* Form elements are built here are used on the cart and checkout forms.
*
* @param $order
* @param $payment_method
* @param $context
* @return array
*/
function commerce_braintree_express_checkout_button_form($order, $payment_method, $context) {
$form = array();
// Make sure the payment method is available.
if (empty($payment_method)) {
return $form;
}
commerce_braintree_initialize($payment_method);
$form['#attached']['library'][] = array(
'commerce_braintree_express_checkout',
'braintree.expresscheckout',
);
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$amount = $order_wrapper->commerce_order_total
->value();
// Determine the correct submit handler for this form.
if (!empty($context['form_id'])) {
if (strpos($context['form_id'], 'commerce_checkout_form') === 0) {
$submit_selector = '.checkout-continue';
}
if ($context['form_id'] == 'views_form_commerce_cart_form_default') {
$submit_selector = '#edit-checkout';
}
}
// Set up an alterable array of JS settings that will
// be used in js/commerce_braintree_express_checkout.js.
$js_settings = array(
'submitSelector' => !empty($submit_selector) ? $submit_selector : '[name=op]',
'drupalForm' => !empty($context['form_id']) ? $context['form_id'] : '',
'environment' => $payment_method['settings']['environment'],
'orderStatus' => $order->status,
'buttonId' => 'commerce-braintree-express-checkout-button',
'nonceSelector' => 'input[name="commerce_payment[payment_details][nonce]"]',
'payloadInput' => 'commerce_braintree_payload',
'instanceId' => COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID,
'buttonStyle' => array(
'size' => 'small',
'color' => 'gold',
'shape' => 'pill',
'label' => 'checkout',
),
'options' => array(
'authorization' => Braintree_ClientToken::generate(),
),
'createPaymentOptions' => array(
'flow' => 'checkout',
'amount' => commerce_currency_amount_to_decimal($amount['amount'], $amount['currency_code']),
'currency' => $amount['currency_code'],
'enableShippingAddress' => TRUE,
),
);
// If a shipping address exists on the order, pass it to PayPal.
if (module_exists('commerce_shipping') && !empty($order->commerce_customer_shipping)) {
try {
$address = $order_wrapper->commerce_customer_shipping->commerce_customer_address
->value();
$js_settings['createPaymentOptions'] += array(
'shippingAddressEditable' => FALSE,
'shippingAddressOverride' => array(
'recipientName' => $address['name_line'],
'line1' => $address['thoroughfare'],
'line2' => $address['premise'],
'city' => $address['locality'],
'countryCode' => $address['country'],
'postalCode' => $address['postal_code'],
'state' => $address['administrative_area'],
),
);
} catch (Exception $ex) {
watchdog('commerce_braintree_express_checkout', 'Unable to access commerce customer shipping address');
}
}
// Allow other modules to alter the JS settings.
drupal_alter('commerce_braintree_express_checkout_js', $js_settings, $payment_method);
// Add Express Checkout JS settings object.
$form['#attached']['js'][] = array(
'data' => array(
'commerceBraintreeExpressCheckout' => $js_settings,
),
'type' => 'setting',
);
// Create a DOM element for the EC button.
$form['commerce_braintree_ec_button'] = array(
'#markup' => '<div id="' . $js_settings['buttonId'] . '"></div>',
'#weight' => !empty($context['form_id']) && $context['form_id'] == 'views_form_commerce_cart_form_default' ? 10 : -1,
);
$form['commerce_braintree_payload'] = array(
'#type' => 'hidden',
);
return $form;
}
/**
* Form submit handler for Express Checkout payload submission.
*/
function commerce_braintree_express_checkout_submit($form, &$form_state) {
$order = $form_state['order'];
$payment_method = commerce_payment_method_instance_load(COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID);
// If a payload was provided, extract it and grab the nonce.
if (!empty($form_state['values']['commerce_braintree_payload'])) {
$payload = json_decode($form_state['values']['commerce_braintree_payload'], TRUE);
if (empty($payload['nonce'])) {
watchdog('commerce_braintree_express_checkout', 'Payload submitted without a nonce.', array(), WATCHDOG_ERROR);
return;
}
// Append the payload data to the order object.
$order->data['commerce_braintree_express_checkout'] = $payload;
if (empty($order->mail)) {
$order->mail = $payload['details']['email'];
}
// Force the payment method on the order to be Express Checkout.
$order->data['payment_method'] = COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID;
// Create a billing information profile for the order with the available info.
if (!empty($payment_method['settings']['update_billing_profiles'])) {
commerce_braintree_express_checkout_create_customer_profile($order, 'billing', $payload, TRUE);
}
// If the shipping module exists on the site, create a shipping information
// profile for the order with the available info.
if (module_exists('commerce_shipping') && !empty($payment_method['settings']['update_shipping_profiles'])) {
commerce_braintree_express_checkout_create_customer_profile($order, 'shipping', $payload, TRUE);
}
// Save the order updates.
commerce_order_save($order);
}
}
/**
* Is Express Checkout enabled or allowed for this order?
*
* @param null $order
* An optional commerce order object.
*
* @return bool
* TRUE if the payment method is available.
*/
function _commerce_braintree_express_checkout_enabled($order = NULL) {
if (!empty($order)) {
$order->payment_methods = array();
rules_invoke_all('commerce_payment_methods', $order);
uasort($order->payment_methods, 'drupal_sort_weight');
return in_array(COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID, array_keys($order->payment_methods));
}
$rule = rules_config_load('commerce_payment_braintree_express_checkout');
return !empty($rule->active);
}
/**
* Creates or updates a customer profile for an order based on information
* obtained from PayPal after an Express Checkout.
*
* @param $order
* The order that was paid via Express Checkout.
* @param $profile_type
* The type of the customer profile that should be created or updated.
* @param $payload
* The payload array from a Express Checkout payload.
* @param $skip_save
* Boolean indicating whether or not this function should skip saving the
* order after setting it to reference the newly created customer profile;
* defaults to TRUE, requiring the caller to save the order.
*/
function commerce_braintree_express_checkout_create_customer_profile($order, $profile_type, $payload, $skip_save = TRUE) {
// First check if the order already references a customer profile of the
// specified type.
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$field_name = variable_get('commerce_customer_profile_' . $profile_type . '_field', '');
// If the associated order field has been set and the order currently
// references a customer profile through it...
if (!empty($field_name) && !empty($order_wrapper->{$field_name})) {
// Update the existing customer profile.
$profile = $order_wrapper->{$field_name}
->value();
}
elseif (!empty($order->data['profiles']['customer_profile_' . $profile_type])) {
// Otherwise look for an association stored in the order's data array.
$profile = commerce_customer_profile_load($order->data['profiles']['customer_profile_' . $profile_type]);
}
// Create a new profile if we could not find an existing one.
if (empty($profile)) {
$profile = commerce_customer_profile_new($profile_type, $order->uid);
}
// Add the order context to the profile to ensure it can be updated without
// resulting in customer profile duplication.
$profile->entity_context = array(
'entity_type' => 'commerce_order',
'entity_id' => $order->order_id,
);
// Prepare an addressfield array to set to the customer profile.
$field = field_info_field('commerce_customer_address');
$instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', $profile_type);
$address = addressfield_default_values($field, $instance);
// Handle setting the name fields for billing profiles.
if ($profile_type == 'billing') {
$address['first_name'] = $payload['details']['firstName'];
$address['last_name'] = $payload['details']['lastName'];
$address['name_line'] = $address['first_name'] . ' ' . $address['last_name'];
}
// Handle setting the name and address for shipping profiles.
if ($profile_type == 'shipping' && !empty($payload['details']['shippingAddress'])) {
$address_values = $payload['details']['shippingAddress'];
// The recipient name is returned as one field. Attempt
// to split the first and last names for address field.
$names = explode(' ', $address_values['recipientName']);
$address['first_name'] = array_shift($names);
$address['last_name'] = implode(' ', $names);
$address['name_line'] = $address_values['recipientName'];
$address = array_merge($address, array(
'country' => !empty($address_values['countryCode']) ? $address_values['countryCode'] : '',
'administrative_area' => !empty($address_values['state']) ? $address_values['state'] : '',
'locality' => !empty($address_values['city']) ? $address_values['city'] : '',
'postal_code' => !empty($address_values['postalCode']) ? $address_values['postalCode'] : '',
'thoroughfare' => !empty($address_values['line1']) ? $address_values['line1'] : '',
'premise' => !empty($address_values['line2']) ? $address_values['line2'] : '',
));
}
// Add the addressfield value to the customer profile.
$profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile);
$profile_wrapper->commerce_customer_address = $address;
// Save the customer profile and update the order to reference it.
$profile_wrapper
->save();
$order_wrapper->{'commerce_customer_' . $profile_type} = $profile_wrapper;
// Save the order if specified.
if (!$skip_save) {
$order_wrapper
->save();
}
}
Functions
Constants
Name![]() |
Description |
---|---|
COMMERCE_BRAINTREE_EXPRESS_CHECKOUT_INSTANCE_ID | @file Provides integration PayPal Express Checkout for Braintree. |