Skip to content

Commit

Permalink
Merge pull request #1517 from senid231/fix-service-renew
Browse files Browse the repository at this point in the history
fix billing service renew
  • Loading branch information
dmitry-sinina authored Jul 22, 2024
2 parents 29a0754 + 91101b2 commit 8ddd6b5
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 21 deletions.
5 changes: 3 additions & 2 deletions app/jobs/jobs/service_renew.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ class ServiceRenew < ::BaseJob

def execute
Billing::Service.ready_for_renew.find_each do |service|
Billing::Service::Renew.new(service).perform
renew(service)
end
end

def renew(service)
Billing::Service::Renew.new(service).perform
Billing::Service::Renew.perform(service)
rescue StandardError => e
log_error(e)
capture_error(e, extra: { service_id: service.id })
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/billing/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class Billing::Service < ApplicationRecord
after_destroy :provisioning_object_after_destroy

scope :ready_for_renew, lambda {
where('renew_period_id is not null AND renew_at <= ? ', Time.current)
where('renew_period_id is not null AND renew_at <= ? ', Time.current).order(renew_at: :asc)
}
scope :one_time_services, lambda {
where('renew_period_id is null')
Expand Down
44 changes: 26 additions & 18 deletions app/models/billing/service/renew.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ class Billing::Service::Renew
Error = Class.new(StandardError)
DESCRIPTION = 'Renew service'

class << self
def perform(service)
new(service).perform
end
end

attr_reader :service
delegate :account, to: :service

Expand All @@ -20,30 +26,32 @@ def perform
service.update!(state_id: Billing::Service::STATE_ID_SUSPENDED)
provisioning_object.after_failed_renew
provisioning_object.after_renew
return
Rails.logger.info { "Not enough balance to renew billing service ##{service.id}" }
else
service.update!(state_id: Billing::Service::STATE_ID_ACTIVE, renew_at: next_renew_at)
transaction = create_transaction
provisioning_object.after_success_renew
provisioning_object.after_renew
transaction
Rails.logger.info { "Success renew billing service ##{service.id}" }
end

service.update!(
state_id: Billing::Service::STATE_ID_ACTIVE,
renew_at: next_renew_at
)

transaction = Billing::Transaction.new(
service:,
account:,
amount: service.renew_price,
description: DESCRIPTION
)
raise Error, "Failed to create transaction: #{transaction.errors.full_messages.to_sentence}" unless transaction.save

provisioning_object.after_success_renew
provisioning_object.after_renew
transaction
end
end

private

def create_transaction
transaction = Billing::Transaction.new(
service:,
account:,
amount: service.renew_price,
description: DESCRIPTION
)
raise Error, "Failed to create transaction: #{transaction.errors.full_messages.to_sentence}" unless transaction.save

transaction
end

def provisioning_object
@provisioning_object ||= service.build_provisioning_object
end
Expand Down
49 changes: 49 additions & 0 deletions spec/jobs/jobs/service_renew_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

RSpec.describe Jobs::ServiceRenew, '#call' do
subject do
job.call
end

let(:job) { described_class.new(double) }
before do
create(:service, renew_period_id: nil, renew_at: 1.day.ago)
create(:service, renew_period_id: Billing::Service::RENEW_PERIOD_ID_DAY, renew_at: nil)
create(:service, renew_period_id: Billing::Service::RENEW_PERIOD_ID_DAY, renew_at: 1.minute.from_now)
create(:service, renew_period_id: Billing::Service::RENEW_PERIOD_ID_MONTH, renew_at: 1.day.from_now)
end

let!(:services_for_renew) do
[
create(:service, renew_period_id: Billing::Service::RENEW_PERIOD_ID_DAY, renew_at: 1.second.ago),
create(:service, renew_period_id: Billing::Service::RENEW_PERIOD_ID_MONTH, renew_at: 1.day.ago),
create(:service, renew_period_id: Billing::Service::RENEW_PERIOD_ID_MONTH, renew_at: 1.month.ago)
]
end

it 'renews correct services' do
services_for_renew.each do |service|
expect(Billing::Service::Renew).to receive(:perform).with(service).once
end
subject
end

context 'when renew raises an error' do
it 'renews all ready services' do
expect(Billing::Service::Renew).to receive(:perform).with(services_for_renew[0]).once.and_raise(StandardError, 'test0')
expect(Billing::Service::Renew).to receive(:perform).with(services_for_renew[1]).once
expect(Billing::Service::Renew).to receive(:perform).with(services_for_renew[2]).once.and_raise(StandardError, 'test2')

expect(CaptureError).to receive(:capture).with(
a_kind_of(StandardError),
hash_including(extra: { service_id: services_for_renew[0].id })
).once
expect(CaptureError).to receive(:capture).with(
a_kind_of(StandardError),
hash_including(extra: { service_id: services_for_renew[2].id })
).once

subject
end
end
end
116 changes: 116 additions & 0 deletions spec/models/billing/service/renew_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# frozen_string_literal: true

RSpec.describe Billing::Service::Renew do
shared_examples :renews_service do
it 'calls provisioning object callbacks' do
stub = instance_double(Billing::Provisioning::Base)
expect(service).to receive(:build_provisioning_object).and_return(stub)
stub
expect(stub).to receive(:before_renew).with(no_args).once.ordered
expect(stub).to receive(:after_success_renew).with(no_args).once.ordered
expect(stub).to receive(:after_renew).with(no_args).once.ordered

subject
end

it 'renews service' do
expect { subject }.to change { service.reload.renew_at }.by(1.day)
expect(service.state_id).to eq(Billing::Service::STATE_ID_ACTIVE)
end

it 'charges account' do
expect { subject }.to change { account.reload.balance }.by(-service.renew_price)
end

it 'creates Billing::Transaction' do
expect { subject }.to change { Billing::Transaction.count }.by(1)
transaction = Billing::Transaction.last!
expect(transaction).to have_attributes(
service:,
account:,
amount: service.renew_price,
description: described_class::DESCRIPTION
)
end
end

shared_examples :suspends_service do
it 'calls provisioning object callbacks' do
stub = instance_double(Billing::Provisioning::Base)
expect(service).to receive(:build_provisioning_object).and_return(stub)
stub
expect(stub).to receive(:before_renew).with(no_args).once.ordered
expect(stub).to receive(:after_failed_renew).with(no_args).once.ordered
expect(stub).to receive(:after_renew).with(no_args).once.ordered

subject
end

it 'suspends service' do
expect { subject }.not_to change { service.reload.renew_at }
expect(service.state_id).to eq(Billing::Service::STATE_ID_SUSPENDED)
end

it 'does not charge account' do
expect { subject }.not_to change { account.reload.balance }
end

it 'does not create Billing::Transaction' do
expect { subject }.to change { Billing::Transaction.count }.by(0)
end
end

describe '.perform' do
subject do
described_class.perform(service)
end

let!(:service_type) do
create(:service_type, service_type_attrs)
end
let(:service_type_attrs) do
{}
end
let!(:account) { create(:account, account_attrs) }
let!(:account_attrs) do
{ balance: 100, min_balance: 0, max_balance: 1000 }
end
let!(:service) { create(:service, service_attrs) }
let!(:service_attrs) do
{
account:,
type: service_type,
renew_price: 10,
initial_price: 0,
renew_period_id: Billing::Service::RENEW_PERIOD_ID_DAY,
renew_at: Time.current.beginning_of_day
}
end

context 'when enough money' do
include_examples :renews_service

context 'when account balance will be less than min_balance' do
let!(:account_attrs) do
super().merge min_balance: 90.01
end

include_examples :suspends_service
end
end

context 'when not enough money' do
let!(:account_attrs) do
super().merge balance: 9.99
end

context 'when service_type with force_renew' do
let(:service_type_attrs) do
super().merge force_renew: true
end

include_examples :renews_service
end
end
end
end

0 comments on commit 8ddd6b5

Please sign in to comment.