diff --git a/src/app-gocardless/bank-factory.js b/src/app-gocardless/bank-factory.js index 1d5d33c70..831c694ec 100644 --- a/src/app-gocardless/bank-factory.js +++ b/src/app-gocardless/bank-factory.js @@ -8,6 +8,7 @@ import IngIngddeff from './banks/ing-ingddeff.js'; import IngPlIngbplpw from './banks/ing-pl-ingbplpw.js'; import IntegrationBank from './banks/integration-bank.js'; import MbankRetailBrexplpw from './banks/mbank-retail-brexplpw.js'; +import NationwideNaiaGB21 from './banks/nationwide-naiagb21.js'; import NorwegianXxNorwnok1 from './banks/norwegian-xx-norwnok1.js'; import SEBKortBankAB from './banks/seb-kort-bank-ab.js'; import SEBPrivat from './banks/seb-privat.js'; @@ -27,6 +28,7 @@ export const banks = [ IngIngddeff, IngPlIngbplpw, MbankRetailBrexplpw, + NationwideNaiaGB21, NorwegianXxNorwnok1, SEBKortBankAB, SEBPrivat, diff --git a/src/app-gocardless/banks/nationwide-naiagb21.js b/src/app-gocardless/banks/nationwide-naiagb21.js new file mode 100644 index 000000000..13908dbd3 --- /dev/null +++ b/src/app-gocardless/banks/nationwide-naiagb21.js @@ -0,0 +1,47 @@ +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['NATIONWIDE_NAIAGB21'], + + accessValidForDays: 90, + + normalizeTransaction(transaction, booked) { + // Nationwide returns pending transactions with a date representing + // the latest a transaction could be booked. This stops actual's + // deduplication logic from working as it only checks 7 days ahead/behind + // and the transactionID from Nationwide changes when a transaction is + // booked + if (!booked) { + const d = new Date(transaction.bookingDate); + d.setDate(d.getDate() - 8); + + const useDate = new Date(Math.min(d.getTime(), new Date().getTime())); + + transaction.bookingDate = useDate.toISOString().slice(0, 10); + } + + console.log(transaction); + + // Nationwide also occasionally returns erroneous transaction_ids + // that are malformed and can even change after import. This will ignore + // these ids and unset them. When a correct ID is returned then it will + // update via the deduplication logic + const debitCreditRegex = /^00(DEB|CRED)IT.+$/; + const validLengths = [ + 40, // Nationwide credit cards + 32, // Nationwide current accounts + ]; + + if ( + transaction.transactionId?.match(debitCreditRegex) || + !validLengths.includes(transaction.transactionId?.length) + ) { + transaction.transactionId = null; + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js b/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js new file mode 100644 index 000000000..5dc549528 --- /dev/null +++ b/src/app-gocardless/banks/tests/nationwide-naiagb21.spec.js @@ -0,0 +1,105 @@ +import Nationwide from '../nationwide-naiagb21.js'; +import { mockTransactionAmount } from '../../services/tests/fixtures.js'; + +describe('Nationwide', () => { + describe('#normalizeTransaction', () => { + it('retains date for booked transaction', () => { + const d = new Date(); + d.setDate(d.getDate() - 7); + + const date = d.toISOString().split('T')[0]; + + const transaction = { + bookingDate: date, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + true, + ); + + expect(normalizedTransaction.date).toEqual(date); + }); + + it('fixes date for pending transactions', () => { + const d = new Date(); + const date = d.toISOString().split('T')[0]; + + const transaction = { + bookingDate: date, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(new Date(normalizedTransaction.date).getTime()).toBeLessThan( + d.getTime(), + ); + }); + + it('keeps transactionId if in the correct format', () => { + const transactionId = 'a896729bb8b30b5ca862fe70bd5967185e2b5d3a'; + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId, + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBe(transactionId); + }); + + it('unsets transactionId if not valid length', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '0123456789', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + + it('unsets transactionId if debit placeholder found', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '00DEBIT202401010000000000-1000SUPERMARKET', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + + it('unsets transactionId if credit placeholder found', () => { + const transaction = { + bookingDate: '2024-01-01T00:00:00Z', + transactionId: '00CREDIT202401010000000000-1000SUPERMARKET', + transactionAmount: mockTransactionAmount, + }; + + const normalizedTransaction = Nationwide.normalizeTransaction( + transaction, + false, + ); + + expect(normalizedTransaction.transactionId).toBeNull(); + }); + }); +}); diff --git a/upcoming-release-notes/372.md b/upcoming-release-notes/372.md new file mode 100644 index 000000000..ed761a219 --- /dev/null +++ b/upcoming-release-notes/372.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [matt-fidd] +--- + +Add bank handler for NATIONWIDE_NAIAGB21 (Nationwide) for more accurate dates and to fix duplicate transaction issues