@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.
Files changed (137) hide show
  1. package/.dockerignore +12 -0
  2. package/README.md +19 -0
  3. package/app.js +11 -0
  4. package/babel.config.json +3 -0
  5. package/bin/@actual-app/sync-server +55 -0
  6. package/docker/alpine.Dockerfile +62 -0
  7. package/docker/ubuntu.Dockerfile +63 -0
  8. package/docker-compose.yml +29 -0
  9. package/jest.config.json +19 -0
  10. package/jest.global-setup.js +101 -0
  11. package/jest.global-teardown.js +6 -0
  12. package/migrations/1694360000000-create-folders.js +25 -0
  13. package/migrations/1694360479680-create-account-db.js +30 -0
  14. package/migrations/1694362247011-create-secret-table.js +16 -0
  15. package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
  16. package/migrations/1718889148000-openid.js +41 -0
  17. package/migrations/1719409568000-multiuser.js +116 -0
  18. package/package.json +64 -0
  19. package/src/account-db.js +239 -0
  20. package/src/accounts/openid.js +361 -0
  21. package/src/accounts/password.js +149 -0
  22. package/src/app-account.js +155 -0
  23. package/src/app-admin.js +410 -0
  24. package/src/app-admin.test.js +381 -0
  25. package/src/app-gocardless/README.md +198 -0
  26. package/src/app-gocardless/app-gocardless.js +274 -0
  27. package/src/app-gocardless/bank-factory.js +91 -0
  28. package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
  29. package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
  30. package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
  31. package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
  32. package/src/app-gocardless/banks/bank.interface.ts +51 -0
  33. package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
  34. package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
  35. package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
  36. package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
  37. package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
  38. package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
  39. package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
  40. package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
  41. package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
  42. package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
  43. package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
  44. package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
  45. package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
  46. package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
  47. package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
  48. package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
  49. package/src/app-gocardless/banks/integration-bank.js +115 -0
  50. package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
  51. package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
  52. package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
  53. package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
  54. package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
  55. package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
  56. package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
  57. package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
  58. package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
  59. package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
  60. package/src/app-gocardless/banks/seb_privat.js +29 -0
  61. package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
  62. package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
  63. package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
  64. package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
  65. package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
  66. package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
  67. package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
  68. package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
  69. package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
  70. package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
  71. package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
  72. package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
  73. package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
  74. package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
  75. package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
  76. package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
  77. package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
  78. package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
  79. package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
  80. package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
  81. package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
  82. package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
  83. package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
  84. package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
  85. package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
  86. package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
  87. package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
  88. package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
  89. package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
  90. package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
  91. package/src/app-gocardless/errors.js +84 -0
  92. package/src/app-gocardless/gocardless-node.types.ts +497 -0
  93. package/src/app-gocardless/gocardless.types.ts +93 -0
  94. package/src/app-gocardless/link.html +18 -0
  95. package/src/app-gocardless/services/gocardless-service.js +620 -0
  96. package/src/app-gocardless/services/tests/fixtures.js +181 -0
  97. package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
  98. package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
  99. package/src/app-gocardless/tests/utils.spec.js +162 -0
  100. package/src/app-gocardless/util/handle-error.js +16 -0
  101. package/src/app-gocardless/utils.js +45 -0
  102. package/src/app-openid.js +108 -0
  103. package/src/app-pluggyai/app-pluggyai.js +215 -0
  104. package/src/app-pluggyai/pluggyai-service.js +120 -0
  105. package/src/app-secrets.js +61 -0
  106. package/src/app-simplefin/app-simplefin.js +418 -0
  107. package/src/app-sync/errors.js +13 -0
  108. package/src/app-sync/services/files-service.js +243 -0
  109. package/src/app-sync/tests/services/files-service.test.js +250 -0
  110. package/src/app-sync/validation.js +77 -0
  111. package/src/app-sync.js +391 -0
  112. package/src/app-sync.test.js +877 -0
  113. package/src/app.js +145 -0
  114. package/src/config-types.ts +44 -0
  115. package/src/db.js +58 -0
  116. package/src/load-config.js +307 -0
  117. package/src/migrations.js +36 -0
  118. package/src/run-migrations.js +8 -0
  119. package/src/scripts/disable-openid.js +44 -0
  120. package/src/scripts/enable-openid.js +53 -0
  121. package/src/scripts/health-check.js +23 -0
  122. package/src/scripts/reset-password.js +51 -0
  123. package/src/secrets.test.js +83 -0
  124. package/src/services/secrets-service.js +94 -0
  125. package/src/services/user-service.js +272 -0
  126. package/src/sql/messages.sql +9 -0
  127. package/src/sync-simple.js +95 -0
  128. package/src/util/hash.js +5 -0
  129. package/src/util/middlewares.js +62 -0
  130. package/src/util/paths.js +13 -0
  131. package/src/util/payee-name.js +45 -0
  132. package/src/util/prompt.js +88 -0
  133. package/src/util/title/index.js +59 -0
  134. package/src/util/title/lower-case.js +93 -0
  135. package/src/util/title/specials.js +21 -0
  136. package/src/util/validate-user.js +68 -0
  137. 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
+ };