In this day and age, it’s very hard to imagine a world without online payments. They permeate every possible sector and purpose, ranging from banking apps to online ticket ordering and charity donations.

Drupal has kept pace with this evolution and is offering enterprise-quality solutions to tackle most online payment needs, most notably Drupal Commerce with secure payment integrations. Drupal Commerce allows developers to implement different gateways to PayPal, Stripe, iDeal, Mollie and Ingenico (previously known as Ogone).

In this blog post, I will explain the possibilities of the Drupal Payment module and describe an example of how to apply it together with Mollie, a rising star in the realm of payment service providers. (My colleague Marek has written a follow-up post on payment gateway testing using Ngrok, check it out here.)

 

Drupal Payment module

Are you looking to make people pay for their membership when they register for an account? Then you will have to integrate an easily manageable payment system into your application.



In situations like these and more, Drupal’s Payment module can act as a bridge to a secure payment integration. You can implement different payment gateways that communicate directly with the Payment module. This means that all incoming payments from various payment service providers are stored in a centralised location.



The Payment module integrates well with Drupal Commerce and Ubercart, but you can even integrate the module into any kind of entity with both the Payment form field and the Payment reference field.



Do you think this might suit your need as an out-of-the-box solution for a simple integration with Drupal Webforms or a basic donation form with Drupal Payment integration? They are available for download on drupal.org.

 

Payment Service Providers

If you would like to receive online payments through you website, you'll have to implement an actual payment service provider. The most commonly used payment providers in the Benelux are Ingenico, Paypal and Mollie.



Mollie

Mollie has become very popular very quickly, because it charges a transaction-based fee instead of a monthly subscription. This means that you will not be charged if there are no transactions, which is perfect for projects that do not (yet) generate a lot of transactions.

To allow for easy integration, Mollie provides developers with a very good API. Drupal (and other) developers can access the available RESTful service or a PHP API library, which makes it possible to implement logic - for example to refund a customer through the API.

If your Drupal project does not require automatic refunding of customers, you can use the mollie_payment module, which uses Mollie’s PHP API library.

 

Example: enabling a payment method

To enable payments with Mollie, you have to define a payment method using the so-called MolliePaymentMethodController. The controller is defined in the Mollie Payment module and uses Mollie's PHP API library to process the requests.

You can add the Payment method through the module install file:

/**
* Add payment method (Mollie)
*/
function MYMODULE_update_7001(){
  $mollie = new PaymentMethod(array(
    'controller' => payment_method_controller_load('MolliePaymentMethodController'),
    'controller_data' => array('mollie_id' => 'test_AA11bb22CC33dd44EE55ff66GG77hh'),
    'name' => 'pay_with_mollie',
    'title_generic' => 'Pay with Mollie',
    'title_specific' => 'Pay with Mollie',
  ));
  entity_save('payment_method', $mollie);
}

 

Forms embedding a payment form

Start by defining a simple form, extendable with multiple form elements available in Drupal’s Form API.

/**
* Callback function to build a basic payment form.
*
* @param array $form
*   The form build array.
* @param array $form_state
*   The form state information.
*/
function MYMODULE_form($form, $form_state) {
 $form = array();

 // Add form actions.
 $form['actions'] = array(
   '#type' => 'actions',
 );
 $form['actions']['save'] = array(
   '#type' => 'submit',
   '#value' => t('Pay with Mollie'),
 );

 return $form;
}

 

This form is then capable to embed a payment form, provided by the Payment module. In order to do this, you should first define a Payment object. This will provide all the payment methods that have to be integrated in the payment form. You can pass context and context data for reference and later use, the currency you are making a payment in and the callback that has to be executed after a payment has been done.

// Define a payment object.
$payment = new Payment();
$payment->context = 'donation';
$payment->context_data = array(
  'time' => time(),
  'type' => 'donation',
);
$payment->currency_code = 'EUR';
$payment->description = 'Basic payment form';
$payment->finish_callback = 'MYMODULE_finish_callback';

 

A single payment object can contain multiple items. Useful if you would like to implement this in a commerce environment. In this example, a single line item will define the total amount that has to be paid. Don't forget to define the price without taxes, because the Payment module will handle all tax calculations.

// Define a payment line item.
$line_item = new PaymentLineItem();
$line_item->amount = 100.00 / 1.21;
$line_item->name = t('EUR 100');
$line_item->tax_rate = 0.21;
$line_item->quantity = 1;

// Add the payment line item to the payment object.
$payment->setLineItem($line_item);

 

By assigning the payment object to the form, you can use the transferred information in a later stage - for instance during validation.

 

// Add the payment object to the form.
$form_state['payment'] = $payment;

You can use multiple payment methods with the payment module. In this example, Mollie is forced as the only payment option available. It is of course also possible to add multiple methods in the payment options and to allow people to pick their payment method of choice.

 

// Get available payment methods and limit this form to Mollie payment.
$payment_methods = $payment->availablePaymentMethods();
$payment_options = array();
foreach ($payment_methods as $payment_method) {
  if ($payment_method->enabled && $payment_method->name == 'pay_with_mollie') {
    $payment_options[] = $payment_method->pmid;
  }
}

To include the payment form into your custom form, you have to call the payment_form_embedded function. The function will use the payment object and the available payment options to build the required form elements and form actions. Then assign the payment elements and submit action to your custom form in order to enable the payment.

// Get the payment embed elements.
$payment_embed_form = payment_form_embedded($form_state, $payment, $payment_options);

// Add the embedded payment form element.
$form['payment'] = $payment_embed_form['elements'];

// Define the form submit callback.
$form['#submit'] = $payment_embed_form['submit'];

When defining the payment object, you actually define a finished callback. This callback will be triggered after a successful payment from the Mollie payment service provider. To be certain, you could check if there is a payment success status within the payment object and run any additional callbacks if needed.

/**
* Handle successful payment from Mollie.
*
* @param \Payment $payment
*   The returned payment object containing all relevant information.
*/
function MYMODULE_finish_callback(Payment $payment) {
  $payment_complete = FALSE;

  // Check if the payment status contains a successful state.
  foreach ($payment->statuses as $status) {
    if ($status->status == 'payment_status_success') {
      $payment_complete = FALSE;
      break;
    }
  }

  if ($payment_complete) {
    drupal_set_message('Your payment has been received.', 'success');
    // @TODO: Implement custom callbacks.
  }
}

 

Conclusion

As you noticed, it's not that hard to implement a payment workflow in your own form! 

One final tip: use form validation to check if all requirements are met before people are redirected to the payment service provider.

I hope this blog post has helped you to make payments in Drupal easier. Feel free to leave a comment below if you have any questions or if you would like to share some thoughts on different possible approaches.