Skip to content

Commit

Permalink
Reapply "feat(invoice): Avoid generating 0 amount charge fees (#3032)" (
Browse files Browse the repository at this point in the history
#3059)

This reverts commit 2cdf33b.
  • Loading branch information
vincent-pochet committed Jan 27, 2025
1 parent d1c844c commit 19b910e
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 148 deletions.
20 changes: 16 additions & 4 deletions app/services/fees/charge_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

module Fees
class ChargeService < BaseService
def initialize(invoice:, charge:, subscription:, boundaries:, current_usage: false, cache_middleware: nil, bypass_aggregation: false, apply_taxes: false)
def initialize(invoice:, charge:, subscription:, boundaries:, context: nil, cache_middleware: nil, bypass_aggregation: false, apply_taxes: false)
@invoice = invoice
@charge = charge
@subscription = subscription
@boundaries = OpenStruct.new(boundaries)
@currency = subscription.plan.amount.currency
@apply_taxes = apply_taxes

@current_usage = current_usage
@context = context
@current_usage = context == :current_usage
@cache_middleware = cache_middleware || Subscriptions::ChargeCacheMiddleware.new(
subscription:, charge:, to_datetime: boundaries[:charges_to_datetime], cache: false
)
Expand All @@ -29,14 +30,16 @@ def call

if invoice.nil? || !invoice.progressive_billing?
init_true_up_fee(
fee: result.fees.first,
fee: result.fees.find { |f| f.charge_filter_id.nil? },
amount_cents: result.fees.sum(&:amount_cents),
precise_amount_cents: result.fees.sum(&:precise_amount_cents)
)
end
return result unless result.success?

ActiveRecord::Base.transaction do
result.fees.reject! { |f| !should_persit_fee?(f, result.fees) }

result.fees.each do |fee|
fee.save!

Expand All @@ -56,7 +59,7 @@ def call

private

attr_accessor :invoice, :charge, :subscription, :boundaries, :current_usage, :currency, :cache_middleware, :bypass_aggregation, :apply_taxes
attr_accessor :invoice, :charge, :subscription, :boundaries, :context, :current_usage, :currency, :cache_middleware, :bypass_aggregation, :apply_taxes

delegate :billable_metric, to: :charge
delegate :organization, to: :subscription
Expand Down Expand Up @@ -173,6 +176,15 @@ def init_fee(amount_result, properties:, charge_filter:)
new_fee
end

def should_persit_fee?(fee, fees)
return true if context == :recurring
return true if fee.units != 0 || fee.amount_cents != 0 || fee.events_count != 0
return true if adjusted_fee(charge_filter: fee.charge_filter, grouped_by: fee.grouped_by).present?
return true if fee.true_up_parent_fee.present?

fees.any? { |f| f.true_up_parent_fee == fee }
end

def adjusted_fee(charge_filter:, grouped_by:)
@adjusted_fee ||= {}

Expand Down
7 changes: 4 additions & 3 deletions app/services/invoices/calculate_fees_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def create_charges_fees(subscription, boundaries)
next if should_not_create_charge_fee?(charge, subscription)

bypass_aggregation = !received_event_codes.include?(charge.billable_metric.code)
Fees::ChargeService.call(invoice:, charge:, subscription:, boundaries:, bypass_aggregation:).raise_if_error!
Fees::ChargeService.call(invoice:, charge:, subscription:, boundaries:, context:, bypass_aggregation:).raise_if_error!
end
end

Expand Down Expand Up @@ -184,13 +184,14 @@ def create_recurring_non_invoiceable_fees(subscription, boundaries)
.find_each do |charge|
next if should_not_create_charge_fee?(charge, subscription)

fee_result = Fees::ChargeService.call(
fee_result = Fees::ChargeService.call!(
invoice: nil,
charge:,
subscription:,
context: :recurring,
boundaries:,
apply_taxes: invoice.customer.anrok_customer.blank?
).raise_if_error!
)

result.non_invoiceable_fees.concat(fee_result.fees)
end
Expand Down
2 changes: 1 addition & 1 deletion app/services/invoices/customer_usage_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def charge_usage(charge)
applied_boundaries = applied_boundaries.merge(charges_to_datetime: max_to_datetime) if max_to_datetime

Fees::ChargeService
.call(invoice:, charge:, subscription:, boundaries: applied_boundaries, current_usage: true, cache_middleware:)
.call(invoice:, charge:, subscription:, boundaries: applied_boundaries, context: :current_usage, cache_middleware:)
.raise_if_error!
.fees
end
Expand Down
2 changes: 1 addition & 1 deletion app/services/invoices/progressive_billing_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def create_generating_invoice

def create_fees
charges.find_each do |charge|
Fees::ChargeService.call(invoice:, charge:, subscription:, boundaries:).raise_if_error!
Fees::ChargeService.call(invoice:, charge:, subscription:, context: :finalize, boundaries:).raise_if_error!
end
end

Expand Down
34 changes: 29 additions & 5 deletions spec/scenarios/invoices/adjusted_charge_fees_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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(:subscription_at) { DateTime.new(2022, 7, 19, 12, 12) }
let(:billable_metric) { create(:billable_metric, organization:, aggregation_type: 'sum_agg', field_name: 'custom') }
let(:unit_precise_amount) { nil }

Expand Down Expand Up @@ -52,8 +52,20 @@
)
end

travel_to(Time.zone.parse("2023-07-23T10:12")) do
create_event(
{
external_subscription_id: customer.external_id,
transaction_id: SecureRandom.uuid,
code: billable_metric.code,
timestamp: Time.current.to_i,
properties: {billable_metric.field_name => 0}
}
)
end

# NOTE: August 19th: Bill subscription
travel_to(DateTime.new(2023, 8, 19, 12, 12)) do
travel_to(Time.zone.parse("2023-08-19T12:12")) do
Subscriptions::BillingService.call
perform_all_enqueued_jobs

Expand All @@ -71,7 +83,7 @@
end

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

Invoices::RefreshDraftJob.perform_later(invoice)
Expand Down Expand Up @@ -113,8 +125,20 @@
)
end

travel_to(Time.zone.parse("2023-07-23T10:12")) do
create_event(
{
external_subscription_id: customer.external_id,
transaction_id: SecureRandom.uuid,
code: billable_metric.code,
timestamp: Time.current.to_i,
properties: {billable_metric.field_name => 0}
}
)
end

# NOTE: August 19th: Bill subscription
travel_to(DateTime.new(2023, 8, 19, 12, 12)) do
travel_to(Time.zone.parse("2023-08-19T12:12")) do
Subscriptions::BillingService.call
perform_all_enqueued_jobs

Expand All @@ -132,7 +156,7 @@
end

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

Invoices::RefreshDraftJob.perform_later(invoice)
Expand Down
36 changes: 36 additions & 0 deletions spec/scenarios/invoices/invoices_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,18 @@
)
end

travel_to(Time.zone.parse("2022-12-16T10:12")) do
create_event(
{
external_subscription_id: customer.external_id,
transaction_id: SecureRandom.uuid,
code: metric.code,
timestamp: Time.current.to_i,
properties: {metric.field_name => 0}
}
)
end

subscription = customer.subscriptions.first

### 20 Dec: Terminate subscription + refresh.
Expand Down Expand Up @@ -400,6 +412,18 @@

subscription = customer.subscriptions.first

travel_to(Time.zone.parse("2022-12-16T10:12")) do
create_event(
{
external_subscription_id: customer.external_id,
transaction_id: SecureRandom.uuid,
code: metric.code,
timestamp: Time.current.to_i,
properties: {metric.field_name => 0}
}
)
end

### 20 Dec: Terminate subscription + refresh.
dec20 = Time.zone.parse('2022-12-20 06:00:00')

Expand Down Expand Up @@ -483,6 +507,18 @@

subscription = customer.subscriptions.first

travel_to(Time.zone.parse("2022-12-16T10:12")) do
create_event(
{
external_subscription_id: customer.external_id,
transaction_id: SecureRandom.uuid,
code: metric.code,
timestamp: Time.current.to_i,
properties: {metric.field_name => 0}
}
)
end

### 20 Dec: Upgrade subscription
dec20 = Time.zone.parse('2022-12-20 06:00:00')

Expand Down
Loading

0 comments on commit 19b910e

Please sign in to comment.