You are here

mollie_payment.module in Mollie Payment 7

Same filename and directory in other branches
  1. 8.2 mollie_payment.module
  2. 7.2 mollie_payment.module

Provides Mollie integration for the Payment platform.

File

mollie_payment.module
View source
<?php

/**
 * @file
 * Provides Mollie integration for the Payment platform.
 */
define('MOLLIE_PAYMENT_RETURN_PATH', 'payment/mollie/return');
define('MOLLIE_PAYMENT_LISTENER_PATH', 'payment/mollie/listener');
define('MOLLIE_PAYMENT_RECURRING_LISTENER_PATH', 'payment/mollie/listener/recurring');

/**
 * Implements hook_menu().
 */
function mollie_payment_menu() {
  $items = array();
  $items[MOLLIE_PAYMENT_RETURN_PATH] = array(
    'page callback' => 'mollie_payment_return',
    'page arguments' => array(
      3,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items[MOLLIE_PAYMENT_LISTENER_PATH] = array(
    'page callback' => 'mollie_payment_listener',
    'page arguments' => array(
      3,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items[MOLLIE_PAYMENT_RECURRING_LISTENER_PATH] = array(
    'page callback' => 'mollie_payment_recurring_listener',
    'page arguments' => array(
      4,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function mollie_payment_permission() {
  return array(
    'administer mollie payment' => array(
      'title' => t('Administer Mollie Payment'),
    ),
  );
}

/**
 * Implements hook_libraries_info().
 */
function mollie_payment_libraries_info() {
  $libraries = array();
  $libraries['mollie_api'] = array(
    'name' => 'Mollie API client for PHP',
    'vendor url' => 'https://www.mollie.nl/',
    'download url' => 'https://github.com/mollie/mollie-api-php',
    'version' => '1.9.1',
    'files' => array(
      'php' => array(
        'src/Mollie/API/Autoloader.php',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_payment_method_controller_info().
 */
function mollie_payment_payment_method_controller_info() {
  return array(
    'MolliePaymentMethodController',
  );
}

/**
 * Implements hook_entity_load().
 */
function mollie_payment_entity_load(array $entities, $entity_type) {
  if ($entity_type == 'payment_method') {
    foreach ($entities as $payment_method) {
      if ($payment_method->controller->name == 'MolliePaymentMethodController') {
        $payment_method->controller_data = mollie_payment_payment_method_configuration_load($payment_method->pmid);
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_ACTION().
 */
function mollie_payment_payment_method_presave(PaymentMethod $payment_method) {
  $payment_method->module = 'mollie_payment';
}

/**
 * Implements hook_ENTITY_TYPE_ACTION().
 */
function mollie_payment_payment_method_insert(PaymentMethod $payment_method) {
  if ($payment_method->controller->name == 'MolliePaymentMethodController') {
    mollie_payment_payment_method_configuration_save($payment_method->pmid, $payment_method->controller_data);
  }
}

/**
 * Implements hook_ENTITY_TYPE_ACTION().
 */
function mollie_payment_payment_method_update(PaymentMethod $payment_method) {
  if ($payment_method->controller->name == 'MolliePaymentMethodController') {
    mollie_payment_payment_method_configuration_save($payment_method->pmid, $payment_method->controller_data);
  }
}

/**
 * Implements hook_ENTITY_TYPE_ACTION().
 */
function mollie_payment_payment_method_delete(PaymentMethod $payment_method) {
  if ($payment_method->controller->name == 'MolliePaymentMethodController') {
    variable_del('mollie_payment_' . $payment_method->pmid . '_controller_data');
  }
}

/**
 * Implements hook_payment_recurring_can_OPERATION() for stop.
 */
function mollie_payment_payment_recurring_can_stop(Payment $payment) {

  // Initialize the client.
  $client = mollie_payment_get_client($payment);
  if ($payment && $client) {
    $customer_id = $payment->context_data['payment']['customer'];
    $subscription_id = $payment->context_data['payment']['subscription'];

    // Mollie API Client throws an exception when trying to get a cancelled
    // subscription.
    try {
      $subscription = $client->customers_subscriptions
        ->withParentId($customer_id)
        ->get($subscription_id);
      if (in_array($subscription->status, array(
        'cancelled',
        'completed',
      ))) {
        return FALSE;
      }
    } catch (Exception $e) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Implements hook_payment_recurring_OPERATION() for stop.
 */
function mollie_payment_payment_recurring_stop(Payment $payment) {

  // Initialize the client.
  $client = mollie_payment_get_client($payment);
  if ($payment && $client) {
    $customer_id = $payment->context_data['payment']['customer'];
    $subscription_id = $payment->context_data['payment']['subscription'];
    $subscription = $client->customers_subscriptions
      ->withParentId($customer_id)
      ->cancel($subscription_id);
    drupal_set_message(t('The status of @subscription is @status.', array(
      '@subscription' => $subscription->description,
      '@status' => $subscription->status,
    )));
  }
}

/**
 * Return callback.
 *
 * @param string $pid
 *   The id of the payment.
 *
 * Mollie is redirecting the visitor here after the payment process. At this
 * point we don't know the status of the payment yet so we can only load
 * the payment and call its finish callback.
 */
function mollie_payment_return($pid) {

  // Load the Payment payment.
  $payment = entity_load_single('payment', $pid);

  // Fetch the Mollie payment id.
  $id = $payment->context_data['payment']['id'];

  // Initialize the client.
  $client = mollie_payment_get_client($payment);
  if ($payment && $client) {

    // Load the Mollie payment.
    $mollie_payment = $client->payments
      ->get($id);

    // Update the status of the Payment payment.
    mollie_payment_update_status($payment, $mollie_payment->status);

    // At this moment this does not work in test mode.
    if (!$payment->method->controller_data['test_mode'] && $mollie_payment->method) {

      // Load the Mollie payment method.
      $method = $client->methods
        ->get($mollie_payment->method);

      // Store the Mollie payment method id and name.
      $payment->context_data['payment']['method'] = $method->id;
      $payment->context_data['payment']['method_name'] = $method->description;
      entity_save('payment', $payment);
    }
  }

  // Finish the Payment payment and hand control back over to the context.
  $payment
    ->finish();
}

/**
 * Listener callback.
 *
 * @param string $pid
 *   The id of the payment.
 *
 * Mollie calls this after the payment status has been changed. Mollie only
 * gives us an id leaving us with the responsibility to get the payment status.
 */
function mollie_payment_listener($pid) {

  // Load the Payment payment.

  /** @var Payment $payment */
  $payment = entity_load_single('payment', $pid);

  // Fetch the Mollie payment id.
  $parameters = drupal_get_query_parameters($_POST);
  $id = $parameters['id'];

  // Initialize the client.
  $client = mollie_payment_get_client($payment);
  if ($payment && $client) {

    // Load the Mollie Payment.
    $mollie_payment = $client->payments
      ->get($id);

    // Update the status of the Payment payment.
    mollie_payment_update_status($payment, $mollie_payment->status);

    // Create a subscription once the mandate is pending or valid.
    if (module_exists('payment_recurring')) {
      $recurring_info = payment_recurring_recurring_payment_info($payment);

      // There is no subscription yet.
      if (!empty($recurring_info) && !isset($payment->context_data['payment']['subscription'])) {

        // This is a recurring payment.
        if (in_array($recurring_info['type'], array(
          'subscription',
          'installments',
        ))) {
          $controller_data = $payment->method->controller_data;
          $recurring_listener_path = MOLLIE_PAYMENT_RECURRING_LISTENER_PATH;
          if (!empty($controller_data['webhook_base_url'])) {
            $recurring_listener_path = $controller_data['webhook_base_url'] . '/' . $recurring_listener_path;
          }

          // Charge automatically.
          $mandates = $client->customers_mandates
            ->withParentId($mollie_payment->customerId)
            ->all();
          foreach ($mandates as $mandate) {
            if (in_array($mandate->status, array(
              'pending',
              'valid',
            ))) {

              // We are allowed to charge automatically.
              $subscription_data = array(
                'amount' => $payment
                  ->totalAmount(TRUE),
                'interval' => $recurring_info['interval'],
                'description' => $payment->description,
                'webhookUrl' => url($recurring_listener_path . '/' . $payment->pid, array(
                  'absolute' => TRUE,
                )),
              );
              if ($recurring_info['type'] == 'installments' && isset($recurring_info['times'])) {
                $subscription_data['times'] = $recurring_info['times'];
              }
              if (isset($recurring_info['startDate'])) {
                $subscription_data['startDate'] = $recurring_info['startDate'];
              }
              $subscription = $client->customers_subscriptions
                ->withParentId($mollie_payment->customerId)
                ->create($subscription_data);
              if ($subscription) {
                $payment->context_data['payment']['customer'] = $mollie_payment->customerId;
                $payment->context_data['payment']['subscription'] = $subscription->id;
                entity_save('payment', $payment);
              }
            }
          }
        }
      }
    }
  }
}

/**
 * Recurring listener callback.
 *
 * @param string $pid
 *   The id of the payment.
 *
 * Mollie calls this after a new recurring payment was made or when the payment
 * status has been changed. Mollie only gives us an id leaving us with the
 * responsibility to get the payment status.
 */
function mollie_payment_recurring_listener($pid) {

  // Load the Payment payment.

  /** @var Payment $payment */
  $payment = entity_load_single('payment', $pid);

  // Fetch the Mollie payment id.
  $parameters = drupal_get_query_parameters($_POST);
  $id = $parameters['id'];

  // Initialize the client.
  $client = mollie_payment_get_client($payment);
  if ($payment && $client) {

    // Load the Mollie Payment.
    $mollie_payment = $client->payments
      ->get($id);
    if (module_exists('payment_recurring') && isset($mollie_payment->subscriptionId)) {

      // This is a new payment for a subscription.

      /** @var Payment $new_payment */
      $new_payment = entity_create('payment', array(
        'method' => $payment->method,
        'currency_code' => 'EUR',
        'amount' => $mollie_payment->amount,
        'description' => $mollie_payment->description,
        'recurring' => array(
          'fpid' => $payment->pid,
        ),
        'context_data' => array(
          'payment' => array(
            'id' => $mollie_payment->id,
            'subscription' => $mollie_payment->subscriptionId,
          ),
        ),
      ));
      $line_items = $payment
        ->getLineItems();

      // Get the first line item to fetch the tax rate.
      $line_item = reset($line_items);
      $new_payment
        ->setLineItem(new PaymentLineItem(array(
        'currency_code' => 'EUR',
        'amount' => $mollie_payment->amount / (1 + $line_item->tax_rate),
        'quantity' => 1,
        'tax_rate' => $line_item->tax_rate,
        'description' => $mollie_payment->description,
      )));
      entity_save('payment', $new_payment);
      mollie_payment_update_status($new_payment, $mollie_payment->status);
    }
  }
}

/**
 * Update status of payment.
 */
function mollie_payment_update_status(Payment $payment, $mollie_status) {
  $payment_status = array(
    'open' => PAYMENT_STATUS_PENDING,
    'cancelled' => PAYMENT_STATUS_CANCELLED,
    'pending' => PAYMENT_STATUS_PENDING,
    'paid' => PAYMENT_STATUS_SUCCESS,
    'paidout' => PAYMENT_STATUS_MONEY_TRANSFERRED,
    'refunded' => PAYMENT_STATUS_CANCELLED,
    'expired' => PAYMENT_STATUS_EXPIRED,
    'failed' => PAYMENT_STATUS_FAILED,
    'charged_back' => PAYMENT_STATUS_CANCELLED,
  );
  $payment
    ->setStatus(new PaymentStatusItem($payment_status[$mollie_status]));
  entity_save('payment', $payment);
}

/**
 * Payment method configuration form elements callback.
 *
 * @param array $form
 *   A Drupal form array.
 * @param array $form_state
 *   The current state of the form.
 *
 * @return array
 *   A Drupal form array.
 */
function mollie_payment_configuration(array $form, array &$form_state) {
  $controller_data = $form_state['payment']->method->controller_data;
  if ($controller_data['advanced']) {
    $payment = $form_state['payment'];
    $client = mollie_payment_get_client($payment);
    if ($client) {
      if (module_exists('payment_recurring') && isset($payment->recurring)) {

        // We can only use methods that support a first payment.
        $methods_list = $client->methods
          ->all(0, 0, array(
          'recurringType' => 'first',
        ));
      }
      else {
        $methods_list = $client->methods
          ->all();
      }
      $methods = array();
      foreach ($methods_list as $method) {
        $methods[$method->id] = $method->description;
      }
      $form['mollie_payment_method'] = array(
        '#type' => 'select',
        '#title' => t('Method'),
        '#options' => $methods,
      );

      // Do not bother users with a form element if there is nothing to choose.
      if (count($methods) < 2) {
        $form['mollie_payment_method']['#type'] = 'hidden';
        reset($methods);
        $form['mollie_payment_method']['#value'] = key($methods);
      }
      $issuer_list = $client->issuers
        ->all();
      $issuers = array(
        '' => t('Choose your issuer'),
      );
      foreach ($issuer_list as $issuer) {
        $issuers[$issuer->id] = $issuer->name;
      }

      // Show the issuers but only if the selected method is 'ideal'.
      $form['mollie_payment_issuer'] = array(
        '#type' => 'select',
        '#title' => t('Issuer'),
        '#options' => $issuers,
        '#states' => array(
          'visible' => array(
            ':input[name="payment_method[payment_method_controller_payment_configuration][mollie_payment_method]"]' => array(
              'value' => 'ideal',
            ),
          ),
        ),
      );
    }
  }
  if ($controller_data['test_mode']) {
    $form['test_mode'] = array(
      '#type' => 'markup',
      '#markup' => theme('html_tag', array(
        'element' => array(
          '#tag' => 'pre',
          '#value' => t('Mollie is in test mode. No real money is being transfered. Be sure to switch off test mode on production websites.'),
        ),
      )),
    );
  }
  return $form;
}
function mollie_payment_configuration_validate(array $form, array &$form_state) {
  $values = drupal_array_get_nested_value($form_state['values'], $form['#parents']);
  $form_state['payment']->method_data['mollie_payment_method'] = $values['mollie_payment_method'];
  $form_state['payment']->method_data['mollie_payment_issuer'] = $values['mollie_payment_issuer'];
}

/**
 * Payment method configuration form elements callback.
 *
 * @param array $form
 *   A Drupal form array.
 * @param array $form_state
 *   The current state of the form.
 *
 * @return array
 *   A Drupal form array.
 */
function mollie_payment_method_configuration(array $form, array &$form_state) {
  $controller_data = $form_state['payment_method']->controller_data;
  if (!is_array($form)) {
    $form = array();
  }

  /* @todo Use test and live ids, let user select test or live mode */
  $form['mollie_api_key'] = array(
    '#type' => 'textfield',
    '#required' => TRUE,
    '#title' => t('Mollie API key'),
    '#description' => t('Your Mollie API key'),
    '#default_value' => isset($controller_data['mollie_api_key']) ? $controller_data['mollie_api_key'] : '',
  );
  $form['mollie_test_api_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Mollie test API key'),
    '#description' => t('Your Mollie test API key'),
    '#default_value' => isset($controller_data['mollie_test_api_key']) ? $controller_data['mollie_test_api_key'] : '',
  );
  $form['webhook_base_url'] = array(
    '#type' => 'textfield',
    '#title' => t('Webhook Base URL'),
    '#description' => t('When testing in a local environment you may want to use a service like ngrok to
    make your environment accessible from the outside. Leave empty for online environments. Please note
    that Mollie checks the webhook URL\'s for a valid TLD. Mollie Payment does not work with a
    local test domain like my-project.dev.'),
    '#default_value' => isset($controller_data['webhook_base_url']) ? $controller_data['webhook_base_url'] : '',
  );
  $form['advanced'] = array(
    '#type' => 'checkbox',
    '#title' => t('Advanced'),
    '#description' => t('In advanced mode the payer can select the payment method in Drupal.'),
    '#default_value' => isset($controller_data['advanced']) ? $controller_data['advanced'] : 0,
  );
  $form['test_mode'] = array(
    '#type' => 'checkbox',
    '#title' => t('Test mode'),
    '#description' => t('In test mode the test API key is used and no real money is transfered.'),
    '#default_value' => isset($controller_data['test_mode']) ? $controller_data['test_mode'] : 0,
  );
  return $form;
}

/**
 * Validation callback for payment method configuration form elements callback.
 *
 * @param array $form
 *   A Drupal form array.
 * @param array $form_state
 *   The current state of the form.
 */
function mollie_payment_method_configuration_validate(array $form, array &$form_state) {
  $values = drupal_array_get_nested_value($form_state['values'], $form['#parents']);
  $form_state['payment_method']->controller_data['mollie_api_key'] = $values['mollie_api_key'];
  $form_state['payment_method']->controller_data['mollie_test_api_key'] = $values['mollie_test_api_key'];
  $form_state['payment_method']->controller_data['webhook_base_url'] = $values['webhook_base_url'];
  $form_state['payment_method']->controller_data['advanced'] = $values['advanced'];
  $form_state['payment_method']->controller_data['test_mode'] = $values['test_mode'];
}

/**
 * Load the configuration from the database.
 */
function mollie_payment_payment_method_configuration_load($pmid) {
  $config = db_select('mollie_payment_payment_method_configurations', 'c')
    ->fields('c', array(
    'configuration',
  ))
    ->condition('pmid', $pmid, '=')
    ->execute()
    ->fetchField();
  return unserialize($config);
}

/**
 * Store the configuration in the database.
 */
function mollie_payment_payment_method_configuration_save($pmid, $config) {
  db_merge('mollie_payment_payment_method_configurations')
    ->key(array(
    'pmid' => $pmid,
  ))
    ->fields(array(
    'configuration' => serialize($config),
  ))
    ->execute();
}

/**
 * Initialize Mollie API client.
 */
function mollie_payment_get_client(Payment $payment) {
  $api_key = mollie_payment_get_api_key($payment);
  try {
    libraries_load('mollie_api');
    $client = new Mollie_API_Client();
    $client
      ->setApiKey($api_key);

    // Register major version of Drupal.
    $client
      ->addVersionString('Drupal/7.x');

    // Register minor version of Drupal.
    $client
      ->addVersionString('Drupal/' . VERSION);
    return $client;
  } catch (Exception $e) {
    drupal_set_message($e
      ->getMessage(), 'error');
    return FALSE;
  }
}

/**
 * Get Mollie API key from payment.
 */
function mollie_payment_get_api_key(Payment $payment) {
  $data = $payment->method->controller_data;
  if ($data['test_mode']) {
    return $data['mollie_test_api_key'];
  }
  return $data['mollie_api_key'];
}

Functions

Namesort descending Description
mollie_payment_configuration Payment method configuration form elements callback.
mollie_payment_configuration_validate
mollie_payment_entity_load Implements hook_entity_load().
mollie_payment_get_api_key Get Mollie API key from payment.
mollie_payment_get_client Initialize Mollie API client.
mollie_payment_libraries_info Implements hook_libraries_info().
mollie_payment_listener Listener callback.
mollie_payment_menu Implements hook_menu().
mollie_payment_method_configuration Payment method configuration form elements callback.
mollie_payment_method_configuration_validate Validation callback for payment method configuration form elements callback.
mollie_payment_payment_method_configuration_load Load the configuration from the database.
mollie_payment_payment_method_configuration_save Store the configuration in the database.
mollie_payment_payment_method_controller_info Implements hook_payment_method_controller_info().
mollie_payment_payment_method_delete Implements hook_ENTITY_TYPE_ACTION().
mollie_payment_payment_method_insert Implements hook_ENTITY_TYPE_ACTION().
mollie_payment_payment_method_presave Implements hook_ENTITY_TYPE_ACTION().
mollie_payment_payment_method_update Implements hook_ENTITY_TYPE_ACTION().
mollie_payment_payment_recurring_can_stop Implements hook_payment_recurring_can_OPERATION() for stop.
mollie_payment_payment_recurring_stop Implements hook_payment_recurring_OPERATION() for stop.
mollie_payment_permission Implements hook_permission().
mollie_payment_recurring_listener Recurring listener callback.
mollie_payment_return Return callback.
mollie_payment_update_status Update status of payment.

Constants

Namesort descending Description
MOLLIE_PAYMENT_LISTENER_PATH
MOLLIE_PAYMENT_RECURRING_LISTENER_PATH
MOLLIE_PAYMENT_RETURN_PATH @file Provides Mollie integration for the Payment platform.