commerce_payment.checkout_pane.inc in Commerce Core 7
Callback functions for the Payment module's checkout panes.
File
modules/payment/includes/commerce_payment.checkout_pane.incView source
<?php
/**
* @file
* Callback functions for the Payment module's checkout panes.
*/
// Constants that govern the behavior of the payment method checkout pane when
// no payment methods are enabled for an order.
define('COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY', 'empty');
define('COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT', 'empty_event');
define('COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE', 'message');
define('COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT', 'message_event');
/**
* Checkout pane callback: returns the payment pane's settings form.
*/
function commerce_payment_pane_settings_form($checkout_pane) {
$form = array();
$form['commerce_payment_pane_require_method'] = array(
'#type' => 'checkbox',
'#title' => t('Require a payment method at all times, preventing checkout if none is available.'),
'#default_value' => variable_get('commerce_payment_pane_require_method', FALSE),
);
$form['commerce_payment_pane_no_method_behavior'] = array(
'#type' => 'radios',
'#title' => t('Checkout pane behavior when no payment methods are enabled for an order'),
'#description' => t('Note: regardless of your selection, no payment transaction will be created for the order upon checkout completion as they represent actual financial transactions.'),
'#options' => array(
COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY => t('Leave the payment checkout pane empty.'),
COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT => t('Leave the payment checkout pane empty and trigger <em>When an order is first paid in full</em> on submission of free orders.'),
COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE => t('Display a message in the pane indicating payment is not required for the order.'),
COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT => t('Display a message in the pane indicating payment is not required and trigger <em>When an order is first paid in full</em> on submission of free orders.'),
),
'#default_value' => variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE),
'#states' => array(
'visible' => array(
':input[name="commerce_payment_pane_require_method"]' => array(
'checked' => FALSE,
),
),
),
);
return $form;
}
/**
* Payment pane: form callback.
*/
function commerce_payment_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array();
// Invoke the payment methods event that will populate the order with
// an array of method IDs for available payment methods.
$order->payment_methods = array();
rules_invoke_all('commerce_payment_methods', $order);
// Sort the payment methods array by the enabling Rules' weight values.
uasort($order->payment_methods, 'drupal_sort_weight');
// Generate an array of payment method options for the checkout form.
$options = array();
foreach ($order->payment_methods as $instance_id => $method_info) {
// Ensure we've received a valid payment method that can be used on the
// checkout form.
if ($payment_method = commerce_payment_method_load($method_info['method_id'])) {
if (!empty($payment_method['checkout'])) {
$options[$instance_id] = $payment_method['display_title'];
}
}
}
// If no payment methods were found, return the empty form.
if (empty($options)) {
if (!variable_get('commerce_payment_pane_require_method', FALSE)) {
$behavior = variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE);
switch ($behavior) {
case COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE:
case COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT:
$pane_form['message'] = array(
'#markup' => '<div>' . t('Payment is not required to complete your order.') . '</div>',
);
break;
case COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY:
case COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT:
default:
break;
}
return $pane_form;
}
else {
$pane_form['message'] = array(
'#markup' => '<div>' . t('Unfortunately we could not find any suitable payment methods, and we require a payment method to complete checkout.') . '<br /><strong>' . t('Please contact us to resolve any issues with your order.') . '</strong></div>',
);
}
}
// Store the payment methods in the form for validation purposes.
$pane_form['payment_methods'] = array(
'#type' => 'value',
'#value' => $order->payment_methods,
);
// If at least one payment option is available...
if (!empty($options)) {
// Add a radio select widget to specify the payment method.
$pane_form['payment_method'] = array(
'#type' => 'radios',
'#options' => $options,
'#ajax' => array(
'callback' => 'commerce_payment_pane_checkout_form_details_refresh',
'wrapper' => 'payment-details',
),
);
// Find the default payment method using either the preselected value stored
// in the order / checkout pane or the first available method.
$pane_values = !empty($form_state['values'][$checkout_pane['pane_id']]) ? $form_state['values'][$checkout_pane['pane_id']] : array();
if (isset($pane_values['payment_method']) && isset($options[$pane_values['payment_method']])) {
$default_value = $pane_values['payment_method'];
}
elseif (isset($form_state['input']['commerce_payment']['payment_method'])) {
$default_value = $form_state['complete form']['commerce_payment']['payment_method']['#default_value'];
}
elseif (isset($order->data['payment_method']) && isset($options[$order->data['payment_method']])) {
$default_value = $order->data['payment_method'];
}
else {
reset($options);
$default_value = key($options);
}
// Set the default value for the payment method radios.
$pane_form['payment_method']['#default_value'] = $default_value;
// Add the payment method specific form elements.
$payment_method = commerce_payment_method_instance_load($pane_form['payment_method']['#default_value']);
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form')) {
$pane_form['payment_details'] = $callback($payment_method, $pane_values, $checkout_pane, $order);
}
else {
$pane_form['payment_details'] = array();
}
$pane_form['payment_details']['#prefix'] = '<div id="payment-details">';
$pane_form['payment_details']['#suffix'] = '</div>';
}
return $pane_form;
}
/**
* Returns the payment details element for display via AJAX.
*/
function commerce_payment_pane_checkout_form_details_refresh($form, $form_state) {
return $form['commerce_payment']['payment_details'];
}
/**
* Payment pane: validation callback.
*/
function commerce_payment_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) {
$pane_id = $checkout_pane['pane_id'];
// Only attempt validation if we actually had payment methods on the form.
if (!empty($form[$pane_id]) && !empty($form_state['values'][$pane_id])) {
$pane_form = $form[$pane_id];
$pane_values = $form_state['values'][$pane_id];
// Only attempt validation if there were payment methods available.
if (!empty($pane_values['payment_methods'])) {
// If the selected payment method was changed...
if ($pane_values['payment_method'] != $pane_form['payment_method']['#default_value']) {
// And the newly selected method has a valid form callback...
if ($payment_method = commerce_payment_method_instance_load($pane_values['payment_method'])) {
if (commerce_payment_method_callback($payment_method, 'submit_form')) {
// Fail validation so the form is rebuilt to include the payment method
// specific form elements.
return FALSE;
}
}
}
// Delegate validation to the payment method callback.
$payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_validate')) {
// Initialize the payment details array to accommodate payment methods
// that don't add any additional details to the checkout pane form.
if (!isset($pane_values['payment_details'])) {
$pane_values['payment_details'] = array();
}
$result = $callback($payment_method, $pane_form['payment_details'], $pane_values['payment_details'], $order, array(
$checkout_pane['pane_id'],
'payment_details',
));
// To prevent payment method validation routines from having to return TRUE
// explicitly, only return FALSE if it was specifically returned. Otherwise
// default to TRUE.
return $result === FALSE ? FALSE : TRUE;
}
}
elseif (variable_get('commerce_payment_pane_require_method', FALSE)) {
drupal_set_message(t('You cannot complete checkout without submitting payment. Please contact us if an error continues to prevent you from seeing valid payment methods for your order.'), 'error');
return FALSE;
}
}
else {
// Otherwise ensure we don't have any leftover payment method data in the
// order's data array.
unset($order->data['payment_method'], $order->data['payment_redirect_key']);
}
// Nothing to validate.
return TRUE;
}
/**
* Payment pane: submit callback.
*/
function commerce_payment_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
// Check to make sure there are no validation issues with other form elements
// before executing payment method callbacks.
if (form_get_errors()) {
drupal_set_message(t('Your payment will not be processed until all errors on the page have been addressed.'), 'warning');
return FALSE;
}
$pane_id = $checkout_pane['pane_id'];
// Only submit if we actually had payment methods on the form.
if (!empty($form[$pane_id]) && !empty($form_state['values'][$pane_id])) {
$pane_form = $form[$pane_id];
$pane_values = $form_state['values'][$pane_id];
// Only process if there were payment methods available.
if ($pane_values['payment_methods']) {
$order->data['payment_method'] = $pane_values['payment_method'];
// If we can calculate a single order total for the order...
if ($balance = commerce_payment_order_balance($order)) {
// Delegate submit to the payment method callback.
$payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_submit')) {
// Initialize the payment details array to accommodate payment methods
// that don't add any additional details to the checkout pane form.
if (empty($pane_values['payment_details'])) {
$pane_values['payment_details'] = array();
}
// If payment fails, rebuild the checkout form without progressing.
if ($callback($payment_method, $pane_form['payment_details'], $pane_values['payment_details'], $order, $balance) === FALSE) {
$form_state['rebuild'] = TRUE;
}
}
}
}
}
else {
// If there were no payment methods on the form, check to see if the pane is
// configured to trigger "When an order is first paid in full" on submission
// for free orders.
$behavior = variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE);
if (in_array($behavior, array(
COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT,
COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT,
))) {
// Check the balance of the order.
$balance = commerce_payment_order_balance($order);
if (!empty($balance) && $balance['amount'] <= 0) {
// Trigger the event now for free orders, simulating payment being
// submitted on pane submission that brings the balance to 0. Use an
// empty transaction, as we wouldn't typically save a transaction where
// a financial transaction has not actually occurred.
rules_invoke_all('commerce_payment_order_paid_in_full', $order, commerce_payment_transaction_new('', $order->order_id));
// Update the order's data array to indicate this just happened.
$order->data['commerce_payment_order_paid_in_full_invoked'] = TRUE;
}
}
}
}
/**
* Payment pane: review callback.
*/
function commerce_payment_pane_review($form, $form_state, &$checkout_pane, $order) {
// Update the checkout pane info array passed in to give it a more specific
// title in the review table.
$checkout_pane['title'] = t('Payment method');
// Load the payment method instance selected in the review pane to return the
// display title of a selected payment method.
if (!empty($order->data['payment_method'])) {
$payment_method = commerce_payment_method_instance_load($order->data['payment_method']);
return $payment_method['display_title'];
}
elseif (in_array(variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE), array(
COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE,
COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT,
))) {
// In the event that the order does not have a selected payment method and
// the checkout pane is configured to display a message that payment is not
// required, show a similar message here.
return t('Payment not required');
}
return NULL;
}
/**
* Payment redirect pane: form callback.
*/
function commerce_payment_redirect_pane_checkout_form(&$form, &$form_state, $checkout_pane, $order) {
// First load the order's specified payment method instance.
if (!empty($order->data['payment_method'])) {
$payment_method = commerce_payment_method_instance_load($order->data['payment_method']);
}
else {
$payment_method = FALSE;
}
// If the payment method doesn't exist or does not require a redirect...
if (!$payment_method || !$payment_method['offsite']) {
if (!$payment_method) {
$log = t('Customer skipped the Payment page because no payment was required.');
}
else {
$log = t('Customer skipped the Payment page because payment was already submitted.');
}
// Advance the customer to the next step of the checkout process.
commerce_payment_redirect_pane_next_page($order, $log);
drupal_goto(commerce_checkout_order_uri($order));
}
// If the user came to the cancel URL...
if (arg(3) == 'back' && arg(4) == $order->data['payment_redirect_key']) {
// Perform any payment cancellation functions if necessary.
if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_back')) {
$callback($order, $payment_method);
}
// Send the customer to the previous step of the checkout process.
commerce_payment_redirect_pane_previous_page($order, t('Customer canceled payment at the payment gateway.'));
drupal_goto(commerce_checkout_order_uri($order));
}
// If the user came to the return URL...
if (arg(3) == 'return' && arg(4) == $order->data['payment_redirect_key']) {
// Check for a validate handler on return.
$validate_callback = commerce_payment_method_callback($payment_method, 'redirect_form_validate');
// If there is no validate handler or if there is and it isn't FALSE...
if (!$validate_callback || $validate_callback($order, $payment_method) !== FALSE) {
// Perform any submit functions if necessary.
if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_submit')) {
$callback($order, $payment_method);
}
// Send the customer on to the next checkout page.
commerce_payment_redirect_pane_next_page($order, t('Customer successfully submitted payment at the payment gateway.'));
drupal_goto(commerce_checkout_order_uri($order));
}
else {
// Otherwise display the failure message and send the customer back.
drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
commerce_payment_redirect_pane_previous_page($order, t('Customer payment submission failed at the payment gateway.'));
drupal_goto(commerce_checkout_order_uri($order));
}
}
// If the function to build the redirect form exists...
if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form')) {
// Generate a key to use in the return URL from the redirected service if it
// does not already exist.
if (empty($order->data['payment_redirect_key'])) {
$order->data['payment_redirect_key'] = drupal_hash_base64(time());
commerce_order_save($order);
}
// If the payment method has the 'offsite_autoredirect' option enabled, add
// the redirection behavior.
if (!empty($payment_method['offsite_autoredirect'])) {
$form['#attached']['js'][] = drupal_get_path('module', 'commerce_payment') . '/commerce_payment.js';
$form['help']['#markup'] = '<div class="checkout-help">' . t('Please wait while you are redirected to the payment server. If nothing happens within 10 seconds, please click on the button below.') . '</div>';
}
// Merge the new form into the current form array, preserving the help text
// if it exists. We also add a wrapper so the form can be easily submitted.
$form += drupal_get_form($callback, $order, $payment_method);
$form['#prefix'] = '<div class="payment-redirect-form">';
$form['#suffix'] = '</div>';
}
else {
// Alert the administrator that the module does not provide a required form.
drupal_set_message(t('The %title payment method indicates it is offsite but does not define the necessary form to process the redirect.', array(
'%title' => $payment_method['title'],
)), 'error');
}
}
/**
* Utility function: return a payment redirect page for POST.
*
* @param $action
* The destination URL the values should be posted to.
* @param $values
* An associative array of values that will be posted to the destination URL.
* @return
* A renderable array.
*/
function commerce_payment_post_redirect_form($action, array $values = array()) {
$form = array(
'#type' => 'form',
'#action' => $action,
'#method' => 'POST',
'#id' => '',
'#attributes' => array(),
);
foreach ($values as $key => $value) {
$form[$value] = array(
'#type' => 'hidden',
'#name' => $key,
'#value' => $value,
'#id' => '',
'#attributes' => array(),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#id' => '',
'#value' => t('Proceed to payment'),
);
return array(
'form' => array(
'#type' => 'markup',
'#markup' => drupal_render($form),
),
);
}
Functions
Name | Description |
---|---|
commerce_payment_pane_checkout_form | Payment pane: form callback. |
commerce_payment_pane_checkout_form_details_refresh | Returns the payment details element for display via AJAX. |
commerce_payment_pane_checkout_form_submit | Payment pane: submit callback. |
commerce_payment_pane_checkout_form_validate | Payment pane: validation callback. |
commerce_payment_pane_review | Payment pane: review callback. |
commerce_payment_pane_settings_form | Checkout pane callback: returns the payment pane's settings form. |
commerce_payment_post_redirect_form | Utility function: return a payment redirect page for POST. |
commerce_payment_redirect_pane_checkout_form | Payment redirect pane: form callback. |