In the application that I am developing on Ruby on Rails, I needed to connect the payment system. The customer entered into an agreement with
Money Online , and the first thing I did, of course, was to check the list of supported systems in Shopify's
ActiveMerchant — there was no such service there, I also looked for ready-made solutions that could simplify the integration, but nothing useful for RoR was found. As a result, it was decided to fork ActiveMerchant and develop integration for this service for it, and later use the developments in the project.
In this article I want to talk about the process of connecting the payment system, in the hope that it will come in handy for someone, because I lacked such information. And it is also possible that information is useful to someone specifically on this payment system.
Before that, I already had an acquaintance with ActiveMerchant: I integrated Robokassa in one of the projects, then
this article helped a lot, and I did not encounter any problems, but I had to look at the module implementation code only a few times to check or understand the work of some specific methods - that’s all. And when integrating my module, I had to study a large enough part of ActiveMerchant.
ActiveMerchant structure
To assist in the integration of such services, there are 3 base classes in the project:
')
- Helper - on the basis of it, the form is built using the method payment_service_for , this method itself generates the form, with all the necessary parameters, the developer can only create the Helper correctly for a specific payment system
- Notification - this class contains methods for checking the validity of information received from the payment system server, for example, when confirming the completion of an operation
- Return is used to handle callbacks, but it is often more convenient to work with helper tools built into the rails, for example, to process incoming parameters than with this class. And in the implementations of this class in various systems there are usually no specific functions.
Each implementation of the payment system contains several classes inherited from the base ones, in some modules there are additional classes that implement additional protocol features. Also for each payment system in ActiveMerchant there is a module in which classes are loaded from files, and which contain methods for creating a specific class included in this module.
Integrating Money Online into ActiveMerchant
The solution to this problem began with the study of
the Money Online
protocol , and everything turned out to be not as transparent as in the case of Robokassa: background information validation, confirmation and callbacks, in fact, everything is simple, but I was mostly confused by the parameters passed during the requests and a lot of different reservations.
General query scheme
Briefly: in the general case, in a positive scenario, everything happens like this: the site sends the system a form with all the necessary parameters, and the user goes to the payment system site, then the payment system in the background asks for confirmation of the correctness of the data from our site. If the data is correct, the user will be redirected to the place where he will see the necessary instructions for keeping the payment. After clicking the "Pay" button, the payment system before accepting the payment, checks the data again, then the payment will be accepted and the system will send a request for payment confirmation to the site, and in the event of a positive answer, the user will be redirected to the callback.
The main features of the protocol that confused me:
- When billing, one parameter is sent, while checking these parameters are returned under other names
- Initially, the protocol assumes that verification is carried out only by the identifier of the user who placed the order, and it is often much more convenient to verify the data by the internal payment identifier
- Although it is possible to transfer an internal payment identifier to the system, it will not be returned during verification and in some cases it will be confirmed when confirming it.
- No parameters are passed to the callbacks.
- When invoicing, there are many additional features to provide convenience to users, but all of these features are implemented in a protocol in different ways.
Because of these features, I had to write several times in support of them (although at first I contacted them via the voice module on the site, but the girl, after listening to my problem, redirected me to an answering machine), and during the same day I received adequate answers.
From the pros, I noticed that if you make a request exactly according to the described protocol, you will get the expected result in return, although in some cases data verification is not performed, but apparently due to the peculiarities of the gateways.
I also liked the ability to invoice in the background in some cases. And I decided to make the most complete support for the billing protocol, but I did not have the opportunity to protest absolutely everything, so there might be some problems.
We also managed to identify one undocumented feature: in response to a background request for creating an invoice for payment via QIWI, it comes along with the described parameters, the iframeSRC parameter with incomprehensible content, without thinking twice, decoded the data from this parameter using base64 and received a link that leads to the form payment through QIWI, and apparently it is assumed that the link should be used to create an iframe.
If you look at the various implementations
of payment system
integration in ActiveMerchant, you can see how the same code can flow into many modules, in some files the code can be a combination of code from several modules and so on, but nowhere are there any complex scripts. I had to write a lot of my own, in particular for the implementation of background requests, so I’m afraid that the request request will not be approved, although in fact I saved all the possibilities and ideas that the base classes carry in my implementation, just added some new ones.
What happened in the end you can see
here . The only thing that leaves my general helper concept framework is data validation, but in general it is completely unnecessary, it should be used only with background queries to avoid possible errors even before the query (in spite of this in my case) when implementing the payment system on the site, I had to get around this dirty-hacking), and some query parameters are also automatically set in
special cases so that the developer would not have to deal with the protocol for a long time. Even my helper has the ability to send a background request, if this is done for a valid mode_type, then the answer will automatically match and this answer will be convenient to work with.
I can’t say that the code turned out to be good, because I’ve been familiar with Ruby for about three months, so I will be happy with any help or advice, I’m also willing to help those who want to integrate any payment system into ActiveMerchant.
An example of creating a payment system on the site
The first step is to enable ActiveMerchant in the project, for this we add it to the Gemfile
gem 'activemerchant', :git => "https://github.com/ovcharik/active_merchant.git", :branch => "dengionline"
For now, you can add it this way, but I sent the
pool request to the official repository, and it may be approved soon.
Next, create the necessary routers
scope 'top_up' do post 'create' => 'top_up#create', :as => 'top_up_create' post 'notify' => 'top_up#notify', :as => 'top_up_notify' post 'check' => 'top_up#check', :as => 'top_up_check' match 'success' => 'top_up#success', :as => 'top_up_success' match 'fail' => 'top_up#fail', :as => 'top_up_fail' end
- create is used to create a payment object and to issue an invoice, in principle it is not mandatory or its logic can be placed elsewhere, but it was more convenient for me to do this.
- success and fail are callbacks to which the user will be redirected after the completion of the transaction, by the way, no parameters are returned to these callbacks from the system.
- notify and check are needed to confirm the payment and check the correctness of the data by the payment system on the site, respectively, at these addresses the payment system will make push requests with certain parameters.
The last 4 addresses are indicated by the payment system when registering with it: in the process it will be necessary to fill in a questionnaire with points to these addresses. The site itself does not provide the possibility of changing addresses, and this is done only through additional requests for support, so I had to test a lot right in production (despite the fact that the project was launched at the development stage and for users it had no effect).
In the project that I am doing, this scenario occurs when paying for services: the user clicking the Pay button sends an ajax request to / top_up / create, the data is checked there, then the background request is sent to the payment system, the successful response is displayed to the user, errors are processed separately . In my case, as a successful response, a form comes with hidden fields and immediately after it is a script that will send this form, that is, if you display this answer somewhere on the page, the user will be implicitly redirected to the gateway page, where he can complete payment.
During a background request, the following happens: I make a background request, the system checks all data and if it is correct, it calls the / top_up / check method, where it should receive a positive response, after checking the system returns the necessary data.
The user sees the form on the gateway site, where he needs to specify the details, and after clicking the “Pay” button, the gateway checks the data in the Money Online system, which in turn checks the data in the project (again at / top_up / check), receives an answer and sends it to the payment gateway. It confirms the payment and notifies the payment system about it, and it already sends a notification to the site (/ top_up / notify), and at the same time, the user is returned to the callback.
Perhaps it's easier to understand this process just by seeing the logs Started POST "/ru/top_up/create" for user_ip at 2013-10-05 14:45:00 +0400 Processing by TopUpController#create as */* Parameters: {"amount"=>"1.0", "authenticity_token"=>"token", "currency"=>"RUB", "order"=>"78", "lang"=>"ru"} Started POST "/top_up/check" for deingionline_ip at 2013-10-05 13:45:00 +0400 Processing by TopUpController#check as */* Parameters: {"amount"=>"0", "userid"=>"example@mail.com", "userid_extra"=>"58", "paymentid"=>"0", "key"=>"key1"} Rendered text template (0.0ms) Completed 200 OK in 16ms (Views: 1.1ms | ActiveRecord: 2.5ms) Completed 200 OK in 557ms (Views: 0.5ms | ActiveRecord: 11.1ms) Started POST "/top_up/check" for deingionline_ip at 2013-10-05 14:46:20 +0400 Processing by TopUpController#check as */* Parameters: {"amount"=>"0", "userid"=>"example@mail.com", "userid_extra"=>"58", "paymentid"=>"0", "key"=>"key1"} Rendered text template (0.0ms) Completed 200 OK in 19ms (Views: 1.0ms | ActiveRecord: 1.9ms) Started POST "/top_up/notify" for deingionline_ip at 2013-10-05 14:46:20 +0400 Processing by TopUpController#notify as */* Parameters: {"amount"=>"1.00", "userid"=>"example@mail.com", "userid_extra"=>"58", "paymentid"=>"123456789", "paymode"=>"mode_type", "orderid"=>"58", "key"=>"key2"} Rendered text template (0.0ms) Completed 200 OK in 127ms (Views: 0.9ms | ActiveRecord: 15.2ms) Started GET "/top_up/success" for user_ip at 2013-10-05 14:46:30 +0400 Processing by TopUpController#success as HTML Rendered ... Completed 200 OK in 21ms (Views: 16.6ms | ActiveRecord: 0.7ms)
In the logs you can see which parameters are transmitted in all cases, and that when accessing / top_up / check the payment system does not transmit the orderid (internal payment identifier in the project, paymentid is the identifier in the payment system), therefore, it was decided to use the userid_extra parameter to identify the payment, but here, too, you need to be careful, as in
some cases it is not returned, you can specify the internal payment number in the userid, but in your personal account in the payment system, you can view all transactions, as well as select this parameter, and if you use a unique identifier as this parameter, then the utility of the sample will be zero.
Controller class TopUpController < ApplicationController include ActiveMerchant::Billing::Integrations
I have all the data sent in the background for the specifics of the answers, but you can use the method described
here .
I’ll also give an example of using a class to get remote information about a payment, I think no one will have any problems using it.
Example $ rails c irb(main):001:0> Dengionline = ActiveMerchant::Billing::Integrations::Dengionline => ActiveMerchant::Billing::Integrations::Dengionline irb(main):002:0> status = Dengionline.status 69, 1234, :secret => "secret" => #<ActiveMerchant::Billing::Integrations::Dengionline::Status:0xb805bb4> irb(main):003:0> status.to_hash => { "id"=>123456789, "amount_rub"=>"1.00", "status"=>9, "status_description"=>"Success", "order"=>"69", "nick"=>"example@mail.com", "date_payment"=>"2013-10-05T13:00:00+04:00", "paymode"=>"mode_type", "currency_project"=>"RUB", "amount_project"=>"1.00", "currency_paymode"=>"RUB" }
In this example, the internal payment identifier is passed as the main parameter; if you did not use it when issuing an invoice, you can send the payment system identifier of the payment system, for this purpose specify: payment => payment_id.