diff --git a/src/app-gocardless/bank-factory.js b/src/app-gocardless/bank-factory.js index e7806942..5a3fb728 100644 --- a/src/app-gocardless/bank-factory.js +++ b/src/app-gocardless/bank-factory.js @@ -21,6 +21,7 @@ import IngPlIngbplpw from './banks/ing_pl_ingbplpw.js'; import IntegrationBank from './banks/integration-bank.js'; import IsyBankItbbitmm from './banks/isybank_itbbitmm.js'; import KbcKredbebb from './banks/kbc_kredbebb.js'; +import LhvLhvbee22 from './banks/lhv-lhvbee22.js'; import MbankRetailBrexplpw from './banks/mbank_retail_brexplpw.js'; import NationwideNaiagb21 from './banks/nationwide_naiagb21.js'; import NbgEthngraaxxx from './banks/nbg_ethngraaxxx.js'; @@ -60,6 +61,7 @@ export const banks = [ IngPlIngbplpw, IsyBankItbbitmm, KbcKredbebb, + LhvLhvbee22, MbankRetailBrexplpw, NationwideNaiagb21, NbgEthngraaxxx, diff --git a/src/app-gocardless/banks/lhv-lhvbee22.js b/src/app-gocardless/banks/lhv-lhvbee22.js new file mode 100644 index 00000000..8f948c65 --- /dev/null +++ b/src/app-gocardless/banks/lhv-lhvbee22.js @@ -0,0 +1,40 @@ +import d from 'date-fns'; + +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['LHV_LHVBEE22'], + + normalizeTransaction(transaction, booked) { + // extract bookingDate and creditorName for card transactions, e.g. + // (..1234) 2025-01-02 09:32 CrustumOU\Poordi 3\Tallinn\10156 ESTEST + // bookingDate: 2025-01-02 + // creditorName: CrustumOU + const cardTxRegex = + /^\(\.\.(\d{4})\) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)$/g; + const cardTxMatch = cardTxRegex.exec( + transaction?.remittanceInformationUnstructured, + ); + + if (cardTxMatch) { + const extractedDate = d.parse(cardTxMatch[2], 'yyyy-MM-dd', new Date()); + + transaction = { + ...transaction, + creditorName: cardTxMatch[4].split('\\')[0].trim(), + }; + + if (booked && d.isValid(extractedDate)) { + transaction = { + ...transaction, + bookingDate: d.format(extractedDate, 'yyyy-MM-dd'), + }; + } + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js b/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js new file mode 100644 index 00000000..72e61fb3 --- /dev/null +++ b/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js @@ -0,0 +1,80 @@ +import LhvLhvbee22 from '../lhv-lhvbee22.js'; + +describe('#normalizeTransaction', () => { + const bookedCardTransaction = { + transactionId: '2025010300000000-1', + bookingDate: '2025-01-03', + valueDate: '2025-01-03', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + creditorName: null, + remittanceInformationUnstructured: + '(..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST', + bankTransactionCode: 'PMNT-CCRD-POSD', + internalTransactionId: 'fa000f86afb2cc7678bcff0000000000', + }; + + it('extracts booked card transaction creditor name', () => { + expect( + LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true) + .creditorName, + ).toEqual('CrustumOU'); + }); + + it('extracts booked card transaction date', () => { + expect( + LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true).bookingDate, + ).toEqual('2025-01-02'); + + expect( + LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true).date, + ).toEqual('2025-01-02'); + }); + + it.each([ + ['regular text', 'Some info'], + ['partial card text', 'PIRKUMS xxx'], + ['null value', null], + ['invalid date', '(..1234) 2025-13-45 09:32 Merchant\\Address'], + ])('normalizes non-card transaction with %s', (_, remittanceInfo) => { + const transaction = { + ...bookedCardTransaction, + remittanceInformationUnstructured: remittanceInfo, + }; + const normalized = LhvLhvbee22.normalizeTransaction(transaction, true); + + expect(normalized.bookingDate).toEqual('2025-01-03'); + expect(normalized.date).toEqual('2025-01-03'); + }); + + const pendingCardTransaction = { + transactionId: '2025010300000000-1', + valueDate: '2025-01-03', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + remittanceInformationUnstructured: + '(..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST', + }; + + it('extracts pending card transaction creditor name', () => { + expect( + LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false) + .creditorName, + ).toEqual('CrustumOU'); + }); + + it('extracts pending card transaction date', () => { + expect( + LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false) + .bookingDate, + ).toEqual(undefined); + + expect( + LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false).date, + ).toEqual('2025-01-03'); + }); +}); diff --git a/upcoming-release-notes/542.md b/upcoming-release-notes/542.md new file mode 100644 index 00000000..8b20a730 --- /dev/null +++ b/upcoming-release-notes/542.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lnagel] +--- + +Add GoCardless formatter for LHV Estonia (`LHV_LHVBEE22`).