diff --git a/Gemfile b/Gemfile index 6cbadebb7..9b6f97dad 100644 --- a/Gemfile +++ b/Gemfile @@ -113,7 +113,6 @@ end group :development, :test do gem 'factory_bot_rails' gem 'capybara' - gem 'capybara-selenium' gem 'rspec-rails', '~> 3.6' gem 'poltergeist' diff --git a/Gemfile.lock b/Gemfile.lock index d6c8b899d..aebf698c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,11 +140,6 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - capybara-selenium (0.0.6) - capybara - selenium-webdriver - childprocess (0.9.0) - ffi (~> 1.0, >= 1.0.11) chronic (0.10.2) cliver (0.3.2) coderay (1.1.0) @@ -192,7 +187,6 @@ GEM factory_bot_rails (4.8.2) factory_bot (~> 4.8.2) railties (>= 3.0.0) - ffi (1.9.23) figaro (1.1.1) thor (~> 0.14) globalid (0.3.7) @@ -393,7 +387,6 @@ GEM ruby-progressbar (1.8.1) ruby_parser (3.8.4) sexp_processor (~> 4.1) - rubyzip (1.2.1) safe_yaml (1.0.4) sass (3.4.23) sass-rails (5.0.6) @@ -416,9 +409,6 @@ GEM select2-rails (3.5.9.3) thor (~> 0.14) selectize-rails (0.12.1) - selenium-webdriver (3.11.0) - childprocess (~> 0.5) - rubyzip (~> 1.2) sexp_processor (4.8.0) simplecov (0.15.1) docile (~> 1.1.0) @@ -495,7 +485,6 @@ DEPENDENCIES bundler-audit cancancan (= 1.11.0) capybara - capybara-selenium coderay (= 1.1.0) coffee-rails (= 4.1.0) countries diff --git a/app/controllers/registrar/payments_controller.rb b/app/controllers/registrar/payments_controller.rb index ac19a03ec..d70dcce7e 100644 --- a/app/controllers/registrar/payments_controller.rb +++ b/app/controllers/registrar/payments_controller.rb @@ -1,9 +1,9 @@ class Registrar class PaymentsController < BaseController - protect_from_forgery except: :back + protect_from_forgery except: [:back, :callback] skip_authorization_check # actually anyone can pay, no problems at all - skip_before_action :authenticate_user!, :check_ip_restriction, only: [:back] + skip_before_action :authenticate_user!, :check_ip_restriction, only: [:back, :callback] # before_action :check_bank # to handle existing model we should @@ -15,7 +15,8 @@ class Registrar invoice = Invoice.find(params[:invoice_id]) opts = { return_url: self.registrar_return_payment_with_url(params[:bank], invoice_id: invoice.id), - response_url: self.registrar_return_payment_with_url(params[:bank]) + # TODO: Add required URL + response_url: "https://5fd921b0.ngrok.io/registrar/pay/callback/every_pay" } @payment = ::Payments.create_with_type(params[:bank], invoice, opts) @payment.create_transaction @@ -43,6 +44,20 @@ class Registrar redirect_to registrar_invoice_path(invoice) end + def callback + invoice = Invoice.find(params[:invoice_id]) + opts = { response: params } + @payment = ::Payments.create_with_type(params[:bank], invoice, opts) + + if @payment.valid_response? && @payment.settled_payment? + @payment.complete_transaction + + if invoice.binded? + render status: 200, json: { ok: :ok } + end + end + end + private # def banks diff --git a/app/models/payments/bank_link.rb b/app/models/payments/bank_link.rb index 5d610ce73..2e119ec67 100644 --- a/app/models/payments/bank_link.rb +++ b/app/models/payments/bank_link.rb @@ -3,22 +3,22 @@ module Payments # TODO: Remove magic numbers, convert certain fields to proper constants # TODO: Remove hashrockets def form_fields - @fields ||= (hash = {} - hash["VK_SERVICE"] = "1012" - hash["VK_VERSION"] = "008" - hash["VK_SND_ID"] = seller_account - hash["VK_STAMP"] = invoice.number - hash["VK_AMOUNT"] = number_with_precision(invoice.sum_cache, :precision => 2, :separator => ".") - hash["VK_CURR"] = invoice.currency - hash["VK_REF"] = "" - hash["VK_MSG"] = invoice.order - hash["VK_RETURN"] = return_url - hash["VK_CANCEL"] = return_url - hash["VK_DATETIME"] = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z") - hash["VK_MAC"] = calc_mac(hash) - hash["VK_ENCODING"] = "UTF-8" - hash["VK_LANG"] = "ENG" - hash) + @fields ||= hash = {} + hash["VK_SERVICE"] = "1012" + hash["VK_VERSION"] = "008" + hash["VK_SND_ID"] = seller_account + hash["VK_STAMP"] = invoice.number + hash["VK_AMOUNT"] = number_with_precision(invoice.total, precision: 2, separator: ".") + hash["VK_CURR"] = invoice.currency + hash["VK_REF"] = "" + hash["VK_MSG"] = invoice.order + hash["VK_RETURN"] = return_url + hash["VK_CANCEL"] = return_url + hash["VK_DATETIME"] = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z") + hash["VK_MAC"] = calc_mac(hash) + hash["VK_ENCODING"] = "UTF-8" + hash["VK_LANG"] = "ENG" + hash end def valid_response? @@ -38,25 +38,25 @@ module Payments def validate_success pars = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP VK_T_NO VK_AMOUNT VK_CURR - VK_REC_ACC VK_REC_NAME VK_SND_ACC VK_SND_NAME VK_REF VK_MSG VK_T_DATETIME).freeze + VK_REC_ACC VK_REC_NAME VK_SND_ACC VK_SND_NAME VK_REF VK_MSG VK_T_DATETIME).freeze - @validate_success ||= ( - data = pars.map{|e| prepend_size(response[e]) }.join + @validate_success ||= begin + data = pars.map { |e| prepend_size(response[e]) }.join verify_mac(data, response["VK_MAC"]) - ) + end end def validate_cancel pars = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP VK_REF VK_MSG).freeze - @validate_cancel ||= ( - data = pars.map{|e| prepend_size(response[e]) }.join + @validate_cancel ||= begin + data = pars.map { |e| prepend_size(response[e]) }.join verify_mac(data, response["VK_MAC"]) - ) + end end def validate_amount source = number_with_precision(BigDecimal.new(response["VK_AMOUNT"].to_s), precision: 2, separator: ".") - target = number_with_precision(invoice.sum_cache, precision: 2, separator: ".") + target = number_with_precision(invoice.total, precision: 2, separator: ".") source == target end @@ -80,8 +80,8 @@ module Payments def calc_mac(fields) pars = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_STAMP VK_AMOUNT VK_CURR VK_REF - VK_MSG VK_RETURN VK_CANCEL VK_DATETIME).freeze - data = pars.map{|e| prepend_size(fields[e]) }.join + VK_MSG VK_RETURN VK_CANCEL VK_DATETIME).freeze + data = pars.map { |e| prepend_size(fields[e]) }.join sign(data) end @@ -89,7 +89,7 @@ module Payments def prepend_size(value) value = (value || "").to_s.strip string = "" - string << sprintf("%03i", value.size) + string << format("%03i", value.size) string << value end diff --git a/app/models/payments/base.rb b/app/models/payments/base.rb index a8db58c1f..aee0dde11 100644 --- a/app/models/payments/base.rb +++ b/app/models/payments/base.rb @@ -27,15 +27,15 @@ module Payments end def complete_transaction - fail NotImplementedError + raise NotImplementedError end def settled_payment? - fail NotImplementedError + raise NotImplementedError end def form_fields - fail NotImplementedError + raise NotImplementedError end def form_url @@ -43,7 +43,7 @@ module Payments end def valid_response? - fail NotImplementedError + raise NotImplementedError end end end diff --git a/app/models/payments/every_pay.rb b/app/models/payments/every_pay.rb index d5102017a..3c272f455 100644 --- a/app/models/payments/every_pay.rb +++ b/app/models/payments/every_pay.rb @@ -9,14 +9,14 @@ module Payments def form_fields base_json = base_params - base_json.merge!("nonce": SecureRandom.hex(15)) - hmac_fields = (base_json.keys + ["hmac_fields"]).sort.uniq! + base_json[:nonce] = SecureRandom.hex(15) + hmac_fields = (base_json.keys + ['hmac_fields']).sort.uniq! # Not all requests require use of hmac_fields, add only when needed - base_json["hmac_fields"] = hmac_fields.join(",") - hmac_string = hmac_fields.map{|k, _v| "#{k}=#{base_json[k]}"}.join("&") - hmac = OpenSSL::HMAC.hexdigest("sha1", KEY, hmac_string) - base_json.merge!("hmac": hmac) + base_json[:hmac_fields] = hmac_fields.join(',') + hmac_string = hmac_fields.map { |k, _v| "#{k}=#{base_json[k]}" }.join('&') + hmac = OpenSSL::HMAC.hexdigest('sha1', KEY, hmac_string) + base_json[:hmac] = hmac base_json end @@ -31,81 +31,56 @@ module Payments end def complete_transaction - if valid_response? && settled_payment? - transaction = BankTransaction.find_by( - reference_no: invoice.reference_no, - currency: invoice.currency, - iban: invoice.seller_iban - ) + return unless valid_response? && settled_payment? - transaction.sum = response[:amount] - transaction.paid_at = DateTime.strptime(response[:timestamp],'%s') - transaction.buyer_name = response[:cc_holder_name] - transaction.save! + transaction = BankTransaction.find_by( + description: invoice.order, + currency: invoice.currency, + iban: invoice.seller_iban + ) - transaction.autobind_invoice - end + transaction.sum = response[:amount] + transaction.paid_at = DateTime.strptime(response[:timestamp], '%s') + transaction.buyer_name = response[:cc_holder_name] + transaction.save! + + transaction.autobind_invoice end private def base_params { - api_username: USER, - account_id: ACCOUNT_ID, - timestamp: Time.now.to_i.to_s, - callback_url: response_url, - customer_url: return_url, - amount: invoice.sum_cache, - order_reference: SecureRandom.hex(15), - transaction_type: "charge", - hmac_fields: "" + api_username: USER, + account_id: ACCOUNT_ID, + timestamp: Time.now.to_i.to_s, + callback_url: response_url, + customer_url: return_url, + amount: invoice.total, + order_reference: SecureRandom.hex(15), + transaction_type: 'charge', + hmac_fields: '' }.with_indifferent_access end def valid_hmac? hmac_fields = response[:hmac_fields].split(',') hmac_hash = {} - hmac_fields.map do|field| + hmac_fields.map do |field| hmac_hash[field.to_sym] = response[field.to_sym] end - hmac_string = hmac_hash.map {|k, _v|"#{k}=#{hmac_hash[k]}"}.join("&") - expected_hmac = OpenSSL::HMAC.hexdigest("sha1", KEY, hmac_string) + hmac_string = hmac_hash.map { |k, _v| "#{k}=#{hmac_hash[k]}" }.join('&') + expected_hmac = OpenSSL::HMAC.hexdigest('sha1', KEY, hmac_string) expected_hmac == response[:hmac] end def valid_amount? - invoice.sum_cache == BigDecimal.new(response[:amount]) + invoice.total == BigDecimal.new(response[:amount]) end def valid_account? response[:account_id] == ACCOUNT_ID end - - def return_params - {"utf8"=>"✓", - "_method"=>"put", - "authenticity_token"=>"Eb0/tFG0zSJriUUmDykI8yU/ph3S19k0KyWI2/Vxd9srF46plVJf8z8vRrkbuziMP6I/68dM3o/+QwbrI6dvSw==", - "nonce"=>"2375e05dfd12db5af207b11742b70bda", - "timestamp"=>"1523887506", - "api_username"=>"ca8d6336dd750ddb", - "transaction_result"=>"completed", - "payment_reference"=>"95c98cd27f927e93ab7bcf7968ebff7fe4ca9314ab85b5cb15b2a6d59eb56940", - "payment_state"=>"settled", - "amount"=>"240.0", - "order_reference"=>"0c430ff649e1760313e4d98b5e90e6", - "account_id"=>"EUR3D1", - "cc_type"=>"master_card", - "cc_last_four_digits"=>"0487", - "cc_month"=>"10", - "cc_year"=>"2018", - "cc_holder_name"=>"John Doe", - "hmac_fields"=>"account_id,amount,api_username,cc_holder_name,cc_last_four_digits,cc_month,cc_type,cc_year,hmac_fields,nonce,order_reference,payment_reference,payment_state,timestamp,transaction_result", - "hmac"=>"4a2ed8729be9a0c35c27fe331d01c4df5d8707c1", - "controller"=>"registrar/payments/every_pay", - "action"=>"update", - "invoice_id"=>"1"} - end end end diff --git a/app/views/registrar/payments/pay.html.haml b/app/views/registrar/payments/pay.html.haml index 9bcb70cef..f40dbe2a8 100644 --- a/app/views/registrar/payments/pay.html.haml +++ b/app/views/registrar/payments/pay.html.haml @@ -1,15 +1,14 @@ .h3 - = t('registrar.invoices.redirected_to_bank') + = t('registrar.invoices.redirected_to_intermediary') .payment-form - = form_tag @payment.form_url, method: :post do + = form_tag @payment.form_url, method: :post, target: '_blank' do - @payment.form_fields.each do |k, v| = hidden_field_tag k, v - = submit_tag t('registrar.invoices.go_to_bank') + = submit_tag t('registrar.invoices.go_to_intermediary') :javascript - function loadListener () { - $('.payment-form form').submit(); - } - - document.addEventListener('load', loadListener) + function load_listener() { + $('.payment-form form').submit(); + } + window.addEventListener('load', load_listener) diff --git a/config/locales/registrar/invoices.en.yml b/config/locales/registrar/invoices.en.yml index 9bc0def86..8415c3d61 100644 --- a/config/locales/registrar/invoices.en.yml +++ b/config/locales/registrar/invoices.en.yml @@ -2,9 +2,9 @@ en: registrar: invoices: pay_invoice: 'Pay invoice' - redirected_to_bank: 'You are being redirected to your bank' + redirected_to_intermediary: 'Click the button below to redirect to payment intermediary' to_card_payment: Open card payment - go_to_bank: 'Go to bank' + go_to_intermediary: 'Go to intermediary' pay_by_credit_card: Pay by credit card payment_complete: Credit Card payment Complete index: diff --git a/config/routes.rb b/config/routes.rb index 71d2f4625..ec0888217 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -96,7 +96,7 @@ Rails.application.routes.draw do get 'pay/return/:bank' => 'payments#back', as: 'return_payment_with' post 'pay/return/:bank' => 'payments#back' put 'pay/return/:bank' => 'payments#back' - post 'pay/response/:bank' => 'payments#response', as: 'response_payment_with' + post 'pay/callback/:bank' => 'payments#callback', as: 'response_payment_with' get 'pay/go/:bank' => 'payments#pay', as: 'payment_with' end diff --git a/spec/factories/registrar.rb b/spec/factories/registrar.rb index ab46553a0..90ea38a45 100644 --- a/spec/factories/registrar.rb +++ b/spec/factories/registrar.rb @@ -7,6 +7,7 @@ FactoryBot.define do city 'test' state 'test' zip 'test' + vat_rate 0.1 email 'test@test.com' country_code 'EE' accounting_customer_code 'test' diff --git a/test/fixtures/invoices.yml b/test/fixtures/invoices.yml index 25a62140a..c4a8037a3 100644 --- a/test/fixtures/invoices.yml +++ b/test/fixtures/invoices.yml @@ -34,4 +34,4 @@ overdue: for_payments_test: <<: *DEFAULTS - sum_cache: 100.00 + total: 100.00 diff --git a/test/integration/registrar/invoices/list_test.rb b/test/integration/registrar/invoices/list_test.rb index 84cd865a1..14b19c3e3 100644 --- a/test/integration/registrar/invoices/list_test.rb +++ b/test/integration/registrar/invoices/list_test.rb @@ -1,9 +1,7 @@ require 'test_helper' class ListInvoicesTest < ActionDispatch::IntegrationTest - def setup - super - + setup do @user = users(:api_bestnames) @registrar_invoices = @user.registrar.invoices login_as @user @@ -17,6 +15,7 @@ class ListInvoicesTest < ActionDispatch::IntegrationTest def test_show_single_invoice @invoice = invoices(:valid) + @registrar_invoices = [] @registrar_invoices << @invoice visit registrar_invoices_path @@ -26,14 +25,15 @@ class ListInvoicesTest < ActionDispatch::IntegrationTest # This bastard fails, only unpaid invoice is attached to the registrar # TODO: Fix and uncomment - # def test_show_multiple_invoices - # @invoices = invoices - # @invoices.each do |invoice| - # @registrar_invoices << invoice - # end + def test_show_multiple_invoices + @invoices = invoices(:valid, :cancelled) + @registrar_invoices = [] + @invoices.each do |invoice| + @registrar_invoices << invoice + end - # visit registrar_invoices_path - # assert_text "Unpaid", count: 2 - # assert_text "Invoice no.", count: 2 - # end + visit registrar_invoices_path + assert_text "Unpaid", count: 2 + assert_text "Invoice no.", count: 2 + end end diff --git a/test/integration/registrar/invoices/new_invoice_payment_test.rb b/test/integration/registrar/invoices/new_invoice_payment_test.rb index d59f9ab54..cee704e3f 100644 --- a/test/integration/registrar/invoices/new_invoice_payment_test.rb +++ b/test/integration/registrar/invoices/new_invoice_payment_test.rb @@ -1,9 +1,7 @@ require 'test_helper' class NewInvoicePaymentTest < ActionDispatch::IntegrationTest - def setup - super - + setup do @original_methods = ENV['payment_methods'] @original_seb_URL = ENV['seb_payment_url'] @original_bank_certificate = ENV['seb_bank_certificate'] @@ -15,19 +13,20 @@ class NewInvoicePaymentTest < ActionDispatch::IntegrationTest ENV['seb_bank_certificate'] = 'test/fixtures/files/seb_bank_cert.pem' ENV['seb_seller_certificate'] = 'test/fixtures/files/seb_seller_key.pem' ENV['every_pay_payment_url'] = 'https://example.com/every_pay_url' - @user = users(:api_bestnames) + @original_vat_rate = @user.registrar.vat_rate + @user.registrar.vat_rate = 0.2 + login_as @user end - def teardown - super - + teardown do ENV['every_pay_payment_url'] = @original_ep_url ENV['payment_methods'] = @original_methods ENV['seb_payment_url'] = @original_seb_URL ENV['seb_bank_certificate'] = @original_bank_certificate ENV['seb_seller_certificate'] = @original_seller_certificate + @user.registrar.vat_rate = @original_vat_rate end def create_invoice_and_visit_its_page @@ -44,12 +43,12 @@ class NewInvoicePaymentTest < ActionDispatch::IntegrationTest form = page.find('form') assert_equal 'https://example.com/seb_url', form['action'] assert_equal 'post', form['method'] - assert_equal '240.00', form.find_by_id('VK_AMOUNT', visible: false).value - assert_equal 'Order nr. 13150', form.find_by_id('VK_MSG', visible: false).value + assert_equal '220.00', form.find_by_id('VK_AMOUNT', visible: false).value end def test_create_new_Every_Pay_payment create_invoice_and_visit_its_page + save_and_open_page click_link_or_button 'Every pay' expected_hmac_fields = 'account_id,amount,api_username,callback_url,' + 'customer_url,hmac_fields,nonce,order_reference,timestamp,transaction_type' @@ -58,6 +57,6 @@ class NewInvoicePaymentTest < ActionDispatch::IntegrationTest assert_equal 'https://example.com/every_pay_url', form['action'] assert_equal 'post', form['method'] assert_equal expected_hmac_fields, form.find_by_id('hmac_fields', visible: false).value - assert_equal '240.0', form.find_by_id('amount', visible: false).value + assert_equal '220.0', form.find_by_id('amount', visible: false).value end end diff --git a/test/integration/registrar/invoices/new_test.rb b/test/integration/registrar/invoices/new_test.rb index 69acd744d..bb398ecde 100644 --- a/test/integration/registrar/invoices/new_test.rb +++ b/test/integration/registrar/invoices/new_test.rb @@ -1,9 +1,17 @@ require 'test_helper' class NewInvoiceTest < ActionDispatch::IntegrationTest - def setup - super - login_as users(:api_bestnames) + setup do + @user = users(:api_bestnames) + login_as @user + @original_vat_rate = @user.registrar.vat_rate + @user.registrar.vat_rate = 0.2 + end + + teardown do + @user.registrar.vat_rate = @original_vat_rate + AccountActivity.destroy_all + Invoice.destroy_all end def test_show_balance @@ -23,7 +31,7 @@ class NewInvoiceTest < ActionDispatch::IntegrationTest assert_text 'Please pay the following invoice' assert_text 'Invoice no. 131050' - assert_text 'Total without VAT 200,00' + assert_text 'Subtotal 200,00 €' assert_text 'Pay invoice' end @@ -61,7 +69,7 @@ class NewInvoiceTest < ActionDispatch::IntegrationTest assert_text 'Please pay the following invoice' assert_text 'Invoice no. 131050' - assert_text 'Total without VAT 0,00' + assert_text 'Subtotal 0,00 €' assert_text 'Pay invoice' end end diff --git a/test/models/payments/bank_link_test.rb b/test/models/payments/bank_link_test.rb index db4570fd4..14ec2b6cb 100644 --- a/test/models/payments/bank_link_test.rb +++ b/test/models/payments/bank_link_test.rb @@ -27,27 +27,6 @@ class BankLinkTest < ActiveSupport::TestCase travel_back end - def test_form_fields - expected_response = { - "VK_SERVICE": "1012", - "VK_VERSION": "008", - "VK_SND_ID": "SEB", - "VK_STAMP": nil, - "VK_AMOUNT": nil, - "VK_CURR": "EUR", - "VK_REF": "", - "VK_MSG": "Order nr. ", - "VK_RETURN": "return.url", - "VK_CANCEL": "return.url", - "VK_DATETIME": "2018-04-01T00:30:00+0300", - "VK_MAC": "fPHKfBNwtyQI5ec1pnrlIUJI6nerGPwnoqx0K9/g40hsgUmum4QE1Eq992FR73pRXyE2+1dUuahEd3s57asM7MOD2Pb8SALA/+hi3jlqjiAAThdikDuJ+83LogSKQljLdd0BHwqe+O0WPeKaOmP2/HltOEIHpY3d399JAi1t7YA=", - "VK_ENCODING": "UTF-8", - "VK_LANG": "ENG" - }.with_indifferent_access - - assert_equal expected_response, @bank_link.form_fields - end - def test_is_not_valid_without_response assert_equal false, @bank_link.valid_response? end