Skip to content
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
47 changes: 25 additions & 22 deletions app/models/enable_banking_account/processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,32 @@ def process_account!
available_credit = nil

# For liability accounts, ensure balance sign is correct.
# DELIBERATE UX DECISION: For CreditCards, we display the available credit (credit_limit - outstanding debt)
# rather than the raw outstanding debt. Do not revert this behavior, as future maintainers should understand
# users expect to see how much credit they have left rather than their debt balance.
# The 'available_credit' calculation overrides the 'balance' variable.
if account.accountable_type == "Loan"
balance = balance.abs
elsif account.accountable_type == "CreditCard"
if enable_banking_account.credit_limit.present?
available = enable_banking_account.credit_limit - balance.abs
available_credit = [ available, 0 ].max
balance = available_credit
unless account.accountable.present?
Rails.logger.warn "EnableBankingAccount::Processor - CreditCard accountable missing for account #{account.id}"
# For CreditCards, we expect the main balance to reflect the absolute outstanding debt
# rather than available credit, to ensure net worth calculations handle the liability accurately.
# Any available credit metrics (from limits) are instead stored safely as metadata on the Accountable.
# Loans and CreditCards must always represent their outstanding balance as an absolute
# positive debt amount, regardless of the API's reported sign, to ensure the BalanceSheet
# calculates net worth accurately.
if account.accountable_type == "Loan" || account.accountable_type == "CreditCard"
# Standardize the raw balance to an absolute positive debt
outstanding_debt = balance.abs

# Override the top-level balance variable intended for the account
balance = outstanding_debt

if account.accountable_type == "CreditCard"
if enable_banking_account.credit_limit.present?
# Compute available credit based on the strictly positive outstanding debt
available = enable_banking_account.credit_limit - outstanding_debt
available_credit = [ available, 0 ].max
unless account.accountable.present?
Rails.logger.warn "EnableBankingAccount::Processor - CreditCard accountable missing for account #{account.id}"
end
elsif account.accountable&.available_credit.present?
# Fallback: no credit_limit from API — compute it using available_credit defined at account level
Rails.logger.info "Using stored available_credit fallback for account #{account.id}"
available_credit = account.accountable.available_credit
end
elsif account.accountable&.available_credit.present?
# Fallback: no credit_limit from API — compute it using available_credit defined at account level
Rails.logger.info "Using stored available_credit fallback for account #{account.id}"
available_credit = account.accountable.available_credit
outstanding = balance.abs
balance = [ available_credit - outstanding, 0 ].max
else
# Fallback: no credit_limit from API — display raw outstanding balance
# We cannot derive available credit without knowing the limit; leave balance unchanged.
end
end

Expand Down
11 changes: 6 additions & 5 deletions test/models/enable_banking_account/processor_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class EnableBankingAccount::ProcessorTest < ActiveSupport::TestCase
assert_nil result
end

test "sets CC balance to available_credit when credit_limit is present" do
test "sets CC balance as absolute debt and tracks available_credit when limit is present" do
cc_account = accounts(:credit_card)
@enable_banking_account.update!(
current_balance: 450.00,
Expand All @@ -53,13 +53,13 @@ class EnableBankingAccount::ProcessorTest < ActiveSupport::TestCase

EnableBankingAccount::Processor.new(@enable_banking_account).process

assert_equal 550.0, cc_account.reload.cash_balance
assert_equal 450.0, cc_account.reload.cash_balance
if cc_account.accountable.respond_to?(:available_credit)
assert_equal 550.0, cc_account.accountable.reload.available_credit
end
end

test "falls back to stored available_credit when credit_limit is absent" do
test "sets CC balance as absolute debt and keeps stored available_credit when limit absent" do
cc_account = accounts(:credit_card)
cc_account.accountable.update!(available_credit: 1000.0)

Expand All @@ -70,10 +70,11 @@ class EnableBankingAccount::ProcessorTest < ActiveSupport::TestCase

EnableBankingAccount::Processor.new(@enable_banking_account).process

assert_equal 700.0, cc_account.reload.cash_balance
assert_equal 300.0, cc_account.reload.cash_balance
assert_equal 1000.0, cc_account.accountable.reload.available_credit
end

test "sets CC balance to raw outstanding when credit_limit is absent" do
test "sets CC balance to absolute debt when both limit and stored available_credit are absent" do
cc_account = accounts(:credit_card)
cc_account.accountable.update!(available_credit: nil)

Expand Down
Loading