You are here

commerce_payment.module in Commerce Core 7

Same filename and directory in other branches
  1. 8.2 modules/payment/commerce_payment.module

Defines the payment system and checkout integration.

File

modules/payment/commerce_payment.module
View source
<?php

/**
 * @file
 * Defines the payment system and checkout integration.
 */

// Local payment transaction status definitions:
// Pending is used when a transaction has been initialized but is still awaiting
// resolution; e.g. a CC authorization awaiting capture or an e-check payment
// pending at the payment provider.
define('COMMERCE_PAYMENT_STATUS_PENDING', 'pending');

// Success is used when a transaction has completed resulting in money being
// transferred from the customer to the store or vice versa.
define('COMMERCE_PAYMENT_STATUS_SUCCESS', 'success');

// Failure is used when a transaction cannot be completed or is rejected.
define('COMMERCE_PAYMENT_STATUS_FAILURE', 'failure');

// Credit card transaction types definitions:
// Used to just authorize an amount on a credit card account.
define('COMMERCE_CREDIT_AUTH_ONLY', 'authorize');

// User to just capture an amount on a credit card account.
define('COMMERCE_CREDIT_CAPTURE_ONLY', 'capture');

// Used to capture funds from a prior authorization.
define('COMMERCE_CREDIT_PRIOR_AUTH_CAPTURE', 'prior_auth_capture');

// Used to authorize and capture money all at once.
define('COMMERCE_CREDIT_AUTH_CAPTURE', 'auth_capture');

// Used to set up a credit card reference through the payment gateway.
define('COMMERCE_CREDIT_REFERENCE_SET', 'reference_set');

// Used to capture funds using a credit card reference.
define('COMMERCE_CREDIT_REFERENCE_TXN', 'reference_txn');

// Used to remove a reference from the payment gateway.
define('COMMERCE_CREDIT_REFERENCE_REMOVE', 'reference_remove');

// Used to credit funds to a reference at the payment gateway.
define('COMMERCE_CREDIT_REFERENCE_CREDIT', 'reference_credit');

// Used to credit funds to a credit card account.
define('COMMERCE_CREDIT_CREDIT', 'credit');

// Used to void a transaction before the transaction clears.
define('COMMERCE_CREDIT_VOID', 'void');

/**
 * Implements of hook_entity_info().
 */
function commerce_payment_entity_info() {
  $return = array(
    'commerce_payment_transaction' => array(
      'label' => t('Commerce Payment transaction'),
      'controller class' => 'CommercePaymentTransactionEntityController',
      'base table' => 'commerce_payment_transaction',
      'revision table' => 'commerce_payment_transaction_revision',
      'fieldable' => FALSE,
      'entity keys' => array(
        'id' => 'transaction_id',
        'revision' => 'revision_id',
        'bundle' => 'payment_method',
        'label' => 'transaction_id',
      ),
      'bundle keys' => array(
        'bundle' => 'payment_method',
      ),
      'bundles' => array(),
      'load hook' => 'commerce_payment_transaction_load',
      'view modes' => array(
        'administrator' => array(
          'label' => t('Administrator'),
          'custom settings' => FALSE,
        ),
      ),
      'uri callback' => 'commerce_payment_transaction_uri',
      'access callback' => 'commerce_payment_transaction_access',
      'access arguments' => array(
        'user key' => 'uid',
        'access tag' => 'commerce_payment_transaction_access',
      ),
      'token type' => 'commerce-payment-transaction',
      'metadata controller class' => '',
      'permission labels' => array(
        'singular' => t('payment transaction'),
        'plural' => t('payment transactions'),
      ),
      // Prevent Redirect alteration of the payment transaction form.
      'redirect' => FALSE,
    ),
  );
  foreach (commerce_payment_methods() as $method_id => $payment_method) {
    $return['commerce_payment_transaction']['bundles'][$method_id] = array(
      'label' => $payment_method['title'],
    );
  }
  return $return;
}

/**
 * Entity uri callback: gives modules a chance to specify a path for a payment
 * transaction.
 */
function commerce_payment_transaction_uri($transaction) {

  // Allow modules to specify a path, returning the first one found.
  foreach (module_implements('commerce_payment_transaction_uri') as $module) {
    $uri = module_invoke($module, 'commerce_payment_transaction_uri', $transaction);

    // If the implementation returned data, use that now.
    if (!empty($uri)) {
      return $uri;
    }
  }
  return NULL;
}

/**
 * Implements hook_hook_info().
 */
function commerce_payment_hook_info() {
  $hooks = array(
    'commerce_payment_totals_row_info' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_totals_row_info_alter' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_method_info' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_method_info_alter' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_methods' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_transaction_status_info' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_transaction_uri' => array(
      'group' => 'commerce',
    ),
    'commerce_transaction_view' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_transaction_access' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_transaction_insert' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_transaction_update' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_transaction_delete' => array(
      'group' => 'commerce',
    ),
    'commerce_payment_order_paid_in_full' => array(
      'group' => 'commerce',
    ),
  );
  return $hooks;
}

/**
 * Implements hook_permission().
 */
function commerce_payment_permission() {
  return array(
    'administer payment methods' => array(
      'title' => t('Administer payment methods'),
      'description' => t('Allows users to configure enabled payment methods.'),
      'restrict access' => TRUE,
    ),
    'administer payments' => array(
      'title' => t('Administer payments'),
      'description' => t('Allows users to perform any payment action for any order and view transaction payloads.'),
      'restrict access' => TRUE,
    ),
    'view payments' => array(
      'title' => t('View payments'),
      'description' => t('Allows users to view the payments made to an order.'),
      'restrict access' => TRUE,
    ),
    'create payments' => array(
      'title' => t('Create payments'),
      'description' => t('Allows users to create new payments for orders.'),
      'restrict access' => TRUE,
    ),
    'update payments' => array(
      'title' => t('Update payments'),
      'description' => t('Allows users to update payments via payment method specific operations links.'),
      'restrict access' => TRUE,
    ),
    'delete payments' => array(
      'title' => t('Delete payments'),
      'description' => t('Allows users to delete payments on orders they can access.'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Implements hook_theme().
 */
function commerce_payment_theme() {
  return array(
    'commerce_payment_transaction' => array(
      'variables' => array(
        'order' => NULL,
        'transaction' => NULL,
        'view_mode' => NULL,
      ),
    ),
    'commerce_payment_transaction_status_text' => array(
      'variables' => array(
        'text' => NULL,
        'transaction_status' => NULL,
      ),
    ),
    'commerce_payment_transaction_status_icon' => array(
      'variables' => array(
        'transaction_status' => NULL,
      ),
    ),
    'commerce_payment_totals' => array(
      'variables' => array(
        'rows' => array(),
        'form' => NULL,
        'totals' => array(),
        'view' => NULL,
      ),
      'path' => drupal_get_path('module', 'commerce_payment') . '/theme',
      'template' => 'commerce-payment-totals',
    ),
  );
}

/**
 * Adds the necessary CSS for the line item summary template.
 */
function template_preprocess_commerce_payment_totals(&$variables) {
  drupal_add_css(drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.admin.css');
}

/**
 * Implements hook_modules_enabled().
 */
function commerce_payment_modules_enabled($modules) {
  commerce_payment_methods_reset();
  _commerce_payment_default_rules_reset($modules);
}

/**
 * Resets default Rules if necessary when modules are enabled or disabled.
 *
 * @param $modules
 *   An array of module names that have been enabled or disabled.
 */
function _commerce_payment_default_rules_reset($modules) {
  $reset_default_rules = FALSE;

  // Look for any module defining a new payment method.
  foreach ($modules as $module) {
    if (function_exists($module . '_commerce_payment_method_info')) {
      $reset_default_rules = TRUE;
    }
  }

  // If we found a module defining a new payment method, we need to rebuild the
  // default Rules especially for this module so the default payment method Rule
  // will appear properly for this module.
  if ($reset_default_rules) {
    entity_defaults_rebuild();
    rules_clear_cache(TRUE);
  }
}

/**
 * Implements hook_commerce_checkout_page_info().
 */
function commerce_payment_commerce_checkout_page_info() {
  $checkout_pages = array();
  $checkout_pages['payment'] = array(
    'title' => t('Payment'),
    'help' => t('Use the button below to proceed to the payment server.'),
    'status_cart' => FALSE,
    'locked' => TRUE,
    'buttons' => FALSE,
    'weight' => 20,
  );
  return $checkout_pages;
}

/**
 * Implements hook_commerce_checkout_pane_info().
 */
function commerce_payment_commerce_checkout_pane_info() {
  $checkout_panes = array();
  $checkout_panes['commerce_payment'] = array(
    'title' => t('Payment'),
    'page' => 'review',
    'file' => 'includes/commerce_payment.checkout_pane.inc',
    'base' => 'commerce_payment_pane',
    'weight' => 10,
  );
  $checkout_panes['commerce_payment_redirect'] = array(
    'title' => t('Off-site payment redirect'),
    'page' => 'payment',
    'locked' => TRUE,
    'file' => 'includes/commerce_payment.checkout_pane.inc',
    'base' => 'commerce_payment_redirect_pane',
  );
  return $checkout_panes;
}

/**
 * Moves an order ahead to the next page via an order update and redirect.
 *
 * Redirected payment methods are responsible for calling this method when
 * receiving external notifications of successful payment.
 *
 * @param $order
 *   An order object.
 * @param $log
 *   Optional log message to use when updating the order status in conjunction
 *   with the redirect to the next checkout page.
 */
function commerce_payment_redirect_pane_next_page($order, $log = '') {

  // Load the order status object for the current order.
  $order_status = commerce_order_status_load($order->status);
  if ($order_status['state'] == 'checkout' && $order_status['checkout_page'] == 'payment') {
    $payment_page = commerce_checkout_page_load($order_status['checkout_page']);
    $next_page = $payment_page['next_page'];
    $order = commerce_order_status_update($order, 'checkout_' . $next_page, FALSE, NULL, $log);

    // Inform modules of checkout completion if the next page is Completed.
    if ($next_page == 'complete') {
      commerce_checkout_complete($order);
    }
  }
}

/**
 * Moves an order back to the previous page via an order update and redirect.
 *
 * Redirected payment methods are responsible for calling this method when
 * receiving external notifications of failed payment.
 *
 * @param $order
 *   An order object.
 * @param $log
 *   Optional log message to use when updating the order status in conjunction
 *   with the redirect to the previous checkout page.
 */
function commerce_payment_redirect_pane_previous_page($order, $log = '') {

  // Load the order status object for the current order.
  $order_status = commerce_order_status_load($order->status);
  if ($order_status['state'] == 'checkout' && $order_status['checkout_page'] == 'payment') {
    $payment_page = commerce_checkout_page_load($order_status['checkout_page']);
    $prev_page = $payment_page['prev_page'];
    $order = commerce_order_status_update($order, 'checkout_' . $prev_page, FALSE, NULL, $log);
  }
}

/**
 * Implements hook_commerce_payment_totals_row_info().
 */
function commerce_payment_commerce_payment_totals_row_info($totals, $order) {
  $rows = array();
  if (count($totals) == 0) {

    // Add a row for the remaining balance on the order.
    if ($order) {
      $balance = commerce_payment_order_balance($order, $totals);
      $rows[] = array(
        'data' => array(
          array(
            'data' => t('Order balance'),
            'class' => array(
              'label',
            ),
          ),
          array(
            'data' => commerce_currency_format($balance['amount'], $balance['currency_code']),
            'class' => array(
              'balance',
            ),
          ),
        ),
        'class' => array(
          'order-balance',
        ),
        'weight' => 10,
      );
    }
  }
  elseif (count($totals) == 1) {

    // Otherwise if there's only a single currency total...
    $currency_code = key($totals);

    // Add a row for the total amount paid.
    $rows[] = array(
      'data' => array(
        array(
          'data' => t('Total paid'),
          'class' => array(
            'label',
          ),
        ),
        array(
          'data' => commerce_currency_format($totals[$currency_code], $currency_code),
          'class' => array(
            'total',
          ),
        ),
      ),
      'class' => array(
        'total-paid',
      ),
      'weight' => 0,
    );

    // Add a row for the remaining balance on the order.
    if ($order) {
      $balance = commerce_payment_order_balance($order, $totals);

      // Fix false balances.
      if (empty($balance)) {
        $formatted_amount = commerce_currency_format(0, NULL);
      }
      else {
        $formatted_amount = commerce_currency_format($balance['amount'], $balance['currency_code']);
      }
      $rows[] = array(
        'data' => array(
          array(
            'data' => t('Order balance'),
            'class' => array(
              'label',
            ),
          ),
          array(
            'data' => $formatted_amount,
            'class' => array(
              'balance',
            ),
          ),
        ),
        'class' => array(
          'order-balance',
        ),
        'weight' => 10,
      );
    }
  }
  else {
    $weight = 0;
    foreach ($totals as $currency_code => $amount) {
      $rows[] = array(
        'data' => array(
          array(
            'data' => t('Total paid (@currency_code)', array(
              '@currency_code' => $currency_code,
            )),
            'class' => array(
              'label',
            ),
          ),
          array(
            'data' => commerce_currency_format($amount, $currency_code),
            'class' => array(
              'total',
            ),
          ),
        ),
        'class' => array(
          'total-paid',
          'total-' . $currency_code,
        ),
        'weight' => $weight++,
      );
    }
  }
  return $rows;
}

/**
 * Implements hook_commerce_payment_transaction_insert().
 *
 * When a new payment transaction is inserted that is already completed, check
 * the order balance and invoke a Rules event if the order is paid in full.
 */
function commerce_payment_commerce_payment_transaction_insert($transaction) {

  // If the inserted transaction was marked as a success and placed against a
  // valid order...
  $transaction_statuses = commerce_payment_transaction_statuses();
  if (!empty($transaction_statuses[$transaction->status]['total']) && ($order = commerce_order_load($transaction->order_id))) {

    // Then check to make sure the event hasn't been invoked for this order.
    if (empty($order->data['commerce_payment_order_paid_in_full_invoked'])) {

      // Check the order balance and invoke the event.
      $balance = commerce_payment_order_balance($order);
      if (!empty($balance) && $balance['amount'] <= 0) {

        // If automatic order revisions are enabled site-wide, create a new
        // revision now with a log message identifying the payment event.
        if (variable_get('commerce_order_auto_revision', TRUE)) {
          $order->revision = TRUE;
          $order->log = t('Order updated to reflect the order has just been paid in full.');
        }

        // Invoke the event including a hook of the same name.
        rules_invoke_all('commerce_payment_order_paid_in_full', $order, $transaction);

        // Update the order's data array to indicate this just happened.
        $order->data['commerce_payment_order_paid_in_full_invoked'] = TRUE;

        // Save the updated order.
        commerce_order_save($order);
      }
    }
  }
}

/**
 * Implements hook_commerce_payment_transaction_update().
 *
 * When an existing transaction is updated from an incomplete status to
 * completed, check the order balance and invoke a Rules event if the order is
 * paid in full.
 */
function commerce_payment_commerce_payment_transaction_update($transaction) {
  commerce_payment_commerce_payment_transaction_insert($transaction);
}

/**
 * Implements hook_commerce_order_delete().
 *
 * Make sure all payment transactions are deleted whenever the
 * order they are associated with is deleted.
 */
function commerce_payment_commerce_order_delete($order) {
  foreach (commerce_payment_transaction_load_multiple(array(), array(
    'order_id' => $order->order_id,
  )) as $transaction) {
    commerce_payment_transaction_delete($transaction->transaction_id);
  }
}

/**
 * Implements hook_views_api().
 */
function commerce_payment_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'commerce_payment') . '/includes/views',
  );
}

/**
 * Returns an array of payment methods defined by enabled modules.
 *
 * @return
 *   An associative array of payment method objects keyed by the method_id.
 */
function commerce_payment_methods() {
  $payment_methods =& drupal_static(__FUNCTION__);

  // If the payment methods haven't been defined yet, do so now.
  if (!isset($payment_methods)) {
    $payment_methods = array();

    // Build the payment methods array, including module names for the purpose
    // of including files if necessary.
    foreach (module_implements('commerce_payment_method_info') as $module) {
      foreach (module_invoke($module, 'commerce_payment_method_info') as $method_id => $payment_method) {
        $payment_method['method_id'] = $method_id;
        $payment_method['module'] = $module;
        $payment_methods[$method_id] = $payment_method;
      }
    }
    drupal_alter('commerce_payment_method_info', $payment_methods);
    foreach ($payment_methods as $method_id => &$payment_method) {
      $defaults = array(
        'method_id' => $method_id,
        'base' => $method_id,
        'title' => '',
        'description' => '',
        'active' => FALSE,
        'checkout' => TRUE,
        'terminal' => TRUE,
        'offsite' => FALSE,
        'offsite_autoredirect' => FALSE,
        'callbacks' => array(),
        'file' => '',
      );
      $payment_method += $defaults;

      // Default the display title to the title if necessary.  The display title
      // is used in instances where the payment method has an official name used
      // as the title (i.e. PayPal WPS) but a different method of display on
      // selection forms (like some text and a set of images).
      if (empty($payment_method['display_title'])) {
        $payment_method['display_title'] = $payment_method['title'];
      }

      // Default the short title to the title if necessary.  Like the display
      // title, the short title is an alternate way of displaying the title to
      // the user consisting of plain text but with unnecessary information
      // stripped off.  The payment method title might be PayPal WPS as it
      // distinguishes itself from other PayPal payment services, but you would
      // only want to display PayPal to the customer as their means of payment.
      if (empty($payment_methods[$method_id]['short_title'])) {
        $payment_method['short_title'] = $payment_method['title'];
      }

      // Merge in default callbacks.
      foreach (array(
        'settings_form',
        'submit_form',
        'submit_form_validate',
        'submit_form_submit',
        'redirect_form',
        'redirect_form_back',
        'redirect_form_validate',
        'redirect_form_submit',
      ) as $callback) {
        if (!isset($payment_method['callbacks'][$callback])) {
          $payment_method['callbacks'][$callback] = $payment_method['base'] . '_' . $callback;
        }
      }
    }
  }
  return $payment_methods;
}

/**
 * Resets the cached list of payment methods.
 */
function commerce_payment_methods_reset() {
  $payment_methods =& drupal_static('commerce_payment_methods');
  $payment_methods = NULL;
  entity_info_cache_clear();
}

/**
 * Returns a payment method array.
 *
 * @param $method_id
 *   The ID of the payment method to return.
 *
 * @return
 *   The fully loaded payment method object or FALSE if not found.
 */
function commerce_payment_method_load($method_id) {
  $payment_methods = commerce_payment_methods();
  return isset($payment_methods[$method_id]) ? $payment_methods[$method_id] : FALSE;
}

/**
 * Returns the title of any or all payment methods.
 *
 * @param $title
 *   String indicating which title to return, either 'title', 'display_title',
 *     or 'short_title'.
 * @param $method
 *   Optional parameter specifying the payment method whose title to return.
 *
 * @return
 *   Either an array of all payment method titles keyed by the machine name or a
 *   string containing the title for the specified type. If a type is specified
 *   that does not exist, this function returns FALSE.
 */
function commerce_payment_method_get_title($title = 'title', $method = NULL) {
  $payment_methods = commerce_payment_methods();

  // Return a method title if specified and it exists.
  if (!empty($method)) {
    if (isset($payment_methods[$method])) {
      return $payment_methods[$method][$title];
    }
    else {

      // Return FALSE if it does not exist.
      return FALSE;
    }
  }

  // Otherwise create an array of all payment method titles.
  $titles = array();
  foreach ($payment_methods as $key => $value) {
    $titles[$key] = $value[$title];
  }
  return $titles;
}

/**
 * Wraps commerce_payment_method_get_title() for the Entity module.
 */
function commerce_payment_method_options_list() {
  return commerce_payment_method_get_title();
}

/**
 * Returns the specified callback for the given payment method if it exists.
 *
 * @param $payment_method
 *   The payment method object.
 * @param $callback
 *   The callback function to return, one of:
 *   - settings_form
 *   - submit_form
 *   - submit_form_validate
 *   - submit_form_submit
 *   - redirect_form
 *   - redirect_form_back
 *   - redirect_form_validate
 *   - redirect_form_submit
 *
 * @return
 *   A string containing the name of the callback function or FALSE if it could
 *     not be found.
 */
function commerce_payment_method_callback($payment_method, $callback) {

  // Include the payment method file if specified.
  if (!empty($payment_method['file'])) {
    $parts = explode('.', $payment_method['file']);
    module_load_include(array_pop($parts), $payment_method['module'], implode('.', $parts));
  }

  // If the specified callback function exists, return it.
  if (!empty($payment_method['callbacks'][$callback]) && is_callable($payment_method['callbacks'][$callback])) {
    return $payment_method['callbacks'][$callback];
  }

  // Otherwise return FALSE.
  return FALSE;
}

/**
 * Returns a payment method instance ID given a payment method ID and the Rule
 *   containing an enabling action with settings.
 *
 * @param $method_id
 *   The ID of the payment method.
 * @param $rule
 *   The Rules configuration object used to provide settings for the method.
 *
 * @return
 *   A string used as the payment method instance ID.
 */
function commerce_payment_method_instance_id($method_id, $rule) {
  $parts = array(
    $method_id,
    $rule->name,
  );
  return implode('|', $parts);
}

/**
 * Returns a payment method instance array which includes the settings specific
 * to the context of the instance.
 *
 * @param $instance_id
 *   A payment method instance ID in the form of a pipe delimited string
 *     containing the method_id and the enabling Rule's name.
 *
 * @return
 *   The payment method instance object which is identical to the payment method
 *     object with the addition of the settings array.
 */
function commerce_payment_method_instance_load($instance_id) {

  // Return FALSE if there is no pipe delimeter in the instance ID.
  if (strpos($instance_id, '|') === FALSE) {
    return FALSE;
  }

  // Explode the method key into its component parts.
  list($method_id, $rule_name) = explode('|', $instance_id);

  // Return FALSE if we didn't receive a proper instance ID.
  if (empty($method_id) || empty($rule_name)) {
    return FALSE;
  }

  // First load the payment method and add the instance ID.
  $payment_method = commerce_payment_method_load($method_id);
  $payment_method['instance_id'] = $instance_id;

  // Then load the Rule configuration that enables the method.
  $rule = rules_config_load($rule_name);

  // Return FALSE if it couldn't be loaded.
  if (empty($rule)) {
    return FALSE;
  }

  // Iterate over its actions to find one with the matching element ID and pull
  // its settings into the payment method object.
  $payment_method['settings'] = array();
  foreach ($rule
    ->actions() as $action) {
    if (is_callable(array(
      $action,
      'getElementName',
    )) && $action
      ->getElementName() == 'commerce_payment_enable_' . $method_id) {
      if (is_array($action->settings['payment_method']) && !empty($action->settings['payment_method']['settings'])) {
        $payment_method['settings'] = $action->settings['payment_method']['settings'];
      }
    }
  }
  return $payment_method;
}

/**
 * Returns an array of transaction payment status objects for the defined
 *   payment statuses.
 *
 * This function invokes hook_commerce_payment_transaction_status_info() so
 * other payment modules can define statuses if necessary. However, it doesn't
 * allow for altering so that existing payment methods cannot be unset. It still
 * does perform an array merge in such a way that the properties for the three
 * core statuses defined by this module may be overridden if the same keys are
 * used in another module's implementation of the info hook.
 */
function commerce_payment_transaction_statuses() {
  $transaction_statuses =& drupal_static(__FUNCTION__);

  // If the statuses haven't been defined yet, do so now.
  if (!isset($transaction_statuses)) {
    $transaction_statuses = module_invoke_all('commerce_payment_transaction_status_info');
    $transaction_statuses += array(
      COMMERCE_PAYMENT_STATUS_PENDING => array(
        'status' => COMMERCE_PAYMENT_STATUS_PENDING,
        'title' => t('Pending'),
        'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-pending.png',
        'total' => FALSE,
      ),
      COMMERCE_PAYMENT_STATUS_SUCCESS => array(
        'status' => COMMERCE_PAYMENT_STATUS_SUCCESS,
        'title' => t('Success'),
        'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-success.png',
        'total' => TRUE,
      ),
      COMMERCE_PAYMENT_STATUS_FAILURE => array(
        'status' => COMMERCE_PAYMENT_STATUS_FAILURE,
        'title' => t('Failure'),
        'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-failure.png',
        'total' => FALSE,
      ),
    );
  }
  return $transaction_statuses;
}

/**
 * Returns an options list of payment transaction statuses.
 */
function commerce_payment_transaction_status_options_list() {

  // Build an array of payment transaction status options.
  $options = array();
  foreach (commerce_payment_transaction_statuses() as $payment_transaction_status) {
    $options[$payment_transaction_status['status']] = $payment_transaction_status['title'];
  }
  return $options;
}

/**
 * Themes the payment transaction entity.
 */
function theme_commerce_payment_transaction($variables) {
  return render($variables['content']);
}

/**
 * Themes the icon representing a payment transaction status.
 */
function theme_commerce_payment_transaction_status_icon($variables) {
  $transaction_status = $variables['transaction_status'];
  return theme('image', array(
    'path' => $transaction_status['icon'],
    'alt' => $transaction_status['title'],
    'title' => $transaction_status['title'],
    'attributes' => array(
      'class' => array(
        drupal_html_class($transaction_status['status']),
      ),
    ),
  ));
}

/**
 * Themes a text value related to a payment transaction status.
 */
function theme_commerce_payment_transaction_status_text($variables) {
  $transaction_status = $variables['transaction_status'];
  return '<span class="' . drupal_html_class($transaction_status['status']) . '">' . $variables['text'] . '</span>';
}

/**
 * Returns the payment transaction status object for the specified status.
 *
 * @param $status
 *   The transaction status string.
 *
 * @return
 *   An object containing the transaction status information or FALSE if the
 *     requested status is not found.
 */
function commerce_payment_transaction_status_load($status) {
  $transaction_statuses = commerce_payment_transaction_statuses();
  return !empty($transaction_statuses[$status]) ? $transaction_statuses[$status] : FALSE;
}

/**
 * Returns an initialized payment transaction object.
 *
 * @param $method_id
 *   The method_id of the payment method for the transaction.
 *
 * @return
 *   A transaction object with all default fields initialized.
 */
function commerce_payment_transaction_new($method_id = '', $order_id = 0) {
  return entity_get_controller('commerce_payment_transaction')
    ->create(array(
    'payment_method' => $method_id,
    'order_id' => $order_id,
  ));
}

/**
 * Saves a payment transaction.
 *
 * @param $transaction
 *   The full transaction object to save.
 *
 * @return
 *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
 */
function commerce_payment_transaction_save($transaction) {
  return entity_get_controller('commerce_payment_transaction')
    ->save($transaction);
}

/**
 * Loads a payment transaction by ID.
 */
function commerce_payment_transaction_load($transaction_id) {
  $transactions = commerce_payment_transaction_load_multiple(array(
    $transaction_id,
  ), array());
  return $transactions ? reset($transactions) : FALSE;
}

/**
 * Loads multiple payment transaction by ID or based on a set of matching conditions.
 *
 * @see entity_load()
 *
 * @param $transaction_ids
 *   An array of transaction IDs.
 * @param $conditions
 *   An array of conditions on the {commerce_payment_transaction} table in the
 *     form 'field' => $value.
 * @param $reset
 *   Whether to reset the internal transaction loading cache.
 *
 * @return
 *   An array of transaction objects indexed by transaction_id.
 */
function commerce_payment_transaction_load_multiple($transaction_ids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('commerce_payment_transaction', $transaction_ids, $conditions, $reset);
}

/**
 * Deletes a payment transaction by ID.
 *
 * @param $transaction_id
 *   The ID of the transaction to delete.
 *
 * @return
 *   TRUE on success, FALSE otherwise.
 */
function commerce_payment_transaction_delete($transaction_id) {
  return commerce_payment_transaction_delete_multiple(array(
    $transaction_id,
  ));
}

/**
 * Deletes multiple payment transactions by ID.
 *
 * @param $transaction_ids
 *   An array of transaction IDs to delete.
 *
 * @return
 *   TRUE on success, FALSE otherwise.
 */
function commerce_payment_transaction_delete_multiple($transaction_ids) {
  return entity_get_controller('commerce_payment_transaction')
    ->delete($transaction_ids);
}

/**
 * Determines access for a variety of operations on payment transactions.
 *
 * @param $op
 *   The operation being performed, one of view, update, create, or delete.
 * @param $transaction
 *   The payment transaction to check.
 * @param $account
 *   The user account attempting the operation; defaults to the current user.
 *
 * @return
 *   TRUE or FALSE indicating access for the operation.
 */
function commerce_payment_transaction_access($op, $transaction, $account = NULL) {
  if (isset($transaction->order_id)) {
    $order = commerce_order_load($transaction->order_id);
    if (!$order) {
      return FALSE;
    }
  }
  else {
    $order = NULL;
  }
  return commerce_payment_transaction_order_access($op, $order, $account);
}

/**
 * Determines access for a variety of operations for payment transactions on a given order.
 *
 * @param $op
 *   The payment transaction operation being performed, one of view, update, create, or delete.
 * @param $order
 *   The order to check against (optional if $op == 'create').
 * @param $account
 *   The user account attempting the operation; defaults to the current user.
 *
 * @return
 *   TRUE or FALSE indicating access for the operation.
 */
function commerce_payment_transaction_order_access($op, $order, $account = NULL) {
  global $user;
  if (empty($account)) {
    $account = clone $user;
  }

  // Grant administrators access to do anything.
  if (user_access('administer payments', $account)) {
    return TRUE;
  }
  switch ($op) {

    // Creating new payment transactions.
    case 'create':
      if (user_access('create payments', $account)) {

        // We currently allow any user to create any payment transaction,
        // regardless of the order, because entity_access() doesn't give us a
        // way to discriminate on the order.
        // @todo: find a way to prevent creating a payment transaction if the
        // user doesn't have access to the order.
        if (!isset($order) || commerce_order_access('update', $order, $account)) {
          return TRUE;
        }
      }
      break;

    // Viewing payment transactions.
    case 'view':
      if (user_access('view payments', $account)) {
        if (commerce_order_access('view', $order, $account)) {
          return TRUE;
        }
      }
      break;
    case 'update':
      if (user_access('update payments', $account)) {
        if (commerce_order_access('view', $order, $account)) {
          return TRUE;
        }
      }
      break;
    case 'delete':
      if (user_access('delete payments', $account)) {
        if (commerce_order_access('update', $order, $account)) {
          return TRUE;
        }
      }
      break;
  }
  return FALSE;
}

/**
 * Implements hook_query_TAG_alter().
 *
 * Implement access control on payment transaction. This is different from other
 * entities because the access to a payment transaction is partially delegated
 * to its order.
 */
function commerce_payment_query_commerce_payment_transaction_access_alter(QueryAlterableInterface $query) {

  // Read the meta-data from the query.
  if (!($account = $query
    ->getMetaData('account'))) {
    global $user;
    $account = $user;
  }

  // If the user has the administration permission, nothing to do.
  if (user_access('administer payments', $account)) {
    return;
  }

  // Join the payment transaction to their orders.
  if (user_access('view payments', $account)) {
    $tables =& $query
      ->getTables();

    // Look for an existing commerce_order table.
    foreach ($tables as $table) {
      if ($table['table'] === 'commerce_order') {
        $order_alias = $table['alias'];
        break;
      }
    }

    // If not found, attempt a join against the first table.
    if (!isset($order_alias)) {
      reset($tables);
      $base_table = key($tables);
      $order_alias = $query
        ->innerJoin('commerce_order', 'co', '%alias.order_id = ' . $base_table . '.order_id');
    }

    // Perform the access control on the order.
    commerce_entity_access_query_alter($query, 'commerce_order', $order_alias);
  }
  else {

    // The user has access to no payment transaction.
    $query
      ->where('1 = 0');
  }
}

/**
 * Calculates the balance of an order by subtracting the total of all successful
 *   transactions from the total of all the line items on the order.
 *
 * @param $order
 *   The fully loaded order object whose balance should be calculated.
 * @param $totals
 *   Optionally submit an array of transaction totals keyed by currency code
 *     with the amount as the value.
 *
 * @return
 *   An array containing the amount and currency code representing the balance
 *     of the order or FALSE if it is impossible to calculate.
 */
function commerce_payment_order_balance($order, $totals = array()) {
  $wrapper = entity_metadata_wrapper('commerce_order', $order);
  $order_total = $wrapper->commerce_order_total
    ->value();

  // Calculate the transaction totals if not supplied.
  if (empty($totals)) {
    $transaction_statuses = commerce_payment_transaction_statuses();
    foreach (commerce_payment_transaction_load_multiple(array(), array(
      'order_id' => $order->order_id,
    )) as $transaction) {

      // If the payment transaction status indicates it should include the
      // current transaction in the total...
      if ($transaction_statuses[$transaction->status]['total']) {

        // Add the transaction to its currency's running total if it exists...
        if (isset($totals[$transaction->currency_code])) {
          $totals[$transaction->currency_code] += $transaction->amount;
        }
        else {

          // Or begin a new running total for the currency.
          $totals[$transaction->currency_code] = $transaction->amount;
        }
      }
    }
  }

  // Only return a balance if the totals array contains a single matching currency.
  if (count($totals) == 1 && isset($totals[$order_total['currency_code']])) {
    return array(
      'amount' => $order_total['amount'] - $totals[$order_total['currency_code']],
      'currency_code' => $order_total['currency_code'],
    );
  }
  elseif (empty($totals)) {
    return array(
      'amount' => $order_total['amount'],
      'currency_code' => $order_total['currency_code'],
    );
  }
  else {
    return FALSE;
  }
}

/**
 * Returns a sorted array of payment totals table rows.
 *
 * @param $totals
 *   An array of payment totals whose keys are currency codes and values are the
 *     total amount paid in each currency.
 * @param $order
 *   If available, the order object to which the payments apply.
 *
 * @return
 *   An array of table row data as expected by theme_table().
 *
 * @see hook_commerce_payment_totals_row_info()
 */
function commerce_payment_totals_rows($totals, $order) {

  // Retrieve rows defined by the hook and allow other modules to alter them.
  $rows = module_invoke_all('commerce_payment_totals_row_info', $totals, $order);
  drupal_alter('commerce_payment_totals_row_info', $rows, $totals, $order);

  // Sort the rows by weight and return the array.
  uasort($rows, 'drupal_sort_weight');
  return $rows;
}

/**
 * Callback for getting payment transaction properties.
 *
 * @see commerce_payment_entity_property_info()
 */
function commerce_payment_transaction_get_properties($transaction, array $options, $name) {
  switch ($name) {
    case 'user':
      return $transaction->uid;
    case 'order':
      return !empty($transaction->order_id) ? $transaction->order_id : commerce_order_new();
    case 'message':
      if ($transaction->message) {
        return t($transaction->message, is_array($transaction->message_variables) ? $transaction->message_variables : array());
      }
      else {
        return '';
      }
  }
}

/**
 * Checks if the order has any associated payment transactions.
 *
 * @param $order
 *   A commerce_order object.
 *
 * @return bool
 *   TRUE if payment transactions exist.
 */
function commerce_payment_order_has_payments($order) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_payment_transaction')
    ->propertyCondition('order_id', $order->order_id, '=')
    ->propertyCondition('status', COMMERCE_PAYMENT_STATUS_FAILURE, '!=')
    ->count();
  return $query
    ->execute() == 1;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Makes sure the user deleting an order has permissions to delete
 * associated payment transactions if they exist on the order
 * being deleted. Prevents deletion of the order if the user
 * does not have access.
 */
function commerce_payment_form_commerce_order_ui_order_delete_form_alter(&$form, &$form_state) {
  if (commerce_payment_order_has_payments($form_state['order'])) {
    if (user_access('administer payments')) {

      // Add a required checkbox to force user to confirm
      // deletion of attached payments as well.
      $form['delete_order_payments'] = array(
        '#type' => 'checkbox',
        '#title' => t('Confirm deletion of order and all of its related payment transactions.'),
        '#required' => TRUE,
      );
      $form['actions']['submit']['#states']['visible'][] = array(
        ':input[name="delete_order_payments"]' => array(
          'checked' => TRUE,
        ),
      );
    }
    else {

      // Inform the user they cannot delete the order due
      // to attached payments.
      $form['description']['#markup'] = '<p>' . t('This order has payment transactions attached to it. You do not have sufficient permissions to delete it.');
      $form['actions']['submit']['#access'] = FALSE;
    }
  }
}

/**
 * Callback for setting payment transaction properties.
 *
 * @see commerce_payment_entity_property_info()
 */
function commerce_payment_transaction_set_properties($transaction, $name, $value) {
  switch ($name) {
    case 'user':
      $transaction->uid = $value;
      break;
    case 'order':
      $transaction->order_id = $value;
      break;
  }
}

/**
 * Implements hook_entity_load().
 *
 * Override payment method settings on the fly.
 */
function commerce_payment_entity_load($entities, $type) {
  if ($type != 'rules_config') {
    return;
  }
  foreach ($entities as $entity) {
    if (!$entity instanceof RulesTriggerableInterface) {
      continue;
    }
    $events = $entity
      ->events();

    // Check for the "Select available payment methods for an order" event.
    if (!in_array('commerce_payment_methods', $events)) {
      continue;
    }

    // Iterate over the actions to find one with the matching element ID and
    // check if we have payment settings override.
    foreach ($entity
      ->actions() as $name => $action) {
      if (is_callable(array(
        $action,
        'getElementName',
      )) && strpos($action
        ->getElementName(), 'commerce_payment_enable_') === 0) {
        if (isset($action->settings['payment_method']) && is_array($action->settings['payment_method']) && !empty($action->settings['payment_method']['settings'])) {
          $instance_id = commerce_payment_method_instance_id($action->settings['payment_method']['method_id'], $entity);
          $settings = variable_get($instance_id, array());
          if (!empty($settings) && is_array($settings)) {
            $action->settings['payment_method']['settings'] = array_replace_recursive($action->settings['payment_method']['settings'], $settings);
            $action
              ->processSettings(TRUE);
          }
        }
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Disable payment method settings form elements that are overridden.
 */
function commerce_payment_form_rules_ui_edit_element_alter(&$form, &$form_state) {
  if (!isset($form['parameter']['payment_method'])) {
    return;
  }

  // Retrieve the Rule from the $form_state.
  $rule = reset($form_state['build_info']['args']);
  $payment_method =& $form['parameter']['payment_method']['settings']['payment_method'];
  $instance_id = commerce_payment_method_instance_id($payment_method['method_id']['#value'], $rule);
  $settings = variable_get($instance_id, array());
  if (!empty($settings) && is_array($settings)) {
    $display_override_message = FALSE;
    foreach (element_get_visible_children($payment_method['settings']) as $key) {
      if (isset($settings[$key]) && isset($payment_method['settings'][$key])) {
        $payment_method['settings'][$key]['#disabled'] = TRUE;
        $payment_method['settings'][$key]['#required'] = FALSE;
        $display_override_message = TRUE;
      }
    }

    // Display a warning message to warn the user about the settings
    // that are overridden.
    if ($display_override_message) {
      drupal_set_message(t('Disabled fields below have been configured in code and cannot be changed here.'), 'warning');
    }
  }
}

Functions

Namesort descending Description
commerce_payment_commerce_checkout_page_info Implements hook_commerce_checkout_page_info().
commerce_payment_commerce_checkout_pane_info Implements hook_commerce_checkout_pane_info().
commerce_payment_commerce_order_delete Implements hook_commerce_order_delete().
commerce_payment_commerce_payment_totals_row_info Implements hook_commerce_payment_totals_row_info().
commerce_payment_commerce_payment_transaction_insert Implements hook_commerce_payment_transaction_insert().
commerce_payment_commerce_payment_transaction_update Implements hook_commerce_payment_transaction_update().
commerce_payment_entity_info Implements of hook_entity_info().
commerce_payment_entity_load Implements hook_entity_load().
commerce_payment_form_commerce_order_ui_order_delete_form_alter Implements hook_form_FORM_ID_alter().
commerce_payment_form_rules_ui_edit_element_alter Implements hook_form_FORM_ID_alter().
commerce_payment_hook_info Implements hook_hook_info().
commerce_payment_methods Returns an array of payment methods defined by enabled modules.
commerce_payment_methods_reset Resets the cached list of payment methods.
commerce_payment_method_callback Returns the specified callback for the given payment method if it exists.
commerce_payment_method_get_title Returns the title of any or all payment methods.
commerce_payment_method_instance_id Returns a payment method instance ID given a payment method ID and the Rule containing an enabling action with settings.
commerce_payment_method_instance_load Returns a payment method instance array which includes the settings specific to the context of the instance.
commerce_payment_method_load Returns a payment method array.
commerce_payment_method_options_list Wraps commerce_payment_method_get_title() for the Entity module.
commerce_payment_modules_enabled Implements hook_modules_enabled().
commerce_payment_order_balance Calculates the balance of an order by subtracting the total of all successful transactions from the total of all the line items on the order.
commerce_payment_order_has_payments Checks if the order has any associated payment transactions.
commerce_payment_permission Implements hook_permission().
commerce_payment_query_commerce_payment_transaction_access_alter Implements hook_query_TAG_alter().
commerce_payment_redirect_pane_next_page Moves an order ahead to the next page via an order update and redirect.
commerce_payment_redirect_pane_previous_page Moves an order back to the previous page via an order update and redirect.
commerce_payment_theme Implements hook_theme().
commerce_payment_totals_rows Returns a sorted array of payment totals table rows.
commerce_payment_transaction_access Determines access for a variety of operations on payment transactions.
commerce_payment_transaction_delete Deletes a payment transaction by ID.
commerce_payment_transaction_delete_multiple Deletes multiple payment transactions by ID.
commerce_payment_transaction_get_properties Callback for getting payment transaction properties.
commerce_payment_transaction_load Loads a payment transaction by ID.
commerce_payment_transaction_load_multiple Loads multiple payment transaction by ID or based on a set of matching conditions.
commerce_payment_transaction_new Returns an initialized payment transaction object.
commerce_payment_transaction_order_access Determines access for a variety of operations for payment transactions on a given order.
commerce_payment_transaction_save Saves a payment transaction.
commerce_payment_transaction_set_properties Callback for setting payment transaction properties.
commerce_payment_transaction_statuses Returns an array of transaction payment status objects for the defined payment statuses.
commerce_payment_transaction_status_load Returns the payment transaction status object for the specified status.
commerce_payment_transaction_status_options_list Returns an options list of payment transaction statuses.
commerce_payment_transaction_uri Entity uri callback: gives modules a chance to specify a path for a payment transaction.
commerce_payment_views_api Implements hook_views_api().
template_preprocess_commerce_payment_totals Adds the necessary CSS for the line item summary template.
theme_commerce_payment_transaction Themes the payment transaction entity.
theme_commerce_payment_transaction_status_icon Themes the icon representing a payment transaction status.
theme_commerce_payment_transaction_status_text Themes a text value related to a payment transaction status.
_commerce_payment_default_rules_reset Resets default Rules if necessary when modules are enabled or disabled.

Constants