Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(event): Use generating invoice service for prepaid credit invoices #1552

Merged
merged 1 commit into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions app/jobs/bill_paid_credit_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ class BillPaidCreditJob < ApplicationJob

retry_on Sequenced::SequenceError

def perform(wallet_transaction, timestamp)
result = Invoices::PaidCreditService.new(
def perform(wallet_transaction, timestamp, invoice: nil)
result = Invoices::PaidCreditService.call(
wallet_transaction:,
timestamp:,
).create
invoice:,
)

result.raise_if_error!
result.raise_if_error! if invoice || result.invoice.nil? || !result.invoice.generating?

# NOTE: retry the job with the already created invoice in a previous failed attempt
self.class.set(wait: 3.seconds).perform_later(
wallet_transaction,
timestamp,
invoice: result.invoice,
)
end
end
59 changes: 29 additions & 30 deletions app/services/invoices/paid_credit_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,28 @@

module Invoices
class PaidCreditService < BaseService
def initialize(wallet_transaction:, timestamp:)
def initialize(wallet_transaction:, timestamp:, invoice: nil)
@customer = wallet_transaction.wallet.customer
@wallet_transaction = wallet_transaction
@timestamp = timestamp

super(nil)
# NOTE: In case of retry when the creation process failed,
# and if the generating invoice was persisted,
# the process can be retried without creating a new invoice
@invoice = invoice

super
end

def create
ActiveRecord::Base.transaction do
invoice = Invoice.create!(
organization: customer.organization,
customer:,
issuing_date:,
payment_due_date:,
net_payment_term: customer.applicable_net_payment_term,
invoice_type: :credit,
payment_status: :pending,
currency:,

# NOTE: No VAT should be applied on as it can be considered as an advance
taxes_rate: 0,
timezone: customer.applicable_timezone,
)
def call
create_generating_invoice unless invoice
result.invoice = invoice

ActiveRecord::Base.transaction do
create_credit_fee(invoice)
compute_amounts(invoice)

invoice.save!

result.invoice = invoice
invoice.finalized!
end

track_invoice_created(result.invoice)
Expand All @@ -44,16 +35,32 @@ def create
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
rescue Sequenced::SequenceError
raise
rescue StandardError => e
result.fail_with_error!(e)
end

private

attr_accessor :customer, :timestamp, :wallet_transaction
attr_accessor :customer, :timestamp, :wallet_transaction, :invoice

def currency
@currency ||= wallet_transaction.wallet.currency
end

def create_generating_invoice
invoice_result = Invoices::CreateGeneratingService.call(
customer:,
invoice_type: :credit,
currency:,
datetime: Time.zone.at(timestamp),
)
invoice_result.raise_if_error!

@invoice = invoice_result.invoice
end

def compute_amounts(invoice)
fee_amounts = invoice.fees.select(:amount_cents, :taxes_amount_cents)

Expand Down Expand Up @@ -94,14 +101,6 @@ def track_invoice_created(invoice)
)
end

def issuing_date
Time.zone.at(timestamp).in_time_zone(customer.applicable_timezone).to_date
end

def payment_due_date
(issuing_date + customer.applicable_net_payment_term.days).to_date
end

def should_deliver_email?
License.premium? &&
customer.organization.email_settings.include?('invoice.finalized')
Expand Down
58 changes: 49 additions & 9 deletions spec/jobs/bill_paid_credit_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@
let(:invoice_service) { instance_double(Invoices::PaidCreditService) }
let(:result) { BaseService::Result.new }

let(:invoice) { nil }

before do
allow(Invoices::PaidCreditService).to receive(:new)
.with(wallet_transaction:, timestamp:)
.and_return(invoice_service)
allow(invoice_service).to receive(:create)
allow(Invoices::PaidCreditService).to receive(:call)
.with(wallet_transaction:, timestamp:, invoice:)
.and_return(result)
end

it 'calls the paid credit service create method' do
it 'calls the paid credit service call method' do
described_class.perform_now(wallet_transaction, timestamp)

expect(Invoices::PaidCreditService).to have_received(:new)
expect(invoice_service).to have_received(:create)
expect(Invoices::PaidCreditService).to have_received(:call)
end

context 'when result is a failure' do
Expand All @@ -34,8 +33,49 @@
described_class.perform_now(wallet_transaction, timestamp)
end.to raise_error(BaseService::FailedResult)

expect(Invoices::PaidCreditService).to have_received(:new)
expect(invoice_service).to have_received(:create)
expect(Invoices::PaidCreditService).to have_received(:call)
end

context 'with a previously created invoice' do
let(:previous_invoice) { create(:invoice, :generating) }
let(:invoice) { previous_invoice }

it 'raises an error' do
expect do
described_class.perform_now(wallet_transaction, timestamp, invoice: previous_invoice)
end.to raise_error(BaseService::FailedResult)

expect(Invoices::PaidCreditService).to have_received(:call)
end
end

context 'when a generating invoice is attached to the result' do
let(:previous_invoice) { create(:invoice, :generating) }

before { result.invoice = previous_invoice }

it 'retries the job with the invoice' do
described_class.perform_now(wallet_transaction, timestamp)

expect(Invoices::PaidCreditService).to have_received(:call)

expect(described_class).to have_been_enqueued
.with(wallet_transaction, timestamp, invoice: previous_invoice)
end
end

context 'when a not generating invoice is attached to the result' do
let(:previous_invoice) { create(:invoice, :draft) }

before { result.invoice = previous_invoice }

it 'raises an error' do
expect do
described_class.perform_now(wallet_transaction, timestamp)
end.to raise_error(BaseService::FailedResult)

expect(Invoices::PaidCreditService).to have_received(:call)
end
end
end
end
46 changes: 35 additions & 11 deletions spec/services/invoices/paid_credit_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@

RSpec.describe Invoices::PaidCreditService, type: :service do
subject(:invoice_service) do
described_class.new(wallet_transaction:, timestamp:)
described_class.new(wallet_transaction:, timestamp:, invoice:)
end

let(:timestamp) { Time.current.to_i }

describe 'create' do
describe 'call' do
let(:customer) { create(:customer) }
let(:subscription) { create(:subscription, customer:) }
let(:wallet) { create(:wallet, customer:) }
let(:wallet_transaction) do
create(:wallet_transaction, wallet:, amount: '15.00', credit_amount: '15.00')
end

let(:invoice) { nil }

before do
wallet_transaction
subscription
allow(SegmentTrackJob).to receive(:perform_later)
end

it 'creates an invoice' do
result = invoice_service.create
result = invoice_service.call

aggregate_failures do
expect(result).to be_success
Expand All @@ -50,13 +52,13 @@

it 'enqueues a SendWebhookJob' do
expect do
invoice_service.create
invoice_service.call
end.to have_enqueued_job(SendWebhookJob)
end

it 'does not enqueue an ActionMailer::MailDeliveryJob' do
expect do
invoice_service.create
invoice_service.call
end.not_to have_enqueued_job(ActionMailer::MailDeliveryJob)
end

Expand All @@ -65,7 +67,7 @@

it 'enqueues an ActionMailer::MailDeliveryJob' do
expect do
invoice_service.create
invoice_service.call
end.to have_enqueued_job(ActionMailer::MailDeliveryJob)
end

Expand All @@ -74,14 +76,14 @@

it 'does not enqueue an ActionMailer::MailDeliveryJob' do
expect do
invoice_service.create
invoice_service.call
end.not_to have_enqueued_job(ActionMailer::MailDeliveryJob)
end
end
end

it 'calls SegmentTrackJob' do
invoice = invoice_service.create.invoice
invoice = invoice_service.call.invoice

expect(SegmentTrackJob).to have_received(:perform_later).with(
membership_id: CurrentContext.membership,
Expand All @@ -101,7 +103,7 @@
allow(payment_create_service)
.to receive(:call)

invoice_service.create
invoice_service.call

expect(Invoices::Payments::CreateService).to have_received(:new)
expect(payment_create_service).to have_received(:call)
Expand All @@ -112,7 +114,7 @@

it 'does not enqueues a SendWebhookJob' do
expect do
invoice_service.create
invoice_service.call
end.not_to have_enqueued_job(SendWebhookJob)
end
end
Expand All @@ -123,10 +125,32 @@
let(:timestamp) { DateTime.parse('2022-11-25 01:00:00').to_i }

it 'assigns the issuing date in the customer timezone' do
result = invoice_service.create
result = invoice_service.call

expect(result.invoice.issuing_date.to_s).to eq('2022-11-24')
end
end

context 'with provided invoice' do
let(:invoice) do
create(:invoice, organization: customer.organization, customer:, invoice_type: :credit, status: :generating)
end

it 'does not re-create an invoice' do
result = invoice_service.call

expect(result).to be_success
expect(result.invoice).to eq(invoice)

expect(result.invoice.fees.count).to eq(1)

expect(result.invoice.fees_amount_cents).to eq(1500)
expect(result.invoice.taxes_amount_cents).to eq(0)
expect(result.invoice.taxes_rate).to eq(0)
expect(result.invoice.total_amount_cents).to eq(1500)

expect(result.invoice).to be_finalized
end
end
end
end
Loading