mirror of
https://github.com/internetee/registry.git
synced 2025-07-24 19:48:28 +02:00
Merge pull request #1676 from internetee/1101-topping-up-credit-account-without-an-invoice
Process payments: Allow direct top up via bank transfer
This commit is contained in:
commit
90b6a0a693
13 changed files with 134 additions and 40 deletions
|
@ -18,7 +18,7 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: https://github.com/internetee/e_invoice.git
|
||||
revision: b374ffd7be77b559b30c7a0210dc0df5ac3ed723
|
||||
revision: 5f8d0029bf1affdbf2bd6e3d1ce87d34066add4d
|
||||
branch: master
|
||||
specs:
|
||||
e_invoice (0.1.0)
|
||||
|
@ -238,7 +238,7 @@ GEM
|
|||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
httpi (2.4.4)
|
||||
httpi (2.4.5)
|
||||
rack
|
||||
socksify
|
||||
i18n (1.8.5)
|
||||
|
@ -467,7 +467,8 @@ GEM
|
|||
i18n
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
wasabi (3.5.0)
|
||||
wasabi (3.6.1)
|
||||
addressable
|
||||
httpi (~> 2.0)
|
||||
nokogiri (>= 1.4.2)
|
||||
webdrivers (4.4.1)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
class SendEInvoiceJob < Que::Job
|
||||
def run(invoice_id)
|
||||
invoice = run_condition(Invoice.find_by(id: invoice_id))
|
||||
def run(invoice_id, payable: true)
|
||||
invoice = run_condition(Invoice.find_by(id: invoice_id), payable: payable)
|
||||
|
||||
invoice.to_e_invoice.deliver
|
||||
invoice.to_e_invoice(payable: payable).deliver
|
||||
ActiveRecord::Base.transaction do
|
||||
invoice.update(e_invoice_sent_at: Time.zone.now)
|
||||
log_success(invoice)
|
||||
|
@ -15,9 +15,9 @@ class SendEInvoiceJob < Que::Job
|
|||
|
||||
private
|
||||
|
||||
def run_condition(invoice)
|
||||
def run_condition(invoice, payable: true)
|
||||
destroy unless invoice
|
||||
destroy if invoice.do_not_send_e_invoice?
|
||||
destroy if invoice.do_not_send_e_invoice? && payable
|
||||
invoice
|
||||
end
|
||||
|
||||
|
|
|
@ -31,16 +31,18 @@ class BankTransaction < ApplicationRecord
|
|||
@registrar ||= Invoice.find_by(reference_no: parsed_ref_number)&.buyer
|
||||
end
|
||||
|
||||
def autobindable?
|
||||
!binded? && registrar && invoice.payable? ? true : false
|
||||
rescue NoMethodError
|
||||
false
|
||||
end
|
||||
|
||||
# For successful binding, reference number, invoice id and sum must match with the invoice
|
||||
def autobind_invoice(manual: false)
|
||||
return if binded?
|
||||
return unless registrar
|
||||
return unless invoice
|
||||
return unless invoice.payable?
|
||||
return unless autobindable?
|
||||
|
||||
channel = manual ? 'admin_payment' : 'system_payment'
|
||||
create_internal_payment_record(channel: channel, invoice: invoice,
|
||||
registrar: registrar)
|
||||
create_internal_payment_record(channel: channel, invoice: invoice, registrar: registrar)
|
||||
end
|
||||
|
||||
def create_internal_payment_record(channel: nil, invoice:, registrar:)
|
||||
|
@ -89,12 +91,11 @@ class BankTransaction < ApplicationRecord
|
|||
end
|
||||
|
||||
def create_activity(registrar, invoice)
|
||||
activity = AccountActivity.new(
|
||||
account: registrar.cash_account, bank_transaction: self,
|
||||
invoice: invoice, sum: invoice.subtotal,
|
||||
currency: currency, description: description,
|
||||
activity_type: AccountActivity::ADD_CREDIT
|
||||
)
|
||||
activity = AccountActivity.new(account: registrar.cash_account, bank_transaction: self,
|
||||
invoice: invoice, sum: invoice.subtotal,
|
||||
currency: currency, description: description,
|
||||
activity_type: AccountActivity::ADD_CREDIT)
|
||||
|
||||
if activity.save
|
||||
reset_pending_registrar_balance_reload
|
||||
true
|
||||
|
@ -103,6 +104,10 @@ class BankTransaction < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def parsed_ref_number
|
||||
reference_no || ref_number_from_description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reset_pending_registrar_balance_reload
|
||||
|
@ -112,17 +117,12 @@ class BankTransaction < ApplicationRecord
|
|||
registrar.save!
|
||||
end
|
||||
|
||||
def parsed_ref_number
|
||||
reference_no || ref_number_from_description
|
||||
end
|
||||
|
||||
def ref_number_from_description
|
||||
(Billing::ReferenceNo::MULTI_REGEXP.match(description) || []).captures.each do |match|
|
||||
break match if match.length == 7 || valid_ref_no?(match)
|
||||
end
|
||||
matches = description.to_s.scan(Billing::ReferenceNo::MULTI_REGEXP).flatten
|
||||
matches.detect { |m| break m if m.length == 7 || valid_ref_no?(m) }
|
||||
end
|
||||
|
||||
def valid_ref_no?(match)
|
||||
return true if Billing::ReferenceNo.valid?(match) && Registrar.find_by(reference_no: match).any?
|
||||
return true if Billing::ReferenceNo.valid?(match) && Registrar.find_by(reference_no: match)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -99,8 +99,8 @@ class Invoice < ApplicationRecord
|
|||
generator.as_pdf
|
||||
end
|
||||
|
||||
def to_e_invoice
|
||||
generator = Invoice::EInvoiceGenerator.new(self)
|
||||
def to_e_invoice(payable: true)
|
||||
generator = Invoice::EInvoiceGenerator.new(self, payable: payable)
|
||||
generator.generate
|
||||
end
|
||||
|
||||
|
@ -112,6 +112,15 @@ class Invoice < ApplicationRecord
|
|||
e_invoice_sent_at.present?
|
||||
end
|
||||
|
||||
def self.create_from_transaction!(transaction)
|
||||
registrar_user = Registrar.find_by(reference_no: transaction.parsed_ref_number)
|
||||
return unless registrar_user
|
||||
|
||||
vat = VatRateCalculator.new(registrar: registrar_user).calculate
|
||||
net = (transaction.sum / (1 + (vat / 100)))
|
||||
registrar_user.issue_prepayment_invoice(net, 'Direct top-up via bank transfer', payable: false)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_default_buyer_vat_no
|
||||
|
@ -119,6 +128,6 @@ class Invoice < ApplicationRecord
|
|||
end
|
||||
|
||||
def calculate_total
|
||||
self.total = subtotal + vat_amount
|
||||
self.total = (subtotal + vat_amount).round(3)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
class Invoice
|
||||
class EInvoiceGenerator
|
||||
attr_reader :invoice
|
||||
attr_reader :payable
|
||||
|
||||
def initialize(invoice)
|
||||
def initialize(invoice, payable)
|
||||
@invoice = invoice
|
||||
@payable = payable
|
||||
end
|
||||
|
||||
def generate
|
||||
|
@ -70,9 +72,10 @@ class Invoice
|
|||
i.total = invoice.total
|
||||
i.currency = invoice.currency
|
||||
i.delivery_channel = %i[internet_bank portal]
|
||||
i.payable = payable
|
||||
end
|
||||
|
||||
EInvoice::EInvoice.new(date: Time.zone.today, invoice: e_invoice_invoice)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ class InvoiceItem < ApplicationRecord
|
|||
delegate :vat_rate, to: :invoice
|
||||
|
||||
def item_sum_without_vat
|
||||
(price * quantity).round(2)
|
||||
(price * quantity).round(3)
|
||||
end
|
||||
alias_method :subtotal, :item_sum_without_vat
|
||||
|
||||
|
@ -14,6 +14,6 @@ class InvoiceItem < ApplicationRecord
|
|||
end
|
||||
|
||||
def total
|
||||
subtotal + vat_amount
|
||||
(subtotal + vat_amount)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,7 @@ class Registrar < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def issue_prepayment_invoice(amount, description = nil)
|
||||
def issue_prepayment_invoice(amount, description = nil, payable: true)
|
||||
vat_rate = ::Invoice::VatRateCalculator.new(registrar: self).calculate
|
||||
|
||||
invoice = invoices.create!(
|
||||
|
@ -99,7 +99,12 @@ class Registrar < ApplicationRecord
|
|||
}
|
||||
]
|
||||
)
|
||||
SendEInvoiceJob.enqueue(invoice.id)
|
||||
|
||||
unless payable
|
||||
InvoiceMailer.invoice_email(invoice: invoice, recipient: billing_email).deliver_now
|
||||
end
|
||||
|
||||
SendEInvoiceJob.enqueue(invoice.id, payable: payable)
|
||||
|
||||
invoice
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class ChangeInvoiceItemPriceScaleToThreePlaces < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
change_column :invoice_items, :price, :decimal, precision: 10, scale: 3
|
||||
end
|
||||
end
|
|
@ -962,7 +962,7 @@ CREATE TABLE public.invoice_items (
|
|||
description character varying NOT NULL,
|
||||
unit character varying NOT NULL,
|
||||
quantity integer NOT NULL,
|
||||
price numeric(10,2) NOT NULL,
|
||||
price numeric(10,3) NOT NULL,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
creator_str character varying,
|
||||
|
@ -4850,6 +4850,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20200811074839'),
|
||||
('20200812090409'),
|
||||
('20200812125810'),
|
||||
('20200908131554');
|
||||
('20200908131554'),
|
||||
('20200910085157');
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ namespace :invoices do
|
|||
reference_no: incoming_transaction.payment_reference_number,
|
||||
description: incoming_transaction.payment_description }
|
||||
transaction = bank_statement.bank_transactions.create!(transaction_attributes)
|
||||
Invoice.create_from_transaction!(transaction) unless transaction.autobindable?
|
||||
|
||||
transaction.autobind_invoice
|
||||
end
|
||||
end
|
||||
|
|
|
@ -156,6 +156,24 @@ class BankTransactionTest < ActiveSupport::TestCase
|
|||
assert transaction.errors.full_messages.include?('Cannot bind cancelled invoice')
|
||||
end
|
||||
|
||||
def test_assumes_7_digit_number_is_reference_no_in_desc
|
||||
statement = BankTransaction.new
|
||||
statement.description = 'number 1234567 defo valid'
|
||||
assert_equal '1234567', statement.parsed_ref_number
|
||||
end
|
||||
|
||||
def test_determines_correct_ref_no_from_description
|
||||
statement = BankTransaction.new
|
||||
ref_no = registrars(:bestnames).reference_no
|
||||
statement.description = "invoice 123 125 55 4521 #{ref_no} 7541 defo valid"
|
||||
assert_equal ref_no.to_s, statement.parsed_ref_number
|
||||
end
|
||||
|
||||
def test_parsed_ref_no_returns_nil_if_ref_not_found
|
||||
statement = BankTransaction.new
|
||||
statement.description = "all invalid 12 123 55 77777 --"
|
||||
assert_nil statement.parsed_ref_number
|
||||
end
|
||||
private
|
||||
|
||||
def create_payable_invoice(attributes)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'test_helper'
|
||||
|
||||
class InvoiceTest < ActiveSupport::TestCase
|
||||
include ActionMailer::TestHelper
|
||||
|
||||
setup do
|
||||
@invoice = invoices(:one)
|
||||
end
|
||||
|
@ -109,4 +111,33 @@ class InvoiceTest < ActiveSupport::TestCase
|
|||
seller_zip: nil)
|
||||
assert_equal 'street, city, state', invoice.seller_address
|
||||
end
|
||||
|
||||
def test_creates_invoice_with_bank_transaction_total
|
||||
registrar = registrars(:bestnames)
|
||||
transaction = bank_transactions(:one).dup
|
||||
transaction.reference_no = registrar.reference_no
|
||||
transaction.sum = 250
|
||||
|
||||
invoice = Invoice.create_from_transaction!(transaction)
|
||||
assert_equal 250, invoice.total
|
||||
|
||||
transaction.sum = 146.88
|
||||
invoice = Invoice.create_from_transaction!(transaction)
|
||||
assert_equal 146.88, invoice.total
|
||||
|
||||
transaction.sum = 0.99
|
||||
invoice = Invoice.create_from_transaction!(transaction)
|
||||
assert_equal 0.99, invoice.total
|
||||
end
|
||||
|
||||
def test_emails_invoice_after_creating_topup_invoice
|
||||
registrar = registrars(:bestnames)
|
||||
transaction = bank_transactions(:one).dup
|
||||
transaction.reference_no = registrar.reference_no
|
||||
transaction.sum = 250
|
||||
|
||||
assert_emails 1 do
|
||||
Invoice.create_from_transaction!(transaction)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,6 +82,25 @@ class ProcessPaymentsTaskTest < ActiveSupport::TestCase
|
|||
assert payment_order.failed?
|
||||
end
|
||||
|
||||
def test_credits_registrar_account_without_invoice_beforehand
|
||||
registrar = registrars(:bestnames)
|
||||
|
||||
assert_changes -> { registrar.accounts.first.balance } do
|
||||
run_task
|
||||
end
|
||||
|
||||
assert_changes -> { registrar.invoices.count } do
|
||||
run_task
|
||||
end
|
||||
end
|
||||
|
||||
def test_topup_creates_invoice_with_total_of_transactioned_amount
|
||||
registrar = registrars(:bestnames)
|
||||
run_task
|
||||
|
||||
assert_equal 0.1, registrar.invoices.last.total
|
||||
end
|
||||
|
||||
def test_output
|
||||
assert_output "Transactions processed: 1\n" do
|
||||
run_task
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue