diff --git a/Gemfile b/Gemfile index fb61fd2baa..59882bed6e 100644 --- a/Gemfile +++ b/Gemfile @@ -92,6 +92,7 @@ gem 'airbrake' gem 'company_register', github: 'internetee/company_register', branch: :master gem 'e_invoice', github: 'internetee/e_invoice', branch: :master +gem 'lhv', github: 'internetee/lhv', tag: 'v0.1.0' group :development do # deploy diff --git a/Gemfile.lock b/Gemfile.lock index 094e337880..966cce0779 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,6 +41,15 @@ GIT hpricot libxml-ruby +GIT + remote: https://github.com/internetee/lhv.git + revision: e211516bc5fff2139584d4da41c17511863c229d + tag: v0.1.0 + specs: + lhv (0.1.0) + keystores + nokogiri + GIT remote: https://github.com/tarmotalu/digidoc_client.git revision: 1645e83a5a548addce383f75703b0275c5310c32 @@ -231,6 +240,7 @@ GEM kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) + keystores (0.4.0) libxml-ruby (3.0.0) loofah (2.2.3) crass (~> 1.0.2) @@ -479,6 +489,7 @@ DEPENDENCIES jquery-rails (= 4.0.4) jquery-ui-rails (= 5.0.5) kaminari (= 0.16.3) + lhv! mina (= 0.3.1) money-rails nokogiri diff --git a/config/application-example.yml b/config/application-example.yml index 0738a92784..de0e8f6819 100644 --- a/config/application-example.yml +++ b/config/application-example.yml @@ -152,6 +152,12 @@ action_mailer_default_port: # default: no port (80) action_mailer_default_from: # no-reply@example.com action_mailer_force_delete_from: # `From` header for `DomainDeleteMailer#forced` email +lhv_keystore: +lhv_keystore_password: +lhv_keystore_alias: +lhv_ca_file: # Needed only in dev mode +lhv_dev_mode: 'false' + # Since the keys for staging are absent from the repo, we need to supply them separate for testing. test: payments_seb_bank_certificate: 'test/fixtures/files/seb_bank_cert.pem' @@ -161,6 +167,9 @@ test: action_mailer_default_host: 'registry.test' action_mailer_default_from: 'no-reply@registry.test' action_mailer_force_delete_from: 'legal@registry.test' + lhv_keystore: 'test/fixtures/files/keystore.jks' + lhv_keystore_password: 'testtest' + lhv_keystore_alias: 'testtest' # Airbrake // Errbit: airbrake_host: "https://your-errbit-host.ee" diff --git a/config/schedule.rb b/config/schedule.rb index d47b45ea91..fe920dc6de 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -57,6 +57,13 @@ every 42.minutes do rake 'domain:discard' end + + # Should be at least once every 4 days, since according to LHV specs: + # "Unread messages older than 5 days are automatically scheduled for deletion" + # https://partners.lhv.ee/en/connect/#messaging + every :day, at: '12:01am' do + rake 'invoices:process_payments' + end end every 10.minutes do diff --git a/lib/tasks/dev/create_bank_transactions/bank_transactions.xml b/lib/tasks/dev/create_bank_transactions/bank_transactions.xml new file mode 100644 index 0000000000..72a53b697d --- /dev/null +++ b/lib/tasks/dev/create_bank_transactions/bank_transactions.xml @@ -0,0 +1,73 @@ + + + + + populated by rake task + 2019-07-28T10:00:00 + 1 + + 0.1 + + ABC Corporation + + + + test3 + TRF + false + 1 + 2019-07-28 + + test + + + + populated by rake task + + EUR + + + + LHVBEE22 + + + + + ABC/090928/CCT001/01 + ABC/4562/2009-09-08 + + + + 0.1 + + SHAR + + + LHVBEE22 + + + + DEF Electronics + + Corn Exchange 5th Floor + + + + + populated by rake task + + + + + 1 + + + + 13 + + + + + + + diff --git a/lib/tasks/dev/create_bank_transactions/create_bank_transactions.rake b/lib/tasks/dev/create_bank_transactions/create_bank_transactions.rake new file mode 100644 index 0000000000..33614d049f --- /dev/null +++ b/lib/tasks/dev/create_bank_transactions/create_bank_transactions.rake @@ -0,0 +1,41 @@ +namespace :dev do + task create_bank_transactions: :environment do + remitter_iban = ENV['remitter_iban'] + beneficiary_iban = Setting.registry_iban + + keystore_password = ENV['lhv_keystore_password'] + keystore_alias = ENV['lhv_keystore_alias'] + keystore = Keystores::JavaKeystore.new + keystore.load(ENV['lhv_keystore'], keystore_password) + cert = keystore.get_certificate(keystore_alias) + key = keystore.get_key(keystore_alias, keystore_password) + + api_base_uri = URI.parse('https://testconnect.lhv.eu/connect-prelive') + request_headers = { 'content-type' => 'application/xml' } + + request_xml = File.binread(File.join(__dir__, 'bank_transactions.xml')) + request_xml_doc = Nokogiri::XML(request_xml) + request_xml_doc.at_css('CstmrCdtTrfInitn > GrpHdr > MsgId').content = SecureRandom.hex + request_xml_doc.at_css('CstmrCdtTrfInitn > PmtInf > DbtrAcct > Id > IBAN') + .content = remitter_iban + request_xml_doc.at_css('CstmrCdtTrfInitn > PmtInf > CdtTrfTxInf > CdtrAcct > Id > IBAN') + .content = beneficiary_iban + request_body = request_xml_doc.to_xml + + http = Net::HTTP.new(api_base_uri.host, api_base_uri.port) + http.use_ssl = api_base_uri.is_a?(URI::HTTPS) + http.cert = cert + http.key = key + http.ca_file = ENV['lhv_ca_file'] + + http.start do + response = http.post(api_base_uri.path + '/payment', request_body, request_headers) + + if response.is_a?(Net::HTTPSuccess) + puts 'Success' + else + puts 'Failure' + end + end + end +end \ No newline at end of file diff --git a/lib/tasks/invoices/process_payments.rake b/lib/tasks/invoices/process_payments.rake new file mode 100644 index 0000000000..6e4c572139 --- /dev/null +++ b/lib/tasks/invoices/process_payments.rake @@ -0,0 +1,49 @@ +namespace :invoices do + task process_payments: :environment do + registry_bank_account_iban = Setting.registry_iban + + keystore_password = ENV['lhv_keystore_password'] + keystore_alias = ENV['lhv_keystore_alias'] + keystore = Keystores::JavaKeystore.new + keystore.load(ENV['lhv_keystore'], keystore_password) + cert = keystore.get_certificate(keystore_alias) + key = keystore.get_key(keystore_alias, keystore_password) + + api = Lhv::ConnectApi.new + api.cert = cert + api.key = key + api.ca_file = ENV['lhv_ca_file'] + api.dev_mode = ENV['lhv_dev_mode'] == 'true' + + incoming_transactions = [] + + api.credit_debit_notification_messages.each do |message| + next unless message.bank_account_iban == registry_bank_account_iban + + message.credit_transactions.each do |credit_transaction| + incoming_transactions << credit_transaction + end + end + + if incoming_transactions.any? + bank_statement = BankStatement.new(bank_code: Setting.registry_bank_code, + iban: Setting.registry_iban) + + ActiveRecord::Base.transaction do + bank_statement.save! + + incoming_transactions.each do |incoming_transaction| + transaction_attributes = { sum: incoming_transaction.amount, + currency: incoming_transaction.currency, + paid_at: incoming_transaction.date, + reference_no: incoming_transaction.payment_reference_number, + description: incoming_transaction.payment_description } + transaction = bank_statement.bank_transactions.create!(transaction_attributes) + transaction.autobind_invoice + end + end + end + + puts "Transactions processed: #{incoming_transactions.size}" + end +end \ No newline at end of file diff --git a/test/fixtures/files/keystore.jks b/test/fixtures/files/keystore.jks new file mode 100644 index 0000000000..7ce34f308d Binary files /dev/null and b/test/fixtures/files/keystore.jks differ diff --git a/test/tasks/invoices/process_payments_test.rb b/test/tasks/invoices/process_payments_test.rb new file mode 100644 index 0000000000..8c3b6ec73d --- /dev/null +++ b/test/tasks/invoices/process_payments_test.rb @@ -0,0 +1,78 @@ +require 'test_helper' + +class ProcessPaymentsTaskTest < ActiveSupport::TestCase + setup do + @payment_amount = payment_amount = 0.1 + @payment_currency = payment_currency = 'EUR' + @payment_date = payment_date = Date.parse('2010-07-05') + @payment_reference_number = payment_reference_number = '13' + @payment_description = payment_description = @invoice_number = '1234' + beneficiary_iban = 'GB33BUKB20201555555555' + + @invoice = create_payable_invoice(number: @invoice_number, + total: payment_amount, + currency: @payment_currency, + reference_no: @payment_reference_number) + Setting.registry_iban = beneficiary_iban + + Lhv::ConnectApi.class_eval do + define_method :credit_debit_notification_messages do + transaction = OpenStruct.new(amount: payment_amount, + currency: payment_currency, + date: payment_date, + payment_reference_number: payment_reference_number, + payment_description: payment_description) + message = OpenStruct.new(bank_account_iban: beneficiary_iban, + credit_transactions: [transaction]) + [message] + end + end + end + + def test_doubles_are_valid + assert Lhv::ConnectApi.method_defined?(:credit_debit_notification_messages) + assert Lhv::ConnectApi::Messages::CreditDebitNotification.method_defined?(:bank_account_iban) + assert Lhv::ConnectApi::Messages::CreditDebitNotification.method_defined?(:credit_transactions) + end + + def test_saves_transactions + assert_difference 'BankStatement.count' do + assert_difference 'BankTransaction.count' do + capture_io { run_task } + end + end + transaction = BankTransaction.last + assert_equal @payment_amount, transaction.sum + assert_equal @payment_currency, transaction.currency + assert_equal @payment_date, transaction.paid_at.to_date + assert_equal @payment_reference_number, transaction.reference_no + assert_equal @payment_description, transaction.description + end + + def test_marks_matched_invoice_as_paid + assert @invoice.unpaid? + + capture_io { run_task } + @invoice.reload + + assert @invoice.paid? + end + + def test_output + assert_output "Transactions processed: 1\n" do + run_task + end + end + + private + + def run_task + Rake::Task['invoices:process_payments'].execute + end + + def create_payable_invoice(attributes = {}) + invoice = invoices(:one) + invoice.update!({ account_activity: nil, cancelled_at: nil }.merge(attributes)) + invoice + end +end \ No newline at end of file