All Ruby on Rails Node JS Android iOS React Native Frontend

A Short Love Story about Adyen and Service Objects

Some time ago, I got assigned to the enigmatic task of analyzing and implementing the integration with a new payment provider for our client’s Austrian services. The requirement was straightforward: the new payment service should be Adyen, as Adyen supports SEPA payments. Here is the story about how it all played out.

Wait, what?

tl;dr: below is some information about the project and the problem. For more technical stuff just scroll down until you see text in a fixed-width font.

Project

The project I implemented this functionality for is called Lemonfrog. It is a set of web apps aimed at pairing users, not only in the dating sense. We can break down Lemonfrog’s services into two main groups:

  • Care services: they connect people looking for a job with people looking for help, e.g. tutor24 (students and tutors) or homeservice24 (all kinds of housework).
  • Dating services, e.g. singlemitkind (for single parents).

Though Lemonfrog itself is based in Switzerland, its services are also present in Austria and Germany, where it also has a large user base.

The main source of income for our client are premium accounts – some types of users require a premium account to be able to contact others. The variety of services and their target users requires more than one type of payment, so next to the most popular payments providers (i.e. Stripe and PayPal) we also have options such as Invoice by email/letter. Remember, not everybody knows how to use a credit card.

Problem

The biggest problem for a Switzerland-based application about targeting Austria is that the Austrian currency is the euro, and the Swiss currency is the franc. The solution to this issue is called the Single Euro Payments Area, the European Union’s payment-integration initiative for simplification of bank transfers denominated in euros.

To put it more simply, thanks to SEPA (both Austria and Switzerland are its members) people from Austria can pay people in Switzerland easily and with smaller costs.

Adyen

The payment solutions provider which fit the requirement of supporting SEPA perfectly is Adyen. It offers many payment methods from around the world (including methods specific for one country and more global ones, like the SEPA bank transfer, as well).

How?

Ok, so now when we knew what the problem was and what tool we should use, the time came to analyze how to use that tool to solve the problem. After a few hours of research, I found only one out-of-the-box solution (the gem called adyen) that could be potentially helpful, but considering our needs and the fact that that gem didn’t seem to be well supported (the last commit was from almost half a year before) we decided that it would better to write our own solution.

There’s no need to delve deeper into the specifics of Adyen, what it provides or how it works – all this information is available in the developer’s documentation. In this article, I will focus only on some suggestions about how to implement the integration with Adyen in your app from the developer’s point of view.

During the ‘investigative’ part of my work, I tried to write down all necessary information:

  • We needed to store three values for each of our services: account name, skin code and HMAC key.
  • The payment flow could be divided into three main actions:
    • Creating and sending a payment request.
    • Receiving and handling the response when the user comes back to our service after completing the payment.
    • Handling notifications about payment status changes.
  • Creating and sending a payment request includes preparing correct request parameters that are in accordance with Adyen’s documentation and a signature for our request.

I decided to split each action into separate service classes – one service object would then be responsible for one action. If you want to know more about service objects, you can find an article about this approach here.

Charge request

To create and successfully send a charge request to Adyen, we need to perform three steps:

  • Build a list of parameters based on the payment data.
  • Sign those parameters.
  • Build request URI based on those params and the environment in which app is currently running.

From our payments controller’s point of view, we only need the URI to which we should redirect the user when an Adyen payment method is chosen. This will be handled by ChargeRequestService:

Its constructor takes two objects as arguments: one with payment data, the other one with data related to the current service and its configuration. Then, it delegates the building parameters to our RequestParamsService.  Based on the @params hash and the current app environment, it builds and returns a URI to Adyen’s live or test endpoint.

Building and signing params

Now, we will have a closer look at how the parameters are built and signed.

In the service responsible for building our parameters, we are using some values defined in constants. The most important one is called PARAMETERS, because it includes the list of all the parameters (in the alphabetical order) that we want to include in the generated hash. The names are written in snake_case, as each name has its own private method responsible for generating its value, but Adyen requires our keys to be written in lowerCamelCase. That’s why we need to call .camelize(:lower) in our build_params method.

Parameters signature

Before returning a hash with the parameters, we want to generate their signature by calling SignatureService with the HMAC key generated through the Adyen panel and saved as adyen_hmac_key.

Here is what the signature generation algorithm should look like according to the documentation:

  1. Sort hash alphabetically by keys.
  2. Escape colons and backslashes in values with \: and \\ respectively.
  3. Concatenate keys with :, then do the same for values and join both strings (also with :).
  4. Convert the HMAC key to its binary representation – it’s considered a hexadecimal value.
  5. Calculate the HMAC with the signing string from step 3 using SHA-256.
  6. Encode results using the Base64 encoding scheme.

On its dashboard, Adyen provides a form to test if a generated signature is correct for given values.

Handling Adyen’s response

After the payment goes through, Adyen redirects the user back to our page and sends us some information about the results of the process. We will handle these responses in a separate service.

First, we want to find the payment in our database based on the received data. The next step is to update this payment’s status. We also want to save the external ID of the payment – it might prove useful for identifying payments.

Responding to changes: handling notifications

SEPA payments are not as fast as payments with a credit card or PayPal – in the SEPA, a payment needs about one working day to be authorized and settled. This makes receiving and reacting to notifications from Adyen crucial for our case. Here is the service responsible for that.

Adyen notifications come as an array of notificationItems – we want to iterate through each of those items and handle each notification event. This is done in the handle_notification_item method. First, we need to check if the payment is found for the given data. Next, we should check whether the notification says that the event was successful. If it wasn’t successful, we can somehow store the data about the failure (send it to Rollbar for example).

Handling events is managed by handle_[event_name] methods, which are dynamically called - in those methods we can implement the code responsible for doing what we want to do in case of this specific event (e.g. for handle_authorisation, we can update the payment state to PAID and trigger a mailer which will inform the user that the payment has been approved). We also want to store information about unknown events (there are plenty of event codes which Adyen can potentially send, but only a few will be useful for us) – in the case of NoMethodError we send the information to Rollbar.

Summary

Adyen can be very useful for your client from the business perspective. It’s good to know that such a tool exists and know how to use it. Unfortunately, there is neither documentation meant for Ruby developers nor any up-to-date out-of-the-box solution. Hopefully, this article will help you with implementing the integration with Adyen in your app.

Why it is impossible to hire quality engineers in London and how to deal with it
Code stories CTA
READ ALSO FROM Ruby/Ruby on Rails
Read also
Need a successful project?
Estimate project or contact us