diff --git a/src/app-gocardless/bank-factory.js b/src/app-gocardless/bank-factory.js index 18bfe2e74..34b1e10fd 100644 --- a/src/app-gocardless/bank-factory.js +++ b/src/app-gocardless/bank-factory.js @@ -7,6 +7,7 @@ import Belfius from './banks/belfius_gkccbebb.js'; import Berliner_Sparkasse_beladebexxx from './banks/berliner_sparkasse_beladebexxx.js'; import BnpBeGebabebb from './banks/bnp-be-gebabebb.js'; import CBCcregbebb from './banks/cbc_cregbebb.js'; +import CommerzbankCobadeff from './banks/commerzbank_cobadeff.js'; import DanskeBankDabNO22 from './banks/danskebank-dabno22.js'; import EasybankBawaatww from './banks/easybank-bawaatww.js'; import EntercardSwednokk from './banks/entercard-swednokk.js'; @@ -44,6 +45,7 @@ export const banks = [ Berliner_Sparkasse_beladebexxx, BnpBeGebabebb, CBCcregbebb, + CommerzbankCobadeff, DanskeBankDabNO22, EasybankBawaatww, EntercardSwednokk, diff --git a/src/app-gocardless/banks/commerzbank_cobadeff.js b/src/app-gocardless/banks/commerzbank_cobadeff.js new file mode 100644 index 000000000..bc00648b5 --- /dev/null +++ b/src/app-gocardless/banks/commerzbank_cobadeff.js @@ -0,0 +1,87 @@ +import Fallback from './integration-bank.js'; +import { amountToInteger, printIban } from '../utils.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['COMMERZBANK_COBADEFF'], + + accessValidForDays: 179, + + normalizeAccount(account) { + return { + account_id: account.id, + institution: account.institution, + mask: account.iban.slice(-4), + iban: account.iban, + name: [account.name, printIban(account)].join(' '), + official_name: account.product, + type: 'checking', + }; + }, + + normalizeTransaction(transaction, _booked) { + // remittanceInformationUnstructured is limited to 140 chars thus ... + // ... missing information form remittanceInformationUnstructuredArray ... + // ... so we recreate it. + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructuredArray.join(' '); + + // The limitations of remittanceInformationUnstructuredArray ... + // ... can result in split keywords. We fix these. Other ... + // ... splits will need to be fixed by user with rules. + const keywords = [ + 'End-to-End-Ref.:', + 'Mandatsref:', + 'Gläubiger-ID:', + 'SEPA-BASISLASTSCHRIFT', + 'Kartenzahlung', + 'Dauerauftrag', + ]; + keywords.forEach((keyword) => { + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured.replace( + RegExp(keyword.split('').join('\\s*'), 'gi'), + ', ' + keyword + ' ', + ); + }); + + // Clean up remittanceInformation, deduplicate payee (removing slashes ... + // ... that are added to the remittanceInformation field), and ... + // ... remove clutter like "End-to-End-Ref.: NOTPROVIDED" + const payee = transaction.creditorName || transaction.debtorName || ''; + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured + .replace(/\s*(,)?\s+/g, '$1 ') + .replace(RegExp(payee.split(' ').join('(/*| )'), 'gi'), ' ') + .replace(', End-to-End-Ref.: NOTPROVIDED', '') + .trim(); + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, + + /** + * For COMMERZBANK_COBADEFF we don't know what balance was + * after each transaction so we have to calculate it by getting + * current balance from the account and subtract all the transactions + * + * As a current balance we use `expected` balance type because it + * corresponds to the current running balance, whereas `interimAvailable` + * holds the remaining credit limit. + */ + calculateStartingBalance(sortedTransactions = [], balances = []) { + const currentBalance = balances.find( + (balance) => 'expected' === balance.balanceType, + ); + + return sortedTransactions.reduce((total, trans) => { + return total - amountToInteger(trans.transactionAmount.amount); + }, amountToInteger(currentBalance.balanceAmount.amount)); + }, +}; diff --git a/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js b/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js new file mode 100644 index 000000000..9667ce226 --- /dev/null +++ b/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js @@ -0,0 +1,110 @@ +import CommerzbankCobadeff from '../commerzbank_cobadeff.js'; + +describe('CommerzbankCobadeff', () => { + describe('#normalizeTransaction', () => { + it('correctly formats remittanceInformationUnstructured', () => { + const transaction = { + endToEndId: '1234567890', + mandateId: '321654', + bookingDate: '2024-12-20', + valueDate: '2024-12-20', + transactionAmount: { + amount: '-12.34', + currency: 'EUR', + }, + creditorName: 'SHOP NAME CITY DE', + remittanceInformationUnstructured: + 'SHOP NAME//CITY/DE\n2024-12-19T15:34:31 KFN 1 AB 1234\nKartenzahlung', + remittanceInformationUnstructuredArray: [ + 'SHOP NAME//CITY/DE', + '2024-12-19T15:34:31 KFN 1 AB 1234', + 'Kartenzahlung', + ], + remittanceInformationStructured: + 'SHOP NAME//CITY/DE 2024-12-19T15:34:31 KFN 1 AB 1234 Kartenzahlung', + internalTransactionId: '3815213adb654baeadfb231c853', + }; + const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction( + transaction, + false, + ); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + '2024-12-19T15:34:31 KFN 1 AB 1234, Kartenzahlung', + ); + }); + + it('correctly formats remittanceInformationUnstructured; repair split keyword', () => { + const transaction = { + endToEndId: '901234567890', + mandateId: 'ABC123DEF456', + bookingDate: '2024-10-11', + valueDate: '2024-10-11', + transactionAmount: { + amount: '-56.78', + currency: 'EUR', + }, + creditorName: 'Long payee name that is eaxtly 35ch', + remittanceInformationUnstructured: + 'Long payee name that is eaxtly 35ch\n901234567890/. Long description tha\nt gets cut and is very long, did I\nmention it is long\nEnd-to-En', + remittanceInformationUnstructuredArray: [ + 'Long payee name that is eaxtly 35ch', + '901234567890/. Long description tha', + 't gets cut and is very long, did I', + 'mention it is long', + 'End-to-En', + 'd-Ref.: 901234567890', + 'Mandatsref: ABC123DEF456', + 'Gläubiger-ID:', + 'AB12CDE0000000000000000012', + 'SEPA-BASISLASTSCHRIFT wiederholend', + ], + remittanceInformationStructured: + 'Long payee name that is eaxtly 35ch 901234567890/. Long description tha t gets cut and is very long, did I mention it is long End-to-En', + internalTransactionId: '812354cfdea36465asdfe', + }; + const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction( + transaction, + false, + ); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + '901234567890/. Long description tha t gets cut and is very long, did I mention it is long, End-to-End-Ref.: 901234567890, Mandatsref: ABC123DEF456, Gläubiger-ID: AB12CDE0000000000000000012, SEPA-BASISLASTSCHRIFT wiederholend', + ); + }); + + it('correctly formats remittanceInformationUnstructured; removing NOTPROVIDED', () => { + const transaction = { + endToEndId: 'NOTPROVIDED', + bookingDate: '2024-12-02', + valueDate: '2024-12-02', + transactionAmount: { + amount: '-9', + currency: 'EUR', + }, + creditorName: 'CREDITOR NAME', + creditorAccount: { + iban: 'CREDITOR000IBAN', + }, + remittanceInformationUnstructured: + 'CREDITOR NAME\nCREDITOR00BIC\nCREDITOR000IBAN\nDESCRIPTION\nEnd-to-End-Ref.: NOTPROVIDED\nDauerauftrag', + remittanceInformationUnstructuredArray: [ + 'CREDITOR NAME', + 'CREDITOR00BIC', + 'CREDITOR000IBAN', + 'DESCRIPTION', + 'End-to-End-Ref.: NOTPROVIDED', + 'Dauerauftrag', + ], + remittanceInformationStructured: + 'CREDITOR NAME CREDITOR00BIC CREDITOR000IBAN DESCRIPTION End-to-End-Ref.: NOTPROVIDED Dauerauftrag', + internalTransactionId: 'f617dc31ab77622bf13d6c95d6dd8b4a', + }; + const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction( + transaction, + false, + ); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'CREDITOR00BIC CREDITOR000IBAN DESCRIPTION, Dauerauftrag', + ); + }); + }); +}); diff --git a/src/app-gocardless/gocardless-node.types.ts b/src/app-gocardless/gocardless-node.types.ts index 539c91e4c..c5ed93205 100644 --- a/src/app-gocardless/gocardless-node.types.ts +++ b/src/app-gocardless/gocardless-node.types.ts @@ -356,7 +356,11 @@ export type Transaction = { /** * Account reference, conditional */ - creditorAccount?: string; + creditorAccount?: + | string + | { + iban?: string; + }; /** * BICFI diff --git a/upcoming-release-notes/537.md b/upcoming-release-notes/537.md new file mode 100644 index 000000000..0d34ffdf3 --- /dev/null +++ b/upcoming-release-notes/537.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [nsulzer] +--- + +Add GoCardless integration for COMMERZBANK_COBADEFF \ No newline at end of file