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(prepaid-credits): Add service for refreshing wallets #1618

Merged
merged 11 commits into from
Feb 1, 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
4 changes: 4 additions & 0 deletions app/graphql/types/wallets/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ class Object < Types::BaseObject

field :balance_cents, GraphQL::Types::BigInt, null: false
field :consumed_amount_cents, GraphQL::Types::BigInt, null: false
field :ongoing_balance_cents, GraphQL::Types::BigInt, null: false
field :ongoing_usage_balance_cents, GraphQL::Types::BigInt, null: false

field :consumed_credits, GraphQL::Types::Float, null: false
field :credits_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_usage_balance, GraphQL::Types::Float, null: false

field :last_balance_sync_at, GraphQL::Types::ISO8601DateTime, null: true
field :last_consumed_credit_at, GraphQL::Types::ISO8601DateTime, null: true
Expand Down
17 changes: 0 additions & 17 deletions app/jobs/clock/refresh_wallets_credits_job.rb

This file was deleted.

15 changes: 15 additions & 0 deletions app/jobs/clock/refresh_wallets_ongoing_balance_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Clock
class RefreshWalletsOngoingBalanceJob < ApplicationJob
queue_as 'clock'

def perform
return unless License.premium?

Wallet.active.find_each do |wallet|
Wallets::RefreshOngoingBalanceJob.perform_later(wallet)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# frozen_string_literal: true

module Wallets
class RefreshCreditsJob < ApplicationJob
class RefreshOngoingBalanceJob < ApplicationJob
queue_as 'wallets'

def perform(wallet)
Wallets::RefreshCreditsService.call(wallet:)
Wallets::Balance::RefreshOngoingService.call(wallet:)
end
end
end
2 changes: 0 additions & 2 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ class Organization < ApplicationRecord

after_create :generate_document_number_prefix

scope :credits_auto_refreshed, -> { where(credits_auto_refreshed: true) }

def logo_url
return if logo.blank?

Expand Down
4 changes: 2 additions & 2 deletions app/models/wallet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
class Wallet < ApplicationRecord
include PaperTrailTraceable

belongs_to :customer
belongs_to :customer, -> { with_discarded }

has_one :organization, through: :customer

has_many :wallet_transactions
has_many :recurring_transaction_rules

monetize :balance_cents
monetize :balance_cents, :ongoing_balance_cents, :ongoing_usage_balance_cents
monetize :consumed_amount_cents

STATUSES = [
Expand Down
2 changes: 2 additions & 0 deletions app/models/wallet_transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ class WalletTransaction < ApplicationRecord
enum status: STATUSES
enum transaction_type: TRANSACTION_TYPES
enum source: SOURCES

scope :pending, -> { where(status: :pending) }
end
4 changes: 4 additions & 0 deletions app/serializers/v1/wallet_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ def serialize
name: model.name,
rate_amount: model.rate_amount,
credits_balance: model.credits_balance,
credits_ongoing_balance: model.credits_ongoing_balance,
credits_ongoing_usage_balance: model.credits_ongoing_usage_balance,
balance_cents: model.balance_cents,
ongoing_balance_cents: model.ongoing_balance_cents,
ongoing_usage_balance_cents: model.ongoing_usage_balance_cents,
consumed_credits: model.consumed_credits,
created_at: model.created_at&.iso8601,
expiration_at: model.expiration_at&.iso8601,
Expand Down
1 change: 1 addition & 0 deletions app/services/invoices/customer_usage_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def call
return result.not_allowed_failure!(code: 'no_active_subscription') if subscription.blank?

result.usage = compute_usage
result.invoice = invoice
result
end

Expand Down
1 change: 1 addition & 0 deletions app/services/wallet_transactions/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def handle_paid_credits(wallet:, paid_credits:)
status: :pending,
source:,
)
Wallets::Balance::IncreaseOngoingService.new(wallet:, credits_amount: paid_credits_amount).call

BillPaidCreditJob.perform_later(
wallet_transaction,
Expand Down
75 changes: 75 additions & 0 deletions app/services/wallets/balance/decrease_ongoing_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module Wallets
module Balance
class DecreaseOngoingService < BaseService
def initialize(wallet:, credits_amount:)
super

@wallet = wallet
@credits_amount = credits_amount
end

def call
ongoing_usage_balance_cents = wallet.ongoing_usage_balance_cents

wallet.update!(
ongoing_usage_balance_cents: amount_cents,
credits_ongoing_usage_balance: credits_amount,
ongoing_balance_cents:,
credits_ongoing_balance:,
)

handle_threshold_top_up(ongoing_usage_balance_cents)

result.wallet = wallet
result
end

private

attr_reader :wallet, :credits_amount

def handle_threshold_top_up(ongoing_usage_balance_cents)
threshold_rule = wallet.recurring_transaction_rules.where(rule_type: :threshold).first

return if threshold_rule.nil? || wallet.credits_ongoing_balance > threshold_rule.threshold_credits
return if ongoing_usage_balance_cents == amount_cents

WalletTransactions::CreateJob.set(wait: 2.seconds).perform_later(
organization_id: wallet.organization.id,
wallet_id: wallet.id,
paid_credits: threshold_rule.paid_credits.to_s,
granted_credits: threshold_rule.granted_credits.to_s,
source: :threshold,
)
end

def currency
@currency ||= wallet.ongoing_balance.currency
end

def amount_cents
@amount_cents ||= wallet.rate_amount * credits_amount * currency.subunit_to_unit
end

def pending_transactions
@pending_transactions ||= wallet.wallet_transactions.pending
end

def ongoing_balance_cents
[
0,
(pending_transactions.sum(:amount) * currency.subunit_to_unit) - amount_cents + wallet.balance_cents,
].max
end

def credits_ongoing_balance
[
0,
pending_transactions.sum(:credit_amount) - credits_amount + wallet.credits_balance,
].max
end
end
end
end
16 changes: 1 addition & 15 deletions app/services/wallets/balance/decrease_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def call
last_consumed_credit_at: Time.current,
)

handle_threshold_top_up
Wallets::Balance::RefreshOngoingService.call(wallet:)

result.wallet = wallet
result
Expand All @@ -32,20 +32,6 @@ def call
private

attr_reader :wallet, :credits_amount

def handle_threshold_top_up
threshold_rule = wallet.recurring_transaction_rules.where(rule_type: :threshold).first

return if threshold_rule.nil? || wallet.credits_balance > threshold_rule.threshold_credits

WalletTransactions::CreateJob.set(wait: 2.seconds).perform_later(
organization_id: wallet.organization.id,
wallet_id: wallet.id,
paid_credits: threshold_rule.paid_credits.to_s,
granted_credits: threshold_rule.granted_credits.to_s,
source: :threshold,
)
end
end
end
end
33 changes: 33 additions & 0 deletions app/services/wallets/balance/increase_ongoing_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Wallets
module Balance
class IncreaseOngoingService < BaseService
def initialize(wallet:, credits_amount:)
super(nil)

@wallet = wallet
@credits_amount = credits_amount
end

def call
currency = wallet.ongoing_balance.currency
amount_cents = wallet.rate_amount * credits_amount * currency.subunit_to_unit

update_params = {
ongoing_balance_cents: wallet.ongoing_balance_cents + amount_cents,
credits_ongoing_balance: wallet.credits_ongoing_balance + credits_amount,
}

wallet.update!(update_params)

result.wallet = wallet
result
end

private

attr_reader :wallet, :credits_amount
end
end
end
3 changes: 2 additions & 1 deletion app/services/wallets/balance/increase_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Wallets
module Balance
class IncreaseService < BaseService
def initialize(wallet:, credits_amount:, reset_consumed_credits: false)
super(nil)
super

@wallet = wallet
@credits_amount = credits_amount
Expand All @@ -27,6 +27,7 @@ def call
end

wallet.update!(update_params)
Wallets::Balance::RefreshOngoingService.call(wallet:)

result.wallet = wallet
result
Expand Down
34 changes: 34 additions & 0 deletions app/services/wallets/balance/refresh_ongoing_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module Wallets
module Balance
class RefreshOngoingService < BaseService
def initialize(wallet:)
@wallet = wallet
super
end

def call
total_amount = customer.active_subscriptions.sum do |subscription|
::Invoices::CustomerUsageService.call(
nil, # current_user
customer_id: customer.external_id,
subscription_id: subscription.external_id,
organization_id: customer.organization_id,
).invoice.total_amount
end
credits_amount = total_amount.to_f.fdiv(wallet.rate_amount)
Wallets::Balance::DecreaseOngoingService.call(wallet:, credits_amount:).raise_if_error!

result.wallet = wallet
result
end

private

attr_reader :wallet

delegate :customer, to: :wallet
end
end
end
14 changes: 0 additions & 14 deletions app/services/wallets/refresh_credits_service.rb

This file was deleted.

4 changes: 2 additions & 2 deletions clock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ module Clockwork
Clock::RefreshDraftInvoicesJob.perform_later
end

every(5.minutes, 'schedule:refresh_wallets_credits') do
Clock::RefreshWalletsCreditsJob.perform_later
every(5.minutes, 'schedule:refresh_wallets_ongoing_balance') do
Clock::RefreshWalletsOngoingBalanceJob.perform_later
end

every(1.hour, 'schedule:terminate_ended_subscriptions', at: '*:05') do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class RemoveCreditsAutoRefreshedFromOrganizations < ActiveRecord::Migration[7.0]
def change
remove_column :organizations, :credits_auto_refreshed, :boolean

change_table :wallets, bulk: true do |t|
t.bigint :ongoing_balance_cents, default: 0, null: false
t.bigint :ongoing_usage_balance_cents, default: 0, null: false

t.decimal :credits_ongoing_balance, precision: 30, scale: 5, default: '0.0', null: false
t.decimal :credits_ongoing_usage_balance, precision: 30, scale: 5, default: '0.0', null: false
end
end
end
7 changes: 5 additions & 2 deletions db/schema.rb

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

Loading
Loading