Skip to content
This repository has been archived by the owner on Feb 25, 2022. It is now read-only.

Introduce config flag for N26 to only import processed transactions #38

Merged
merged 3 commits into from
Jan 2, 2019
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
1 change: 1 addition & 0 deletions config.sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ accounts:
ynab_id: # last hash in the url when you click on the account in YNAB
username: # n26 username
password: # n26 password
skip_pending_transactions: false # default: false, only imports transactions when they're processed
set_category: false # default: false, sets the N26 category name as category
25 changes: 10 additions & 15 deletions lib/dumper/n26.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def initialize(params = {})
@password = params.fetch('password')
@iban = params.fetch('iban')
@set_category = params.fetch('set_category', false)
@skip_pending_transactions = params.fetch('skip_pending_transactions',
false)
@categories = {}
end

Expand All @@ -28,10 +30,15 @@ def fetch_transactions
end

client.transactions(count: 100)
.reject { |t| t['pending'] } # Only transactions that aren't pending
.select { |t| accept?(t) }
.map { |t| to_ynab_transaction(t) }
end

def accept?(transaction)
return true unless @skip_pending_transactions
already_processed?(transaction)
end

private

def check_authorization!(client)
Expand Down Expand Up @@ -81,30 +88,18 @@ def withdrawal?(transaction)
end

def import_id(transaction)
data = [calculated_timestamp(transaction),
data = [transaction['visibleTS'],
transaction['transactionNature'],
transaction['amount'],
transaction['accountId']].join

Digest::MD5.hexdigest(data)
end

# N26 seems to have an internal timezone mismatch in their database.
# Transactions that are not processed yet have the `visibleTS` value
# in UTC but processed transactions have timezone Europe/Berlin.
# => This method checks if the transaction was processed or not.
# If it's already processed it will just take the value, if not it will
# add the current offset to make it Europe/Berlin timezone.
def calculated_timestamp(transaction)
return transaction['visibleTS'] if alread_processed?(transaction)
offset_to_utc = Time.now.in_time_zone('Europe/Berlin').utc_offset
transaction['visibleTS'] + offset_to_utc * 1000
end

# All very recent transactions with the credit card have
# the type value set to "AA". So we assume that this is an
# indicator to check if a transaction has been processed or not.
def alread_processed?(transaction)
def already_processed?(transaction)
transaction['type'] != 'AA'
end
end
Expand Down
52 changes: 41 additions & 11 deletions spec/dumper/n26_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

RSpec.describe Dumper::N26, vcr: vcr_options do
subject(:object) { Dumper::N26.new(params) }
let(:skip_pending_transactions) { false }

let(:params) do
{
'ynab_id' => '123466',
'username' => 'username',
'password' => 'password',
'iban' => 'DE89370400440532013000'
'iban' => 'DE89370400440532013000',
'skip_pending_transactions' => skip_pending_transactions
}
end

Expand Down Expand Up @@ -159,19 +161,47 @@
it 'sets it correctly' do
expect(method).to eq('46c9ccde424652bc013dca9b408dcdec')
end
end

describe '.accept?' do
subject(:accept?) { object.accept?(transaction) }

it 'is the same for a pending transaction and a processed transaction' do
expect(
object.send(:import_id, transaction_pending)
).to eq(object.send(:import_id, transaction_processed))
context 'when skip_pending_transactions feature is disabled' do
context 'when the transaction is pending' do
let(:transaction) { transaction_pending }

it 'returns true' do
expect(accept?).to be_truthy
end
end

context 'when the transaction is processed' do
let(:transaction) { transaction_processed }

it 'returns true' do
expect(accept?).to be_truthy
end
end
end
end

describe '#calculated_timestamp' do
it 'is the same for a pending transaction and a processed transaction' do
expect(
object.send(:calculated_timestamp, transaction_pending)
).to eq(object.send(:calculated_timestamp, transaction_processed))
context 'when skip_pending_transactions feature is disabled' do
let(:skip_pending_transactions) { true }

context 'when the transaction is pending' do
let(:transaction) { transaction_pending }

it 'returns false' do
expect(accept?).to be_falsy
end
end

context 'when the transaction is processed' do
let(:transaction) { transaction_processed }

it 'returns true' do
expect(accept?).to be_truthy
end
end
end
end
end