@@ -6,52 +6,113 @@ class AppliedPrepaidCreditsService < BaseService
66
77 def initialize ( invoice :, wallets :, max_wallet_decrease_attempts : DEFAULT_MAX_WALLET_DECREASE_ATTEMPTS )
88 @invoice = invoice
9- @wallet = wallets . first
9+ @wallets = wallets
1010 @max_wallet_decrease_attempts = max_wallet_decrease_attempts
1111 raise ArgumentError , "max_wallet_decrease_attempts must be between 1 and #{ DEFAULT_MAX_WALLET_DECREASE_ATTEMPTS } (inclusive)" if max_wallet_decrease_attempts < 1 || max_wallet_decrease_attempts > DEFAULT_MAX_WALLET_DECREASE_ATTEMPTS
1212
1313 super ( nil )
1414 end
1515
16- activity_loggable (
17- action : "wallet_transaction.created" ,
18- record : -> { result . wallet_transaction }
19- )
20-
2116 def call
22- if already_applied ?
17+ if wallets_already_applied ?
2318 return result . service_failure! ( code : "already_applied" , message : "Prepaid credits already applied" )
2419 end
2520
26- amount_cents = compute_amount
27-
28- ApplicationRecord . transaction do
29- wallet_transaction = create_wallet_transaction ( amount_cents )
30- result . wallet_transaction = wallet_transaction
31- amount_cents = wallet_transaction . amount_cents
32-
33- with_optimistic_lock_retry ( wallet ) do
34- Wallets ::Balance ::DecreaseService . call ( wallet :, wallet_transaction :)
21+ result . prepaid_credit_amount_cents ||= 0
22+ result . wallet_transactions ||= [ ]
23+
24+ ActiveRecord ::Base . transaction do
25+ ordered_remaining_amounts = calculate_amounts_for_fees_by_type_and_bm
26+ wallets . each do |wallet |
27+ wallet . reload
28+ wallet_fee_transactions = [ ]
29+ wallet_targets_array = wallet . wallet_targets . map do |wt |
30+ if wt &.billable_metric_id
31+ [ "charge" , wt . billable_metric_id ]
32+ end
33+ end
34+ wallet_types_array = wallet . allowed_fee_types
35+
36+ ordered_remaining_amounts . each do |fee_key , remaining_amount |
37+ next if remaining_amount <= 0
38+
39+ next unless applicable_fee? ( fee_key :, targets : wallet_targets_array , types : wallet_types_array )
40+
41+ used_amount = wallet_fee_transactions . sum { |t | t [ :amount_cents ] }
42+ remaining_wallet_balance = wallet . balance_cents - used_amount
43+ next if remaining_wallet_balance <= 0
44+
45+ transaction_amount = [ remaining_amount , remaining_wallet_balance ] . min
46+ next if transaction_amount <= 0
47+
48+ ordered_remaining_amounts [ fee_key ] -= transaction_amount
49+ wallet_fee_transactions << {
50+ fee_key : fee_key ,
51+ amount_cents : transaction_amount
52+ }
53+ end
54+
55+ total_amount_cents = wallet_fee_transactions . sum { |t | t [ :amount_cents ] }
56+ next if total_amount_cents <= 0
57+
58+ wallet_transaction = create_wallet_transaction ( wallet , total_amount_cents )
59+ amount_cents = wallet_transaction . amount_cents
60+
61+ with_optimistic_lock_retry ( wallet ) do
62+ Wallets ::Balance ::DecreaseService . call ( wallet :, wallet_transaction :, skip_refresh : true )
63+ end
64+
65+ result . wallet_transactions << wallet_transaction
66+ result . prepaid_credit_amount_cents += amount_cents
67+ invoice . prepaid_credit_amount_cents += amount_cents
3568 end
36- end
3769
38- result . prepaid_credit_amount_cents = amount_cents
39- invoice . prepaid_credit_amount_cents += amount_cents
40-
41- SendWebhookJob . perform_after_commit ( "wallet_transaction.created" , result . wallet_transaction )
70+ Customers ::RefreshWalletsService . call ( customer :, include_generating_invoices : true )
71+ invoice . save! if invoice . changed?
72+ end
4273
74+ schedule_webhook_notifications ( result . wallet_transactions )
4375 result
4476 rescue ActiveRecord ::RecordInvalid => e
4577 result . record_validation_failure! ( record : e . record )
4678 end
4779
4880 private
4981
50- attr_accessor :invoice , :wallet , :max_wallet_decrease_attempts
82+ attr_accessor :invoice , :wallets , :max_wallet_decrease_attempts
5183
52- delegate :balance_cents , to : :wallet
84+ delegate :customer , to : :invoice
85+
86+ def schedule_webhook_notifications ( wallet_transactions )
87+ wallet_transactions . each do |wt |
88+ Utils ::ActivityLog . produce_after_commit ( wt , "wallet_transaction.created" )
89+ SendWebhookJob . perform_after_commit ( "wallet_transaction.created" , wt )
90+ end
91+ end
92+
93+ def calculate_amounts_for_fees_by_type_and_bm
94+ remaining = Hash . new ( 0 )
95+
96+ invoice . fees . includes ( :charge ) . find_each do |fee |
97+ cap = fee . sub_total_excluding_taxes_amount_cents +
98+ fee . taxes_precise_amount_cents -
99+ fee . precise_credit_notes_amount_cents
100+
101+ next if cap <= 0
102+ key = [ fee . fee_type , fee . charge &.billable_metric_id ]
103+ remaining [ key ] += cap
104+ end
105+
106+ remaining . sort_by { |_ , v | -v } . to_h
107+ end
53108
54- def create_wallet_transaction ( amount_cents )
109+ def wallets_already_applied?
110+ return false unless invoice
111+
112+ WalletTransaction . exists? ( invoice_id : invoice . id , wallet_id : wallets . map ( &:id ) )
113+ end
114+
115+ def create_wallet_transaction ( wallet , amount_cents )
55116 wallet_credit = WalletCredit . from_amount_cents ( wallet :, amount_cents :)
56117
57118 result = WalletTransactions ::CreateService . call! (
@@ -82,45 +143,12 @@ def with_optimistic_lock_retry(wallet, &block)
82143 end
83144 end
84145
85- def already_applied?
86- invoice &.wallet_transactions &.exists?
87- end
88-
89- def compute_amount
90- if wallet . limited_to_billable_metrics? && billable_metric_limited_fees
91- bm_limited_fees = billable_metric_limited_fees
92- remaining_fees = invoice . fees - bm_limited_fees
93- remaining_fees = remaining_fees . reject { |fee | fee . fee_type == "charge" }
94- else
95- bm_limited_fees = [ ]
96- remaining_fees = invoice . fees
97- end
98-
99- fee_type_limited_fees = if wallet . limited_fee_types?
100- remaining_fees . filter { |fee | wallet . allowed_fee_types . include? ( fee . fee_type ) }
101- elsif wallet . limited_to_billable_metrics? && billable_metric_limited_fees
102- [ ]
103- else
104- remaining_fees
105- end
106-
107- if wallet . limited_fee_types? || wallet . limited_to_billable_metrics?
108- [ balance_cents , limited_fees_total ( bm_limited_fees + fee_type_limited_fees ) ] . min
109- else
110- [ balance_cents , invoice . total_amount_cents ] . min
111- end
112- end
113-
114- def billable_metric_limited_fees
115- @billable_metric_limited_fees ||= invoice . fees
116- . joins ( charge : :billable_metric )
117- . where ( billable_metric : { id : wallet . wallet_targets . pluck ( :billable_metric_id ) } )
118- end
146+ def applicable_fee? ( fee_key :, targets :, types :)
147+ target_match = targets . include? ( fee_key )
148+ type_match = types . include? ( fee_key . first )
149+ unrestricted_wallet = targets . empty? && types . empty?
119150
120- def limited_fees_total ( applicable_fees )
121- applicable_fees . sum do |f |
122- f . sub_total_excluding_taxes_precise_amount_cents + f . taxes_precise_amount_cents - f . precise_credit_notes_amount_cents
123- end
151+ target_match || type_match || unrestricted_wallet
124152 end
125153 end
126154end
0 commit comments