@actual-app/sync-server 25.4.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +12 -0
- package/README.md +19 -0
- package/app.js +11 -0
- package/babel.config.json +3 -0
- package/bin/@actual-app/sync-server +55 -0
- package/docker/alpine.Dockerfile +62 -0
- package/docker/ubuntu.Dockerfile +63 -0
- package/docker-compose.yml +29 -0
- package/jest.config.json +19 -0
- package/jest.global-setup.js +101 -0
- package/jest.global-teardown.js +6 -0
- package/migrations/1694360000000-create-folders.js +25 -0
- package/migrations/1694360479680-create-account-db.js +30 -0
- package/migrations/1694362247011-create-secret-table.js +16 -0
- package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
- package/migrations/1718889148000-openid.js +41 -0
- package/migrations/1719409568000-multiuser.js +116 -0
- package/package.json +64 -0
- package/src/account-db.js +239 -0
- package/src/accounts/openid.js +361 -0
- package/src/accounts/password.js +149 -0
- package/src/app-account.js +155 -0
- package/src/app-admin.js +410 -0
- package/src/app-admin.test.js +381 -0
- package/src/app-gocardless/README.md +198 -0
- package/src/app-gocardless/app-gocardless.js +274 -0
- package/src/app-gocardless/bank-factory.js +91 -0
- package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
- package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
- package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
- package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
- package/src/app-gocardless/banks/bank.interface.ts +51 -0
- package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
- package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
- package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
- package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
- package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
- package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
- package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
- package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
- package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
- package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
- package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
- package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
- package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
- package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
- package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
- package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
- package/src/app-gocardless/banks/integration-bank.js +115 -0
- package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
- package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
- package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
- package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
- package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
- package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
- package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
- package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
- package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
- package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
- package/src/app-gocardless/banks/seb_privat.js +29 -0
- package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
- package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
- package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
- package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
- package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
- package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
- package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
- package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
- package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
- package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
- package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
- package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
- package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
- package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
- package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
- package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
- package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
- package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
- package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
- package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
- package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
- package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
- package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
- package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
- package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
- package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
- package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
- package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
- package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
- package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
- package/src/app-gocardless/errors.js +84 -0
- package/src/app-gocardless/gocardless-node.types.ts +497 -0
- package/src/app-gocardless/gocardless.types.ts +93 -0
- package/src/app-gocardless/link.html +18 -0
- package/src/app-gocardless/services/gocardless-service.js +620 -0
- package/src/app-gocardless/services/tests/fixtures.js +181 -0
- package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
- package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
- package/src/app-gocardless/tests/utils.spec.js +162 -0
- package/src/app-gocardless/util/handle-error.js +16 -0
- package/src/app-gocardless/utils.js +45 -0
- package/src/app-openid.js +108 -0
- package/src/app-pluggyai/app-pluggyai.js +215 -0
- package/src/app-pluggyai/pluggyai-service.js +120 -0
- package/src/app-secrets.js +61 -0
- package/src/app-simplefin/app-simplefin.js +418 -0
- package/src/app-sync/errors.js +13 -0
- package/src/app-sync/services/files-service.js +243 -0
- package/src/app-sync/tests/services/files-service.test.js +250 -0
- package/src/app-sync/validation.js +77 -0
- package/src/app-sync.js +391 -0
- package/src/app-sync.test.js +877 -0
- package/src/app.js +145 -0
- package/src/config-types.ts +44 -0
- package/src/db.js +58 -0
- package/src/load-config.js +307 -0
- package/src/migrations.js +36 -0
- package/src/run-migrations.js +8 -0
- package/src/scripts/disable-openid.js +44 -0
- package/src/scripts/enable-openid.js +53 -0
- package/src/scripts/health-check.js +23 -0
- package/src/scripts/reset-password.js +51 -0
- package/src/secrets.test.js +83 -0
- package/src/services/secrets-service.js +94 -0
- package/src/services/user-service.js +272 -0
- package/src/sql/messages.sql +9 -0
- package/src/sync-simple.js +95 -0
- package/src/util/hash.js +5 -0
- package/src/util/middlewares.js +62 -0
- package/src/util/paths.js +13 -0
- package/src/util/payee-name.js +45 -0
- package/src/util/prompt.js +88 -0
- package/src/util/title/index.js +59 -0
- package/src/util/title/lower-case.js +93 -0
- package/src/util/title/specials.js +21 -0
- package/src/util/validate-user.js +68 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as d from 'date-fns';
|
|
2
|
+
|
|
3
|
+
import { formatPayeeName } from '../../util/payee-name.js';
|
|
4
|
+
import {
|
|
5
|
+
amountToInteger,
|
|
6
|
+
printIban,
|
|
7
|
+
sortByBookingDateOrValueDate,
|
|
8
|
+
} from '../utils.js';
|
|
9
|
+
|
|
10
|
+
const SORTED_BALANCE_TYPE_LIST = [
|
|
11
|
+
'closingBooked',
|
|
12
|
+
'expected',
|
|
13
|
+
'forwardAvailable',
|
|
14
|
+
'interimAvailable',
|
|
15
|
+
'interimBooked',
|
|
16
|
+
'nonInvoiced',
|
|
17
|
+
'openingBooked',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
21
|
+
export default {
|
|
22
|
+
institutionIds: ['IntegrationBank'],
|
|
23
|
+
|
|
24
|
+
normalizeAccount(account) {
|
|
25
|
+
console.debug(
|
|
26
|
+
'Available account properties for new institution integration',
|
|
27
|
+
{ account: JSON.stringify(account) },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
account_id: account.id,
|
|
32
|
+
institution: account.institution,
|
|
33
|
+
mask: (account?.iban || '0000').slice(-4),
|
|
34
|
+
iban: account?.iban || null,
|
|
35
|
+
name: [
|
|
36
|
+
account.name ?? account.displayName ?? account.product,
|
|
37
|
+
printIban(account),
|
|
38
|
+
account.currency,
|
|
39
|
+
]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join(' '),
|
|
42
|
+
official_name: account.product ?? `integration-${account.institution_id}`,
|
|
43
|
+
type: 'checking',
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
normalizeTransaction(transaction, _booked, editedTransaction = null) {
|
|
48
|
+
const trans = editedTransaction ?? transaction;
|
|
49
|
+
|
|
50
|
+
const date =
|
|
51
|
+
trans.date ||
|
|
52
|
+
transaction.bookingDate ||
|
|
53
|
+
transaction.bookingDateTime ||
|
|
54
|
+
transaction.valueDate ||
|
|
55
|
+
transaction.valueDateTime;
|
|
56
|
+
|
|
57
|
+
// If we couldn't find a valid date field we filter out this transaction
|
|
58
|
+
// and hope that we will import it again once the bank has processed the
|
|
59
|
+
// transaction further.
|
|
60
|
+
if (!date) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const notes =
|
|
65
|
+
trans.notes ??
|
|
66
|
+
trans.remittanceInformationUnstructured ??
|
|
67
|
+
trans.remittanceInformationUnstructuredArray?.join(' ');
|
|
68
|
+
|
|
69
|
+
transaction.remittanceInformationUnstructuredArrayString =
|
|
70
|
+
transaction.remittanceInformationUnstructuredArray?.join(',');
|
|
71
|
+
transaction.remittanceInformationStructuredArrayString =
|
|
72
|
+
transaction.remittanceInformationStructuredArray?.join(',');
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...transaction,
|
|
76
|
+
payeeName: trans.payeeName ?? formatPayeeName(trans),
|
|
77
|
+
date: d.format(d.parseISO(date), 'yyyy-MM-dd'),
|
|
78
|
+
notes,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
sortTransactions(transactions = []) {
|
|
83
|
+
console.debug(
|
|
84
|
+
'Available (first 10) transactions properties for new integration of institution in sortTransactions function',
|
|
85
|
+
{ top10Transactions: JSON.stringify(transactions.slice(0, 10)) },
|
|
86
|
+
);
|
|
87
|
+
return sortByBookingDateOrValueDate(transactions);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
91
|
+
console.debug(
|
|
92
|
+
'Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function',
|
|
93
|
+
{
|
|
94
|
+
balances: JSON.stringify(balances),
|
|
95
|
+
top10SortedTransactions: JSON.stringify(
|
|
96
|
+
sortedTransactions.slice(0, 10),
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const currentBalance = balances
|
|
102
|
+
.filter(item => SORTED_BALANCE_TYPE_LIST.includes(item.balanceType))
|
|
103
|
+
.sort(
|
|
104
|
+
(a, b) =>
|
|
105
|
+
SORTED_BALANCE_TYPE_LIST.indexOf(a.balanceType) -
|
|
106
|
+
SORTED_BALANCE_TYPE_LIST.indexOf(b.balanceType),
|
|
107
|
+
)[0];
|
|
108
|
+
return sortedTransactions.reduce(
|
|
109
|
+
(total, trans) => {
|
|
110
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
111
|
+
},
|
|
112
|
+
amountToInteger(currentBalance?.balanceAmount?.amount || 0),
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Fallback from './integration-bank.js';
|
|
2
|
+
|
|
3
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
4
|
+
export default {
|
|
5
|
+
...Fallback,
|
|
6
|
+
|
|
7
|
+
institutionIds: ['ISYBANK_ITBBITMM'],
|
|
8
|
+
|
|
9
|
+
// It has been reported that valueDate is more accurate than booking date
|
|
10
|
+
// when it is provided
|
|
11
|
+
normalizeTransaction(transaction, booked) {
|
|
12
|
+
const editedTrans = { ...transaction };
|
|
13
|
+
|
|
14
|
+
editedTrans.date = transaction.valueDate ?? transaction.bookingDate;
|
|
15
|
+
|
|
16
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Fallback from './integration-bank.js';
|
|
2
|
+
import { extractPayeeNameFromRemittanceInfo } from './util/extract-payeeName-from-remittanceInfo.js';
|
|
3
|
+
|
|
4
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
5
|
+
export default {
|
|
6
|
+
...Fallback,
|
|
7
|
+
|
|
8
|
+
institutionIds: ['KBC_KREDBEBB'],
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* For negative amounts, the only payee information we have is returned in
|
|
12
|
+
* remittanceInformationUnstructured.
|
|
13
|
+
*/
|
|
14
|
+
normalizeTransaction(transaction, booked) {
|
|
15
|
+
const editedTrans = { ...transaction };
|
|
16
|
+
|
|
17
|
+
if (Number(transaction.transactionAmount.amount) > 0) {
|
|
18
|
+
editedTrans.payeeName =
|
|
19
|
+
transaction.debtorName ||
|
|
20
|
+
transaction.remittanceInformationUnstructured ||
|
|
21
|
+
'undefined';
|
|
22
|
+
} else {
|
|
23
|
+
editedTrans.payeeName =
|
|
24
|
+
transaction.creditorName ||
|
|
25
|
+
extractPayeeNameFromRemittanceInfo(
|
|
26
|
+
transaction.remittanceInformationUnstructured,
|
|
27
|
+
['Betaling met', 'Domiciliëring', 'Overschrijving'],
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import d from 'date-fns';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: ['LHV_LHVBEE22'],
|
|
10
|
+
|
|
11
|
+
normalizeTransaction(transaction, booked) {
|
|
12
|
+
const editedTrans = { ...transaction };
|
|
13
|
+
|
|
14
|
+
// extract bookingDate and creditorName for card transactions, e.g.
|
|
15
|
+
// (..1234) 2025-01-02 09:32 CrustumOU\Poordi 3\Tallinn\10156 ESTEST
|
|
16
|
+
// bookingDate: 2025-01-02
|
|
17
|
+
// creditorName: CrustumOU
|
|
18
|
+
const cardTxRegex =
|
|
19
|
+
/^\(\.\.(\d{4})\) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)$/g;
|
|
20
|
+
const cardTxMatch = cardTxRegex.exec(
|
|
21
|
+
transaction?.remittanceInformationUnstructured,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (cardTxMatch) {
|
|
25
|
+
const extractedDate = d.parse(cardTxMatch[2], 'yyyy-MM-dd', new Date());
|
|
26
|
+
|
|
27
|
+
editedTrans.payeeName = cardTxMatch[4].split('\\')[0].trim();
|
|
28
|
+
|
|
29
|
+
if (booked && d.isValid(extractedDate)) {
|
|
30
|
+
editedTrans.date = d.format(extractedDate, 'yyyy-MM-dd');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: ['MBANK_RETAIL_BREXPLPW'],
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* When requesting transaction details for MBANK_RETAIL_BREXPLPW
|
|
13
|
+
* using gocardless API, it seems that bookingDate and valueDate are swapped.
|
|
14
|
+
* valueDate will always come before bookingDate, so as a simple fix,
|
|
15
|
+
* I have overwritten integration-bank.normalizeTransaction() here,
|
|
16
|
+
* swapped dates back (by giving valueDate higher priority) and
|
|
17
|
+
* called parent method with edited transaction as argument
|
|
18
|
+
*/
|
|
19
|
+
normalizeTransaction(transaction, booked) {
|
|
20
|
+
const editedTrans = { ...transaction };
|
|
21
|
+
|
|
22
|
+
const date =
|
|
23
|
+
transaction.valueDate ||
|
|
24
|
+
transaction.valueDateTime ||
|
|
25
|
+
transaction.bookingDate ||
|
|
26
|
+
transaction.bookingDateTime;
|
|
27
|
+
|
|
28
|
+
editedTrans.date = date;
|
|
29
|
+
|
|
30
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
sortTransactions(transactions = []) {
|
|
34
|
+
return transactions.sort(
|
|
35
|
+
(a, b) => Number(b.transactionId) - Number(a.transactionId),
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* For MBANK_RETAIL_BREXPLPW we don't know what balance was
|
|
41
|
+
* after each transaction so we have to calculate it by getting
|
|
42
|
+
* current balance from the account and subtract all the transactions
|
|
43
|
+
*
|
|
44
|
+
* As a current balance we use `interimBooked` balance type because
|
|
45
|
+
* it includes transaction placed during current day
|
|
46
|
+
*/
|
|
47
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
48
|
+
const currentBalance = balances.find(
|
|
49
|
+
balance => 'interimBooked' === balance.balanceType,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return sortedTransactions.reduce((total, trans) => {
|
|
53
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
54
|
+
}, amountToInteger(currentBalance.balanceAmount.amount));
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Fallback from './integration-bank.js';
|
|
2
|
+
|
|
3
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
4
|
+
export default {
|
|
5
|
+
...Fallback,
|
|
6
|
+
|
|
7
|
+
institutionIds: ['NATIONWIDE_NAIAGB21'],
|
|
8
|
+
|
|
9
|
+
normalizeTransaction(transaction, booked) {
|
|
10
|
+
const editedTrans = { ...transaction };
|
|
11
|
+
|
|
12
|
+
// Nationwide can sometimes return pending transactions with a date
|
|
13
|
+
// representing the latest a transaction could be booked. This stops
|
|
14
|
+
// actual's deduplication logic from working as it only checks 7 days
|
|
15
|
+
// ahead/behind and the transactionID from Nationwide changes when a
|
|
16
|
+
// transaction is booked
|
|
17
|
+
if (!booked) {
|
|
18
|
+
const useDate = new Date(
|
|
19
|
+
Math.min(
|
|
20
|
+
new Date(transaction.bookingDate).getTime(),
|
|
21
|
+
new Date().getTime(),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
editedTrans.date = useDate.toISOString().slice(0, 10);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Nationwide also occasionally returns erroneous transaction_ids
|
|
28
|
+
// that are malformed and can even change after import. This will ignore
|
|
29
|
+
// these ids and unset them. When a correct ID is returned then it will
|
|
30
|
+
// update via the deduplication logic
|
|
31
|
+
const debitCreditRegex = /^00(DEB|CRED)IT.+$/;
|
|
32
|
+
const validLengths = [
|
|
33
|
+
40, // Nationwide credit cards
|
|
34
|
+
32, // Nationwide current accounts
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
transaction.transactionId?.match(debitCreditRegex) ||
|
|
39
|
+
!validLengths.includes(transaction.transactionId?.length)
|
|
40
|
+
) {
|
|
41
|
+
transaction.transactionId = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: ['NBG_ETHNGRAAXXX'],
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fixes for the pending transactions:
|
|
13
|
+
* - Corrects amount to negative (nbg erroneously omits the minus sign in pending transactions)
|
|
14
|
+
* - Removes prefix 'ΑΓΟΡΑ' from remittance information to align with the booked transaction (necessary for fuzzy matching to work)
|
|
15
|
+
*/
|
|
16
|
+
normalizeTransaction(transaction, booked) {
|
|
17
|
+
const editedTrans = { ...transaction };
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
!transaction.transactionId &&
|
|
21
|
+
transaction.remittanceInformationUnstructured.startsWith('ΑΓΟΡΑ ')
|
|
22
|
+
) {
|
|
23
|
+
transaction.transactionAmount = {
|
|
24
|
+
amount: '-' + transaction.transactionAmount.amount,
|
|
25
|
+
currency: transaction.transactionAmount.currency,
|
|
26
|
+
};
|
|
27
|
+
editedTrans.remittanceInformationUnstructured =
|
|
28
|
+
transaction.remittanceInformationUnstructured.substring(6);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* For NBG_ETHNGRAAXXX we don't know what balance was
|
|
36
|
+
* after each transaction so we have to calculate it by getting
|
|
37
|
+
* current balance from the account and subtract all the transactions
|
|
38
|
+
*
|
|
39
|
+
* As a current balance we use `interimBooked` balance type because
|
|
40
|
+
* it includes transaction placed during current day
|
|
41
|
+
*/
|
|
42
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
43
|
+
const currentBalance = balances.find(
|
|
44
|
+
balance => 'interimAvailable' === balance.balanceType,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return sortedTransactions.reduce((total, trans) => {
|
|
48
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
49
|
+
}, amountToInteger(currentBalance.balanceAmount.amount));
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: [
|
|
10
|
+
'NORWEGIAN_NO_NORWNOK1',
|
|
11
|
+
'NORWEGIAN_SE_NORWNOK1',
|
|
12
|
+
'NORWEGIAN_DE_NORWNOK1',
|
|
13
|
+
'NORWEGIAN_DK_NORWNOK1',
|
|
14
|
+
'NORWEGIAN_ES_NORWNOK1',
|
|
15
|
+
'NORWEGIAN_FI_NORWNOK1',
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
normalizeTransaction(transaction, booked) {
|
|
19
|
+
const editedTrans = { ...transaction };
|
|
20
|
+
|
|
21
|
+
if (booked) {
|
|
22
|
+
editedTrans.date = transaction.bookingDate;
|
|
23
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* For pending transactions there are two possibilities:
|
|
28
|
+
*
|
|
29
|
+
* - Either a `valueDate` was set, in which case it corresponds to when the
|
|
30
|
+
* transaction actually occurred, or
|
|
31
|
+
* - There is no date field, in which case we try to parse the correct date
|
|
32
|
+
* out of the `remittanceInformationStructured` field.
|
|
33
|
+
*
|
|
34
|
+
* If neither case succeeds then we return `null` causing this transaction
|
|
35
|
+
* to be filtered out for now, and hopefully we'll be able to import it
|
|
36
|
+
* once the bank has processed it further.
|
|
37
|
+
*/
|
|
38
|
+
if (transaction.valueDate !== undefined) {
|
|
39
|
+
editedTrans.date = transaction.valueDate;
|
|
40
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (transaction.remittanceInformationStructured) {
|
|
44
|
+
const remittanceInfoRegex = / (\d{4}-\d{2}-\d{2}) /;
|
|
45
|
+
const matches =
|
|
46
|
+
transaction.remittanceInformationStructured.match(remittanceInfoRegex);
|
|
47
|
+
if (matches) {
|
|
48
|
+
editedTrans.date = matches[1];
|
|
49
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* For NORWEGIAN_XX_NORWNOK1 we don't know what balance was
|
|
58
|
+
* after each transaction so we have to calculate it by getting
|
|
59
|
+
* current balance from the account and subtract all the transactions
|
|
60
|
+
*
|
|
61
|
+
* As a current balance we use `expected` balance type because it
|
|
62
|
+
* corresponds to the current running balance, whereas `interimAvailable`
|
|
63
|
+
* holds the remaining credit limit.
|
|
64
|
+
*/
|
|
65
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
66
|
+
const currentBalance = balances.find(
|
|
67
|
+
balance => 'expected' === balance.balanceType,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return sortedTransactions.reduce((total, trans) => {
|
|
71
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
72
|
+
}, amountToInteger(currentBalance.balanceAmount.amount));
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Fallback from './integration-bank.js';
|
|
2
|
+
|
|
3
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
4
|
+
export default {
|
|
5
|
+
...Fallback,
|
|
6
|
+
|
|
7
|
+
institutionIds: ['REVOLUT_REVOLT21'],
|
|
8
|
+
|
|
9
|
+
normalizeTransaction(transaction, booked) {
|
|
10
|
+
const editedTrans = { ...transaction };
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
transaction.remittanceInformationUnstructuredArray[0].startsWith(
|
|
14
|
+
'Bizum payment from: ',
|
|
15
|
+
)
|
|
16
|
+
) {
|
|
17
|
+
editedTrans.payeeName =
|
|
18
|
+
transaction.remittanceInformationUnstructuredArray[0].replace(
|
|
19
|
+
'Bizum payment from: ',
|
|
20
|
+
'',
|
|
21
|
+
);
|
|
22
|
+
editedTrans.remittanceInformationUnstructured =
|
|
23
|
+
transaction.remittanceInformationUnstructuredArray[1];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
transaction.remittanceInformationUnstructuredArray[0].startsWith(
|
|
28
|
+
'Bizum payment to: ',
|
|
29
|
+
)
|
|
30
|
+
) {
|
|
31
|
+
editedTrans.remittanceInformationUnstructured =
|
|
32
|
+
transaction.remittanceInformationUnstructuredArray[1];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: ['SANDBOXFINANCE_SFIN0000'],
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* For SANDBOXFINANCE_SFIN0000 we don't know what balance was
|
|
13
|
+
* after each transaction so we have to calculate it by getting
|
|
14
|
+
* current balance from the account and subtract all the transactions
|
|
15
|
+
*
|
|
16
|
+
* As a current balance we use `interimBooked` balance type because
|
|
17
|
+
* it includes transaction placed during current day
|
|
18
|
+
*/
|
|
19
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
20
|
+
const currentBalance = balances.find(
|
|
21
|
+
balance => 'interimAvailable' === balance.balanceType,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return sortedTransactions.reduce((total, trans) => {
|
|
25
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
26
|
+
}, amountToInteger(currentBalance.balanceAmount.amount));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: [
|
|
10
|
+
'SEB_KORT_AB_NO_SKHSFI21',
|
|
11
|
+
'SEB_KORT_AB_SE_SKHSFI21',
|
|
12
|
+
'SEB_CARD_ESSESESS',
|
|
13
|
+
],
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sign of transaction amount needs to be flipped for SEB credit cards
|
|
17
|
+
*/
|
|
18
|
+
normalizeTransaction(transaction, booked) {
|
|
19
|
+
const editedTrans = { ...transaction };
|
|
20
|
+
|
|
21
|
+
// Creditor name is stored in additionInformation for SEB
|
|
22
|
+
editedTrans.creditorName = transaction.additionalInformation;
|
|
23
|
+
transaction.transactionAmount = {
|
|
24
|
+
// Flip transaction amount sign
|
|
25
|
+
amount: (-parseFloat(transaction.transactionAmount.amount)).toString(),
|
|
26
|
+
currency: transaction.transactionAmount.currency,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* For SEB_KORT_AB_NO_SKHSFI21 and SEB_KORT_AB_SE_SKHSFI21 we don't know what balance was
|
|
34
|
+
* after each transaction so we have to calculate it by getting
|
|
35
|
+
* current balance from the account and subtract all the transactions
|
|
36
|
+
*
|
|
37
|
+
* As a current balance we use `expected` and `nonInvoiced` balance types because it
|
|
38
|
+
* corresponds to the current running balance, whereas `interimAvailable`
|
|
39
|
+
* holds the remaining credit limit.
|
|
40
|
+
*/
|
|
41
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
42
|
+
const currentBalance = balances.find(
|
|
43
|
+
balance => 'expected' === balance.balanceType,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const nonInvoiced = balances.find(
|
|
47
|
+
balance => 'nonInvoiced' === balance.balanceType,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return sortedTransactions.reduce(
|
|
51
|
+
(total, trans) => {
|
|
52
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
53
|
+
},
|
|
54
|
+
-amountToInteger(currentBalance.balanceAmount.amount) +
|
|
55
|
+
amountToInteger(nonInvoiced.balanceAmount.amount),
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: ['SEB_ESSESESS_PRIVATE'],
|
|
10
|
+
|
|
11
|
+
normalizeTransaction(transaction, booked) {
|
|
12
|
+
const editedTrans = { ...transaction };
|
|
13
|
+
|
|
14
|
+
// Creditor name is stored in additionInformation for SEB
|
|
15
|
+
editedTrans.creditorName = transaction.additionalInformation;
|
|
16
|
+
|
|
17
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
21
|
+
const currentBalance = balances.find(
|
|
22
|
+
balance => 'interimBooked' === balance.balanceType,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return sortedTransactions.reduce((total, trans) => {
|
|
26
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
27
|
+
}, amountToInteger(currentBalance.balanceAmount.amount));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Fallback from './integration-bank.js';
|
|
2
|
+
|
|
3
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
4
|
+
export default {
|
|
5
|
+
...Fallback,
|
|
6
|
+
|
|
7
|
+
institutionIds: [
|
|
8
|
+
'SPARNORD_SPNODK22',
|
|
9
|
+
'LAGERNES_BANK_LAPNDKK1',
|
|
10
|
+
'ANDELSKASSEN_FALLESKASSEN_FAELDKK1',
|
|
11
|
+
],
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Banks on the BEC backend only give information regarding the transaction in additionalInformation
|
|
15
|
+
*/
|
|
16
|
+
normalizeTransaction(transaction, booked) {
|
|
17
|
+
const editedTrans = { ...transaction };
|
|
18
|
+
|
|
19
|
+
editedTrans.remittanceInformationUnstructured =
|
|
20
|
+
transaction.additionalInformation;
|
|
21
|
+
|
|
22
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { amountToInteger } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
import Fallback from './integration-bank.js';
|
|
4
|
+
|
|
5
|
+
/** @type {import('./bank.interface.js').IBank} */
|
|
6
|
+
export default {
|
|
7
|
+
...Fallback,
|
|
8
|
+
|
|
9
|
+
institutionIds: ['SPK_KARLSRUHE_KARSDE66XXX'],
|
|
10
|
+
|
|
11
|
+
normalizeTransaction(transaction, booked) {
|
|
12
|
+
const editedTrans = { ...transaction };
|
|
13
|
+
|
|
14
|
+
let remittanceInformationUnstructured;
|
|
15
|
+
|
|
16
|
+
if (transaction.remittanceInformationUnstructured) {
|
|
17
|
+
remittanceInformationUnstructured =
|
|
18
|
+
transaction.remittanceInformationUnstructured;
|
|
19
|
+
} else if (transaction.remittanceInformationStructured) {
|
|
20
|
+
remittanceInformationUnstructured =
|
|
21
|
+
transaction.remittanceInformationStructured;
|
|
22
|
+
} else if (transaction.remittanceInformationStructuredArray?.length > 0) {
|
|
23
|
+
remittanceInformationUnstructured =
|
|
24
|
+
transaction.remittanceInformationStructuredArray?.join(' ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (transaction.additionalInformation) {
|
|
28
|
+
remittanceInformationUnstructured +=
|
|
29
|
+
' ' + transaction.additionalInformation;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const usefulCreditorName =
|
|
33
|
+
transaction.ultimateCreditor ||
|
|
34
|
+
transaction.creditorName ||
|
|
35
|
+
transaction.debtorName;
|
|
36
|
+
|
|
37
|
+
editedTrans.creditorName = usefulCreditorName;
|
|
38
|
+
editedTrans.remittanceInformationUnstructured =
|
|
39
|
+
remittanceInformationUnstructured;
|
|
40
|
+
|
|
41
|
+
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* For SANDBOXFINANCE_SFIN0000 we don't know what balance was
|
|
46
|
+
* after each transaction so we have to calculate it by getting
|
|
47
|
+
* current balance from the account and subtract all the transactions
|
|
48
|
+
*
|
|
49
|
+
* As a current balance we use `interimBooked` balance type because
|
|
50
|
+
* it includes transaction placed during current day
|
|
51
|
+
*/
|
|
52
|
+
calculateStartingBalance(sortedTransactions = [], balances = []) {
|
|
53
|
+
const currentBalance = balances.find(
|
|
54
|
+
balance => 'interimAvailable' === balance.balanceType,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return sortedTransactions.reduce((total, trans) => {
|
|
58
|
+
return total - amountToInteger(trans.transactionAmount.amount);
|
|
59
|
+
}, amountToInteger(currentBalance.balanceAmount.amount));
|
|
60
|
+
},
|
|
61
|
+
};
|