mirror of
https://github.com/we-promise/sure
synced 2026-04-25 17:15:07 +02:00
EnableBanking: use remittance for CARD-* names and merchants (#1478)
* EnableBanking: skip CARD-* counterparty in name # Conflicts: # test/models/enable_banking_entry/processor_test.rb # Conflicts: # test/models/enable_banking_entry/processor_test.rb * Fix whitespace in remittance_information array Whitespace added before 'ACME SHOP' in remittance_information. Signed-off-by: Juan José Mata <jjmata@jjmata.com> * Fix merchant creation for Wise and prefer remittance for Entry name if counterparty is CARD-XXX * Fix review * Handle scalars * Handle empty strings * Fix review * Make truncate not use ellipsis at the end --------- Signed-off-by: Juan José Mata <jjmata@jjmata.com> Co-authored-by: quentinreytinas <quentin@reytinas.fr> Co-authored-by: Juan José Mata <jjmata@jjmata.com>
This commit is contained in:
@@ -73,41 +73,32 @@ class EnableBankingEntry::Processor
|
||||
# Build name from available Enable Banking transaction fields
|
||||
# Priority: counterparty name > bank_transaction_code description > remittance_information
|
||||
|
||||
# Determine counterparty based on transaction direction
|
||||
# For outgoing payments (DBIT), counterparty is the creditor (who we paid)
|
||||
# For incoming payments (CRDT), counterparty is the debtor (who paid us)
|
||||
counterparty = if credit_debit_indicator == "CRDT"
|
||||
data.dig(:debtor, :name) || data[:debtor_name]
|
||||
else
|
||||
data.dig(:creditor, :name) || data[:creditor_name]
|
||||
end
|
||||
counterparty = counterparty_name
|
||||
return counterparty if counterparty.present? && !technical_card_counterparty?(counterparty)
|
||||
|
||||
return counterparty if counterparty.present?
|
||||
# Some institutions (e.g. Wise) use technical CARD-* identifiers as counterparties
|
||||
# Prefer remittance_information first in that case since it contains the real merchant label for Wise
|
||||
if technical_card_counterparty?(counterparty)
|
||||
remittance = primary_remittance_information
|
||||
return remittance.truncate(100) if remittance.present?
|
||||
end
|
||||
|
||||
# Fall back to bank_transaction_code description
|
||||
bank_tx_description = data.dig(:bank_transaction_code, :description)
|
||||
return bank_tx_description if bank_tx_description.present?
|
||||
|
||||
# Fall back to remittance_information
|
||||
remittance = data[:remittance_information]
|
||||
return remittance.first.truncate(100) if remittance.is_a?(Array) && remittance.first.present?
|
||||
remittance = primary_remittance_information
|
||||
return remittance.truncate(100) if remittance.present?
|
||||
|
||||
# Final fallback: use transaction type indicator
|
||||
credit_debit_indicator == "CRDT" ? "Incoming Transfer" : "Outgoing Transfer"
|
||||
end
|
||||
|
||||
def merchant
|
||||
# For outgoing payments (DBIT), merchant is the creditor (who we paid)
|
||||
# For incoming payments (CRDT), merchant is the debtor (who paid us)
|
||||
merchant_name = if credit_debit_indicator == "CRDT"
|
||||
data.dig(:debtor, :name) || data[:debtor_name]
|
||||
else
|
||||
data.dig(:creditor, :name) || data[:creditor_name]
|
||||
end
|
||||
|
||||
return nil unless merchant_name.present?
|
||||
|
||||
merchant_name = merchant_name.to_s.strip
|
||||
# Use the counterparty when it is human readable; otherwise fall back to remittance
|
||||
# for CARD-* transactions where the remittance often contains the actual merchant
|
||||
merchant_name = merchant_name_candidate
|
||||
return nil if merchant_name.blank?
|
||||
|
||||
merchant_id = Digest::MD5.hexdigest(merchant_name.downcase)
|
||||
@@ -183,6 +174,40 @@ class EnableBankingEntry::Processor
|
||||
data[:credit_debit_indicator]
|
||||
end
|
||||
|
||||
def counterparty_name
|
||||
# Determine counterparty based on transaction direction
|
||||
# For outgoing payments (DBIT), counterparty is the creditor (who we paid)
|
||||
# For incoming payments (CRDT), counterparty is the debtor (who paid us)
|
||||
if credit_debit_indicator == "CRDT"
|
||||
data.dig(:debtor, :name).presence || data[:debtor_name].presence
|
||||
else
|
||||
data.dig(:creditor, :name).presence || data[:creditor_name].presence
|
||||
end
|
||||
end
|
||||
|
||||
def technical_card_counterparty?(value)
|
||||
# Some providers expose card transactions with CARD-<digits> placeholders instead of a real counterparty name
|
||||
value.to_s.strip.match?(/\ACARD-\d+\z/i)
|
||||
end
|
||||
|
||||
def primary_remittance_information
|
||||
remittance = data[:remittance_information]
|
||||
Array.wrap(remittance)
|
||||
.map { |value| value.to_s.strip.presence }
|
||||
.compact
|
||||
.first
|
||||
end
|
||||
|
||||
def merchant_name_candidate
|
||||
counterparty = counterparty_name.to_s.strip
|
||||
return counterparty if counterparty.present? && !technical_card_counterparty?(counterparty)
|
||||
# For technical CARD-* counterparties, reuse remittance as the best merchant candidate
|
||||
remittance = primary_remittance_information
|
||||
return remittance.truncate(100, omission: "") if remittance.present? && technical_card_counterparty?(counterparty)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def amount
|
||||
# Sure convention: positive = outflow (debit/expense), negative = inflow (credit/income)
|
||||
# amount_value already applies this: DBIT → +absolute, CRDT → -absolute
|
||||
|
||||
Reference in New Issue
Block a user