⬆️ ⬇️

Integrating Money Online into ActiveMerchant

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:

')



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.



image

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:





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 




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 #    create skip_before_filter :verify_authenticity_token, :except => [:create] #     ActiveMerchant before_filter :create_notification, :only => [:check, :notify] #    before_filter :find_payment, :only => [:check, :notify] #   def create authorize! :create, Payment r = { :success => false, :errors => {:base => []} } errors = false #    ,      #   activemerchant     @payment = Payment.new({ :user => current_user, :amount => params[:amount] }) #   unless errors or @payment.save r[:errors].merge! @payment.errors.messages errors = true end unless errors #    ActiveMerchant helper=Dengionline.helper @payment.id, CONFIG["dengionline"]["project"], { :amount => @payment.amount, :nickname => @payment.user.email, #     :nick_extra => @payment.id, #         :transaction_type => CONFIG["dengionline"]["mode_type"], #           :source => CONFIG["dengionline"]["source"], #  ,       :secret => CONFIG["dengionline"]["secret"], #    ,      , :method => :credit_card, :mode => :background } begin #    ,    mode_type   #  ,    ,   - #      ,    xml, #     # ,       mode_type #       if (helper.valid? or helper.errors.size > 1 or helper.errors[0] != "mode_type") r[:errors][:base] << I18n.t("views.top_up.create.technical_error") break end #   ,     #       response = helper.background_request! #      ,   xml #    if response.errors.empty? and response.fail? r[:errors][:base] << response.comment break end #    parse_error -  ,     #      200 if (response.success? or response.errors.empty? or response.errors.size > 1 or response.errors[0] != "parse_error") r[:errors][:base] << I18n.t("views.top_up.create.gateway_error") break end #   parse_error,      #      ,     #    ,    r[:success] = true r[:body] = response.body end until true end render :json => r end #   def check if (@notification.acknowledged? and @payment and @payment.wait? and @payment.valid?) render :text => @notification.generate_response("YES") else render :text => @notification.generate_response("NO") end end #   def notify if (@notification.acknowledged? and @payment and @payment.amount == @notification.amount and @payment.amount > 0) #       #      #              #      saved = true if @payment.wait? @payment.do_payment_id = @notification.payment_id @payment.paid! saved = @payment.save Notifier.payment_paid(@payment).deliver if saved end if @payment.paid? and saved render :text => @notification.generate_response("YES") else render :text => @notification.generate_response("NO") end else render :text => @notification.generate_response("NO") end end #      def success end def fail end private def create_notification @notification = Dengionline.notification(request.raw_post, { :secret => CONFIG["dengionline"]["secret"] }) end def find_payment #  ,     nil,   @payment = Payment.where(:id => @notification.nick_extra).first end end 




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.



Source: https://habr.com/ru/post/196978/



All Articles