mirror of
https://github.com/we-promise/sure.git
synced 2026-09-02 05:11:05 +00:00
* Wise connection get full transaction history and adjust for fees * undo devcontainers change * address comments in PR
149 lines
4.4 KiB
Ruby
149 lines
4.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class WiseStatement::Processor
|
|
def initialize(statement, wise_account:)
|
|
@statement = statement.with_indifferent_access
|
|
@wise_account = wise_account
|
|
end
|
|
|
|
def process
|
|
unless account.present?
|
|
Rails.logger.warn "WiseStatement::Processor - No linked account for wise_account #{wise_account.id}, skipping #{safe_id}"
|
|
return :skipped
|
|
end
|
|
|
|
result = import_main_transaction
|
|
|
|
if fee.positive?
|
|
begin
|
|
import_fee_transaction
|
|
rescue => e
|
|
Rails.logger.warn "WiseStatement::Processor - Fee transaction failed for statement #{safe_id}: #{e.message}"
|
|
end
|
|
end
|
|
|
|
result
|
|
rescue ArgumentError => e
|
|
Rails.logger.error "WiseStatement::Processor - Validation error for statement #{safe_id}: #{e.message}"
|
|
raise
|
|
rescue => e
|
|
Rails.logger.error "WiseStatement::Processor - Error for statement #{safe_id}: #{e.class}: #{e.message}"
|
|
raise
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :statement, :wise_account
|
|
|
|
def account
|
|
@account ||= wise_account.current_account
|
|
end
|
|
|
|
def import_adapter
|
|
@import_adapter ||= Account::ProviderImportAdapter.new(account)
|
|
end
|
|
|
|
def import_main_transaction
|
|
import_adapter.import_transaction(
|
|
external_id: "wise_statement_#{transaction_id}",
|
|
amount: amount,
|
|
currency: currency,
|
|
date: date,
|
|
name: name,
|
|
source: "wise",
|
|
extra: extra
|
|
)
|
|
end
|
|
|
|
# The statement amount excludes fees, so the fee is imported as its own
|
|
# entry (mirroring WiseEntry::Processor) to preserve the balance impact.
|
|
def import_fee_transaction
|
|
import_adapter.import_transaction(
|
|
external_id: "wise_statement_#{transaction_id}_fee",
|
|
amount: fee,
|
|
currency: currency,
|
|
date: date,
|
|
name: I18n.t("wise_items.entries.fee_name"),
|
|
source: "wise",
|
|
extra: { wise: { statement_id: transaction_id, type: "FEE", fee: fee } }
|
|
)
|
|
end
|
|
|
|
def transaction_id
|
|
statement[:referenceNumber].presence || statement[:id].presence || digest
|
|
end
|
|
|
|
def safe_id
|
|
statement[:referenceNumber].presence || statement[:id].presence || "unknown"
|
|
end
|
|
|
|
# Wise statements use a signed amount: credits are positive and debits are
|
|
# negative. Sure uses the opposite sign convention for imported entries.
|
|
# The statement amount is the full balance impact and includes fees, so the
|
|
# reported totalFees are excluded to match the transfer importer's net amount.
|
|
def amount
|
|
signed = statement.dig(:amount, :value).to_d
|
|
net = signed.abs - fee
|
|
net = 0 if net.negative?
|
|
|
|
signed.negative? ? net : -net
|
|
end
|
|
|
|
def currency
|
|
statement.dig(:amount, :currency).presence || wise_account.currency
|
|
end
|
|
|
|
def date
|
|
raw = statement[:date].presence
|
|
raise ArgumentError, "Wise statement missing date" unless raw
|
|
|
|
Time.parse(raw.to_s).in_time_zone(account.family.timezone).to_date
|
|
rescue ArgumentError
|
|
raise
|
|
rescue => e
|
|
raise ArgumentError, "Unable to parse Wise statement date #{raw.inspect}: #{e.message}"
|
|
end
|
|
|
|
def name
|
|
details = statement[:details]
|
|
base = details[:description].presence if details.is_a?(Hash) && details[:description].present?
|
|
base ||= details[:reference].presence if details.is_a?(Hash) && details[:reference].present?
|
|
base ||= statement[:referenceNumber].presence || I18n.t("wise_items.entries.default_name")
|
|
|
|
payment_reference.present? ? "#{base} #{payment_reference}" : base
|
|
end
|
|
|
|
def extra
|
|
{
|
|
wise: {
|
|
statement_id: transaction_id,
|
|
statement_type: statement[:type],
|
|
reference: statement[:referenceNumber],
|
|
payment_reference: payment_reference,
|
|
fee: fee.positive? ? fee : nil
|
|
}.compact
|
|
}
|
|
end
|
|
|
|
# Fee reported by Wise in totalFees. Only subtracted when denominated in the
|
|
# transaction's currency so values are never mixed.
|
|
def fee
|
|
total_fees = statement[:totalFees]
|
|
return 0 unless total_fees.is_a?(Hash)
|
|
|
|
amount_currency = statement.dig(:amount, :currency)
|
|
fee_currency = total_fees[:currency].presence
|
|
return 0 if amount_currency.present? && fee_currency.present? && amount_currency != fee_currency
|
|
|
|
total_fees[:value].to_d
|
|
end
|
|
|
|
def payment_reference
|
|
statement.dig(:details, :paymentReference).presence
|
|
end
|
|
|
|
def digest
|
|
Digest::SHA256.hexdigest(statement.to_json)[0, 24]
|
|
end
|
|
end
|