Skip to content

Commit

Permalink
feat(tresholds): Add GraphQL for usage thresholds (#2397)
Browse files Browse the repository at this point in the history
## Roadmap Task

👉  https://getlago.canny.io/feature-requests/p/progressive-billing

## 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 GraphQL for usage thresholds.
  • Loading branch information
ivannovosad authored Aug 20, 2024
1 parent abd7434 commit 3a70c5c
Show file tree
Hide file tree
Showing 21 changed files with 548 additions and 34 deletions.
1 change: 1 addition & 0 deletions app/graphql/mutations/plans/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Create < BaseMutation

argument :charges, [Types::Charges::Input]
argument :minimum_commitment, Types::Commitments::Input, required: false
argument :usage_thresholds, [Types::UsageThresholds::Input], required: false

type Types::Plans::Object

Expand Down
1 change: 1 addition & 0 deletions app/graphql/mutations/plans/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Update < BaseMutation

argument :charges, [Types::Charges::Input]
argument :minimum_commitment, Types::Commitments::Input, required: false
argument :usage_thresholds, [Types::UsageThresholds::Input], required: false

type Types::Plans::Object

Expand Down
1 change: 1 addition & 0 deletions app/graphql/types/plans/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Object < Types::BaseObject
field :parent, Types::Plans::Object, null: true
field :pay_in_advance, Boolean, null: false
field :trial_period, Float
field :usage_thresholds, [Types::UsageThresholds::Object]

field :charges, [Types::Charges::Object]
field :taxes, [Types::Taxes::Object]
Expand Down
1 change: 1 addition & 0 deletions app/graphql/types/subscriptions/plan_overrides_input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class PlanOverridesInput < Types::BaseInputObject
argument :name, String, required: false
argument :tax_codes, [String], required: false
argument :trial_period, Float, required: false
argument :usage_thresholds, [Types::Subscriptions::UsageThresholdOverridesInput], required: false
end
end
end
11 changes: 11 additions & 0 deletions app/graphql/types/subscriptions/usage_threshold_overrides_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Types
module Subscriptions
class UsageThresholdOverridesInput < Types::BaseInputObject
argument :amount_cents, GraphQL::Types::BigInt, required: true
argument :recurring, Boolean, required: false
argument :threshold_display_name, String, required: false
end
end
end
15 changes: 15 additions & 0 deletions app/graphql/types/usage_thresholds/input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Types
module UsageThresholds
class Input < BaseInputObject
graphql_name 'UsageThresholdInput'

argument :id, ID, required: false

argument :amount_cents, GraphQL::Types::BigInt, required: false
argument :recurring, Boolean, required: false
argument :threshold_display_name, String, required: false
end
end
end
18 changes: 18 additions & 0 deletions app/graphql/types/usage_thresholds/object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Types
module UsageThresholds
class Object < Types::BaseObject
graphql_name 'UsageThreshold'

field :id, ID, null: false

field :amount_cents, GraphQL::Types::BigInt, null: false
field :recurring, Boolean, null: false
field :threshold_display_name, String, null: true

field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end
end
13 changes: 5 additions & 8 deletions app/services/plans/override_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,11 @@ def call
Charges::OverrideService.call(charge:, params: charge_params)
end

if License.premium? && plan.organization.premium_integrations.include?('progressive_billing')
plan.usage_thresholds.find_each do |threshold|
threshold_params = (
params[:usage_thresholds]&.find { |p| p[:id] == threshold.id } || {}
).merge(plan_id: new_plan.id)

UsageThresholds::OverrideService.call(threshold:, params: threshold_params)
end
if params[:usage_thresholds].present? &&
License.premium? &&
plan.organization.premium_integrations.include?('progressive_billing')

UsageThresholds::OverrideService.call(usage_thresholds_params: params[:usage_thresholds], new_plan: new_plan)
end

if params[:minimum_commitment].present? && License.premium?
Expand Down
29 changes: 15 additions & 14 deletions app/services/usage_thresholds/override_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,36 @@

module UsageThresholds
class OverrideService < BaseService
def initialize(threshold:, params:)
@threshold = threshold
@params = params
def initialize(usage_thresholds_params:, new_plan:)
@usage_thresholds_params = usage_thresholds_params
@new_plan = new_plan

super
end

def call
ActiveRecord::Base.transaction do
new_threshold = threshold.dup.tap do |c|
c.amount_cents = params[:amount_cents] if params.key?(:amount_cents)
c.recurring = params[:recurring] if params.key?(:recurring)
c.threshold_display_name = params[:threshold_display_name] if params.key?(:threshold_display_name)
c.plan_id = params[:plan_id]
end
new_threshold.save!
usage_thresholds_params.each do |params|
usage_threshold = new_plan.usage_thresholds.new(
plan_id: new_plan.id,
threshold_display_name: params[:threshold_display_name],
amount_cents: params[:amount_cents],
recurring: params[:recurring] || false
)

result.usage_threshold = new_threshold
usage_threshold.save!
end
end

result.usage_thresholds = new_plan.usage_thresholds
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
rescue BaseService::FailedResult => e
e.result
result.fail_with_error!
end

private

attr_reader :threshold, :params
attr_reader :usage_thresholds_params, :new_plan
end
end
26 changes: 26 additions & 0 deletions schema.graphql

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

Loading

0 comments on commit 3a70c5c

Please sign in to comment.