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.
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.
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:
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.
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.
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).
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:
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.
To create and successfully send a charge request to Adyen, we need to perform three steps:
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.
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.
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:
On its dashboard, Adyen provides a form to test if a generated signature is correct for given values.
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.
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.
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.