ActiveMerchantにMoney Onlineを統合する

Ruby on Railsで開発しているアプリケーションでは、支払いシステムを接続する必要がありました。 顧客はMoney Onlineと契約を結びました。もちろん、私が最初にしたことは、Shopify ActiveMerchantでサポートされているシステムのリストを確認することでした -このサービスは存在しませんでしたが、統合を簡素化できる既製のソリューションも探しましたが、RoRでは有用なものはありませんでした その結果、ActiveMerchantを分岐して、このサービスの統合を開発し、後でプロジェクトでその経験を活用することが決定されました。



この記事では、誰かに役立つことを期待して、支払いシステムを接続するプロセスについてお話したいと思います。そのような情報がなかったからです。 また、情報が誰か、特にこの支払いシステムで役立つ可能性もあります。



それ以前は、ActiveMerchantに既に精通していました:ロボカッサを統合したプロジェクトの1つで- この記事は大いに役立ち、問題は発生しませんでしたが、モジュール実装コードを確認または理解するために数回だけ調べる必要がありました特定の方法の作業-それだけです。 また、モジュールを統合するとき、ActiveMerchantのかなりの部分を学ぶ必要がありました。



ActiveMerchant構造


このようなサービスの統合を支援するために、プロジェクトには3つの基本クラスがあります。





支払いシステムの各実装には、ベースから継承されたいくつかのクラスが含まれます。一部のモジュールには、プロトコルの追加機能を実装する追加クラスがあります。 また、ActiveMerchantの各支払いシステムには、ファイルからクラスがロードされるモジュールがあり、このモジュールに含まれる特定のクラスを作成するためのメソッドが含まれています。



ActiveMerchantにMoney Onlineを統合する


この問題の解決 Money Online プロトコルの調査から始まり、ロボカッサの場合のようにすべてがそこに透明ではないことが判明しました:情報のバックグラウンド検証、確認、コールバック、実際、すべては単純ですが、リクエスト中に送信されるパラメーターと多くの異なるパラメーターにかなり混乱しました予約。



画像

一般的なクエリスキーム



要するに、一般的な場合、肯定的なシナリオでは、すべてが次のように行われます:サイトはシステムにすべての必要なパラメーターを含むフォームを送信し、ユーザーが支払いシステムのウェブサイトにアクセスすると、バックグラウンドの支払いシステムはウェブサイトからのデータの正確性の確認を求めます。 データが正しい場合、ユーザーは支払いを行うために必要な指示が表示されている場所にリダイレクトされます。 「Pay」ボタンをクリックした後、支払いシステムは、支払いを受け入れる前にデータの正確性を再度チェックし、支払いが受け入れられ、システムは支払い確認のリクエストをサイトに送信し、答えがはいの場合、ユーザーはコールバックにリダイレクトされます。



私を混乱させたプロトコルの主な機能:





これらの機能のため、私はそれらをサポートするために数回書く必要がありましたが(最初はサイトの音声モジュールを介して連絡しましたが、女の子は私の問題を聞いた後、留守番電話にリダイレクトしました)、その日のうちに適切な回答を受け取りました。



プラスの点として、説明したプロトコルに正確に従ってリクエストを行うと、期待どおりの結果が得られることがわかりましたが、場合によってはデータはチェックされませんが、明らかにゲートウェイの機能が原因です。



また、場合によってはバックグラウンドで請求できることも気に入りました。 そして、請求プロトコルのサポートを可能な限り完全にすることにしましたが、すべてを完全にテストする機会がなかったため、いくつかの問題が発生する可能性があります。



また、文書化されていない1つの機会を特定することができました:QIWIを介して支払い用のアカウントを作成するバックグラウンドリクエストに応じて、説明されたパラメーター、理解できないコンテンツを含むiframeSRCパラメーター、2回考えることなく、このパラメーターからのデータをbase64でデコードし、フォームにつながるリンクを受け取りましたQIWIを介した支払い。リンクを使用してiframeを作成する必要があると思われます。



ActiveMerchant での支払いシステム統合のさまざまな実装見ると、同じコードが多くのモジュールにどのように流れることができるか、いくつかのファイルではコードが複数のモジュールのコードの組み合わせなどであることがわかりますが、複雑なものはありませんスクリプト。 特にバックグラウンドクエリの実装のために、私は多くのことを書かなければならなかったので、リクエストプールが承認されないのではないかと思います。



最後に起こったことはここで見ることができます 。 ヘルパーの一般的な概念のフレームワークを超えている唯一のものはデータ検証ですが、一般的には絶対に必要ではなく、バックグラウンドリクエストでのみ使用して、リクエストの前でもエラーが発生しないようにする必要があります(これにもかかわらず、サイトに支払いシステムを実装するとき、私は汚いハックでこれを回避する必要がありました)、開発者が長い間プロトコルに対処する必要がないように、いくつかの要求パラメーターも特別な場合に自動的に設定されます。 私のヘルパーには、バックグラウンド要求を送信する機会もあります。これが有効なmode_typeに対して行われた場合、回答は自動的に解析され、この回答を使用すると便利です。



私は約3か月ルビーに精通しているため、コードが良かったとは言えません。どのような支援やアドバイスも喜んで受けます。また、支払いシステムをActiveMerchantに統合したい人を支援する準備ができています。



サイトで支払いシステムを作成する例


最初のステップは、プロジェクトにActiveMerchantを含めることです。そのため、Gemfileに追加します

gem 'activemerchant', :git => "https://github.com/ovcharik/active_merchant.git", :branch => "dengionline"
      
      





これまでのところ、これを追加できますが、 リクエストプールを公​​式リポジトリに送信しました。おそらくすぐに承認されるでしょう。



次に、必要なルーターを作成します

 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
      
      







最後の4つの住所は、登録時に支払いシステムに示されます。その過程で、これらの住所にポイントがあるアンケートに記入する必要があります。 サイト自体はアドレスを変更する機能を提供しておらず、これは追加のサポートリクエストによってのみ行われるため、本番環境で直接多くのテストを行う必要がありました(プロジェクトが開始されたにもかかわらず、まだ開発中であり、これはユーザーに影響しませんでした)。



私がやっているプロジェクトでは、このシナリオはサービスの支払い時に発生します:ユーザーは、「Pay」ボタンをクリックして、/ top_up / createにajaxリクエストを送信し、そこでデータをチェックしてから、バックグラウンドリクエストが支払いシステムに送信され、成功した応答がユーザーに表示され、エラーが個別に処理されます。 私の場合、成功した応答として、非表示フィールドを持つフォームが入り、その直後にこのフォームを送信するスクリプトがあります。つまり、この応答がページのどこかに表示されると、ユーザーは暗黙的にゲートウェイページにリダイレクトされます支払い。



バックグラウンドリクエスト中に、次のことが起こります:バックグラウンドリクエストを行い、システムはすべてのデータをチェックし、それが正しい場合、システムが必要なデータを返すことを確認した後、/ top_up / checkメソッドを呼び出します。



ゲートウェイのWebサイトで、ユーザーは詳細を指定する必要があるフォームを確認し、「Pay」ボタンをクリックした後、ゲートウェイはMoney Onlineシステムのデータをチェックし、プロジェクトのデータをチェックし(再び/ top_up / checkで)、応答を受信し、支払いゲートウェイに渡します。 彼は支払いを確認し、それについて支払いシステムに通知し、彼女はすでにサイトに通知を送信し(/ top_up / notify)、同時にユーザーはコールバックに戻ります。



ログを見るだけでこのプロセスを理解するのは簡単かもしれません
 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)
      
      







ログでは、すべての場合に送信されるパラメーターを確認できます。また、/ top_up / checkにアクセスすると、支払いシステムはorderid(プロジェクトの内部支払い識別子、paymentidは支払いシステムの識別子)を送信しないため、支払いを識別するためにuserid_extraパラメーターを使用することが決定されました、ただし、ここでは注意が必要です。返されない場合もあるため、ユーザーIDで内部支払い番号を指定できますが、支払いシステムのアカウントでは、すべてのトランザクションを確認できるほか、 このパラメーター、およびこのパラメーターとして一意の識別子を使用する場合、サンプリングオプションのユーティリティはゼロになります。



コントローラー
 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
      
      







応答の性質上、すべてのデータをバックグラウンドで送信していますが、 ここで説明する方法を使用することもできます



また、クラスを使用してリモート支払い情報を受信する例を示します。これを使用しても問題は発生しないと思います。



 $ 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" }
      
      







この例では、内部支払い識別子がメインパラメーターとして渡されます。課金時に使用しなかった場合は、支払いシステムの支払い識別子を転送できます。このため、最後のパラメーターであるpayment => payment_idを指定します。






All Articles