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(edit-draft-invoice): Apply adjusted fee on subscription fee #1574

Merged
merged 10 commits into from
Jan 22, 2024
8 changes: 4 additions & 4 deletions app/services/adjusted_fees/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ def call
subscription: fee.subscription,
charge: fee.charge,
group: fee.group,
adjusted_units: params[:unit_amount_cents]&.blank?,
adjusted_amount: params[:unit_amount_cents]&.present?,
adjusted_units: params[:unit_amount_cents].blank?,
adjusted_amount: params[:unit_amount_cents].present?,
invoice_display_name: params[:invoice_display_name],
fee_type: fee.fee_type,
properties: fee.properties,
units: params[:units],
unit_amount_cents: params[:unit_amount_cents],
units: params[:units] || 0,
unit_amount_cents: params[:unit_amount_cents] || 0,
)

adjusted_fee.save!
Expand Down
55 changes: 43 additions & 12 deletions app/services/fees/subscription_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,29 @@ def create
return result if already_billed?

new_amount_cents = compute_amount.round
new_fee = initialize_fee(new_amount_cents)
new_fee.precise_unit_amount = new_fee.unit_amount.to_f

ActiveRecord::Base.transaction do
new_fee.save!
adjusted_fee.update!(fee: new_fee) if invoice.draft? && adjusted_fee
end

result.fee = new_fee
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end

private

attr_reader :invoice, :subscription, :boundaries

delegate :customer, to: :invoice
delegate :previous_subscription, :plan, to: :subscription

new_fee = Fee.new(
def initialize_fee(new_amount_cents)
base_fee = Fee.new(
invoice:,
subscription:,
amount_cents: new_amount_cents,
Expand All @@ -29,21 +50,31 @@ def create
taxes_amount_cents: 0,
unit_amount_cents: new_amount_cents,
)
new_fee.precise_unit_amount = new_fee.unit_amount.to_f
new_fee.save!

result.fee = new_fee
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end
return base_fee unless invoice.draft?
return base_fee unless adjusted_fee

private
units = adjusted_fee.units
unit_amount_cents = adjusted_fee.unit_amount_cents.round
amount_cents = adjusted_fee.adjusted_units? ? (units * new_amount_cents) : (units * unit_amount_cents)

attr_reader :invoice, :subscription, :boundaries
base_fee.amount_cents = amount_cents.round
base_fee.units = units
base_fee.unit_amount_cents = adjusted_fee.adjusted_units? ? new_amount_cents : unit_amount_cents
base_fee.invoice_display_name = adjusted_fee.invoice_display_name

delegate :customer, to: :invoice
delegate :previous_subscription, :plan, to: :subscription
base_fee
end

def adjusted_fee
return @adjusted_fee if defined? @adjusted_fee

@adjusted_fee = AdjustedFee
.where(invoice:, subscription:, fee_type: :subscription)
.where("properties->>'from_datetime' = ?", boundaries.from_datetime&.iso8601(3))
.where("properties->>'to_datetime' = ?", boundaries.to_datetime&.iso8601(3))
.first
end

def already_billed?
existing_fee = invoice.fees.subscription_kind.find_by(subscription_id: subscription.id)
Expand Down
137 changes: 137 additions & 0 deletions spec/scenarios/invoices/adjusted_subscription_fees_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# frozen_string_literal: true

require 'rails_helper'

describe 'Adjusted Subscription Fees Scenario', :scenarios, type: :request, transaction: false do
let(:organization) { create(:organization, webhook_url: nil, email_settings: '') }

let(:customer) { create(:customer, organization:, invoice_grace_period: 5) }
let(:subscription_at) { DateTime.new(2023, 7, 19, 12, 12) }
let(:unit_amount_cents) { nil }

let(:adjusted_fee_params) do
{
invoice_display_name: 'test-name-25',
unit_amount_cents:,
units: 3,
}
end

let(:monthly_plan) do
create(
:plan,
organization:,
interval: 'monthly',
amount_cents: 12_900,
pay_in_advance: false,
)
end

around { |test| lago_premium!(&test) }

context 'with adjusted units' do
it 'creates invoices correctly' do
# NOTE: Jul 19th: create the subscription
travel_to(subscription_at) do
create_subscription(
{
external_customer_id: customer.external_id,
external_id: customer.external_id,
plan_code: monthly_plan.code,
billing_time: 'anniversary',
subscription_at: subscription_at.iso8601,
},
)
end

# NOTE: August 19th: Bill subscription
travel_to(DateTime.new(2023, 8, 19, 12, 12)) do
Subscriptions::BillingService.call
perform_all_enqueued_jobs

invoice = customer.invoices.order(created_at: :desc).first
fee = invoice.fees.first

expect(invoice.status).to eq('draft')
expect(invoice.total_amount_cents).to eq(12_900)

AdjustedFees::CreateService.call(organization:, fee:, params: adjusted_fee_params)
perform_all_enqueued_jobs

expect(invoice.reload.status).to eq('draft')
expect(invoice.reload.total_amount_cents).to eq(38_700)
end

# NOTE: August 20th: Refresh and finalize invoice
travel_to(DateTime.new(2023, 8, 20, 12, 12)) do
invoice = customer.invoices.order(created_at: :desc).first

Invoices::RefreshDraftJob.perform_later(invoice)
perform_all_enqueued_jobs

expect(invoice.reload.status).to eq('draft')
expect(invoice.reload.total_amount_cents).to eq(38_700)

Invoices::FinalizeJob.perform_later(invoice)
perform_all_enqueued_jobs

expect(invoice.reload.status).to eq('finalized')
expect(invoice.reload.total_amount_cents).to eq(38_700)
end
end
end

context 'with adjusted amount' do
let(:unit_amount_cents) { 15_000 }

it 'creates invoices correctly' do
# NOTE: Jul 19th: create the subscription
travel_to(subscription_at) do
create_subscription(
{
external_customer_id: customer.external_id,
external_id: customer.external_id,
plan_code: monthly_plan.code,
billing_time: 'anniversary',
subscription_at: subscription_at.iso8601,
},
)
end

# NOTE: August 19th: Bill subscription
travel_to(DateTime.new(2023, 8, 19, 12, 12)) do
Subscriptions::BillingService.call
perform_all_enqueued_jobs

invoice = customer.invoices.order(created_at: :desc).first
fee = invoice.fees.first

expect(invoice.status).to eq('draft')
expect(invoice.total_amount_cents).to eq(12_900)

AdjustedFees::CreateService.call(organization:, fee:, params: adjusted_fee_params)
perform_all_enqueued_jobs

expect(invoice.reload.status).to eq('draft')
expect(invoice.reload.total_amount_cents).to eq(45_000)
end

# NOTE: August 20th: Refresh and finalize invoice
travel_to(DateTime.new(2023, 8, 20, 12, 12)) do
invoice = customer.invoices.order(created_at: :desc).first

Invoices::RefreshDraftJob.perform_later(invoice)
perform_all_enqueued_jobs

expect(invoice.reload.status).to eq('draft')
expect(invoice.reload.total_amount_cents).to eq(45_000)

Invoices::FinalizeJob.perform_later(invoice)
perform_all_enqueued_jobs

expect(invoice.reload.status).to eq('finalized')
expect(invoice.reload.total_amount_cents).to eq(45_000)
end
end
end
end
94 changes: 94 additions & 0 deletions spec/services/fees/subscription_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,100 @@
end
end
end

context 'when there is adjusted fee' do
let(:adjusted_fee) do
create(
:adjusted_fee,
invoice:,
subscription:,
properties:,
adjusted_units: true,
adjusted_amount: false,
units: 3,
)
end
let(:properties) do
{
from_datetime: boundaries[:from_datetime],
to_datetime: boundaries[:to_datetime],
}
end

before do
adjusted_fee
invoice.draft!
end

context 'with adjusted units' do
it 'creates a fee' do
result = fees_subscription_service.create

expect(result.fee).to have_attributes(
id: String,
invoice_id: invoice.id,
amount_cents: 300,
amount_currency: 'EUR',
units: 3,
events_count: nil,
payment_status: 'pending',
unit_amount_cents: 100,
precise_unit_amount: 1,
)
end
end

context 'with adjusted amount' do
let(:adjusted_fee) do
create(
:adjusted_fee,
invoice:,
subscription:,
properties:,
adjusted_units: false,
adjusted_amount: true,
units: 3,
unit_amount_cents: 200,
)
end

it 'creates a fee' do
result = fees_subscription_service.create

expect(result.fee).to have_attributes(
id: String,
invoice_id: invoice.id,
amount_cents: 600,
amount_currency: 'EUR',
units: 3,
events_count: nil,
payment_status: 'pending',
unit_amount_cents: 200,
precise_unit_amount: 2,
)
end
end

context 'with invoice NOT in draft status' do
before { invoice.finalized! }

it 'creates a fee without using adjusted fee attributes' do
result = fees_subscription_service.create

expect(result.fee).to have_attributes(
id: String,
invoice_id: invoice.id,
amount_cents: 100,
amount_currency: 'EUR',
units: 1,
events_count: nil,
payment_status: 'pending',
unit_amount_cents: 100,
precise_unit_amount: 1,
)
end
end
end
end

context 'when subscription has never been billed' do
Expand Down
Loading