You are here

commerce_cop.module in Commerce Custom Offline Payments 7

Custom offline payment methods for Drupal Commerce.

Allows store administrators to easily define custom offline payment methods as Drupal Commerce payment methods.

File

commerce_cop.module
View source
<?php

/**
 * @file
 * Custom offline payment methods for Drupal Commerce.
 *
 * Allows store administrators to easily define custom offline payment methods
 * as Drupal Commerce payment methods.
 */

/**
 * Implements hook_menu().
 */
function commerce_cop_menu() {
  $items = array();
  $items['admin/commerce/config/custom-offline-payments'] = array(
    'title' => 'Custom Offline Payments',
    'description' => 'Manage custom offline payment methods',
    'access arguments' => array(
      'administer custom offline payments',
    ),
    'file' => 'commerce_cop.admin.inc',
    'page callback' => 'commerce_cop_admin_overview',
    'weight' => 3,
  );
  $items['admin/commerce/config/custom-offline-payments/add'] = array(
    'title' => 'Add payment method',
    'access arguments' => array(
      'administer custom offline payments',
    ),
    'file' => 'commerce_cop.admin.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_cop_edit_payment_form',
    ),
    'type' => MENU_LOCAL_ACTION,
  );
  $items['admin/commerce/config/custom-offline-payments/%custom_offline_payment'] = array(
    'title' => 'Edit payment method',
    'access arguments' => array(
      'administer custom offline payments',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_cop_edit_payment_form',
      4,
    ),
    'file' => 'commerce_cop.admin.inc',
  );
  $items['admin/commerce/config/custom-offline-payments/%custom_offline_payment/edit'] = array(
    'title' => 'Edit',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'weight' => 0,
  );
  $items['admin/commerce/config/custom-offline-payments/%custom_offline_payment/delete'] = array(
    'title' => 'Delete',
    'access arguments' => array(
      'administer custom offline payments',
    ),
    'file' => 'commerce_cop.admin.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_cop_delete_payment_form',
      4,
    ),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 10,
  );

  // Add a menu item for capturing authorizations.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/offline-payment-confirm'] = array(
    'title' => 'Confirm payment',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_cop_confirm_payment_form',
      3,
      5,
    ),
    'access callback' => 'commerce_cop_payment_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function commerce_cop_permission() {
  return array(
    'administer custom offline payments' => array(
      'title' => t('Administer custom offline payment methods'),
      'description' => t('Create, update, edit the custom offline payment methods.'),
    ),
  );
}

/**
 * User access callback for cashing cheque
 */
function commerce_cop_payment_access($order, $transaction) {
  if (custom_offline_payment_load($transaction->payment_method) && $transaction->status != COMMERCE_PAYMENT_STATUS_SUCCESS) {

    // Allow access if the user can update payments on this order.
    return commerce_payment_transaction_access('update', $transaction);
  }

  // Return FALSE if the transaction is not with a Custom offline Payment and is not pending.
  return FALSE;
}

/**
 * Form to Receive the Payment
 */
function commerce_cop_confirm_payment_form($form, &$form_state, $order, $transaction) {
  $form_state['order'] = $order;
  $form_state['transaction'] = $transaction;

  // Load and store the payment method instance for this transaction.
  $payment_method = commerce_payment_method_instance_load($transaction->instance_id);
  $form_state['payment_method'] = $payment_method;
  $balance = commerce_payment_order_balance($order);
  if ($balance['amount'] > 0 && $balance['amount'] < $transaction->amount) {
    $default_amount = $balance['amount'];
  }
  else {
    $default_amount = $transaction->amount;
  }

  // Convert the price amount to a user friendly decimal value.
  $default_amount = commerce_currency_amount_to_decimal($default_amount, $transaction->currency_code);
  $description = implode('<br />', array(
    t('Order balance: @balance', array(
      '@balance' => commerce_currency_format($balance['amount'], $balance['currency_code']),
    )),
  ));
  $payment = custom_offline_payment_load($transaction->payment_method);
  $form['amount'] = array(
    '#type' => 'textfield',
    '#title' => t('Confirm %payment amount', array(
      '%payment' => $payment['title'],
    )),
    '#description' => $description,
    '#default_value' => $default_amount,
    '#field_suffix' => check_plain($transaction->currency_code),
    '#size' => 16,
  );

  // Fieldable payment transactions.
  if (module_exists('commerce_payment_fields') && !empty($payment_method['fieldable'])) {
    $instances = field_info_instances('commerce_payment_transaction', $transaction->payment_method);
    if (!empty($instances)) {

      // Add the field widgets for the profile.
      field_attach_form('commerce_payment_transaction', $transaction, $form, $form_state);
    }
  }
  $form = confirm_form($form, t('What amount do you want to confirm?'), 'admin/commerce/orders/' . $order->order_id . '/payment', '', t('Confirm'), t('Cancel'), 'confirm');
  return $form;
}

/**
 * Validate handler: ensure a valid amount is given.
 */
function commerce_cop_confirm_payment_form_validate($form, &$form_state) {
  $transaction = $form_state['transaction'];
  $amount = $form_state['values']['amount'];

  // Ensure a positive numeric amount has been entered for capture.
  if (!is_numeric($amount) || $amount <= 0) {
    form_set_error('amount', t('You must specify a positive numeric amount to capture.'));
  }

  // Ensure the amount is less than or equal to the transaction amount.
  if ($amount > commerce_currency_amount_to_decimal($transaction->amount, $transaction->currency_code)) {
    form_set_error('amount', t('You cannot confirm more than transaction amount.'));
  }

  // Fieldable payment transactions.
  if (!empty($form_state['field'])) {

    // Notify field widgets to validate their data.
    field_attach_form_validate('commerce_payment_transaction', $transaction, $form, $form_state);
  }
}

/**
 * Submit handler: confirm the "offline" payment
 */
function commerce_cop_confirm_payment_form_submit($form, &$form_state) {
  $transaction = $form_state['transaction'];
  $amount = $form_state['values']['amount'];

  // Update the transaction amount to the actual confirmed amount.
  $transaction->amount = commerce_currency_decimal_to_amount($amount, $transaction->currency_code);
  $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;

  // Append a confirmed indication to the result message.
  $transaction->message .= '<br />' . 'Confirmed: @date';
  $transaction->message_variables['@date'] = format_date(REQUEST_TIME, 'short');

  // Fieldable payment transactions.
  if (!empty($form_state['field'])) {

    // Notify field widgets.
    field_attach_submit('commerce_payment_transaction', $transaction, $form, $form_state);
  }
  commerce_payment_transaction_save($transaction);
  drupal_set_message(t('Payment confirmed successfully.'));
  $form_state['redirect'] = 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment';
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_cop_commerce_payment_method_info() {
  $payment_methods = array();
  foreach (commerce_cop_get_payments() as $method_id => $payment) {
    $payment_methods[$method_id] = array(
      'base' => 'commerce_cop',
      'title' => $payment['title'],
      'description' => $payment['description'],
      'active' => $payment['status'],
      'checkout' => $payment['checkout'],
      'terminal' => $payment['terminal'],
      'fieldable' => $payment['fieldable'],
    );
  }
  return $payment_methods;
}

/**
 * Payment method callback: settings form.
 */
function commerce_cop_settings_form($settings = NULL) {
  $settings = (array) $settings + array(
    'information' => array(
      'value' => '',
      'format' => 'plain_text',
    ),
  );
  $form = array();
  $form['information'] = array(
    '#type' => 'text_format',
    '#title' => t('Information'),
    '#description' => t('Information you would like to be shown to users when they select this payment method, such as delivery payment details.'),
    '#default_value' => $settings['information']['value'],
    '#format' => $settings['information']['format'],
  );
  if (module_exists('token')) {
    $form['token_tree'] = array(
      '#theme' => 'token_tree',
      '#token_types' => array(
        'site',
        'commerce_order',
        'commerce-order',
      ),
      '#dialog' => TRUE,
    );
  }
  return $form;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function commerce_cop_form_rules_ui_edit_element_alter(&$form, &$form_state) {

  // Use the custom offline payment definition information for the payment action,
  // if the action payment information was not set yet.
  if (!empty($form['parameter']['payment_method'])) {
    $custom_offline_payment = custom_offline_payment_load($form['parameter']['payment_method']['settings']['payment_method']['method_id']['#value']);
    if ($custom_offline_payment && empty($form['parameter']['payment_method']['settings']['payment_method']['settings']['information']['#default_value'])) {
      $form['parameter']['payment_method']['settings']['payment_method']['settings']['information']['#default_value'] = $custom_offline_payment['information'];
      $form['parameter']['payment_method']['settings']['payment_method']['settings']['information']['#format'] = $custom_offline_payment['format'];
    }
  }
}

/**
 * Payment method callback: checkout form.
 */
function commerce_cop_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
  $form = array();
  $payment_info = NULL;
  if (!empty($payment_method['settings']['information'])) {
    $payment_info = check_markup($payment_method['settings']['information']['value'], $payment_method['settings']['information']['format']);
  }
  else {
    $payment = custom_offline_payment_load($payment_method['method_id']);
    if ($payment && !empty($payment['information'])) {
      $payment_info = $payment['information'];
    }
  }
  if ($payment_info) {

    // Run token replacement.
    $payment_info = token_replace($payment_info, array(
      'commerce_order' => $order,
    ), array(
      'language' => $GLOBALS['language'],
      'clear' => TRUE,
    ));
    $form['commerce_cop_info'] = array(
      '#markup' => $payment_info,
    );
  }

  // Need to create a dummy value to solve http://drupal.org/node/1230666
  // Probably an issue in the main commerce module
  $form['transaction'] = array(
    '#type' => 'hidden',
    '#value' => 'dummy_value',
  );
  return $form;
}

/**
 * Payment method callback: checkout form validation.
 */
function commerce_cop_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) {

  // empty
}

/**
 * Payment method callback: checkout form submission.
 */
function commerce_cop_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
  $order->data['commerce_cop'] = $pane_values;

  // Drupal Commerce Payment Transaction Fields integration.
  // If installed and payment fieldable, the payment transaction object
  // with the fields data will be available here in the pane values.
  // @see https://www.drupal.org/node/2272735
  // @see https://www.drupal.org/node/2293025
  if (is_object($pane_values['transaction'])) {
    $transaction = $pane_values['transaction'];
  }
  else {
    $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id);
  }
  commerce_cop_transaction($transaction, $payment_method, $order, $charge);
}

/**
 * Creates a cheque payment transaction for the specified charge amount.
 *
 * @param $transaction
 *   The transaction instance object.
 * @param $payment_method
 *   The payment method instance object used to charge this payment.
 * @param $order
 *   The order object the payment applies to.
 * @param $charge
 *   An array indicating the amount and currency code to charge.
 */
function commerce_cop_transaction($transaction, $payment_method, $order, $charge) {
  $transaction->instance_id = $payment_method['instance_id'];
  $transaction->amount = $charge['amount'];
  $transaction->currency_code = $charge['currency_code'];
  $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
  $transaction->message = $payment_method['description'];
  commerce_payment_transaction_save($transaction);
}

/**
 * Function to get custom payments.
 */
function commerce_cop_get_payments() {
  static $payments = array();
  if (empty($payments)) {
    $q = db_select('commerce_custom_offline_payment', 'cop');
    $q
      ->fields('cop');
    $q
      ->orderBy('cop.title');
    $q = $q
      ->execute();
    while ($payment = $q
      ->fetchAssoc()) {
      $payments[$payment['id']] = $payment;
    }
  }
  return $payments;
}

/**
 * Load a custom offline payment by ID.
 * @param string $id
 */
function custom_offline_payment_load($id) {
  static $custom_offline_payments = array();
  if (!isset($custom_offline_payments[$id])) {

    // Get custom payment properties from own table.
    $payment = db_select('commerce_custom_offline_payment', 'cop');
    $payment
      ->fields('cop');
    $payment
      ->condition('id', $id);
    $payment = $payment
      ->execute();
    $payment = $payment
      ->fetchAssoc();

    // Put payment to static variable.
    $custom_offline_payments[$id] = $payment;
  }
  return $custom_offline_payments[$id];
}

/**
 * Delete a checkout payment.
 * @param array $payment
 */
function commerce_cop_payment_save($payment, $skip_reset = FALSE) {
  if (!isset($payment['id'])) {
    $payment['id'] = $payment['id'];
  }
  $counter = db_query('SELECT 1 FROM {commerce_custom_offline_payment} WHERE id = :id', array(
    ':id' => $payment['id'],
  ));
  $counter = $counter
    ->fetchColumn();
  if ($counter) {
    $payment_record = drupal_write_record('commerce_custom_offline_payment', $payment, array(
      'id',
    ));
  }
  else {
    $payment_record = drupal_write_record('commerce_custom_offline_payment', $payment);
  }

  // Clear the necessary caches.
  if (!$skip_reset) {
    entity_defaults_rebuild();
    rules_clear_cache(TRUE);
  }
  return $payment_record;
}

/**
 * Delete a checkout payment.
 * @param array $payment
 */
function commerce_cop_payment_delete($payment, $skip_reset = FALSE) {
  $delete = db_delete('commerce_custom_offline_payment');
  $delete
    ->condition('id', $payment['id']);
  $delete
    ->execute();

  // Clear the necessary caches.
  if (!$skip_reset) {
    entity_defaults_rebuild();
    rules_clear_cache(TRUE);
  }
}

/**
 * Implements hook_features_api().
 */
function commerce_cop_features_api() {
  return array(
    'commerce_custom_offline_payment' => array(
      'name' => t('Commerce Custom Offline Payments'),
      'default_hook' => 'commerce_custom_offline_payments',
      'feature_source' => TRUE,
      'file' => drupal_get_path('module', 'commerce_cop') . '/commerce_custom_offline_payment.features.inc',
    ),
  );
}

/**
 * Implements hook_token_info_alter().
 */
function commerce_cop_token_info_alter(&$info) {
  $info['tokens']['commerce-order']['payment-method-information'] = array(
    'name' => t('Payment method information'),
    'description' => t('Data or instructions to make payment.'),
  );
}

/**
 * Implements hook_tokens().
 */
function commerce_cop_tokens($type, $tokens, array $data = array(), array $options = array()) {
  static $token_replacement_running;
  $replacements = array();
  if ($type == 'commerce-order' && !empty($data['commerce-order'])) {
    $order = $data['commerce-order'];

    // Get the order payment method if it is set.
    if (isset($order->data['payment_method'])) {
      $payment_method = commerce_payment_method_instance_load($order->data['payment_method']);

      // Use the payment method information settings if exists.
      if (!empty($payment_method['settings']['information'])) {
        $payment_info = check_markup($payment_method['settings']['information']['value'], $payment_method['settings']['information']['format']);
      }
      else {
        $payment = custom_offline_payment_load($payment_method['method_id']);
        if ($payment && !empty($payment['information'])) {
          $payment_info = $payment['information'];

          // Run token replacement.
          if (!isset($token_replacement_running)) {

            // We need to avoid recursion since commerce_cop_tokens() calls this
            // function too. So if a token replacement is running don't trigger another
            // token replacement.
            $token_replacement_running = TRUE;
            $payment_info = token_replace($payment_info, array(
              'commerce_order' => $order,
            ), array(
              'language' => $GLOBALS['language'],
              'clear' => TRUE,
            ));
            unset($token_replacement_running);
          }
        }
      }
      if (!empty($payment_info)) {
        foreach ($tokens as $name => $original) {
          switch ($name) {
            case 'payment-method-information':
              $replacements[$original] = $payment_info;
              break;
          }
        }
      }
    }
  }
  return $replacements;
}

Functions

Namesort descending Description
commerce_cop_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_cop_confirm_payment_form Form to Receive the Payment
commerce_cop_confirm_payment_form_submit Submit handler: confirm the "offline" payment
commerce_cop_confirm_payment_form_validate Validate handler: ensure a valid amount is given.
commerce_cop_features_api Implements hook_features_api().
commerce_cop_form_rules_ui_edit_element_alter Implements hook_form_FORM_ID_alter().
commerce_cop_get_payments Function to get custom payments.
commerce_cop_menu Implements hook_menu().
commerce_cop_payment_access User access callback for cashing cheque
commerce_cop_payment_delete Delete a checkout payment.
commerce_cop_payment_save Delete a checkout payment.
commerce_cop_permission Implements hook_permission().
commerce_cop_settings_form Payment method callback: settings form.
commerce_cop_submit_form Payment method callback: checkout form.
commerce_cop_submit_form_submit Payment method callback: checkout form submission.
commerce_cop_submit_form_validate Payment method callback: checkout form validation.
commerce_cop_tokens Implements hook_tokens().
commerce_cop_token_info_alter Implements hook_token_info_alter().
commerce_cop_transaction Creates a cheque payment transaction for the specified charge amount.
custom_offline_payment_load Load a custom offline payment by ID.