Skip to content

Commit

Permalink
feat(ProgressiveBilling) - Add historical_usage_amount_cents to Lifet…
Browse files Browse the repository at this point in the history
…imeUsage (#2457)

## Context

There's always a risk of customers not paying invoices generated at the
end of a billing period. It would be beneficial to bill customers at
given thresholds (units vs. amount) rather than waiting until the end of
the period. This approach would allow for removing customer access if
invoices are not paid and prevent having a highest amount of unpaid
invoices.

## Description

Add a `historical_usage_amount_cents` column to `LifetimeUsage`, this
column can be filled in with usage coming from an external system.

We take the historical usage into account when checking if thresholds
have been passed.
  • Loading branch information
nudded authored Aug 22, 2024
1 parent 2fc9a59 commit e7597a4
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 3 deletions.
2 changes: 2 additions & 0 deletions app/models/lifetime_usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class LifetimeUsage < ApplicationRecord

validates :current_usage_amount_cents, numericality: {greater_than_or_equal_to: 0}
validates :invoiced_usage_amount_cents, numericality: {greater_than_or_equal_to: 0}
validates :historical_usage_amount_cents, numericality: {greater_than_or_equal_to: 0}

monetize :current_usage_amount_cents,
:invoiced_usage_amount_cents,
Expand All @@ -28,6 +29,7 @@ class LifetimeUsage < ApplicationRecord
# current_usage_amount_cents :bigint default(0), not null
# current_usage_amount_refreshed_at :datetime
# deleted_at :datetime
# historical_usage_amount_cents :bigint default(0), not null
# invoiced_usage_amount_cents :bigint default(0), not null
# invoiced_usage_amount_refreshed_at :datetime
# recalculate_current_usage :boolean default(FALSE), not null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def call
actual_current_usage = lifetime_usage.current_usage_amount_cents - progressive_billed_amount
# we can end up in a situation where this goes below zero, in that case no thresholds are passed
return result if actual_current_usage.negative?
invoiced_usage = lifetime_usage.invoiced_usage_amount_cents + progressive_billed_amount
invoiced_usage = lifetime_usage.historical_usage_amount_cents + lifetime_usage.invoiced_usage_amount_cents + progressive_billed_amount

# Get the largest threshold amount
# in case there are no fixed_thresholds, this will return nil which to_i will convert to 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddHistoricalUsageToLifetimeUsage < ActiveRecord::Migration[7.1]
def change
add_column :lifetime_usages, :historical_usage_amount_cents, :bigint, default: 0, null: false
end
end
3 changes: 2 additions & 1 deletion db/schema.rb

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

11 changes: 11 additions & 0 deletions spec/models/lifetime_usage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
lifetime_usage.invoiced_usage_amount_cents = 1
expect(lifetime_usage).to be_valid
end

it 'requires that historical_usage_amount_cents is positive' do
lifetime_usage.historical_usage_amount_cents = -1
expect(lifetime_usage).not_to be_valid

lifetime_usage.historical_usage_amount_cents = 0
expect(lifetime_usage).to be_valid

lifetime_usage.historical_usage_amount_cents = 1
expect(lifetime_usage).to be_valid
end
end

describe ".needs_recalculation scope" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
RSpec.describe LifetimeUsages::UsageThresholds::CheckService, type: :service do
subject(:service) { described_class.new(lifetime_usage:, progressive_billed_amount:) }

let(:lifetime_usage) { create(:lifetime_usage, subscription:, recalculate_current_usage:, recalculate_invoiced_usage:) }
let(:lifetime_usage) { create(:lifetime_usage, subscription:, historical_usage_amount_cents:, recalculate_current_usage:, recalculate_invoiced_usage:) }
let(:progressive_billed_amount) { 0 }
let(:recalculate_current_usage) { true }
let(:recalculate_invoiced_usage) { true }
let(:subscription) { create(:subscription, customer_id: customer.id) }
let(:organization) { subscription.organization }
let(:customer) { create(:customer) }
let(:historical_usage_amount_cents) { 0 }

def create_thresholds(subscription, amounts:, recurring: nil)
amounts.each do |amount|
Expand Down Expand Up @@ -294,4 +295,32 @@ def validate_thresholds(mapping)
end
end
end

context "with historical_usage_amount_cents" do
let(:historical_usage_amount_cents) { 11 }

context "with multiple fixed thresholds" do
before do
create_thresholds(subscription, amounts: [10, 20, 31, 40])
end

it "calculates the passed thresholds correctly" do
validate_thresholds({
[0, 7] => [],
[0, 9] => [20],
[0, 10] => [20],
[9, 0] => [],
[0, 31] => [20, 31, 40],
[8, 2] => [20],
[8, 20] => [20, 31],
[8, 31] => [20, 31, 40],
[11, 1] => [],
[11, 10] => [31],
[21, 20] => [40],
[40, 2] => [],
[50, 0] => []
})
end
end
end
end

0 comments on commit e7597a4

Please sign in to comment.