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(ProgressiveBilling): Bill current usage #2461

Merged
merged 1 commit into from
Aug 22, 2024
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
5 changes: 1 addition & 4 deletions app/models/credit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ def item_code

def item_name
return coupon&.name if applied_coupon_id?

if progressive_billing_invoice_id?
return progressive_billing_invoice.fees.first&.invoice_name
end
return progressive_billing_invoice.number if progressive_billing_invoice_id?

# TODO: change it depending on invoice template
credit_note.invoice.number
Expand Down
15 changes: 4 additions & 11 deletions app/models/fee.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class Fee < ApplicationRecord
belongs_to :subscription, optional: true
belongs_to :charge_filter, -> { with_discarded }, optional: true
belongs_to :group, -> { with_discarded }, optional: true
belongs_to :usage_threshold, -> { with_discarded }, optional: true
belongs_to :invoiceable, polymorphic: true, optional: true
belongs_to :true_up_parent_fee, class_name: 'Fee', optional: true

Expand All @@ -35,7 +34,7 @@ class Fee < ApplicationRecord
monetize :unit_amount_cents, disable_validation: true, allow_nil: true, with_model_currency: :currency

# TODO: Deprecate add_on type in the near future
FEE_TYPES = %i[charge add_on subscription credit commitment progressive_billing].freeze
FEE_TYPES = %i[charge add_on subscription credit commitment].freeze
PAYMENT_STATUS = %i[pending succeeded failed refunded].freeze

enum fee_type: FEE_TYPES
Expand Down Expand Up @@ -66,7 +65,6 @@ def item_id
return billable_metric.id if charge?
return add_on.id if add_on?
return invoiceable_id if credit?
return usage_threshold_id if progressive_billing?

subscription_id
end
Expand All @@ -75,31 +73,30 @@ def item_type
return BillableMetric.name if charge?
return AddOn.name if add_on?
return WalletTransaction.name if credit?
return UsageThreshold.name if progressive_billing?

Subscription.name
end

def item_code
return billable_metric.code if charge?
return add_on.code if add_on?
return fee_type if credit? || progressive_billing?
return fee_type if credit?

subscription.plan.code
end

def item_name
return billable_metric.name if charge?
return add_on.name if add_on?
return fee_type if credit? || progressive_billing?
return fee_type if credit?

subscription.plan.name
end

def item_description
return billable_metric.description if charge?
return add_on.description if add_on?
return fee_type if credit? || progressive_billing?
return fee_type if credit?

subscription.plan.description
end
Expand All @@ -109,7 +106,6 @@ def invoice_name
return charge.invoice_display_name.presence || billable_metric.name if charge?
return add_on.invoice_name if add_on?
return fee_type if credit?
return usage_threshold.invoice_name if progressive_billing?

subscription.plan.invoice_display_name
end
Expand Down Expand Up @@ -202,7 +198,6 @@ def has_charge_filters?
# pay_in_advance_event_transaction_id :string
# subscription_id :uuid
# true_up_parent_fee_id :uuid
# usage_threshold_id :uuid
#
# Indexes
#
Expand All @@ -218,7 +213,6 @@ def has_charge_filters?
# index_fees_on_pay_in_advance_event_transaction_id (pay_in_advance_event_transaction_id) WHERE (deleted_at IS NULL)
# index_fees_on_subscription_id (subscription_id)
# index_fees_on_true_up_parent_fee_id (true_up_parent_fee_id)
# index_fees_on_usage_threshold_id (usage_threshold_id)
#
# Foreign Keys
#
Expand All @@ -229,5 +223,4 @@ def has_charge_filters?
# fk_rails_... (invoice_id => invoices.id)
# fk_rails_... (subscription_id => subscriptions.id)
# fk_rails_... (true_up_parent_fee_id => fees.id)
# fk_rails_... (usage_threshold_id => usage_thresholds.id)
#
2 changes: 1 addition & 1 deletion app/services/credits/progressive_billing_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def call
remaining_to_credit = total_subscription_amount

progressive_billing_invoices.each do |progressive_billing_invoice|
amount_to_credit = progressive_billing_invoice.fees.progressive_billing.sum(:amount_cents)
amount_to_credit = progressive_billing_invoice.fees_amount_cents

if amount_to_credit > remaining_to_credit
# TODO: create credit note for (amount_to_credit - remaining_credit)
Expand Down
2 changes: 1 addition & 1 deletion app/services/fees/apply_taxes_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def applicable_taxes
return fee.add_on.taxes if fee.add_on? && fee.add_on.taxes.any?
return fee.charge.taxes if fee.charge? && fee.charge.taxes.any?
return fee.invoiceable.taxes if fee.commitment? && fee.invoiceable.taxes.any?
if (fee.charge? || fee.subscription? || fee.commitment? || fee.progressive_billing?) && fee.subscription.plan.taxes.any?
if (fee.charge? || fee.subscription? || fee.commitment?) && fee.subscription.plan.taxes.any?
return fee.subscription.plan.taxes
end
return customer.taxes if customer.taxes.any?
Expand Down
59 changes: 0 additions & 59 deletions app/services/fees/create_from_usage_threshold_service.rb

This file was deleted.

79 changes: 36 additions & 43 deletions app/services/invoices/progressive_billing_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def initialize(usage_thresholds:, lifetime_usage:, timestamp: Time.current)
def call
ActiveRecord::Base.transaction do
create_generating_invoice
create_threshold_fees
create_fees

invoice.fees_amount_cents = invoice.fees.sum(:amount_cents)
invoice.sub_total_excluding_taxes_amount_cents = invoice.fees_amount_cents
Expand All @@ -27,9 +27,11 @@ def call
invoice.finalized!
end

# TODO: deduct previous progressive billing invoices

Utils::SegmentTrack.invoice_created(invoice)
SendWebhookJob.perform_later('invoice.created', invoice)
GeneratePdfAndNotifyJob.perform_later(invoice:, email: should_deliver_email?)
Invoices::GeneratePdfAndNotifyJob.perform_later(invoice:, email: should_deliver_email?)
Integrations::Aggregator::Invoices::CreateJob.perform_later(invoice:) if invoice.should_sync_invoice?
Integrations::Aggregator::SalesOrders::CreateJob.perform_later(invoice:) if invoice.should_sync_sales_order?
Invoices::Payments::CreateService.call(invoice)
Expand Down Expand Up @@ -66,54 +68,45 @@ def create_generating_invoice
@invoice = invoice_result.invoice
end

def sorted_thresholds
fixed = usage_thresholds.select { |t| !t.recurring }.sort_by(&:amount_cents)
recurring = usage_thresholds.select(&:recurring)
fixed + recurring
end

def create_threshold_fees
sorted_thresholds.each do |usage_threshold|
fee_result = Fees::CreateFromUsageThresholdService
.call(usage_threshold:, invoice:, amount_cents: amount_cents(usage_threshold))
fee_result.raise_if_error!
fee_result.fee
def create_fees
charges.find_each do |charge|
Fees::ChargeService.call(invoice:, charge:, subscription:, boundaries:).raise_if_error!
end
end

def should_deliver_email?
License.premium? && subscription.organization.email_settings.include?('invoice.finalized')
end

def amount_cents(usage_threshold)
if usage_threshold.recurring?
# NOTE: Recurring is always the last threshold.
# Amount is the current lifetime usage without already invoiced thresholds
# The recurring threshold can be reached multiple time, so we need to compute the number of times
units = (total_lifetime_usage_amount_cents - invoiced_amount_cents) / usage_threshold.amount_cents
units * usage_threshold.amount_cents
else
# NOTE: Amount to bill if the current threshold minus the usage that have already been invoiced
result_amount = usage_threshold.amount_cents - invoiced_amount_cents

# NOTE: Add the amount to the invoiced_amount_cents for next non recurring threshold
@invoiced_amount_cents += result_amount

result_amount
end
def charges
subscription
.plan
.charges
.joins(:billable_metric)
.includes(:taxes, billable_metric: :organization, filters: {values: :billable_metric_filter})
.where(invoiceable: true)
.where(pay_in_advance: false)
.where(billable_metrics: {recurring: false})
end

# NOTE: Sum of usage that have already been invoiced
def invoiced_amount_cents
@invoiced_amount_cents ||= subscription.invoices
.finalized
.where(invoice_type: %w[subscription progressive_billing])
.sum { |invoice| invoice.fees.where(fee_type: %w[charge progressive_billing]).sum(:amount_cents) }
def boundaries
return @boundaries if defined?(@boundaries)

invoice_subscription = invoice.invoice_subscriptions.first
date_service = Subscriptions::DatesService.new_instance(
subscription,
timestamp,
current_usage: true
)

@boundaries = {
from_datetime: invoice_subscription.from_datetime,
to_datetime: invoice_subscription.to_datetime,
charges_from_datetime: invoice_subscription.charges_from_datetime,
charges_to_datetime: invoice_subscription.charges_to_datetime,
timestamp: timestamp,
charges_duration: date_service.charges_duration_in_days
}
end

# NOTE: Current lifetime usage amount
def total_lifetime_usage_amount_cents
@total_lifetime_usage_amount_cents ||= lifetime_usage.invoiced_usage_amount_cents + lifetime_usage.current_usage_amount_cents
def should_deliver_email?
License.premium? && subscription.organization.email_settings.include?('invoice.finalized')
end

def create_credit_note_credit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def progressive_billed_amount
subscription.invoices
.finalized
.progressive_billing
.sum { |invoice| invoice.fees.progressive_billing.sum(:amount_cents) }
vincent-pochet marked this conversation as resolved.
Show resolved Hide resolved
.sum(:fees_amount_cents)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class RemoveUsageThresholdRelationFromFees < ActiveRecord::Migration[7.1]
def up
remove_column :fees, :usage_threshold_id
end

def down
end
end
5 changes: 1 addition & 4 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion schema.graphql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 0 additions & 14 deletions spec/factories/fees.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,4 @@
invoiceable_type { 'Commitment' }
invoiceable_id { commitment.id }
end

factory :progressive_billing_fee, class: 'Fee' do
invoice
fee_type { 'progressive_billing' }
subscription { create(:subscription) }

amount_cents { 200 }
amount_currency { 'EUR' }
taxes_amount_cents { 2 }

usage_threshold { create(:usage_threshold, plan: subscription.plan) }
invoiceable_type { 'UsageThreshold' }
invoiceable_id { usage_threshold.id }
end
end
Loading