From 0a4444b5561a464eee39d7028fe88b72830d4ac5 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Wed, 11 Jan 2023 12:25:49 +0200 Subject: [PATCH 1/9] creating sync with billing --- .../eis_billing/invoices_controller.rb | 71 ++++ .../eis_billing/payment_status_controller.rb | 46 +-- app/models/concerns/invoice/cancellable.rb | 18 ++ app/models/concerns/invoice/payable.rb | 30 ++ config/routes.rb | 1 + db/structure.sql | 3 + test/integration/eis_billing/invoices_test.rb | 306 ++++++++++++++++++ .../eis_billing/payment_status_test.rb | 57 +++- test/models/invoice_test.rb | 105 +++++- 9 files changed, 577 insertions(+), 60 deletions(-) create mode 100644 app/controllers/eis_billing/invoices_controller.rb create mode 100644 test/integration/eis_billing/invoices_test.rb diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb new file mode 100644 index 000000000..0b4ce1281 --- /dev/null +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -0,0 +1,71 @@ +module EisBilling + class InvoicesController < BaseController + TYPE = 'PaymentOrders::EveryPay'.freeze + + before_action :load_invoice, only: :update + + def update + if @invoice.update(modified_params) + payment_orders_handler + + render json: { + message: 'Invoice data was successfully updated' + }, status: :ok + else + render json: { + error: @message.errors.full_messages + }, status: :unprocessable_entity + end + end + + private + + def payment_orders_handler + if @invoice.payment_orders.present? + return if (@invoice.paid? && status == 'paid') || (@invoice.cancelled? && status == 'cancelled') + + if status == 'cancelled' || status == 'failed' + @invoice.cancel_manualy + elsif status == 'paid' + @invoice.autobind_manually + end + else + return unless status == 'paid' + + @invoice.autobind_manually + end + end + + def status + case params[:invoice][:status] + when 'paid' + 'paid' + when 'cancelled' + 'cancelled' + when 'failed' + 'failed' + else + 'issued' + end + end + + def load_invoice + @invoice = Invoice.find_by(number: params[:invoice][:invoice_number]) + + if @invoice.nil? + render json: { + error: { + message: "Invoice with #{params[:invoice][:invoice_number]} number not found" + } + }, status: :not_found and return + end + end + + def modified_params + { + in_directo: params[:invoice][:in_directo], + e_invoice_sent_at: params[:invoice][:sent_at_omniva] + } + end + end +end diff --git a/app/controllers/eis_billing/payment_status_controller.rb b/app/controllers/eis_billing/payment_status_controller.rb index 015eed64a..383d47a1d 100644 --- a/app/controllers/eis_billing/payment_status_controller.rb +++ b/app/controllers/eis_billing/payment_status_controller.rb @@ -3,19 +3,20 @@ module EisBilling TYPE = 'PaymentOrders::EveryPay'.freeze def update - payment_status = define_payment_status(params[:payment_state]) invoice = Invoice.find_by(number: params[:order_reference]) - return if invoice.paid? + if invoice.paid? + render json: { message: 'Invoice already paid' }, status: :ok + else + invoice.process_payment( + payment_type: TYPE, + everypay_response: params, + payment_status: define_payment_status(params[:payment_state]), + sum: params[:standing_amount], + transaction_time: params[:transaction_time] + ) - bank = create_bank_transfer(invoice: invoice, sum: params[:standing_amount], paid_at: params[:transaction_time]) - create_payment_order(invoice: invoice, everypay_response: params, payment_status: payment_status) - bank.bind_invoice(params[:order_reference]) - - respond_to do |format| - format.json do - render status: :ok, content_type: 'application/json', layout: false, json: { message: 'ok' } - end + render json: { message: 'Payment is proccessing' }, status: :ok end end @@ -26,30 +27,5 @@ module EisBilling :failed end - - def create_payment_order(invoice:, everypay_response:, payment_status:) - payment = PaymentOrder.new - payment.type = TYPE - payment.invoice = invoice - payment.response = everypay_response - payment.status = payment_status - payment.save - - payment - end - - def create_bank_transfer(invoice:, sum:, paid_at:) - bank = BankTransaction.new - bank.description = invoice.order - bank.reference_no = invoice.reference_no - bank.currency = invoice.currency - bank.iban = invoice.seller_iban - bank.sum = sum - bank.paid_at = paid_at - bank.buyer_name = invoice.buyer_name - bank.save - - bank - end end end diff --git a/app/models/concerns/invoice/cancellable.rb b/app/models/concerns/invoice/cancellable.rb index 9b1c6435b..29c09f47c 100644 --- a/app/models/concerns/invoice/cancellable.rb +++ b/app/models/concerns/invoice/cancellable.rb @@ -31,4 +31,22 @@ module Invoice::Cancellable def not_cancelled? !cancelled? end + + def cancel_manualy + account_activity = AccountActivity.find_by(invoice_id: id) + account_activity_dup = account_activity.dup + account_activity_dup.sum = -account_activity.sum.to_i + account_activity_dup.save + account_activity.update(invoice_id: nil) + account_activity_dup.update(invoice_id: nil) + mark_cancelled_payment_order + account_activity.save && account_activity_dup.save + end + + private + + def mark_cancelled_payment_order + payment_order = payment_orders.last + payment_order.update(notes: 'Cancelled') + end end diff --git a/app/models/concerns/invoice/payable.rb b/app/models/concerns/invoice/payable.rb index 855ea8f41..79e02c92d 100644 --- a/app/models/concerns/invoice/payable.rb +++ b/app/models/concerns/invoice/payable.rb @@ -23,4 +23,34 @@ module Invoice::Payable def unpaid? !paid? end + + def process_payment(**options) + payment = options[:payment_type].constantize.new(invoice: self) + payment.response = options[:everypay_response] + payment.status = options[:payment_status] + payment.save! + + bank_transaction = payment.base_transaction(sum: options[:sum], + paid_at: options[:transaction_time] || Time.zone.now, + buyer_name: buyer_name) + bank_transaction.bind_invoice(number) + end + + def autobind_manually + return if paid? + + bank_statement = BankStatement.new( + bank_code: Setting.registry_bank_code, + iban: Setting.registry_iban + ) + bank_statement.bank_transactions.build( + description: description, + sum: total, + reference_no: reference_no, + paid_at: Time.zone.now.to_date, + currency: 'EUR' + ) + bank_statement.save! + bank_statement.bind_invoices(manual: true) + end end diff --git a/config/routes.rb b/config/routes.rb index d8d52f322..581a572f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,6 +16,7 @@ Rails.application.routes.draw do put '/directo_response', to: 'directo_response#update', as: 'directo_response' put '/e_invoice_response', to: 'e_invoice_response#update', as: 'e_invoice_response' post '/lhv_connect_transactions', to: 'lhv_connect_transactions#create', as: 'lhv_connect_transactions' + resource :invoices, only: [:update] end namespace :epp do diff --git a/db/structure.sql b/db/structure.sql index 6286053ce..b10129d30 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4447,6 +4447,7 @@ CREATE INDEX index_log_domains_on_object ON public.log_domains USING gin (object -- +<<<<<<< HEAD -- Name: index_log_domains_on_object_changes; Type: INDEX; Schema: public; Owner: - -- @@ -4454,6 +4455,8 @@ CREATE INDEX index_log_domains_on_object_changes ON public.log_domains USING gin -- +======= +>>>>>>> creating sync with billing -- Name: index_log_domains_on_whodunnit; Type: INDEX; Schema: public; Owner: - -- diff --git a/test/integration/eis_billing/invoices_test.rb b/test/integration/eis_billing/invoices_test.rb new file mode 100644 index 000000000..2edbc533d --- /dev/null +++ b/test/integration/eis_billing/invoices_test.rb @@ -0,0 +1,306 @@ +require 'test_helper' + +class StubAuthorization < ApplicationController + skip_authorization_check + + def authorized + true + end +end + +EisBilling::BaseController = StubAuthorization + +class EInvoiceResponseTest < ApplicationIntegrationTest + setup do + sign_in users(:api_bestnames) + @invoice = invoices(:one) + + response_message = { + message: 'got it' + } + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_status') + .to_return(status: 200, body: response_message.to_json, headers: {}) + end + + test 'it should update status of invoice if payment order is existed' do + @invoice.update(total: 120.0) + @invoice.reload + + incoming_params = { + invoice: { + invoice_number: @invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 120.0, + status: 'paid', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + @invoice.account_activity.delete && @invoice.reload + + assert_equal @invoice.payment_orders.pluck(:status), %w[issued issued] + put eis_billing_invoices_path, params: incoming_params + @invoice.reload + @invoice.payment_orders.each(&:reload) + + invoice = Invoice.find(@invoice.id) + assert_equal invoice.payment_orders.pluck(:status), %w[issued issued paid] + end + + test 'it should update invoice data as directo and omniva' do + incoming_params = { + invoice: { + invoice_number: @invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 270.0, + status: 'unpaid', + in_directo: true, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + assert_equal @invoice.payment_orders.pluck(:status), %w[issued issued] + assert_nil @invoice.e_invoice_sent_at + refute @invoice.in_directo + + put eis_billing_invoices_path, params: incoming_params + + @invoice.payment_orders.each(&:reload) + @invoice.reload + + assert_equal @invoice.payment_orders.pluck(:status), %w[issued issued] + assert @invoice.in_directo + assert_not_nil @invoice.e_invoice_sent_at + end + + test 'it should create new payment order if payment order and activity are missing, but status has paid status' do + invoice = invoices(:one) + + invoice.payment_orders.destroy_all and invoice.account_activity.destroy + invoice.update(total: 120.0) + invoice.reload + + incoming_params = { + invoice: { + invoice_number: invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 120.0, + status: 'paid', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + assert invoice.payment_orders.empty? + assert_nil invoice.account_activity + + put eis_billing_invoices_path, params: incoming_params + + invoice.reload + invoice.payment_orders.each(&:reload) + + assert_equal invoice.payment_orders.count, 1 + assert invoice.payment_orders.first.paid? + assert invoice.account_activity + end + + test 'it should ignore payment order creation if payment status is not paid and payment order not existed' do + incoming_params = { + invoice: { + invoice_number: @invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 270.0, + status: 'cancelled', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + @invoice.payment_orders.destroy_all and @invoice.account_activity.destroy + @invoice.reload + + assert @invoice.payment_orders.empty? + assert_nil @invoice.account_activity + + put eis_billing_invoices_path, params: incoming_params and @invoice.reload + + assert @invoice.payment_orders.empty? + assert_nil @invoice.account_activity + end + + test 'it should add balance if payment order mark as paid' do + invoice = invoices(:one) + item = invoice.items.first + + invoice.payment_orders.destroy_all and invoice.account_activity.destroy + invoice.update(total: 120.0) && invoice.reload + item.update(price: 100.0) && item.reload + + incoming_params = { + invoice: { + invoice_number: invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 120.0, + status: 'paid', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + assert invoice.payment_orders.empty? + assert_nil invoice.account_activity + + account = invoice.buyer.accounts.first + + assert_equal account.balance.to_f, 100.0 + + put eis_billing_invoices_path, params: incoming_params + + invoice.reload + invoice.payment_orders.each(&:reload) + account.reload + + assert_equal invoice.payment_orders.count, 1 + assert invoice.payment_orders.first.paid? + assert invoice.account_activity + + assert_equal account.balance.to_f, 200.0 + end + + test 'should change nothing if invoice is already paid' do + assert @invoice.account_activity.present? + assert @invoice.payment_orders.present? + + account = @invoice.buyer.accounts.first + assert_equal account.balance.to_f, 100.0 + assert @invoice.paid? + + incoming_params = { + invoice: { + invoice_number: @invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: @invoice.total, + status: 'paid', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + put eis_billing_invoices_path, params: incoming_params + account.reload + + assert_equal account.balance.to_f, 100.0 + end + + test 'it should decrease balance and again add if user change paid invoice to cancel and then again to paid' do + invoice = invoices(:one) + item = invoice.items.first + + invoice.payment_orders.destroy_all and invoice.account_activity.destroy + invoice.update(total: 120.0) && invoice.reload + item.update(price: 100.0) && item.reload + + add_balance_params = { + invoice: { + invoice_number: invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 120.0, + status: 'paid', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + assert invoice.payment_orders.empty? + assert_nil invoice.account_activity + + account = invoice.buyer.accounts.first + + assert_equal account.balance.to_f, 100.0 + + put eis_billing_invoices_path, params: add_balance_params + + invoice.reload + invoice.payment_orders.each(&:reload) + account.reload + + assert_equal invoice.payment_orders.count, 1 + assert invoice.payment_orders.first.paid? + assert invoice.account_activity + + assert_equal account.balance.to_f, 200.0 + + decrease_balance_params = { + invoice: { + invoice_number: invoice.number, + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 120.0, + status: 'cancelled', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + put eis_billing_invoices_path, params: decrease_balance_params + invoice.reload + invoice.payment_orders.each(&:reload) + account.reload + + assert_equal account.balance.to_f, 100.0 + end + + test 'it should return an error if invoice not existing' do + incoming_params = { + invoice: { + invoice_number: 'nonexisted-invoice', + initiator: 'registry', + payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', + transaction_amount: 120.0, + status: 'paid', + in_directo: false, + everypay_response: { + 'some' => 'some' + }, + sent_at_omniva: Time.zone.now - 10.minutes + } + } + + put eis_billing_invoices_path, params: incoming_params + registry_response = JSON.parse(response.body).with_indifferent_access[:error] + + assert_equal registry_response[:message], 'Invoice with nonexisted-invoice number not found' + end +end diff --git a/test/integration/eis_billing/payment_status_test.rb b/test/integration/eis_billing/payment_status_test.rb index 69c50e120..f13a1d0a1 100644 --- a/test/integration/eis_billing/payment_status_test.rb +++ b/test/integration/eis_billing/payment_status_test.rb @@ -9,10 +9,7 @@ class PaymentStatusTest < ApplicationIntegrationTest Spy.on_instance_method(EisBilling::BaseController, :authorized).and_return(true) end - def shoudl_update_buyer_balance - assert @invoice.paid? - assert_equal @invoice.buyer.balance.to_f, 100.0 - + test 'should mark an invoice as paid' do payload = { payment_state: 'settled', order_reference: @unpaid.number, @@ -20,13 +17,57 @@ class PaymentStatusTest < ApplicationIntegrationTest transaction_time: Time.zone.now, } - put eis_billing_payment_status_path, params: payload + item = @unpaid.items.first - @invoice.reload - @invoice.buyer.reload - @registrar.reload + refute @unpaid.paid? + assert_equal @unpaid.buyer.balance.to_f, 100.0 + assert_equal item.price, 5.0 + + put eis_billing_payment_status_path, params: payload + @unpaid.reload + assert_equal @unpaid.buyer.balance.to_f, 105.0 + end + + test 'ignore additonal callbacks if invoice is already paid' do + payload = { + payment_state: 'settled', + order_reference: @unpaid.number, + standing_amount: @unpaid.total, + transaction_time: Time.zone.now, + } + + item = @unpaid.items.first + + refute @unpaid.paid? + assert_equal @unpaid.buyer.balance.to_f, 100.0 + assert_equal item.price, 5.0 + + put eis_billing_payment_status_path, params: payload + @unpaid.reload + assert_equal @unpaid.buyer.balance.to_f, 105.0 + assert @unpaid.paid? + + put eis_billing_payment_status_path, params: payload + @unpaid.reload + + assert_equal @unpaid.buyer.balance.to_f, 105.0 + assert @unpaid.paid? + end + + test 'send callback to already paid invoice' do + payload = { + payment_state: 'settled', + order_reference: @invoice.number, + standing_amount: @invoice.total, + transaction_time: Time.zone.now, + } assert @invoice.paid? assert_equal @invoice.buyer.balance.to_f, 100.0 + + put eis_billing_payment_status_path, params: payload + @invoice.reload + assert_equal @invoice.buyer.balance.to_f, 100.0 + assert @invoice.paid? end end diff --git a/test/models/invoice_test.rb b/test/models/invoice_test.rb index 344af9dcc..18d5c79d2 100644 --- a/test/models/invoice_test.rb +++ b/test/models/invoice_test.rb @@ -6,6 +6,8 @@ class InvoiceTest < ActiveSupport::TestCase setup do @invoice = invoices(:one) Spy.on_instance_method(EisBilling::BaseController, :authorized).and_return(true) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_status') + .to_return(status: :ok, headers: {}) end def test_fixture_is_valid @@ -124,32 +126,32 @@ class InvoiceTest < ActiveSupport::TestCase transaction.sum = 250 invoice_n = Invoice.order(number: :desc).last.number - stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator") + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator') .to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 3}\"}", headers: {}) - stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_generator") - .to_return(status: 200, body: "{\"everypay_link\":\"http://link.test\"}", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_generator') + .to_return(status: 200, body: '{"everypay_link":"http://link.test"}', headers: {}) - stub_request(:put, "https://registry:3000/eis_billing/e_invoice_response"). - to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 3}\"}, {\"date\":\"#{Time.zone.now-10.minutes}\"}", headers: {}) + stub_request(:put, 'https://registry:3000/eis_billing/e_invoice_response') + .to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 3}\"}, {\"date\":\"#{Time.zone.now - 10.minutes}\"}", headers: {}) - stub_request(:post, "https://eis_billing_system:3000/api/v1/e_invoice/e_invoice"). - to_return(status: 200, body: "", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/e_invoice/e_invoice') + .to_return(status: 200, body: '', headers: {}) invoice = Invoice.create_from_transaction!(transaction) assert_equal 250, invoice.total invoice_n = Invoice.order(number: :desc).last.number - stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator"). - to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 4}\"}", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator') + .to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 4}\"}", headers: {}) transaction.sum = 146.88 invoice = Invoice.create_from_transaction!(transaction) assert_equal 146.88, invoice.total invoice_n = Invoice.order(number: :desc).last.number - stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator"). - to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 5}\"}", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator') + .to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 5}\"}", headers: {}) transaction.sum = 0.99 invoice = Invoice.create_from_transaction!(transaction) @@ -158,14 +160,14 @@ class InvoiceTest < ActiveSupport::TestCase def test_emails_invoice_after_creating_topup_invoice invoice_n = Invoice.order(number: :desc).last.number - stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_generator"). - to_return(status: 200, body: "{\"everypay_link\":\"http://link.test\"}", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_generator') + .to_return(status: 200, body: '{"everypay_link":"http://link.test"}', headers: {}) - stub_request(:put, "https://registry:3000/eis_billing/e_invoice_response"). - to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 3}\"}, {\"date\":\"#{Time.zone.now-10.minutes}\"}", headers: {}) + stub_request(:put, 'https://registry:3000/eis_billing/e_invoice_response') + .to_return(status: 200, body: "{\"invoice_number\":\"#{invoice_n + 3}\"}, {\"date\":\"#{Time.zone.now - 10.minutes}\"}", headers: {}) - stub_request(:post, "https://eis_billing_system:3000/api/v1/e_invoice/e_invoice"). - to_return(status: 200, body: "", headers: {}) + stub_request(:post, 'https://eis_billing_system:3000/api/v1/e_invoice/e_invoice') + .to_return(status: 200, body: '', headers: {}) registrar = registrars(:bestnames) transaction = bank_transactions(:one).dup @@ -179,4 +181,73 @@ class InvoiceTest < ActiveSupport::TestCase Invoice.create_from_transaction!(transaction) end end + + def test_create_payment_order + everypay_response = { + 'some' => 'some' + } + @invoice.payment_orders.delete_all + @invoice.account_activity.delete and @invoice.reload + assert @invoice.payment_orders.empty? + assert_nil @invoice.account_activity + + @invoice.process_payment(payment_type: 'PaymentOrders::EveryPay', + everypay_response: everypay_response, + payment_status: 'paid', + sum: @invoice.total, + transaction_time: Time.zone.now - 10.minutes - 23.seconds) + + @invoice.reload + + assert_equal @invoice.payment_orders.count, 1 + assert @invoice.account_activity.present? + end + + def test_should_raise_error_if_bill_already_paid + everypay_response = { + 'some' => 'some' + } + assert @invoice.payment_orders.present? + + assert_raises(ActiveRecord::RecordInvalid) do + @invoice.process_payment(payment_type: 'PaymentOrders::EveryPay', + everypay_response: everypay_response, + payment_status: 'paid', + sum: @invoice.total, + transaction_time: Time.zone.now - 10.minutes - 23.seconds) + end + end + + def test_should_manually_autobin_invoice + @invoice.payment_orders.destroy_all && @invoice.account_activity.destroy + @invoice.reload + + account = @invoice.buyer.accounts.first + item = @invoice.items.first + + assert @invoice.payment_orders.empty? + assert @invoice.account_activity.nil? + assert_equal account.balance.to_f, 100.0 + assert_equal item.price, 5.0 + + @invoice.autobind_manually + @invoice.reload + account.reload + + assert_equal account.balance.to_f, 105.0 + assert @invoice.payment_orders.present? + assert @invoice.account_activity.present? + end + + def test_cannot_to_increase_balance_already_paid_invoice_by_manually_autobind + assert @invoice.paid? + + account = @invoice.buyer.accounts.first + assert_equal account.balance.to_f, 100.0 + + @invoice.autobind_manually + @invoice.reload + + assert_equal account.balance.to_f, 100.0 + end end From 7b05e1e28af0b8ff5eee16c4f871f1f94bb1694b Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Wed, 11 Jan 2023 12:46:03 +0200 Subject: [PATCH 2/9] refactoring --- .../eis_billing/invoices_controller.rb | 54 ++++++++++--------- test/integration/eis_billing/invoices_test.rb | 2 +- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index 0b4ce1281..58d1d72e0 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -1,6 +1,10 @@ module EisBilling class InvoicesController < BaseController TYPE = 'PaymentOrders::EveryPay'.freeze + PAID = 'paid'.freeze + CANCELLED = 'cancelled'.freeze + ISSUED = 'issued'.freeze + FAILED = 'failed'.freeze before_action :load_invoice, only: :update @@ -9,11 +13,11 @@ module EisBilling payment_orders_handler render json: { - message: 'Invoice data was successfully updated' + message: 'Invoice data was successfully updated', }, status: :ok else render json: { - error: @message.errors.full_messages + error: @message.errors.full_messages, }, status: :unprocessable_entity end end @@ -22,49 +26,51 @@ module EisBilling def payment_orders_handler if @invoice.payment_orders.present? - return if (@invoice.paid? && status == 'paid') || (@invoice.cancelled? && status == 'cancelled') + return if (@invoice.paid? && status.paid?) || (@invoice.cancelled? && status.cancelled?) - if status == 'cancelled' || status == 'failed' + if status.cancelled? || status.failed? @invoice.cancel_manualy - elsif status == 'paid' + elsif status.paid? @invoice.autobind_manually end else - return unless status == 'paid' + return unless status.paid? @invoice.autobind_manually end end def status - case params[:invoice][:status] - when 'paid' - 'paid' - when 'cancelled' - 'cancelled' - when 'failed' - 'failed' - else - 'issued' - end + status = case params[:invoice][:status] + when 'paid' + 'paid' + when 'cancelled' + 'cancelled' + when 'failed' + 'failed' + else + 'issued' + end + + Struct.new(:paid?, :cancelled?, :issued?, :failed?) + .new(status == PAID, status == CANCELLED, status == ISSUED, status == FAILED) end def load_invoice @invoice = Invoice.find_by(number: params[:invoice][:invoice_number]) + return if @invoice.present? - if @invoice.nil? - render json: { - error: { - message: "Invoice with #{params[:invoice][:invoice_number]} number not found" - } - }, status: :not_found and return - end + render json: { + error: { + message: "Invoice with #{params[:invoice][:invoice_number]} number not found", + } + }, status: :not_found and return end def modified_params { in_directo: params[:invoice][:in_directo], - e_invoice_sent_at: params[:invoice][:sent_at_omniva] + e_invoice_sent_at: params[:invoice][:sent_at_omniva], } end end diff --git a/test/integration/eis_billing/invoices_test.rb b/test/integration/eis_billing/invoices_test.rb index 2edbc533d..592af6d02 100644 --- a/test/integration/eis_billing/invoices_test.rb +++ b/test/integration/eis_billing/invoices_test.rb @@ -26,7 +26,7 @@ class EInvoiceResponseTest < ApplicationIntegrationTest @invoice.update(total: 120.0) @invoice.reload - incoming_params = { + incoming_params = { invoice: { invoice_number: @invoice.number, initiator: 'registry', From 1b377dadcd2b9b54e592235fbb04209276efec04 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Wed, 11 Jan 2023 16:28:06 +0200 Subject: [PATCH 3/9] added more conditions for unpaid and cancelled statuses --- app/controllers/eis_billing/invoices_controller.rb | 7 +++++-- app/models/concerns/invoice/cancellable.rb | 2 ++ test/integration/eis_billing/invoices_test.rb | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index 58d1d72e0..859ed6018 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -26,12 +26,15 @@ module EisBilling def payment_orders_handler if @invoice.payment_orders.present? - return if (@invoice.paid? && status.paid?) || (@invoice.cancelled? && status.cancelled?) + return if (@invoice.paid? && status.paid?) || (@invoice.unpaid? && status.issued?) - if status.cancelled? || status.failed? + if status.issued? @invoice.cancel_manualy elsif status.paid? @invoice.autobind_manually + else + # TODO + # CANCELLED end else return unless status.paid? diff --git a/app/models/concerns/invoice/cancellable.rb b/app/models/concerns/invoice/cancellable.rb index 29c09f47c..139f1e334 100644 --- a/app/models/concerns/invoice/cancellable.rb +++ b/app/models/concerns/invoice/cancellable.rb @@ -33,6 +33,8 @@ module Invoice::Cancellable end def cancel_manualy + return unless cancellable? + account_activity = AccountActivity.find_by(invoice_id: id) account_activity_dup = account_activity.dup account_activity_dup.sum = -account_activity.sum.to_i diff --git a/test/integration/eis_billing/invoices_test.rb b/test/integration/eis_billing/invoices_test.rb index 592af6d02..734b93b45 100644 --- a/test/integration/eis_billing/invoices_test.rb +++ b/test/integration/eis_billing/invoices_test.rb @@ -303,4 +303,6 @@ class EInvoiceResponseTest < ApplicationIntegrationTest assert_equal registry_response[:message], 'Invoice with nonexisted-invoice number not found' end + + test 'it should ignore if you trying to set failed status to canceled' end From 36d629fe2b47b4dd933c95730ee110942b97197c Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Fri, 13 Jan 2023 12:02:26 +0200 Subject: [PATCH 4/9] fixed status handlers --- .../eis_billing/invoices_controller.rb | 40 ++++++++++++++----- app/models/concerns/invoice/cancellable.rb | 2 - test/integration/eis_billing/invoices_test.rb | 7 ++-- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index 859ed6018..d7150f8a1 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -3,21 +3,26 @@ module EisBilling TYPE = 'PaymentOrders::EveryPay'.freeze PAID = 'paid'.freeze CANCELLED = 'cancelled'.freeze - ISSUED = 'issued'.freeze + ISSUED = 'unpaid'.freeze FAILED = 'failed'.freeze before_action :load_invoice, only: :update def update - if @invoice.update(modified_params) - payment_orders_handler + p '==========' + p params + p '==========' + + if @invoice.update(modified_params) && payment_orders_handler render json: { message: 'Invoice data was successfully updated', }, status: :ok else render json: { - error: @message.errors.full_messages, + error: { + message: @invoice.errors.full_messages + } }, status: :unprocessable_entity end end @@ -25,16 +30,33 @@ module EisBilling private def payment_orders_handler + p '-----' + p @invoice.cancelled? + p status.issued? + p status + p '------' + if @invoice.payment_orders.present? - return if (@invoice.paid? && status.paid?) || (@invoice.unpaid? && status.issued?) + if @invoice.cancelled? && status.paid? || @invoice.cancelled? && status.issued? + @invoice.errors.add(:base, 'Unable to change status of record') + + return false + end + + if @invoice.paid? && (status.failed? || status.cancelled?) + @invoice.errors.add(:base, 'Unable to change status of record') + + return false + end + + return true if (@invoice.paid? && status.paid?) || (@invoice.unpaid? && status.issued?) || (@invoice.cancelled? && status.cancelled?) if status.issued? @invoice.cancel_manualy elsif status.paid? @invoice.autobind_manually else - # TODO - # CANCELLED + @invoice.cancel end else return unless status.paid? @@ -44,7 +66,7 @@ module EisBilling end def status - status = case params[:invoice][:status] + status = case params[:status][:status] when 'paid' 'paid' when 'cancelled' @@ -52,7 +74,7 @@ module EisBilling when 'failed' 'failed' else - 'issued' + 'unpaid' end Struct.new(:paid?, :cancelled?, :issued?, :failed?) diff --git a/app/models/concerns/invoice/cancellable.rb b/app/models/concerns/invoice/cancellable.rb index 139f1e334..29c09f47c 100644 --- a/app/models/concerns/invoice/cancellable.rb +++ b/app/models/concerns/invoice/cancellable.rb @@ -33,8 +33,6 @@ module Invoice::Cancellable end def cancel_manualy - return unless cancellable? - account_activity = AccountActivity.find_by(invoice_id: id) account_activity_dup = account_activity.dup account_activity_dup.sum = -account_activity.sum.to_i diff --git a/test/integration/eis_billing/invoices_test.rb b/test/integration/eis_billing/invoices_test.rb index 734b93b45..55ea9e99a 100644 --- a/test/integration/eis_billing/invoices_test.rb +++ b/test/integration/eis_billing/invoices_test.rb @@ -256,7 +256,7 @@ class EInvoiceResponseTest < ApplicationIntegrationTest assert_equal invoice.payment_orders.count, 1 assert invoice.payment_orders.first.paid? assert invoice.account_activity - + assert invoice.paid? assert_equal account.balance.to_f, 200.0 decrease_balance_params = { @@ -265,7 +265,7 @@ class EInvoiceResponseTest < ApplicationIntegrationTest initiator: 'registry', payment_reference: '93b29d54ae08f7728e72ee3fe0e88855cd1d266912039d7d23fa2b54b7e1b349', transaction_amount: 120.0, - status: 'cancelled', + status: 'unpaid', in_directo: false, everypay_response: { 'some' => 'some' @@ -278,6 +278,7 @@ class EInvoiceResponseTest < ApplicationIntegrationTest invoice.reload invoice.payment_orders.each(&:reload) account.reload + assert invoice.unpaid? assert_equal account.balance.to_f, 100.0 end @@ -303,6 +304,4 @@ class EInvoiceResponseTest < ApplicationIntegrationTest assert_equal registry_response[:message], 'Invoice with nonexisted-invoice number not found' end - - test 'it should ignore if you trying to set failed status to canceled' end From c9ed31771bba048da182b7e8dc99e887da1d6b78 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Fri, 13 Jan 2023 13:28:29 +0200 Subject: [PATCH 5/9] fixed tests --- .../eis_billing/invoices_controller.rb | 50 ++++++------------- app/models/concerns/invoice/cancellable.rb | 2 +- db/structure.sql | 3 -- test/integration/eis_billing/invoices_test.rb | 29 +++++++---- 4 files changed, 35 insertions(+), 49 deletions(-) diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index d7150f8a1..fb9ee04d1 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -9,10 +9,6 @@ module EisBilling before_action :load_invoice, only: :update def update - p '==========' - p params - p '==========' - if @invoice.update(modified_params) && payment_orders_handler render json: { @@ -30,43 +26,27 @@ module EisBilling private def payment_orders_handler - p '-----' - p @invoice.cancelled? - p status.issued? - p status - p '------' - - if @invoice.payment_orders.present? - if @invoice.cancelled? && status.paid? || @invoice.cancelled? && status.issued? - @invoice.errors.add(:base, 'Unable to change status of record') - - return false - end - - if @invoice.paid? && (status.failed? || status.cancelled?) - @invoice.errors.add(:base, 'Unable to change status of record') - - return false - end - - return true if (@invoice.paid? && status.paid?) || (@invoice.unpaid? && status.issued?) || (@invoice.cancelled? && status.cancelled?) - - if status.issued? - @invoice.cancel_manualy - elsif status.paid? - @invoice.autobind_manually - else - @invoice.cancel - end - else - return unless status.paid? + return false if @invoice.cancelled? && status.paid? || @invoice.cancelled? && status.issued? + return false if @invoice.paid? && (status.failed? || status.cancelled?) + case + when @invoice.paid? && status.paid? + true + when @invoice.unpaid? && status.issued? + true + when @invoice.cancelled? && (status.cancelled? || status.failed?) + true + when status.issued? + @invoice.cancel_manualy + when status.paid? @invoice.autobind_manually + else + @invoice.cancel end end def status - status = case params[:status][:status] + status = case params[:status] when 'paid' 'paid' when 'cancelled' diff --git a/app/models/concerns/invoice/cancellable.rb b/app/models/concerns/invoice/cancellable.rb index 29c09f47c..df1211979 100644 --- a/app/models/concerns/invoice/cancellable.rb +++ b/app/models/concerns/invoice/cancellable.rb @@ -35,7 +35,7 @@ module Invoice::Cancellable def cancel_manualy account_activity = AccountActivity.find_by(invoice_id: id) account_activity_dup = account_activity.dup - account_activity_dup.sum = -account_activity.sum.to_i + account_activity_dup.sum = -account_activity.sum.to_f account_activity_dup.save account_activity.update(invoice_id: nil) account_activity_dup.update(invoice_id: nil) diff --git a/db/structure.sql b/db/structure.sql index b10129d30..6286053ce 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4447,7 +4447,6 @@ CREATE INDEX index_log_domains_on_object ON public.log_domains USING gin (object -- -<<<<<<< HEAD -- Name: index_log_domains_on_object_changes; Type: INDEX; Schema: public; Owner: - -- @@ -4455,8 +4454,6 @@ CREATE INDEX index_log_domains_on_object_changes ON public.log_domains USING gin -- -======= ->>>>>>> creating sync with billing -- Name: index_log_domains_on_whodunnit; Type: INDEX; Schema: public; Owner: - -- diff --git a/test/integration/eis_billing/invoices_test.rb b/test/integration/eis_billing/invoices_test.rb index 55ea9e99a..682b29623 100644 --- a/test/integration/eis_billing/invoices_test.rb +++ b/test/integration/eis_billing/invoices_test.rb @@ -38,7 +38,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'paid' } @invoice.account_activity.delete && @invoice.reload @@ -65,7 +66,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'unpaid' } assert_equal @invoice.payment_orders.pluck(:status), %w[issued issued] @@ -89,7 +91,7 @@ class EInvoiceResponseTest < ApplicationIntegrationTest invoice.update(total: 120.0) invoice.reload - incoming_params = { + incoming_params = { invoice: { invoice_number: invoice.number, initiator: 'registry', @@ -101,7 +103,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'paid' } assert invoice.payment_orders.empty? @@ -130,7 +133,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'cancelled' } @invoice.payment_orders.destroy_all and @invoice.account_activity.destroy @@ -165,7 +169,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'paid' } assert invoice.payment_orders.empty? @@ -208,7 +213,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'paid' } put eis_billing_invoices_path, params: incoming_params @@ -237,7 +243,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'paid' } assert invoice.payment_orders.empty? @@ -271,7 +278,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'unpaid' } put eis_billing_invoices_path, params: decrease_balance_params @@ -296,7 +304,8 @@ class EInvoiceResponseTest < ApplicationIntegrationTest 'some' => 'some' }, sent_at_omniva: Time.zone.now - 10.minutes - } + }, + status: 'paid' } put eis_billing_invoices_path, params: incoming_params From a4bf967e1d97adbd0b17ad0dfab39ba040033e46 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Tue, 7 Feb 2023 11:05:40 +0200 Subject: [PATCH 6/9] added state machine for invoice --- .../eis_billing/invoices_controller.rb | 69 ++++++++++--------- app/models/invoice_state_machine.rb | 44 ++++++++++++ app/models/payment_orders/every_pay.rb | 4 ++ 3 files changed, 83 insertions(+), 34 deletions(-) create mode 100644 app/models/invoice_state_machine.rb diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index fb9ee04d1..81ff9b988 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -9,8 +9,9 @@ module EisBilling before_action :load_invoice, only: :update def update - if @invoice.update(modified_params) && payment_orders_handler - + # if @invoice.update(modified_params) && payment_orders_handler + state = InvoiceStateMachine.new(invoice: @invoice, status: params[:status]) + if @invoice.update(modified_params) && state.call render json: { message: 'Invoice data was successfully updated', }, status: :ok @@ -25,41 +26,41 @@ module EisBilling private - def payment_orders_handler - return false if @invoice.cancelled? && status.paid? || @invoice.cancelled? && status.issued? - return false if @invoice.paid? && (status.failed? || status.cancelled?) + # def payment_orders_handler + # return false if @invoice.cancelled? && status.paid? || @invoice.cancelled? && status.issued? + # return false if @invoice.paid? && (status.failed? || status.cancelled?) - case - when @invoice.paid? && status.paid? - true - when @invoice.unpaid? && status.issued? - true - when @invoice.cancelled? && (status.cancelled? || status.failed?) - true - when status.issued? - @invoice.cancel_manualy - when status.paid? - @invoice.autobind_manually - else - @invoice.cancel - end - end + # case + # when @invoice.paid? && status.paid? + # true + # when @invoice.unpaid? && status.issued? + # true + # when @invoice.cancelled? && (status.cancelled? || status.failed?) + # true + # when status.issued? + # @invoice.cancel_manualy + # when status.paid? + # @invoice.autobind_manually + # else + # @invoice.cancel + # end + # end - def status - status = case params[:status] - when 'paid' - 'paid' - when 'cancelled' - 'cancelled' - when 'failed' - 'failed' - else - 'unpaid' - end + # def status + # status = case params[:status] + # when 'paid' + # 'paid' + # when 'cancelled' + # 'cancelled' + # when 'failed' + # 'failed' + # else + # 'unpaid' + # end - Struct.new(:paid?, :cancelled?, :issued?, :failed?) - .new(status == PAID, status == CANCELLED, status == ISSUED, status == FAILED) - end + # Struct.new(:paid?, :cancelled?, :issued?, :failed?) + # .new(status == PAID, status == CANCELLED, status == ISSUED, status == FAILED) + # end def load_invoice @invoice = Invoice.find_by(number: params[:invoice][:invoice_number]) diff --git a/app/models/invoice_state_machine.rb b/app/models/invoice_state_machine.rb new file mode 100644 index 000000000..051925d71 --- /dev/null +++ b/app/models/invoice_state_machine.rb @@ -0,0 +1,44 @@ +# enum status: %i[unpaid paid cancelled failed] + +class InvoiceStateMachine + attr_reader :invoice, :status + + def initialize(invoice:, status:) + @invoice = invoice + @status = status.to_sym + end + + def call + case status + when :paid + mark_as_paid + when :cancelled + mark_as_cancel + when :unpaid + mark_as_unpaid + else + raise "Inavalid state #{invoice.status}" + end + end + + private + + def mark_as_paid + raise "Inavalid state #{invoice.status}" unless invoice.unpaid? || invoice.paid? + + invoice.autobind_manually + end + + def mark_as_cancel + # Paid invoice cannot be cancelled? + raise "Inavalid state #{invoice.status}" unless invoice.cancellable? || invoice.cancelled? + + invoice.cancel + end + + def mark_as_unpaid + raise "Inavalid state #{invoice.status}" unless invoice.paid? && invoice.payment_orders.present? || invoice.unpaid? + + invoice.cancel_manualy + end +end diff --git a/app/models/payment_orders/every_pay.rb b/app/models/payment_orders/every_pay.rb index 2695c20e0..15526e4d7 100644 --- a/app/models/payment_orders/every_pay.rb +++ b/app/models/payment_orders/every_pay.rb @@ -30,6 +30,10 @@ module PaymentOrders valid_hmac? && valid_amount? && valid_account? end + def payment_reference? + response['payment_reference'].present? + end + def settled_payment? SUCCESSFUL_PAYMENT.include?(response['payment_state']) end From e00e87a15c46ec203bbc6f33ba4b6d0e7665e695 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Tue, 7 Feb 2023 12:55:02 +0200 Subject: [PATCH 7/9] change logic of state machine --- app/models/invoice_state_machine.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/models/invoice_state_machine.rb b/app/models/invoice_state_machine.rb index 051925d71..210263a57 100644 --- a/app/models/invoice_state_machine.rb +++ b/app/models/invoice_state_machine.rb @@ -17,28 +17,36 @@ class InvoiceStateMachine when :unpaid mark_as_unpaid else - raise "Inavalid state #{invoice.status}" + push_error end end private def mark_as_paid - raise "Inavalid state #{invoice.status}" unless invoice.unpaid? || invoice.paid? + return push_error unless invoice.payable? || invoice.paid? invoice.autobind_manually + invoice end def mark_as_cancel - # Paid invoice cannot be cancelled? - raise "Inavalid state #{invoice.status}" unless invoice.cancellable? || invoice.cancelled? + return push_error unless invoice.cancellable? || invoice.cancelled? invoice.cancel + invoice end def mark_as_unpaid - raise "Inavalid state #{invoice.status}" unless invoice.paid? && invoice.payment_orders.present? || invoice.unpaid? + return push_error if invoice.paid? || !invoice.cancellable? invoice.cancel_manualy + invoice + end + + def push_error + invoice.errors.add(:base, "Inavalid state #{status}") + + false end end From d28afca00f50695f2df41d2f5b90fda844294926 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Tue, 7 Feb 2023 16:10:20 +0200 Subject: [PATCH 8/9] change logic, added tests --- .../eis_billing/invoices_controller.rb | 43 ------- app/models/invoice_state_machine.rb | 11 +- app/models/payment_order.rb | 4 + app/models/payment_orders/every_pay.rb | 4 - test/models/invoice_state_machinte_test.rb | 105 ++++++++++++++++++ 5 files changed, 115 insertions(+), 52 deletions(-) create mode 100644 test/models/invoice_state_machinte_test.rb diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index 81ff9b988..23a64c83f 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -1,15 +1,8 @@ module EisBilling class InvoicesController < BaseController - TYPE = 'PaymentOrders::EveryPay'.freeze - PAID = 'paid'.freeze - CANCELLED = 'cancelled'.freeze - ISSUED = 'unpaid'.freeze - FAILED = 'failed'.freeze - before_action :load_invoice, only: :update def update - # if @invoice.update(modified_params) && payment_orders_handler state = InvoiceStateMachine.new(invoice: @invoice, status: params[:status]) if @invoice.update(modified_params) && state.call render json: { @@ -26,42 +19,6 @@ module EisBilling private - # def payment_orders_handler - # return false if @invoice.cancelled? && status.paid? || @invoice.cancelled? && status.issued? - # return false if @invoice.paid? && (status.failed? || status.cancelled?) - - # case - # when @invoice.paid? && status.paid? - # true - # when @invoice.unpaid? && status.issued? - # true - # when @invoice.cancelled? && (status.cancelled? || status.failed?) - # true - # when status.issued? - # @invoice.cancel_manualy - # when status.paid? - # @invoice.autobind_manually - # else - # @invoice.cancel - # end - # end - - # def status - # status = case params[:status] - # when 'paid' - # 'paid' - # when 'cancelled' - # 'cancelled' - # when 'failed' - # 'failed' - # else - # 'unpaid' - # end - - # Struct.new(:paid?, :cancelled?, :issued?, :failed?) - # .new(status == PAID, status == CANCELLED, status == ISSUED, status == FAILED) - # end - def load_invoice @invoice = Invoice.find_by(number: params[:invoice][:invoice_number]) return if @invoice.present? diff --git a/app/models/invoice_state_machine.rb b/app/models/invoice_state_machine.rb index 210263a57..cd1886665 100644 --- a/app/models/invoice_state_machine.rb +++ b/app/models/invoice_state_machine.rb @@ -1,5 +1,3 @@ -# enum status: %i[unpaid paid cancelled failed] - class InvoiceStateMachine attr_reader :invoice, :status @@ -24,21 +22,24 @@ class InvoiceStateMachine private def mark_as_paid - return push_error unless invoice.payable? || invoice.paid? + return push_error unless invoice.payable? + return true if invoice.paid? invoice.autobind_manually invoice end def mark_as_cancel - return push_error unless invoice.cancellable? || invoice.cancelled? + return push_error unless invoice.cancellable? + return true if invoice.cancelled? invoice.cancel invoice end def mark_as_unpaid - return push_error if invoice.paid? || !invoice.cancellable? + return push_error if invoice.paid? && invoice.payment_orders.last.payment_reference? || invoice.cancelled? + return true unless invoice.paid? invoice.cancel_manualy invoice diff --git a/app/models/payment_order.rb b/app/models/payment_order.rb index fd6684347..5e7069e1b 100644 --- a/app/models/payment_order.rb +++ b/app/models/payment_order.rb @@ -64,6 +64,10 @@ class PaymentOrder < ApplicationRecord false end + def payment_reference? + response && response['payment_reference'].present? + end + def base_transaction(sum:, paid_at:, buyer_name:) BankTransaction.new( description: invoice.order, diff --git a/app/models/payment_orders/every_pay.rb b/app/models/payment_orders/every_pay.rb index 15526e4d7..2695c20e0 100644 --- a/app/models/payment_orders/every_pay.rb +++ b/app/models/payment_orders/every_pay.rb @@ -30,10 +30,6 @@ module PaymentOrders valid_hmac? && valid_amount? && valid_account? end - def payment_reference? - response['payment_reference'].present? - end - def settled_payment? SUCCESSFUL_PAYMENT.include?(response['payment_state']) end diff --git a/test/models/invoice_state_machinte_test.rb b/test/models/invoice_state_machinte_test.rb new file mode 100644 index 000000000..319da1aa2 --- /dev/null +++ b/test/models/invoice_state_machinte_test.rb @@ -0,0 +1,105 @@ +require 'test_helper' + +class InvoiceTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + + setup do + @invoice = invoices(:one) + @unpaid = invoices(:unpaid) + + stub_request(:post, 'https://eis_billing_system:3000/api/v1/invoice_generator/invoice_status') + .with( + body: '{"invoice_number":2,"status":"paid"}' + ) + .to_return(status: 200, body: '', headers: {}) + end + + def test_unpaid_invoice_can_be_change_status_to_paid + assert !@unpaid.paid? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'paid').call + @unpaid.reload + + assert @unpaid.paid? + end + + def test_no_any_errors_if_invoice_with_unpaid_status_set_again_unpaid + assert !@unpaid.paid? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'unpaid').call + @unpaid.reload + + assert !@unpaid.paid? + assert @unpaid.errors.empty? + end + + def test_only_unpaid_invoice_can_be_cancelled + assert !@unpaid.paid? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'cancelled').call + @unpaid.reload + + assert @unpaid.cancelled? + + assert @invoice.paid? + InvoiceStateMachine.new(invoice: @invoice, status: 'cancelled').call + @invoice.reload + + assert_equal @invoice.errors.full_messages.join, 'Inavalid state cancelled' + assert @invoice.errors.present? + end + + def test_cancelled_invoiced_cannot_be_unpaid + assert !@unpaid.paid? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'cancelled').call + @unpaid.reload + + assert @unpaid.cancelled? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'unpaid').call + @unpaid.reload + + assert @unpaid.cancelled? + + assert @unpaid.errors.present? + assert_equal @unpaid.errors.full_messages.join, 'Inavalid state unpaid' + end + + def test_if_paid_invoice_not_have_response_from_everypay_it_can_be_unpaid_back + assert !@unpaid.paid? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'paid').call + @unpaid.reload + + assert @unpaid.paid? + assert_nil @unpaid.payment_orders.last.payment_reference? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'unpaid').call + @unpaid.reload + + assert !@unpaid.paid? + end + + def test_if_paid_invoice_has_response_from_everypay_it_cannot_be_rollback + assert !@unpaid.paid? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'paid').call + @unpaid.reload + + assert @unpaid.paid? + payment_order = @unpaid.payment_orders.last + payment_order.response = {} + payment_order.response[:payment_reference] = 'responsefromeveryapy' + payment_order.save && payment_order.reload + + assert @unpaid.payment_orders.last.payment_reference? + + InvoiceStateMachine.new(invoice: @unpaid, status: 'unpaid').call + @unpaid.reload + + assert @unpaid.paid? + assert @unpaid.errors.present? + assert_equal @unpaid.errors.full_messages.join, 'Inavalid state unpaid' + end +end From 06d34e30a86e37e167e98d08625612be5fd65145 Mon Sep 17 00:00:00 2001 From: olegphenomenon Date: Mon, 27 Feb 2023 09:48:45 +0200 Subject: [PATCH 9/9] update state machine --- app/models/invoice_state_machine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/invoice_state_machine.rb b/app/models/invoice_state_machine.rb index cd1886665..047477800 100644 --- a/app/models/invoice_state_machine.rb +++ b/app/models/invoice_state_machine.rb @@ -38,7 +38,7 @@ class InvoiceStateMachine end def mark_as_unpaid - return push_error if invoice.paid? && invoice.payment_orders.last.payment_reference? || invoice.cancelled? + return push_error if invoice.paid? && invoice.payment_orders&.last&.payment_reference? || invoice.cancelled? return true unless invoice.paid? invoice.cancel_manualy