You are here

commerce_mollie.module in Commerce Mollie 7

Same filename and directory in other branches
  1. 8 commerce_mollie.module

File

commerce_mollie.module
View source
<?php

/**
 * @file
 * Drupal Commerce Module: Mollie Payment
 *
 * Implements Mollie payment services for use with Drupal Commerce.
 * Accept iDEAL, Mister Cash, Creditcard, bank transfer, PayPal, and
 * paysafecard online payments without fixed monthly costs or any punishing
 * registration procedures.
 *
 * @author: Geoffrey de Vlugt <geoffrey@renaissance.nl>
 */
require "Mollie/API/Autoloader.php";

/**
 * Implements hook_init().
 */
function commerce_mollie_init() {
  if (strtolower(commerce_default_currency()) != 'eur') {
    drupal_set_message(t('The Commerce Mollie payment module only supports the Euro as the default currency'), 'error');
  }
}

/**
 * Implements hook_menu().
 */
function commerce_mollie_menu() {
  $items = array();
  $items['commerce_mollie/webhook'] = array(
    'page callback' => 'commerce_mollie_webhook',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['checkout/%commerce_order/payment/response/%'] = array(
    'title' => 'Commerce Mollie Payment',
    'page callback' => 'commerce_mollie_return',
    'page arguments' => array(
      1,
      4,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/mollie-status-request'] = array(
    'title' => 'Request payment status',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_mollie_request_payment_status_form',
      3,
      5,
    ),
    'access callback' => 'commerce_mollie_transfer_transaction_confirm_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function commerce_mollie_permission() {
  $perms = array();
  $perms['mollie payment status request'] = array(
    'title' => t('Request payment status'),
    'description' => t('Perform a request with Mollie about the status of a Payment.'),
  );
  return $perms;
}

/**
 * Access callback function to limit access to Mollie payments.
 */
function commerce_mollie_transfer_transaction_confirm_access($order, $transaction = NULL, $account = NULL) {
  if (user_access('mollie payment status request')) {
    $access = commerce_payment_transaction_access('update', $transaction, $account);
    if ($access && $transaction) {
      if ($transaction->payment_method != 'commerce_mollie') {
        $access = FALSE;
      }
    }
    return $access;
  }
  return FALSE;
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_mollie_commerce_payment_method_info() {
  $payment_methods = array();
  $payment_methods['commerce_mollie'] = array(
    'title' => t('Mollie'),
    'description' => t('Integrates Mollie payment services support.'),
    'active' => TRUE,
    'offsite' => TRUE,
    'offsite_autoredirect' => FALSE,
  );
  return $payment_methods;
}

/**
 * Implements hook_commerce_settings_form().
 */
function commerce_mollie_settings_form($settings = NULL) {
  $form = array();

  // Merge default settings into the stored settings array.
  $settings = (array) $settings + array(
    'commerce_mollie_api_key_live' => '',
    'commerce_mollie_api_key_test' => '',
    'commerce_mollie_test_mode' => 0,
    'commerce_mollie_redirect_page_button_text' => 'Pay via Mollie',
    'commerce_mollie_redirect_page_text' => 'Continue with checkout to complete payment via Mollie.',
  );
  $form['commerce_mollie_api_key_live'] = array(
    '#type' => 'textfield',
    '#title' => t('Mollie live API key'),
    '#description' => t('Your Mollie live API key.'),
    '#default_value' => $settings['commerce_mollie_api_key_live'],
    '#required' => TRUE,
    '#element_validate' => array(
      '_commerce_mollie_api_key_element_validate',
    ),
  );
  $form['commerce_mollie_api_key_test'] = array(
    '#type' => 'textfield',
    '#title' => t('Mollie test API key'),
    '#description' => t('Your Mollie test API key.'),
    '#default_value' => $settings['commerce_mollie_api_key_test'],
    '#required' => TRUE,
    '#element_validate' => array(
      '_commerce_mollie_api_key_element_validate',
    ),
  );
  $form['commerce_mollie_test_mode'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable test mode'),
    '#description' => t('Check this option to enable test mode.'),
    '#default_value' => $settings['commerce_mollie_test_mode'],
  );
  $form['commerce_mollie_redirect_page_button_text'] = array(
    '#type' => 'textfield',
    '#title' => t('Payment button text'),
    '#description' => t('The text of the payment button that redirects the user to the Mollie website.'),
    '#default_value' => $settings['commerce_mollie_redirect_page_button_text'],
    '#required' => TRUE,
  );
  $form['commerce_mollie_redirect_page_text'] = array(
    '#type' => 'textarea',
    '#title' => t('Payment page text'),
    '#description' => t('The text displayed above the payment button that redirects the user to the Mollie website.'),
    '#default_value' => $settings['commerce_mollie_redirect_page_text'],
    '#required' => FALSE,
  );
  return $form;
}

/**
 * Implements hook_commerce_redirect_form().
 *
 * Returns the form that is submitted to Mollie.
 */
function commerce_mollie_redirect_form($form, &$form_state, $order, $payment_method) {

  // Get amount (in cents) and the currency code.
  $wrapper = entity_metadata_wrapper('commerce_order', $order);
  $amount = $wrapper->commerce_order_total->amount
    ->value();
  $currency_code = $wrapper->commerce_order_total->currency_code
    ->value();

  // Order description which will be submitted to Mollie.
  $payment_description = t("Order @order_id - @site_name", array(
    '@order_id' => $order->order_number,
    '@site_name' => variable_get('site_name', ''),
  ));

  // Create a new payment transaction for this order  and set the status to pending.
  $transaction = commerce_payment_transaction_new('commerce_mollie', $order->order_id);
  $transaction->instance_id = $payment_method['instance_id'];
  $transaction->amount = $amount;
  $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
  $transaction->currency_code = $currency_code;

  // Save payment transaction (needed because we send the transaction ID to Mollie as metadata.
  commerce_payment_transaction_save($transaction);

  // The url of the page the customer should be returned back to by Mollie.
  $redirect_url = url('checkout/' . $order->order_id . '/payment/response/' . $order->data['payment_redirect_key'], array(
    'absolute' => TRUE,
  ));

  // The webhook url Mollie should callback to update the transaction status.
  $webhook_url = url('commerce_mollie/webhook', array(
    'absolute' => TRUE,
  ));
  try {
    $mollie = new Mollie_API_Client();
    $mollie
      ->setApiKey($payment_method['settings']['commerce_mollie_test_mode'] ? $payment_method['settings']['commerce_mollie_api_key_test'] : $payment_method['settings']['commerce_mollie_api_key_live']);
    $payment = $mollie->payments
      ->create(array(
      "amount" => commerce_currency_amount_to_decimal($amount, $currency_code),
      "description" => $payment_description,
      "redirectUrl" => $redirect_url,
      "webhookUrl" => $webhook_url,
      "metadata" => array(
        "order_id" => $order->order_id,
        "transaction_id" => $transaction->transaction_id,
        "currency_code" => $currency_code,
      ),
    ));

    // Store the Mollie payment ID.
    $transaction->remote_id = $payment->id;

    // Save payment transaction.
    commerce_payment_transaction_save($transaction);

    // Set redirect url.
    $form['#action'] = $payment
      ->getPaymentUrl();
    $form['mollie_information'] = array(
      '#markup' => '<p class="commerce-mollie-info">' . t($payment_method['settings']['commerce_mollie_redirect_page_text']) . '</p>',
    );
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t($payment_method['settings']['commerce_mollie_redirect_page_button_text']),
    );
  } catch (Mollie_API_Exception $e) {
    watchdog('commerce_mollie', 'Could not create a Mollie payment. Error: %error', array(
      '%error' => htmlspecialchars($e
        ->getMessage()),
    ), WATCHDOG_ERROR);
    drupal_set_message(t('An error occured while preparing your Mollie payment. Please try again or contact the webmaster.'), 'error');
    drupal_goto('cart/checkout');
  }
  return $form;
}

/**
 * Response return url for Mollie.
 *
 * We have a intermediate url to retrieve the payment status. This is not
 * returned by the Mollie on the redirect url.
 *
 * @param $order
 * @param $payment_redirect_key
 *
 * @return void
 */
function commerce_mollie_return($order, $payment_redirect_key) {
  if ($payment_redirect_key == $order->data['payment_redirect_key']) {

    // Load non failed payments.
    $transaction_query = new EntityFieldQuery();
    $transaction_query
      ->entityCondition('entity_type', 'commerce_payment_transaction')
      ->propertyCondition('status', 'failure', '!=')
      ->propertyCondition('order_id', $order->order_id);
    $transaction = $transaction_query
      ->execute();
    if (!empty($transaction['commerce_payment_transaction'])) {
      $transaction = entity_load('commerce_payment_transaction', array_keys($transaction['commerce_payment_transaction']));
      $transaction = reset($transaction);
    }
    else {
      $transaction = NULL;
    }
    if (!empty($transaction)) {
      $transaction = commerce_mollie_request_status($order, $transaction);
      commerce_payment_transaction_save($transaction);
      if ($transaction->status == COMMERCE_PAYMENT_STATUS_FAILURE) {
        drupal_set_message(t('You cancelled your payment. Please proceed again or choose a different payment option.'));
        commerce_payment_redirect_pane_previous_page($order);
        drupal_goto('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key']);
      }
      else {
        commerce_payment_redirect_pane_next_page($order);
        drupal_goto('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key']);
      }
    }
    else {
      commerce_payment_redirect_pane_previous_page($order);
      drupal_goto('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key']);
    }
  }
  else {
    watchdog('commerce_mollie', 'Return payment url used with incorrect redirect key.', array(), WATCHDOG_WARNING);
    drupal_not_found();
  }
}

/**
 * Handles the callback/webhook call from Mollie and set the payment status accordingly.
 */
function commerce_mollie_webhook() {
  $payment_method = _commerce_mollie_get_payment_method_instance();
  try {
    $mollie = new Mollie_API_Client();
    $mollie
      ->setApiKey($payment_method['settings']['commerce_mollie_test_mode'] ? $payment_method['settings']['commerce_mollie_api_key_test'] : $payment_method['settings']['commerce_mollie_api_key_live']);

    // Fetch the payment information made offsite from Mollie.
    $payment = $mollie->payments
      ->get($_POST["id"]);

    // Load the previously stored Drupal Commerce payment transaction.
    $transaction = commerce_payment_transaction_load($payment->metadata->transaction_id);
    $order = commerce_order_load($payment->metadata->order_id);
    if ($payment
      ->isPaid() == TRUE) {
      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
      $transaction->message = t('Successful Mollie payment with transaction ID: %transaction_id for order ID: %order_id', array(
        '%transaction_id' => $payment->metadata->transaction_id,
        '%order_id' => $payment->metadata->order_id,
      ));
      commerce_payment_redirect_pane_next_page($order);
    }
    else {
      if ($payment
        ->isOpen() == FALSE) {
        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
        $transaction->message = t('Mollie payment with transaction ID: %transaction_id for order ID: %order_id failed', array(
          '%transaction_id' => $payment->metadata->transaction_id,
          '%order_id' => $payment->metadata->order_id,
        ));
        commerce_payment_redirect_pane_previous_page($order);
      }
    }
    watchdog('commerce_mollie', $transaction->message, array(), WATCHDOG_NOTICE);
    commerce_payment_transaction_save($transaction);
  } catch (Mollie_API_Exception $e) {
    watchdog('commerce_mollie', 'Could not load Mollie payment from webhook call. Error: %error', array(
      '%error' => htmlspecialchars($e
        ->getMessage()),
    ), WATCHDOG_ERROR);
  }
}

/**
 * Retrieve a new status update about the payment.
 *
 * @param $form
 * @param $form_state
 * @param $order
 * @param $transaction
 *
 * @return mixed
 */
function commerce_mollie_request_payment_status_form($form, &$form_state, $order, $transaction) {
  $transaction = commerce_mollie_request_status($order, $transaction);
  commerce_payment_transaction_save($transaction);
  drupal_goto('admin/commerce/orders/' . $order->order_id . '/payment');
  return $form;
}

/**
 * Request new payment status.
 *
 * @param $order
 * @param $transaction
 *
 * return stdClass
 *  Returns updated transaction object.
 */
function commerce_mollie_request_status($order, $transaction) {
  $payment_method = _commerce_mollie_get_payment_method_instance();
  try {
    $mollie = new Mollie_API_Client();
    $mollie
      ->setApiKey($payment_method['settings']['commerce_mollie_test_mode'] ? $payment_method['settings']['commerce_mollie_api_key_test'] : $payment_method['settings']['commerce_mollie_api_key_live']);
    $payment = $mollie->payments
      ->get($transaction->remote_id);
    if (!empty($payment)) {

      // Set status
      switch ($payment->status) {

        // Awaiting = COMMERCE_PAYMENT_STATUS_PENDING
        case 'open':
        case 'pending':
          $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
          $transaction->remote_status = $payment->status;
          $transaction->message = commerce_mollie_status_explained($payment->status);
          break;

        // Cancelled = COMMERCE_PAYMENT_STATUS_FAILURE
        case 'cancelled':
        case 'expired':
          $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
          $transaction->remote_status = $payment->status;
          $transaction->message = commerce_mollie_status_explained($payment->status);
          break;

        // Paid = COMMERCE_PAYMENT_STATUS_SUCCESS
        case 'paid':
        case 'paidout':
          $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
          $transaction->remote_status = $payment->status;
          $transaction->message = commerce_mollie_status_explained($payment->status);
          break;
        default:
          break;
      }
      return $transaction;
    }
  } catch (Mollie_API_Exception $e) {
    watchdog('commerce_mollie', 'Could not retrieve payment information. Error: %error', array(
      '%error' => htmlspecialchars($e
        ->getMessage()),
    ), WATCHDOG_ERROR);
    drupal_set_message(t('An error occured while retrieving payment status information from Mollie. Please try again later or contact your webmaster.'), 'error');
  }
  return $transaction;
}

/**
 * Returns the Mollie payment method instance.
 *
 * @return object|bool
 *  The payment method object for Mollie or false if none was found.
 */
function _commerce_mollie_get_payment_method_instance() {
  $payment_method = FALSE;
  $event = rules_get_cache('event_commerce_payment_methods');

  // Look for a Rule enabling this payment method.
  foreach ($event
    ->getIterator() as $rule) {
    foreach ($rule
      ->actions() as $action) {
      if ($action
        ->getElementName() == 'commerce_payment_enable_commerce_mollie') {
        $instance_id = commerce_payment_method_instance_id('commerce_mollie', $rule);
        $payment_method = commerce_payment_method_instance_load($instance_id);
      }
    }
  }
  return $payment_method;
}

/**
 * Validates the entered Mollie live and test API keys.
 *
 * @param $element
 *  The form element
 * @param $form_state
 *  The form state
 * @param $form
 *  The form
 */
function _commerce_mollie_api_key_element_validate($element, &$form_state, $form) {
  if ($element['#title'] == 'Mollie live API key' && strpos($element['#value'], 'live') !== 0) {
    form_error($element, t('Your Mollie live API key should start with <em>live</em>.'));
  }
  else {
    if ($element['#title'] == 'Mollie test API key' && strpos($element['#value'], 'test') !== 0) {
      form_error($element, t('Your Mollie test API key should start with <em>test</em>.'));
    }
  }
}

/**
 * Return a explaining message with the Mollie status.
 *
 * @param $status
 *
 * @return string
 *   The explanation with a status.
 */
function commerce_mollie_status_explained($status) {
  $status_messages = array(
    'open' => t('The payment is created but nothing happened yet.'),
    'cancelled' => t('The payment has been cancelled by the customer.'),
    'pending' => t('The payment is in transit.'),
    'expired' => t('The payment has expired.'),
    'paid' => t('The payment has been paid.'),
    'paidout' => t('The payment has been paid to your bank account.'),
    'refunded' => t('The payment has had a refund to the customer.'),
  );
  if (array_key_exists($status, $status_messages)) {
    return $status_messages[$status];
  }
  return '';
}

Functions

Namesort descending Description
commerce_mollie_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_mollie_init Implements hook_init().
commerce_mollie_menu Implements hook_menu().
commerce_mollie_permission Implements hook_permission().
commerce_mollie_redirect_form Implements hook_commerce_redirect_form().
commerce_mollie_request_payment_status_form Retrieve a new status update about the payment.
commerce_mollie_request_status Request new payment status.
commerce_mollie_return Response return url for Mollie.
commerce_mollie_settings_form Implements hook_commerce_settings_form().
commerce_mollie_status_explained Return a explaining message with the Mollie status.
commerce_mollie_transfer_transaction_confirm_access Access callback function to limit access to Mollie payments.
commerce_mollie_webhook Handles the callback/webhook call from Mollie and set the payment status accordingly.
_commerce_mollie_api_key_element_validate Validates the entered Mollie live and test API keys.
_commerce_mollie_get_payment_method_instance Returns the Mollie payment method instance.