@lucianpacurar/iso20022.js 0.2.11 → 0.2.12
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/dist/index.d.ts +387 -18
- package/dist/index.js +977 -283
- package/dist/index.mjs +977 -284
- package/dist/src/camt/003/cash-management-get-account.d.ts +2 -2
- package/dist/src/camt/004/cash-management-return-account.d.ts +4 -4
- package/dist/src/camt/005/cash-management-get-transaction.d.ts +2 -2
- package/dist/src/camt/006/cash-management-return-transaction.d.ts +4 -4
- package/dist/src/camt/index.d.ts +5 -5
- package/dist/src/index.d.ts +5 -3
- package/dist/src/iso20022.d.ts +118 -0
- package/dist/src/lib/types.d.ts +56 -0
- package/dist/src/pain/001/payment-initiation.d.ts +2 -2
- package/dist/src/pain/001/sepa-credit-payment-initiation.d.ts +1 -1
- package/dist/src/pain/001/sepa-multi-credit-payment-initiation.d.ts +1 -1
- package/dist/src/pain/008/index.d.ts +2 -0
- package/dist/src/pain/008/sepa-direct-debit-payment-initiation.d.ts +198 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -7151,10 +7151,12 @@ const exportAccountIdentification = (accountId) => {
|
|
|
7151
7151
|
const obj = {
|
|
7152
7152
|
Othr: {
|
|
7153
7153
|
Id: accountId.id,
|
|
7154
|
-
}
|
|
7154
|
+
},
|
|
7155
7155
|
};
|
|
7156
7156
|
if (accountId.schemeName) {
|
|
7157
|
-
obj.Othr.SchmeNm = {
|
|
7157
|
+
obj.Othr.SchmeNm = {
|
|
7158
|
+
Cd: accountId.schemeName,
|
|
7159
|
+
}; // TODO: Add support for Prtry scheme name
|
|
7158
7160
|
}
|
|
7159
7161
|
if (accountId.issuer) {
|
|
7160
7162
|
obj.Othr.Issr = accountId.issuer;
|
|
@@ -7193,7 +7195,9 @@ const parseAmountToMinorUnits = (rawAmount, currency = 'USD') => {
|
|
|
7193
7195
|
precision: getCurrencyPrecision(currency),
|
|
7194
7196
|
});
|
|
7195
7197
|
// Also make sure Javascript number parsing error do not happen.
|
|
7196
|
-
return new Decimal(rawAmount)
|
|
7198
|
+
return new Decimal(rawAmount)
|
|
7199
|
+
.mul(10 ** currencyObject.getPrecision())
|
|
7200
|
+
.toNumber();
|
|
7197
7201
|
};
|
|
7198
7202
|
const exportAmountToString = (amount, currency = 'USD') => {
|
|
7199
7203
|
const currencyObject = Dinero({
|
|
@@ -7243,10 +7247,16 @@ const parseAdditionalInformation = (additionalInformation) => {
|
|
|
7243
7247
|
const parseMessageHeader = (rawHeader) => {
|
|
7244
7248
|
return {
|
|
7245
7249
|
id: rawHeader.MsgId,
|
|
7246
|
-
creationDateTime: rawHeader.CreDtTm
|
|
7250
|
+
creationDateTime: rawHeader.CreDtTm
|
|
7251
|
+
? parseDate(rawHeader.CreDtTm)
|
|
7252
|
+
: undefined,
|
|
7247
7253
|
queryName: rawHeader.QueryNm,
|
|
7248
|
-
requestType: rawHeader.ReqTp?.PmtCtrl ||
|
|
7249
|
-
|
|
7254
|
+
requestType: rawHeader.ReqTp?.PmtCtrl ||
|
|
7255
|
+
rawHeader.ReqTp?.Enqry ||
|
|
7256
|
+
rawHeader.ReqTp?.Prtry,
|
|
7257
|
+
originalMessageHeader: rawHeader.OrgnlBizQry
|
|
7258
|
+
? parseMessageHeader(rawHeader.OrgnlBizQry)
|
|
7259
|
+
: undefined,
|
|
7250
7260
|
};
|
|
7251
7261
|
};
|
|
7252
7262
|
const exportMessageHeader = (header) => {
|
|
@@ -7431,7 +7441,7 @@ class SWIFTCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7431
7441
|
* @param {SWIFTCreditPaymentInitiationConfig} config - The configuration object.
|
|
7432
7442
|
*/
|
|
7433
7443
|
constructor(config) {
|
|
7434
|
-
super({ type:
|
|
7444
|
+
super({ type: 'swift' });
|
|
7435
7445
|
this.initiatingParty = config.initiatingParty;
|
|
7436
7446
|
this.paymentInstructions = config.paymentInstructions;
|
|
7437
7447
|
this.messageId =
|
|
@@ -7504,9 +7514,10 @@ class SWIFTCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7504
7514
|
const parser = new fxp.XMLParser({ ignoreAttributes: false });
|
|
7505
7515
|
const xml = parser.parse(rawXml);
|
|
7506
7516
|
if (!xml.Document) {
|
|
7507
|
-
throw new InvalidXmlError(
|
|
7517
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
7508
7518
|
}
|
|
7509
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
7519
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
7520
|
+
xml.Document['@_Xmlns']);
|
|
7510
7521
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001')) {
|
|
7511
7522
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
7512
7523
|
}
|
|
@@ -7519,8 +7530,8 @@ class SWIFTCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7519
7530
|
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr?.Id,
|
|
7520
7531
|
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
7521
7532
|
agent: {
|
|
7522
|
-
bic: xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt?.FinInstnId?.BIC
|
|
7523
|
-
}
|
|
7533
|
+
bic: xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt?.FinInstnId?.BIC,
|
|
7534
|
+
},
|
|
7524
7535
|
};
|
|
7525
7536
|
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
7526
7537
|
? xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf
|
|
@@ -7532,34 +7543,38 @@ class SWIFTCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7532
7543
|
const creditor = {
|
|
7533
7544
|
name: inst.Cdtr.Nm,
|
|
7534
7545
|
agent: {
|
|
7535
|
-
bic: inst.CdtrAgt?.FinInstnId?.BIC
|
|
7546
|
+
bic: inst.CdtrAgt?.FinInstnId?.BIC,
|
|
7536
7547
|
},
|
|
7537
|
-
account:
|
|
7548
|
+
account: inst.CdtrAcct?.Id?.IBAN || inst.CdtrAcct?.Id?.Othr?.Id
|
|
7549
|
+
? parseAccount(inst.CdtrAcct)
|
|
7550
|
+
: undefined,
|
|
7538
7551
|
address: {
|
|
7539
7552
|
streetName: inst.Cdtr.PstlAdr.StrtNm,
|
|
7540
7553
|
buildingNumber: inst.Cdtr.PstlAdr.BldgNb,
|
|
7541
7554
|
postalCode: inst.Cdtr.PstlAdr.PstCd,
|
|
7542
7555
|
townName: inst.Cdtr.PstlAdr.TwnNm,
|
|
7543
7556
|
countrySubDivision: inst.Cdtr.PstlAdr.CtrySubDvsn,
|
|
7544
|
-
country: inst.Cdtr.PstlAdr.Ctry
|
|
7545
|
-
}
|
|
7557
|
+
country: inst.Cdtr.PstlAdr.Ctry,
|
|
7558
|
+
},
|
|
7546
7559
|
};
|
|
7547
7560
|
// Return instruction with validated data
|
|
7548
7561
|
return {
|
|
7549
7562
|
type: 'swift',
|
|
7550
7563
|
direction: 'credit',
|
|
7551
7564
|
...(inst.PmtId.InstrId && { id: inst.PmtId.InstrId.toString() }),
|
|
7552
|
-
...(inst.PmtId.EndToEndId && {
|
|
7565
|
+
...(inst.PmtId.EndToEndId && {
|
|
7566
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
7567
|
+
}),
|
|
7553
7568
|
amount,
|
|
7554
7569
|
currency,
|
|
7555
|
-
creditor
|
|
7570
|
+
creditor,
|
|
7556
7571
|
};
|
|
7557
7572
|
});
|
|
7558
7573
|
return new SWIFTCreditPaymentInitiation({
|
|
7559
7574
|
messageId,
|
|
7560
7575
|
creationDate,
|
|
7561
7576
|
initiatingParty: baseInitiatingParty,
|
|
7562
|
-
paymentInstructions: paymentInstructions
|
|
7577
|
+
paymentInstructions: paymentInstructions,
|
|
7563
7578
|
});
|
|
7564
7579
|
}
|
|
7565
7580
|
serialize() {
|
|
@@ -7567,7 +7582,7 @@ class SWIFTCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7567
7582
|
const xml = {
|
|
7568
7583
|
'?xml': {
|
|
7569
7584
|
'@version': '1.0',
|
|
7570
|
-
'@encoding': 'UTF-8'
|
|
7585
|
+
'@encoding': 'UTF-8',
|
|
7571
7586
|
},
|
|
7572
7587
|
Document: {
|
|
7573
7588
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -7645,7 +7660,7 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7645
7660
|
* @param {SEPACreditPaymentInitiationConfig} config - The configuration object for the SEPA credit transfer.
|
|
7646
7661
|
*/
|
|
7647
7662
|
constructor(config) {
|
|
7648
|
-
super({ type:
|
|
7663
|
+
super({ type: 'sepa' });
|
|
7649
7664
|
this.initiatingParty = config.initiatingParty;
|
|
7650
7665
|
this.paymentInstructions = config.paymentInstructions;
|
|
7651
7666
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
@@ -7667,9 +7682,11 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7667
7682
|
sumPaymentInstructions(instructions) {
|
|
7668
7683
|
this.validateAllInstructionsHaveSameCurrency();
|
|
7669
7684
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
7670
|
-
return instructionDineros
|
|
7685
|
+
return instructionDineros
|
|
7686
|
+
.reduce((acc, next) => {
|
|
7671
7687
|
return acc.add(next);
|
|
7672
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
7688
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
7689
|
+
.toFormat('0.00');
|
|
7673
7690
|
}
|
|
7674
7691
|
/**
|
|
7675
7692
|
* Validates the payment initiation data according to SEPA requirements.
|
|
@@ -7687,8 +7704,10 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7687
7704
|
// Validates that all payment instructions have the same currency
|
|
7688
7705
|
// TODO: Remove this when we figure out how to run sumPaymentInstructions safely
|
|
7689
7706
|
validateAllInstructionsHaveSameCurrency() {
|
|
7690
|
-
if (!this.paymentInstructions.every(
|
|
7691
|
-
|
|
7707
|
+
if (!this.paymentInstructions.every(i => {
|
|
7708
|
+
return i.currency === this.paymentInstructions[0].currency;
|
|
7709
|
+
})) {
|
|
7710
|
+
throw new Error('In order to calculate the payment instructions sum, all payment instruction currencies must be the same.');
|
|
7692
7711
|
}
|
|
7693
7712
|
}
|
|
7694
7713
|
/**
|
|
@@ -7699,7 +7718,10 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7699
7718
|
creditTransfer(instruction) {
|
|
7700
7719
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
7701
7720
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
7702
|
-
const dinero = Dinero({
|
|
7721
|
+
const dinero = Dinero({
|
|
7722
|
+
amount: instruction.amount,
|
|
7723
|
+
currency: instruction.currency,
|
|
7724
|
+
});
|
|
7703
7725
|
return {
|
|
7704
7726
|
PmtId: {
|
|
7705
7727
|
InstrId: paymentInstructionId,
|
|
@@ -7711,15 +7733,19 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7711
7733
|
'@Ccy': instruction.currency,
|
|
7712
7734
|
},
|
|
7713
7735
|
},
|
|
7714
|
-
...(instruction.creditor.agent && {
|
|
7736
|
+
...(instruction.creditor.agent && {
|
|
7737
|
+
CdtrAgt: this.agent(instruction.creditor.agent),
|
|
7738
|
+
}),
|
|
7715
7739
|
Cdtr: this.party(instruction.creditor),
|
|
7716
7740
|
CdtrAcct: {
|
|
7717
7741
|
Id: { IBAN: instruction.creditor.account.iban },
|
|
7718
7742
|
Ccy: instruction.currency,
|
|
7719
7743
|
},
|
|
7720
|
-
RmtInf: instruction.remittanceInformation
|
|
7721
|
-
|
|
7722
|
-
|
|
7744
|
+
RmtInf: instruction.remittanceInformation
|
|
7745
|
+
? {
|
|
7746
|
+
Ustrd: instruction.remittanceInformation,
|
|
7747
|
+
}
|
|
7748
|
+
: undefined,
|
|
7723
7749
|
};
|
|
7724
7750
|
}
|
|
7725
7751
|
/**
|
|
@@ -7731,7 +7757,7 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7731
7757
|
const xml = {
|
|
7732
7758
|
'?xml': {
|
|
7733
7759
|
'@version': '1.0',
|
|
7734
|
-
'@encoding': 'UTF-8'
|
|
7760
|
+
'@encoding': 'UTF-8',
|
|
7735
7761
|
},
|
|
7736
7762
|
Document: {
|
|
7737
7763
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -7763,7 +7789,7 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7763
7789
|
PmtTpInf: {
|
|
7764
7790
|
SvcLvl: { Cd: 'SEPA' },
|
|
7765
7791
|
...(this.categoryPurpose && {
|
|
7766
|
-
CtgyPurp: { Cd: this.categoryPurpose }
|
|
7792
|
+
CtgyPurp: { Cd: this.categoryPurpose },
|
|
7767
7793
|
}),
|
|
7768
7794
|
},
|
|
7769
7795
|
ReqdExctnDt: this.creationDate.toISOString().split('T').at(0),
|
|
@@ -7773,8 +7799,8 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7773
7799
|
ChrgBr: 'SLEV',
|
|
7774
7800
|
// payments[]
|
|
7775
7801
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
7776
|
-
}
|
|
7777
|
-
}
|
|
7802
|
+
},
|
|
7803
|
+
},
|
|
7778
7804
|
},
|
|
7779
7805
|
};
|
|
7780
7806
|
return builder.build(xml);
|
|
@@ -7783,9 +7809,10 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7783
7809
|
const parser = new fxp.XMLParser({ ignoreAttributes: false });
|
|
7784
7810
|
const xml = parser.parse(rawXml);
|
|
7785
7811
|
if (!xml.Document) {
|
|
7786
|
-
throw new InvalidXmlError(
|
|
7812
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
7787
7813
|
}
|
|
7788
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
7814
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
7815
|
+
xml.Document['@_Xmlns']);
|
|
7789
7816
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
7790
7817
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
7791
7818
|
}
|
|
@@ -7796,19 +7823,27 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7796
7823
|
}
|
|
7797
7824
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
7798
7825
|
const initiatingParty = {
|
|
7799
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
7800
|
-
|
|
7826
|
+
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
7827
|
+
xml.Document.CstmrCdtTrfInitn.PmtInf.Dbtr.Nm,
|
|
7828
|
+
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.OrgId.Othr
|
|
7829
|
+
.Id,
|
|
7801
7830
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
7802
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
7831
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
7803
7832
|
};
|
|
7804
|
-
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
7833
|
+
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
7834
|
+
? xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf
|
|
7835
|
+
: [xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf];
|
|
7805
7836
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
7806
7837
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
7807
7838
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
7808
7839
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
7809
7840
|
return {
|
|
7810
|
-
...(inst.PmtId.InstrId && {
|
|
7811
|
-
|
|
7841
|
+
...(inst.PmtId.InstrId && {
|
|
7842
|
+
id: inst.PmtId.InstrId.toString(),
|
|
7843
|
+
}),
|
|
7844
|
+
...(inst.PmtId.EndToEndId && {
|
|
7845
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
7846
|
+
}),
|
|
7812
7847
|
type: 'sepa',
|
|
7813
7848
|
direction: 'credit',
|
|
7814
7849
|
amount: amount,
|
|
@@ -7817,25 +7852,46 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
|
|
|
7817
7852
|
name: inst.Cdtr?.Nm,
|
|
7818
7853
|
agent: parseAgent(inst.CdtrAgt),
|
|
7819
7854
|
account: parseAccount(inst.CdtrAcct),
|
|
7820
|
-
...(
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7855
|
+
...(rawPostalAddress &&
|
|
7856
|
+
(rawPostalAddress.StreetName ||
|
|
7857
|
+
rawPostalAddress.BldgNb ||
|
|
7858
|
+
rawPostalAddress.PstlCd ||
|
|
7859
|
+
rawPostalAddress.TwnNm ||
|
|
7860
|
+
rawPostalAddress.Ctry)
|
|
7861
|
+
? {
|
|
7862
|
+
address: {
|
|
7863
|
+
...(rawPostalAddress.StrtNm && {
|
|
7864
|
+
streetName: rawPostalAddress.StrtNm.toString(),
|
|
7865
|
+
}),
|
|
7866
|
+
...(rawPostalAddress.BldgNb && {
|
|
7867
|
+
buildingNumber: rawPostalAddress.BldgNb.toString(),
|
|
7868
|
+
}),
|
|
7869
|
+
...(rawPostalAddress.TwnNm && {
|
|
7870
|
+
townName: rawPostalAddress.TwnNm.toString(),
|
|
7871
|
+
}),
|
|
7872
|
+
...(rawPostalAddress.CtrySubDvsn && {
|
|
7873
|
+
countrySubDivision: rawPostalAddress.CtrySubDvsn.toString(),
|
|
7874
|
+
}),
|
|
7875
|
+
...(rawPostalAddress.PstCd && {
|
|
7876
|
+
postalCode: rawPostalAddress.PstCd.toString(),
|
|
7877
|
+
}),
|
|
7878
|
+
...(rawPostalAddress.Ctry && {
|
|
7879
|
+
country: rawPostalAddress.Ctry,
|
|
7880
|
+
}),
|
|
7881
|
+
},
|
|
7828
7882
|
}
|
|
7829
|
-
|
|
7883
|
+
: {}),
|
|
7830
7884
|
},
|
|
7831
|
-
...(inst.RmtInf?.Ustrd && {
|
|
7885
|
+
...(inst.RmtInf?.Ustrd && {
|
|
7886
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
7887
|
+
}),
|
|
7832
7888
|
};
|
|
7833
7889
|
});
|
|
7834
7890
|
return new SEPACreditPaymentInitiation({
|
|
7835
7891
|
messageId: messageId,
|
|
7836
7892
|
creationDate: creationDate,
|
|
7837
7893
|
initiatingParty: initiatingParty,
|
|
7838
|
-
paymentInstructions: paymentInstructions
|
|
7894
|
+
paymentInstructions: paymentInstructions,
|
|
7839
7895
|
});
|
|
7840
7896
|
}
|
|
7841
7897
|
}
|
|
@@ -7878,7 +7934,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7878
7934
|
* @param {SEPAMultiCreditPaymentInitiationConfig} config - The configuration object for the SEPA multi credit transfer.
|
|
7879
7935
|
*/
|
|
7880
7936
|
constructor(config) {
|
|
7881
|
-
super({ type:
|
|
7937
|
+
super({ type: 'sepa' });
|
|
7882
7938
|
this.initiatingParty = config.initiatingParty;
|
|
7883
7939
|
this.paymentInstructions = config.paymentInstructions;
|
|
7884
7940
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
@@ -7941,8 +7997,10 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7941
7997
|
* @throws {Error} If payment instructions have different currencies.
|
|
7942
7998
|
*/
|
|
7943
7999
|
validateGroupInstructionsHaveSameCurrency(payments) {
|
|
7944
|
-
if (!payments.every(
|
|
7945
|
-
|
|
8000
|
+
if (!payments.every(i => {
|
|
8001
|
+
return i.currency === payments[0].currency;
|
|
8002
|
+
})) {
|
|
8003
|
+
throw new Error('In order to calculate the payment instructions sum, all payment instruction currencies within a group must be the same.');
|
|
7946
8004
|
}
|
|
7947
8005
|
}
|
|
7948
8006
|
/**
|
|
@@ -7953,7 +8011,10 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7953
8011
|
creditTransfer(instruction) {
|
|
7954
8012
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
7955
8013
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
7956
|
-
const dinero = Dinero({
|
|
8014
|
+
const dinero = Dinero({
|
|
8015
|
+
amount: instruction.amount,
|
|
8016
|
+
currency: instruction.currency,
|
|
8017
|
+
});
|
|
7957
8018
|
return {
|
|
7958
8019
|
PmtId: {
|
|
7959
8020
|
InstrId: paymentInstructionId,
|
|
@@ -7965,15 +8026,19 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7965
8026
|
'@Ccy': instruction.currency,
|
|
7966
8027
|
},
|
|
7967
8028
|
},
|
|
7968
|
-
...(instruction.creditor.agent && {
|
|
8029
|
+
...(instruction.creditor.agent && {
|
|
8030
|
+
CdtrAgt: this.agent(instruction.creditor.agent),
|
|
8031
|
+
}),
|
|
7969
8032
|
Cdtr: this.party(instruction.creditor),
|
|
7970
8033
|
CdtrAcct: {
|
|
7971
8034
|
Id: { IBAN: instruction.creditor.account.iban },
|
|
7972
8035
|
Ccy: instruction.currency,
|
|
7973
8036
|
},
|
|
7974
|
-
RmtInf: instruction.remittanceInformation
|
|
7975
|
-
|
|
7976
|
-
|
|
8037
|
+
RmtInf: instruction.remittanceInformation
|
|
8038
|
+
? {
|
|
8039
|
+
Ustrd: instruction.remittanceInformation,
|
|
8040
|
+
}
|
|
8041
|
+
: undefined,
|
|
7977
8042
|
};
|
|
7978
8043
|
}
|
|
7979
8044
|
/**
|
|
@@ -7985,7 +8050,10 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7985
8050
|
// Generate one PmtInf entry per individual payment
|
|
7986
8051
|
const paymentInfoEntries = this.paymentInstructions.flatMap((group, groupIndex) => {
|
|
7987
8052
|
return group.payments.map((payment, paymentIndex) => {
|
|
7988
|
-
const dinero = Dinero({
|
|
8053
|
+
const dinero = Dinero({
|
|
8054
|
+
amount: payment.amount,
|
|
8055
|
+
currency: payment.currency,
|
|
8056
|
+
});
|
|
7989
8057
|
const pmtInfId = sanitize(`${this.paymentInformationIdBase}-${groupIndex + 1}-${paymentIndex + 1}`, 35);
|
|
7990
8058
|
const requestedExecutionDate = payment.requestedPaymentExecutionDate || new Date();
|
|
7991
8059
|
return {
|
|
@@ -7996,7 +8064,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
7996
8064
|
PmtTpInf: {
|
|
7997
8065
|
SvcLvl: { Cd: 'SEPA' },
|
|
7998
8066
|
...(group.categoryPurpose && {
|
|
7999
|
-
CtgyPurp: { Cd: group.categoryPurpose }
|
|
8067
|
+
CtgyPurp: { Cd: group.categoryPurpose },
|
|
8000
8068
|
}),
|
|
8001
8069
|
},
|
|
8002
8070
|
ReqdExctnDt: requestedExecutionDate.toISOString().split('T')[0],
|
|
@@ -8011,7 +8079,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8011
8079
|
const xml = {
|
|
8012
8080
|
'?xml': {
|
|
8013
8081
|
'@version': '1.0',
|
|
8014
|
-
'@encoding': 'UTF-8'
|
|
8082
|
+
'@encoding': 'UTF-8',
|
|
8015
8083
|
},
|
|
8016
8084
|
Document: {
|
|
8017
8085
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8037,7 +8105,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8037
8105
|
},
|
|
8038
8106
|
},
|
|
8039
8107
|
PmtInf: paymentInfoEntries,
|
|
8040
|
-
}
|
|
8108
|
+
},
|
|
8041
8109
|
},
|
|
8042
8110
|
};
|
|
8043
8111
|
return builder.build(xml);
|
|
@@ -8055,10 +8123,11 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8055
8123
|
const xml = parser.parse(rawXml);
|
|
8056
8124
|
// Validate XML structure
|
|
8057
8125
|
if (!xml.Document) {
|
|
8058
|
-
throw new InvalidXmlError(
|
|
8126
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8059
8127
|
}
|
|
8060
8128
|
// Validate namespace
|
|
8061
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8129
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8130
|
+
xml.Document['@_Xmlns']);
|
|
8062
8131
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8063
8132
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8064
8133
|
}
|
|
@@ -8068,7 +8137,8 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8068
8137
|
// Extract top-level initiating party from GrpHdr
|
|
8069
8138
|
const topLevelInitiatingParty = {
|
|
8070
8139
|
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm,
|
|
8071
|
-
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
8140
|
+
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
8141
|
+
?.Id,
|
|
8072
8142
|
};
|
|
8073
8143
|
// Normalize PmtInf to array (handle both single object and array cases)
|
|
8074
8144
|
const rawPmtInf = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf)
|
|
@@ -8086,7 +8156,9 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8086
8156
|
// Extract optional category purpose
|
|
8087
8157
|
const categoryPurpose = pmtInf.PmtTpInf?.CtgyPurp?.Cd;
|
|
8088
8158
|
// Extract requested execution date
|
|
8089
|
-
const requestedExecutionDate = pmtInf.ReqdExctnDt
|
|
8159
|
+
const requestedExecutionDate = pmtInf.ReqdExctnDt
|
|
8160
|
+
? new Date(pmtInf.ReqdExctnDt)
|
|
8161
|
+
: undefined;
|
|
8090
8162
|
// Normalize CdtTrfTxInf to array
|
|
8091
8163
|
const rawInstructions = Array.isArray(pmtInf.CdtTrfTxInf)
|
|
8092
8164
|
? pmtInf.CdtTrfTxInf
|
|
@@ -8097,29 +8169,56 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8097
8169
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8098
8170
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8099
8171
|
return {
|
|
8100
|
-
...(inst.PmtId.InstrId && {
|
|
8101
|
-
|
|
8172
|
+
...(inst.PmtId.InstrId && {
|
|
8173
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8174
|
+
}),
|
|
8175
|
+
...(inst.PmtId.EndToEndId && {
|
|
8176
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8177
|
+
}),
|
|
8102
8178
|
type: 'sepa',
|
|
8103
8179
|
direction: 'credit',
|
|
8104
8180
|
amount: amount,
|
|
8105
8181
|
currency: currency,
|
|
8106
|
-
...(requestedExecutionDate && {
|
|
8182
|
+
...(requestedExecutionDate && {
|
|
8183
|
+
requestedPaymentExecutionDate: requestedExecutionDate,
|
|
8184
|
+
}),
|
|
8107
8185
|
creditor: {
|
|
8108
8186
|
name: inst.Cdtr?.Nm,
|
|
8109
8187
|
agent: parseAgent(inst.CdtrAgt),
|
|
8110
8188
|
account: parseAccount(inst.CdtrAcct),
|
|
8111
|
-
...(
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8189
|
+
...(rawPostalAddress &&
|
|
8190
|
+
(rawPostalAddress.StrtNm ||
|
|
8191
|
+
rawPostalAddress.BldgNb ||
|
|
8192
|
+
rawPostalAddress.PstCd ||
|
|
8193
|
+
rawPostalAddress.TwnNm ||
|
|
8194
|
+
rawPostalAddress.Ctry)
|
|
8195
|
+
? {
|
|
8196
|
+
address: {
|
|
8197
|
+
...(rawPostalAddress.StrtNm && {
|
|
8198
|
+
streetName: rawPostalAddress.StrtNm.toString(),
|
|
8199
|
+
}),
|
|
8200
|
+
...(rawPostalAddress.BldgNb && {
|
|
8201
|
+
buildingNumber: rawPostalAddress.BldgNb.toString(),
|
|
8202
|
+
}),
|
|
8203
|
+
...(rawPostalAddress.TwnNm && {
|
|
8204
|
+
townName: rawPostalAddress.TwnNm.toString(),
|
|
8205
|
+
}),
|
|
8206
|
+
...(rawPostalAddress.CtrySubDvsn && {
|
|
8207
|
+
countrySubDivision: rawPostalAddress.CtrySubDvsn.toString(),
|
|
8208
|
+
}),
|
|
8209
|
+
...(rawPostalAddress.PstCd && {
|
|
8210
|
+
postalCode: rawPostalAddress.PstCd.toString(),
|
|
8211
|
+
}),
|
|
8212
|
+
...(rawPostalAddress.Ctry && {
|
|
8213
|
+
country: rawPostalAddress.Ctry,
|
|
8214
|
+
}),
|
|
8215
|
+
},
|
|
8119
8216
|
}
|
|
8120
|
-
|
|
8217
|
+
: {}),
|
|
8121
8218
|
},
|
|
8122
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8219
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8220
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8221
|
+
}),
|
|
8123
8222
|
};
|
|
8124
8223
|
});
|
|
8125
8224
|
return {
|
|
@@ -8167,7 +8266,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8167
8266
|
paymentInformationId;
|
|
8168
8267
|
formattedPaymentSum;
|
|
8169
8268
|
constructor(config) {
|
|
8170
|
-
super({ type:
|
|
8269
|
+
super({ type: 'rtp' });
|
|
8171
8270
|
this.initiatingParty = config.initiatingParty;
|
|
8172
8271
|
this.paymentInstructions = config.paymentInstructions;
|
|
8173
8272
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
@@ -8185,9 +8284,11 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8185
8284
|
*/
|
|
8186
8285
|
sumPaymentInstructions(instructions) {
|
|
8187
8286
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
8188
|
-
return instructionDineros
|
|
8287
|
+
return instructionDineros
|
|
8288
|
+
.reduce((acc, next) => {
|
|
8189
8289
|
return acc.add(next);
|
|
8190
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8290
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8291
|
+
.toFormat('0.00');
|
|
8191
8292
|
}
|
|
8192
8293
|
/**
|
|
8193
8294
|
* Validates the payment initiation data according to SEPA requirements.
|
|
@@ -8209,7 +8310,10 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8209
8310
|
creditTransfer(instruction) {
|
|
8210
8311
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
8211
8312
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8212
|
-
const dinero = Dinero({
|
|
8313
|
+
const dinero = Dinero({
|
|
8314
|
+
amount: instruction.amount,
|
|
8315
|
+
currency: instruction.currency,
|
|
8316
|
+
});
|
|
8213
8317
|
return {
|
|
8214
8318
|
PmtId: {
|
|
8215
8319
|
InstrId: paymentInstructionId,
|
|
@@ -8230,9 +8334,11 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8230
8334
|
},
|
|
8231
8335
|
},
|
|
8232
8336
|
},
|
|
8233
|
-
RmtInf: instruction.remittanceInformation
|
|
8234
|
-
|
|
8235
|
-
|
|
8337
|
+
RmtInf: instruction.remittanceInformation
|
|
8338
|
+
? {
|
|
8339
|
+
Ustrd: instruction.remittanceInformation,
|
|
8340
|
+
}
|
|
8341
|
+
: undefined,
|
|
8236
8342
|
};
|
|
8237
8343
|
}
|
|
8238
8344
|
/**
|
|
@@ -8244,7 +8350,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8244
8350
|
const xml = {
|
|
8245
8351
|
'?xml': {
|
|
8246
8352
|
'@version': '1.0',
|
|
8247
|
-
'@encoding': 'UTF-8'
|
|
8353
|
+
'@encoding': 'UTF-8',
|
|
8248
8354
|
},
|
|
8249
8355
|
Document: {
|
|
8250
8356
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8273,7 +8379,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8273
8379
|
CtrlSum: this.formattedPaymentSum,
|
|
8274
8380
|
PmtTpInf: {
|
|
8275
8381
|
SvcLvl: { Cd: 'URNS' },
|
|
8276
|
-
LclInstrm: { Prtry:
|
|
8382
|
+
LclInstrm: { Prtry: 'RTP' },
|
|
8277
8383
|
},
|
|
8278
8384
|
ReqdExctnDt: this.creationDate.toISOString().split('T').at(0),
|
|
8279
8385
|
Dbtr: this.party(this.initiatingParty),
|
|
@@ -8282,8 +8388,8 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8282
8388
|
ChrgBr: 'SLEV',
|
|
8283
8389
|
// payments[]
|
|
8284
8390
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
8285
|
-
}
|
|
8286
|
-
}
|
|
8391
|
+
},
|
|
8392
|
+
},
|
|
8287
8393
|
},
|
|
8288
8394
|
};
|
|
8289
8395
|
return builder.build(xml);
|
|
@@ -8292,9 +8398,10 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8292
8398
|
const parser = new fxp.XMLParser({ ignoreAttributes: false });
|
|
8293
8399
|
const xml = parser.parse(rawXml);
|
|
8294
8400
|
if (!xml.Document) {
|
|
8295
|
-
throw new InvalidXmlError(
|
|
8401
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8296
8402
|
}
|
|
8297
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8403
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8404
|
+
xml.Document['@_Xmlns']);
|
|
8298
8405
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8299
8406
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8300
8407
|
}
|
|
@@ -8305,19 +8412,27 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8305
8412
|
}
|
|
8306
8413
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
8307
8414
|
const initiatingParty = {
|
|
8308
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8309
|
-
|
|
8415
|
+
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8416
|
+
xml.Document.CstmrCdtTrfInitn.PmtInf.Dbtr.Nm,
|
|
8417
|
+
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.OrgId?.Othr?.Id ||
|
|
8418
|
+
xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.OrgId?.BICOrBEI,
|
|
8310
8419
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
8311
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
8420
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
8312
8421
|
};
|
|
8313
|
-
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
8422
|
+
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
8423
|
+
? xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf
|
|
8424
|
+
: [xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf];
|
|
8314
8425
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
8315
8426
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
8316
8427
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8317
8428
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8318
8429
|
return {
|
|
8319
|
-
...(inst.PmtId.InstrId && {
|
|
8320
|
-
|
|
8430
|
+
...(inst.PmtId.InstrId && {
|
|
8431
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8432
|
+
}),
|
|
8433
|
+
...(inst.PmtId.EndToEndId && {
|
|
8434
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8435
|
+
}),
|
|
8321
8436
|
type: 'sepa',
|
|
8322
8437
|
direction: 'credit',
|
|
8323
8438
|
amount: amount,
|
|
@@ -8326,25 +8441,46 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8326
8441
|
name: inst.Cdtr?.Nm,
|
|
8327
8442
|
agent: parseAgent(inst.CdtrAgt),
|
|
8328
8443
|
account: parseAccount(inst.CdtrAcct),
|
|
8329
|
-
...(
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8444
|
+
...(rawPostalAddress &&
|
|
8445
|
+
(rawPostalAddress.StreetName ||
|
|
8446
|
+
rawPostalAddress.BldgNb ||
|
|
8447
|
+
rawPostalAddress.PstlCd ||
|
|
8448
|
+
rawPostalAddress.TwnNm ||
|
|
8449
|
+
rawPostalAddress.Ctry)
|
|
8450
|
+
? {
|
|
8451
|
+
address: {
|
|
8452
|
+
...(rawPostalAddress.StrtNm && {
|
|
8453
|
+
streetName: rawPostalAddress.StrtNm.toString(),
|
|
8454
|
+
}),
|
|
8455
|
+
...(rawPostalAddress.BldgNb && {
|
|
8456
|
+
buildingNumber: rawPostalAddress.BldgNb.toString(),
|
|
8457
|
+
}),
|
|
8458
|
+
...(rawPostalAddress.TwnNm && {
|
|
8459
|
+
townName: rawPostalAddress.TwnNm.toString(),
|
|
8460
|
+
}),
|
|
8461
|
+
...(rawPostalAddress.CtrySubDvsn && {
|
|
8462
|
+
countrySubDivision: rawPostalAddress.CtrySubDvsn.toString(),
|
|
8463
|
+
}),
|
|
8464
|
+
...(rawPostalAddress.PstCd && {
|
|
8465
|
+
postalCode: rawPostalAddress.PstCd.toString(),
|
|
8466
|
+
}),
|
|
8467
|
+
...(rawPostalAddress.Ctry && {
|
|
8468
|
+
country: rawPostalAddress.Ctry,
|
|
8469
|
+
}),
|
|
8470
|
+
},
|
|
8337
8471
|
}
|
|
8338
|
-
|
|
8472
|
+
: {}),
|
|
8339
8473
|
},
|
|
8340
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8474
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8475
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8476
|
+
}),
|
|
8341
8477
|
};
|
|
8342
8478
|
});
|
|
8343
8479
|
return new RTPCreditPaymentInitiation({
|
|
8344
8480
|
messageId: messageId,
|
|
8345
8481
|
creationDate: creationDate,
|
|
8346
8482
|
initiatingParty: initiatingParty,
|
|
8347
|
-
paymentInstructions: paymentInstructions
|
|
8483
|
+
paymentInstructions: paymentInstructions,
|
|
8348
8484
|
});
|
|
8349
8485
|
}
|
|
8350
8486
|
}
|
|
@@ -8379,14 +8515,14 @@ const ACHLocalInstrumentCode = {
|
|
|
8379
8515
|
RepresentedCheck: 'RCK',
|
|
8380
8516
|
};
|
|
8381
8517
|
const ACHLocalInstrumentCodeDescriptionMap = {
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8518
|
+
CCD: 'Corporate Credit or Debit',
|
|
8519
|
+
PPD: 'Prearranged Payment and Deposit',
|
|
8520
|
+
WEB: 'Internet-Initiated Entry',
|
|
8521
|
+
TEL: 'Telephone-Initiated Entry',
|
|
8522
|
+
POP: 'Point-of-Purchase Entry',
|
|
8523
|
+
ARC: 'Accounts Receivable Entry',
|
|
8524
|
+
BOC: 'Back Office Conversion',
|
|
8525
|
+
RCK: 'Represented Check Entry',
|
|
8390
8526
|
};
|
|
8391
8527
|
|
|
8392
8528
|
/**
|
|
@@ -8445,13 +8581,14 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8445
8581
|
instructionPriority;
|
|
8446
8582
|
formattedPaymentSum;
|
|
8447
8583
|
constructor(config) {
|
|
8448
|
-
super({ type:
|
|
8584
|
+
super({ type: 'ach' });
|
|
8449
8585
|
this.initiatingParty = config.initiatingParty;
|
|
8450
8586
|
this.paymentInstructions = config.paymentInstructions;
|
|
8451
8587
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
8452
8588
|
this.creationDate = config.creationDate || new Date();
|
|
8453
8589
|
this.paymentInformationId = sanitize(v4(), 35);
|
|
8454
|
-
this.localInstrument =
|
|
8590
|
+
this.localInstrument =
|
|
8591
|
+
config.localInstrument || ACHLocalInstrumentCode.CorporateCreditDebit;
|
|
8455
8592
|
this.serviceLevel = 'NURG'; // Normal Urgency
|
|
8456
8593
|
this.instructionPriority = 'NORM'; // Normal Priority
|
|
8457
8594
|
this.formattedPaymentSum = this.sumPaymentInstructions(this.paymentInstructions);
|
|
@@ -8466,9 +8603,11 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8466
8603
|
*/
|
|
8467
8604
|
sumPaymentInstructions(instructions) {
|
|
8468
8605
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
8469
|
-
return instructionDineros
|
|
8606
|
+
return instructionDineros
|
|
8607
|
+
.reduce((acc, next) => {
|
|
8470
8608
|
return acc.add(next);
|
|
8471
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8609
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8610
|
+
.toFormat('0.00');
|
|
8472
8611
|
}
|
|
8473
8612
|
/**
|
|
8474
8613
|
* Validates the payment initiation data according to ACH requirements.
|
|
@@ -8496,7 +8635,10 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8496
8635
|
creditTransfer(instruction) {
|
|
8497
8636
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
8498
8637
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8499
|
-
const dinero = Dinero({
|
|
8638
|
+
const dinero = Dinero({
|
|
8639
|
+
amount: instruction.amount,
|
|
8640
|
+
currency: instruction.currency,
|
|
8641
|
+
});
|
|
8500
8642
|
return {
|
|
8501
8643
|
PmtId: {
|
|
8502
8644
|
InstrId: paymentInstructionId,
|
|
@@ -8521,9 +8663,11 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8521
8663
|
},
|
|
8522
8664
|
Ccy: instruction.currency,
|
|
8523
8665
|
},
|
|
8524
|
-
RmtInf: instruction.remittanceInformation
|
|
8525
|
-
|
|
8526
|
-
|
|
8666
|
+
RmtInf: instruction.remittanceInformation
|
|
8667
|
+
? {
|
|
8668
|
+
Ustrd: instruction.remittanceInformation,
|
|
8669
|
+
}
|
|
8670
|
+
: undefined,
|
|
8527
8671
|
};
|
|
8528
8672
|
}
|
|
8529
8673
|
/**
|
|
@@ -8535,7 +8679,7 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8535
8679
|
const xml = {
|
|
8536
8680
|
'?xml': {
|
|
8537
8681
|
'@version': '1.0',
|
|
8538
|
-
'@encoding': 'UTF-8'
|
|
8682
|
+
'@encoding': 'UTF-8',
|
|
8539
8683
|
},
|
|
8540
8684
|
Document: {
|
|
8541
8685
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8573,8 +8717,8 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8573
8717
|
ChrgBr: 'SHAR',
|
|
8574
8718
|
// payments[]
|
|
8575
8719
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
8576
|
-
}
|
|
8577
|
-
}
|
|
8720
|
+
},
|
|
8721
|
+
},
|
|
8578
8722
|
},
|
|
8579
8723
|
};
|
|
8580
8724
|
return builder.build(xml);
|
|
@@ -8588,12 +8732,17 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8588
8732
|
* @throws {Error} If multiple payment information blocks are found.
|
|
8589
8733
|
*/
|
|
8590
8734
|
static fromXML(rawXml) {
|
|
8591
|
-
const parser = new fxp.XMLParser({
|
|
8735
|
+
const parser = new fxp.XMLParser({
|
|
8736
|
+
ignoreAttributes: false,
|
|
8737
|
+
attributeNamePrefix: '@_',
|
|
8738
|
+
textNodeName: '#text',
|
|
8739
|
+
});
|
|
8592
8740
|
const xml = parser.parse(rawXml);
|
|
8593
8741
|
if (!xml.Document) {
|
|
8594
|
-
throw new InvalidXmlError(
|
|
8742
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8595
8743
|
}
|
|
8596
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8744
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8745
|
+
xml.Document['@_Xmlns']);
|
|
8597
8746
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8598
8747
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8599
8748
|
}
|
|
@@ -8606,19 +8755,27 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8606
8755
|
xml.Document.CstmrCdtTrfInitn.PmtInf.PmtTpInf;
|
|
8607
8756
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
8608
8757
|
const initiatingParty = {
|
|
8609
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8610
|
-
|
|
8758
|
+
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8759
|
+
xml.Document.CstmrCdtTrfInitn.PmtInf.Dbtr.Nm,
|
|
8760
|
+
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.OrgId?.BICOrBEI ||
|
|
8761
|
+
xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.OrgId?.Othr?.Id,
|
|
8611
8762
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
8612
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
8763
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
8613
8764
|
};
|
|
8614
|
-
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
8765
|
+
const rawInstructions = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf)
|
|
8766
|
+
? xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf
|
|
8767
|
+
: [xml.Document.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf];
|
|
8615
8768
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
8616
8769
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
8617
8770
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8618
8771
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8619
8772
|
return {
|
|
8620
|
-
...(inst.PmtId.InstrId && {
|
|
8621
|
-
|
|
8773
|
+
...(inst.PmtId.InstrId && {
|
|
8774
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8775
|
+
}),
|
|
8776
|
+
...(inst.PmtId.EndToEndId && {
|
|
8777
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8778
|
+
}),
|
|
8622
8779
|
type: 'ach',
|
|
8623
8780
|
direction: 'credit',
|
|
8624
8781
|
amount: amount,
|
|
@@ -8627,41 +8784,458 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8627
8784
|
name: inst.Cdtr?.Nm,
|
|
8628
8785
|
agent: parseAgent(inst.CdtrAgt),
|
|
8629
8786
|
account: parseAccount(inst.CdtrAcct),
|
|
8630
|
-
...(
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8787
|
+
...(rawPostalAddress &&
|
|
8788
|
+
(rawPostalAddress.StrtNm ||
|
|
8789
|
+
rawPostalAddress.BldgNb ||
|
|
8790
|
+
rawPostalAddress.PstCd ||
|
|
8791
|
+
rawPostalAddress.TwnNm ||
|
|
8792
|
+
rawPostalAddress.Ctry)
|
|
8793
|
+
? {
|
|
8794
|
+
address: {
|
|
8795
|
+
...(rawPostalAddress.StrtNm && {
|
|
8796
|
+
streetName: rawPostalAddress.StrtNm.toString(),
|
|
8797
|
+
}),
|
|
8798
|
+
...(rawPostalAddress.BldgNb && {
|
|
8799
|
+
buildingNumber: rawPostalAddress.BldgNb.toString(),
|
|
8800
|
+
}),
|
|
8801
|
+
...(rawPostalAddress.TwnNm && {
|
|
8802
|
+
townName: rawPostalAddress.TwnNm.toString(),
|
|
8803
|
+
}),
|
|
8804
|
+
...(rawPostalAddress.CtrySubDvsn && {
|
|
8805
|
+
countrySubDivision: rawPostalAddress.CtrySubDvsn.toString(),
|
|
8806
|
+
}),
|
|
8807
|
+
...(rawPostalAddress.PstCd && {
|
|
8808
|
+
postalCode: rawPostalAddress.PstCd.toString(),
|
|
8809
|
+
}),
|
|
8810
|
+
...(rawPostalAddress.Ctry && {
|
|
8811
|
+
country: rawPostalAddress.Ctry,
|
|
8812
|
+
}),
|
|
8813
|
+
},
|
|
8638
8814
|
}
|
|
8639
|
-
|
|
8815
|
+
: {}),
|
|
8640
8816
|
},
|
|
8641
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8817
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8818
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8819
|
+
}),
|
|
8642
8820
|
};
|
|
8643
8821
|
});
|
|
8644
8822
|
return new ACHCreditPaymentInitiation({
|
|
8645
8823
|
messageId: messageId,
|
|
8646
8824
|
creationDate: creationDate,
|
|
8647
8825
|
initiatingParty: initiatingParty,
|
|
8648
|
-
paymentInstructions: paymentInstructions
|
|
8826
|
+
paymentInstructions: paymentInstructions,
|
|
8827
|
+
});
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
|
|
8831
|
+
/**
|
|
8832
|
+
* Represents a SEPA Direct Debit Payment Initiation.
|
|
8833
|
+
* This class handles the creation and serialization of SEPA direct debit messages
|
|
8834
|
+
* with multiple payment information blocks (multiple creditors) according to the ISO20022 pain.008 standard.
|
|
8835
|
+
* @class
|
|
8836
|
+
* @extends PaymentInitiation
|
|
8837
|
+
* @param {SEPADirectDebitPaymentInitiationConfig} config - The configuration for the SEPA Direct Debit Payment Initiation message.
|
|
8838
|
+
* @example
|
|
8839
|
+
* ```typescript
|
|
8840
|
+
* // Creating a SEPA direct debit message
|
|
8841
|
+
* const payment = new SEPADirectDebitPaymentInitiation({
|
|
8842
|
+
* initiatingParty: { name: 'Company Ltd', id: '12345' },
|
|
8843
|
+
* paymentInstructions: [
|
|
8844
|
+
* {
|
|
8845
|
+
* creditor: creditor1,
|
|
8846
|
+
* creditorSchemeId: 'DE96ZZZ00000345986',
|
|
8847
|
+
* requestedCollectionDate: new Date('2025-11-22'),
|
|
8848
|
+
* sequenceType: 'RCUR',
|
|
8849
|
+
* payments: [debit1, debit2]
|
|
8850
|
+
* }
|
|
8851
|
+
* ]
|
|
8852
|
+
* });
|
|
8853
|
+
* ```
|
|
8854
|
+
*/
|
|
8855
|
+
class SEPADirectDebitPaymentInitiation extends PaymentInitiation {
|
|
8856
|
+
initiatingParty;
|
|
8857
|
+
messageId;
|
|
8858
|
+
creationDate;
|
|
8859
|
+
paymentInstructions;
|
|
8860
|
+
paymentInformationIdBase;
|
|
8861
|
+
formattedPaymentSum;
|
|
8862
|
+
totalTransactionCount;
|
|
8863
|
+
/**
|
|
8864
|
+
* Creates an instance of SEPADirectDebitPaymentInitiation.
|
|
8865
|
+
* @param {SEPADirectDebitPaymentInitiationConfig} config - The configuration object for the SEPA direct debit.
|
|
8866
|
+
*/
|
|
8867
|
+
constructor(config) {
|
|
8868
|
+
super({ type: 'sepa' });
|
|
8869
|
+
this.initiatingParty = config.initiatingParty;
|
|
8870
|
+
this.paymentInstructions = config.paymentInstructions;
|
|
8871
|
+
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
8872
|
+
this.creationDate = config.creationDate || new Date();
|
|
8873
|
+
this.paymentInformationIdBase = sanitize(v4(), 35);
|
|
8874
|
+
this.totalTransactionCount = this.countAllTransactions();
|
|
8875
|
+
this.formattedPaymentSum = this.sumAllPayments();
|
|
8876
|
+
this.validate();
|
|
8877
|
+
}
|
|
8878
|
+
/**
|
|
8879
|
+
* Counts the total number of transactions across all payment instruction groups.
|
|
8880
|
+
* @private
|
|
8881
|
+
* @returns {number} The total count of all transactions.
|
|
8882
|
+
*/
|
|
8883
|
+
countAllTransactions() {
|
|
8884
|
+
return this.paymentInstructions.reduce((total, group) => {
|
|
8885
|
+
return total + group.payments.length;
|
|
8886
|
+
}, 0);
|
|
8887
|
+
}
|
|
8888
|
+
/**
|
|
8889
|
+
* Calculates the sum of all payment instructions across all groups.
|
|
8890
|
+
* @private
|
|
8891
|
+
* @returns {string} The total sum formatted as a string with 2 decimal places.
|
|
8892
|
+
*/
|
|
8893
|
+
sumAllPayments() {
|
|
8894
|
+
let totalAmount = 0;
|
|
8895
|
+
let currency = null;
|
|
8896
|
+
for (const group of this.paymentInstructions) {
|
|
8897
|
+
for (const payment of group.payments) {
|
|
8898
|
+
if (currency === null) {
|
|
8899
|
+
currency = payment.currency;
|
|
8900
|
+
}
|
|
8901
|
+
totalAmount += payment.amount;
|
|
8902
|
+
}
|
|
8903
|
+
}
|
|
8904
|
+
if (currency === null) {
|
|
8905
|
+
throw new Error('No payments found');
|
|
8906
|
+
}
|
|
8907
|
+
return Dinero({ amount: totalAmount, currency }).toFormat('0.00');
|
|
8908
|
+
}
|
|
8909
|
+
/**
|
|
8910
|
+
* Validates the payment initiation data according to SEPA requirements.
|
|
8911
|
+
* @private
|
|
8912
|
+
* @throws {Error} If messageId exceeds 35 characters.
|
|
8913
|
+
* @throws {Error} If any group's payment instructions have different currencies.
|
|
8914
|
+
*/
|
|
8915
|
+
validate() {
|
|
8916
|
+
if (this.messageId.length > 35) {
|
|
8917
|
+
throw new Error('messageId must not exceed 35 characters');
|
|
8918
|
+
}
|
|
8919
|
+
// Validate each group has same currency within its payments
|
|
8920
|
+
for (const group of this.paymentInstructions) {
|
|
8921
|
+
this.validateGroupInstructionsHaveSameCurrency(group.payments);
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
/**
|
|
8925
|
+
* Validates that all payment instructions in a group have the same currency (EUR).
|
|
8926
|
+
* @private
|
|
8927
|
+
* @param {AtLeastOne<SEPADirectDebitPaymentInstruction>} payments - Array of payment instructions.
|
|
8928
|
+
* @throws {Error} If payment instructions have different currencies.
|
|
8929
|
+
*/
|
|
8930
|
+
validateGroupInstructionsHaveSameCurrency(payments) {
|
|
8931
|
+
if (!payments.every(i => {
|
|
8932
|
+
return i.currency === payments[0].currency;
|
|
8933
|
+
})) {
|
|
8934
|
+
throw new Error('In order to calculate the payment instructions sum, all payment instruction currencies within a group must be the same.');
|
|
8935
|
+
}
|
|
8936
|
+
}
|
|
8937
|
+
/**
|
|
8938
|
+
* Generates payment information for a single SEPA direct debit transfer instruction.
|
|
8939
|
+
* @param {SEPADirectDebitPaymentInstruction} instruction - The payment instruction.
|
|
8940
|
+
* @returns {Object} The payment information object formatted according to SEPA direct debit specifications.
|
|
8941
|
+
*/
|
|
8942
|
+
directDebitTransfer(instruction) {
|
|
8943
|
+
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8944
|
+
const dinero = Dinero({
|
|
8945
|
+
amount: instruction.amount,
|
|
8946
|
+
currency: instruction.currency,
|
|
8947
|
+
});
|
|
8948
|
+
return {
|
|
8949
|
+
PmtId: {
|
|
8950
|
+
EndToEndId: endToEndId,
|
|
8951
|
+
},
|
|
8952
|
+
InstdAmt: {
|
|
8953
|
+
'#': dinero.toFormat('0.00'),
|
|
8954
|
+
'@Ccy': instruction.currency,
|
|
8955
|
+
},
|
|
8956
|
+
DrctDbtTx: {
|
|
8957
|
+
MndtRltdInf: {
|
|
8958
|
+
MndtId: instruction.mandate.mandateId,
|
|
8959
|
+
DtOfSgntr: instruction.mandate.dateOfSignature
|
|
8960
|
+
.toISOString()
|
|
8961
|
+
.split('T')[0],
|
|
8962
|
+
AmdmntInd: instruction.mandate.amendmentIndicator,
|
|
8963
|
+
...(instruction.mandate.amendmentIndicator &&
|
|
8964
|
+
instruction.mandate.amendmentInformation && {
|
|
8965
|
+
AmdmntInfDtls: {
|
|
8966
|
+
...(instruction.mandate.amendmentInformation
|
|
8967
|
+
.originalMandateId && {
|
|
8968
|
+
OrgnlMndtId: instruction.mandate.amendmentInformation.originalMandateId,
|
|
8969
|
+
}),
|
|
8970
|
+
...(instruction.mandate.amendmentInformation
|
|
8971
|
+
.originalCreditorSchemeId && {
|
|
8972
|
+
OrgnlCdtrSchmeId: {
|
|
8973
|
+
...(instruction.mandate.amendmentInformation
|
|
8974
|
+
.originalCreditorSchemeId.name && {
|
|
8975
|
+
Nm: instruction.mandate.amendmentInformation
|
|
8976
|
+
.originalCreditorSchemeId.name,
|
|
8977
|
+
}),
|
|
8978
|
+
...(instruction.mandate.amendmentInformation
|
|
8979
|
+
.originalCreditorSchemeId.id && {
|
|
8980
|
+
Id: {
|
|
8981
|
+
PrvtId: {
|
|
8982
|
+
Othr: {
|
|
8983
|
+
Id: instruction.mandate.amendmentInformation
|
|
8984
|
+
.originalCreditorSchemeId.id,
|
|
8985
|
+
SchmeNm: { Prtry: 'SEPA' },
|
|
8986
|
+
},
|
|
8987
|
+
},
|
|
8988
|
+
},
|
|
8989
|
+
}),
|
|
8990
|
+
},
|
|
8991
|
+
}),
|
|
8992
|
+
},
|
|
8993
|
+
}),
|
|
8994
|
+
},
|
|
8995
|
+
},
|
|
8996
|
+
DbtrAgt: this.agent(instruction.debtor.agent),
|
|
8997
|
+
Dbtr: this.party(instruction.debtor),
|
|
8998
|
+
DbtrAcct: this.account(instruction.debtor.account),
|
|
8999
|
+
...(instruction.remittanceInformation && {
|
|
9000
|
+
RmtInf: {
|
|
9001
|
+
Ustrd: instruction.remittanceInformation,
|
|
9002
|
+
},
|
|
9003
|
+
}),
|
|
9004
|
+
};
|
|
9005
|
+
}
|
|
9006
|
+
/**
|
|
9007
|
+
* Serializes the SEPA direct debit initiation to an XML string.
|
|
9008
|
+
* @returns {string} The XML representation of the SEPA direct debit initiation.
|
|
9009
|
+
*/
|
|
9010
|
+
serialize() {
|
|
9011
|
+
const builder = PaymentInitiation.getBuilder();
|
|
9012
|
+
// Generate one PmtInf entry per creditor group
|
|
9013
|
+
const paymentInfoEntries = this.paymentInstructions.map((group, groupIndex) => {
|
|
9014
|
+
const pmtInfId = sanitize(`${this.paymentInformationIdBase}-${groupIndex + 1}`, 35);
|
|
9015
|
+
const localInstrument = group.localInstrument || 'CORE';
|
|
9016
|
+
const batchBooking = group.batchBooking !== undefined ? group.batchBooking : false;
|
|
9017
|
+
// Calculate sum for this group
|
|
9018
|
+
let groupSum = 0;
|
|
9019
|
+
for (const payment of group.payments) {
|
|
9020
|
+
groupSum += payment.amount;
|
|
9021
|
+
}
|
|
9022
|
+
const groupCtrlSum = Dinero({
|
|
9023
|
+
amount: groupSum,
|
|
9024
|
+
currency: 'EUR',
|
|
9025
|
+
}).toFormat('0.00');
|
|
9026
|
+
return {
|
|
9027
|
+
PmtInfId: pmtInfId,
|
|
9028
|
+
PmtMtd: 'DD',
|
|
9029
|
+
BtchBookg: batchBooking,
|
|
9030
|
+
NbOfTxs: group.payments.length.toString(),
|
|
9031
|
+
CtrlSum: groupCtrlSum,
|
|
9032
|
+
PmtTpInf: {
|
|
9033
|
+
SvcLvl: { Cd: 'SEPA' },
|
|
9034
|
+
LclInstrm: { Cd: localInstrument },
|
|
9035
|
+
SeqTp: group.sequenceType,
|
|
9036
|
+
...(group.categoryPurpose && {
|
|
9037
|
+
CtgyPurp: { Cd: group.categoryPurpose },
|
|
9038
|
+
}),
|
|
9039
|
+
},
|
|
9040
|
+
ReqdColltnDt: group.requestedCollectionDate
|
|
9041
|
+
.toISOString()
|
|
9042
|
+
.split('T')[0],
|
|
9043
|
+
Cdtr: this.party(group.creditor),
|
|
9044
|
+
CdtrAcct: this.account(group.creditor.account),
|
|
9045
|
+
CdtrAgt: this.agent(group.creditor.agent),
|
|
9046
|
+
ChrgBr: 'SLEV',
|
|
9047
|
+
CdtrSchmeId: {
|
|
9048
|
+
Id: {
|
|
9049
|
+
PrvtId: {
|
|
9050
|
+
Othr: {
|
|
9051
|
+
Id: group.creditorSchemeId,
|
|
9052
|
+
SchmeNm: { Prtry: 'SEPA' },
|
|
9053
|
+
},
|
|
9054
|
+
},
|
|
9055
|
+
},
|
|
9056
|
+
},
|
|
9057
|
+
DrctDbtTxInf: group.payments.map(payment => this.directDebitTransfer(payment)),
|
|
9058
|
+
};
|
|
9059
|
+
});
|
|
9060
|
+
const xml = {
|
|
9061
|
+
'?xml': {
|
|
9062
|
+
'@version': '1.0',
|
|
9063
|
+
'@encoding': 'UTF-8',
|
|
9064
|
+
},
|
|
9065
|
+
Document: {
|
|
9066
|
+
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.008.001.02',
|
|
9067
|
+
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
9068
|
+
'@xsi:schemaLocation': 'urn:iso:std:iso:20022:tech:xsd:pain.008.001.02 pain.008.001.02.xsd',
|
|
9069
|
+
CstmrDrctDbtInitn: {
|
|
9070
|
+
GrpHdr: {
|
|
9071
|
+
MsgId: this.messageId,
|
|
9072
|
+
CreDtTm: this.creationDate.toISOString(),
|
|
9073
|
+
NbOfTxs: this.totalTransactionCount.toString(),
|
|
9074
|
+
CtrlSum: this.formattedPaymentSum,
|
|
9075
|
+
InitgPty: {
|
|
9076
|
+
Nm: this.initiatingParty.name,
|
|
9077
|
+
...(this.initiatingParty.id && {
|
|
9078
|
+
Id: {
|
|
9079
|
+
OrgId: {
|
|
9080
|
+
Othr: {
|
|
9081
|
+
Id: this.initiatingParty.id,
|
|
9082
|
+
},
|
|
9083
|
+
},
|
|
9084
|
+
},
|
|
9085
|
+
}),
|
|
9086
|
+
},
|
|
9087
|
+
},
|
|
9088
|
+
PmtInf: paymentInfoEntries,
|
|
9089
|
+
},
|
|
9090
|
+
},
|
|
9091
|
+
};
|
|
9092
|
+
return builder.build(xml);
|
|
9093
|
+
}
|
|
9094
|
+
/**
|
|
9095
|
+
* Parses an XML string and creates a SEPADirectDebitPaymentInitiation instance.
|
|
9096
|
+
* Supports multiple PmtInf blocks in the XML document.
|
|
9097
|
+
* @param {string} rawXml - The XML string to parse.
|
|
9098
|
+
* @returns {SEPADirectDebitPaymentInitiation} A new instance created from the XML data.
|
|
9099
|
+
* @throws {InvalidXmlError} If the XML format is invalid.
|
|
9100
|
+
* @throws {InvalidXmlNamespaceError} If the namespace is not pain.008.
|
|
9101
|
+
*/
|
|
9102
|
+
static fromXML(rawXml) {
|
|
9103
|
+
const parser = new fxp.XMLParser({ ignoreAttributes: false });
|
|
9104
|
+
const xml = parser.parse(rawXml);
|
|
9105
|
+
// Validate XML structure
|
|
9106
|
+
if (!xml.Document) {
|
|
9107
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
9108
|
+
}
|
|
9109
|
+
// Validate namespace
|
|
9110
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
9111
|
+
xml.Document['@_Xmlns']);
|
|
9112
|
+
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.008')) {
|
|
9113
|
+
throw new InvalidXmlNamespaceError('Invalid PAIN.008 namespace');
|
|
9114
|
+
}
|
|
9115
|
+
// Extract GrpHdr data
|
|
9116
|
+
const messageId = xml.Document.CstmrDrctDbtInitn.GrpHdr.MsgId;
|
|
9117
|
+
const creationDate = new Date(xml.Document.CstmrDrctDbtInitn.GrpHdr.CreDtTm);
|
|
9118
|
+
// Extract top-level initiating party from GrpHdr
|
|
9119
|
+
const topLevelInitiatingParty = {
|
|
9120
|
+
name: xml.Document.CstmrDrctDbtInitn.GrpHdr.InitgPty.Nm,
|
|
9121
|
+
id: xml.Document.CstmrDrctDbtInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
9122
|
+
?.Id,
|
|
9123
|
+
};
|
|
9124
|
+
// Normalize PmtInf to array (handle both single object and array cases)
|
|
9125
|
+
const rawPmtInf = Array.isArray(xml.Document.CstmrDrctDbtInitn.PmtInf)
|
|
9126
|
+
? xml.Document.CstmrDrctDbtInitn.PmtInf
|
|
9127
|
+
: [xml.Document.CstmrDrctDbtInitn.PmtInf];
|
|
9128
|
+
// Map each PmtInf to SEPADirectDebitPaymentInstructionGroup
|
|
9129
|
+
const paymentInstructions = rawPmtInf.map((pmtInf) => {
|
|
9130
|
+
// Extract creditor info as the group's collecting party
|
|
9131
|
+
const groupCreditor = {
|
|
9132
|
+
name: pmtInf.Cdtr.Nm,
|
|
9133
|
+
id: pmtInf.Cdtr.Id?.OrgId?.Othr?.Id,
|
|
9134
|
+
agent: parseAgent(pmtInf.CdtrAgt),
|
|
9135
|
+
account: parseAccount(pmtInf.CdtrAcct),
|
|
9136
|
+
};
|
|
9137
|
+
// Extract creditor scheme ID
|
|
9138
|
+
const creditorSchemeId = pmtInf.CdtrSchmeId?.Id?.PrvtId?.Othr?.Id || '';
|
|
9139
|
+
// Extract optional category purpose
|
|
9140
|
+
const categoryPurpose = pmtInf.PmtTpInf?.CtgyPurp?.Cd;
|
|
9141
|
+
// Extract local instrument (CORE or B2B)
|
|
9142
|
+
const localInstrument = pmtInf.PmtTpInf?.LclInstrm?.Cd || 'CORE';
|
|
9143
|
+
// Extract sequence type from PmtInf level
|
|
9144
|
+
const sequenceType = pmtInf.PmtTpInf?.SeqTp || 'RCUR';
|
|
9145
|
+
// Extract requested collection date
|
|
9146
|
+
const requestedCollectionDate = new Date(pmtInf.ReqdColltnDt);
|
|
9147
|
+
// Extract batch booking
|
|
9148
|
+
const batchBooking = pmtInf.BtchBookg === 'true' || pmtInf.BtchBookg === true;
|
|
9149
|
+
// Normalize DrctDbtTxInf to array
|
|
9150
|
+
const rawInstructions = Array.isArray(pmtInf.DrctDbtTxInf)
|
|
9151
|
+
? pmtInf.DrctDbtTxInf
|
|
9152
|
+
: [pmtInf.DrctDbtTxInf];
|
|
9153
|
+
// Parse each DrctDbtTxInf to SEPADirectDebitPaymentInstruction
|
|
9154
|
+
const payments = rawInstructions.map((inst) => {
|
|
9155
|
+
const currency = inst.InstdAmt['@_Ccy'];
|
|
9156
|
+
const amount = parseAmountToMinorUnits(Number(inst.InstdAmt['#text']), currency);
|
|
9157
|
+
// Parse mandate information
|
|
9158
|
+
const mandateInfo = inst.DrctDbtTx?.MndtRltdInf;
|
|
9159
|
+
const mandate = {
|
|
9160
|
+
mandateId: mandateInfo?.MndtId,
|
|
9161
|
+
dateOfSignature: new Date(mandateInfo?.DtOfSgntr),
|
|
9162
|
+
amendmentIndicator: mandateInfo?.AmdmntInd === 'true' ||
|
|
9163
|
+
mandateInfo?.AmdmntInd === true,
|
|
9164
|
+
...(mandateInfo?.AmdmntInd &&
|
|
9165
|
+
mandateInfo?.AmdmntInfDtls && {
|
|
9166
|
+
amendmentInformation: {
|
|
9167
|
+
...(mandateInfo.AmdmntInfDtls.OrgnlMndtId && {
|
|
9168
|
+
originalMandateId: mandateInfo.AmdmntInfDtls
|
|
9169
|
+
.OrgnlMndtId,
|
|
9170
|
+
}),
|
|
9171
|
+
...(mandateInfo.AmdmntInfDtls.OrgnlCdtrSchmeId && {
|
|
9172
|
+
originalCreditorSchemeId: {
|
|
9173
|
+
...(mandateInfo.AmdmntInfDtls.OrgnlCdtrSchmeId.Nm && {
|
|
9174
|
+
name: mandateInfo.AmdmntInfDtls.OrgnlCdtrSchmeId
|
|
9175
|
+
.Nm,
|
|
9176
|
+
}),
|
|
9177
|
+
...(mandateInfo.AmdmntInfDtls.OrgnlCdtrSchmeId.Id?.PrvtId
|
|
9178
|
+
?.Othr?.Id && {
|
|
9179
|
+
id: mandateInfo.AmdmntInfDtls.OrgnlCdtrSchmeId.Id.PrvtId
|
|
9180
|
+
.Othr.Id,
|
|
9181
|
+
}),
|
|
9182
|
+
},
|
|
9183
|
+
}),
|
|
9184
|
+
},
|
|
9185
|
+
}),
|
|
9186
|
+
};
|
|
9187
|
+
return {
|
|
9188
|
+
...(inst.PmtId.EndToEndId && {
|
|
9189
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
9190
|
+
}),
|
|
9191
|
+
type: 'sepa',
|
|
9192
|
+
direction: 'debit',
|
|
9193
|
+
amount: amount,
|
|
9194
|
+
currency: currency,
|
|
9195
|
+
debtor: {
|
|
9196
|
+
name: inst.Dbtr?.Nm,
|
|
9197
|
+
agent: parseAgent(inst.DbtrAgt),
|
|
9198
|
+
account: parseAccount(inst.DbtrAcct),
|
|
9199
|
+
},
|
|
9200
|
+
mandate: mandate,
|
|
9201
|
+
...(inst.RmtInf?.Ustrd && {
|
|
9202
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
9203
|
+
}),
|
|
9204
|
+
};
|
|
9205
|
+
});
|
|
9206
|
+
return {
|
|
9207
|
+
creditor: groupCreditor,
|
|
9208
|
+
creditorSchemeId: creditorSchemeId,
|
|
9209
|
+
payments: payments,
|
|
9210
|
+
requestedCollectionDate: requestedCollectionDate,
|
|
9211
|
+
sequenceType: sequenceType,
|
|
9212
|
+
localInstrument: localInstrument,
|
|
9213
|
+
...(categoryPurpose && { categoryPurpose }),
|
|
9214
|
+
batchBooking: batchBooking,
|
|
9215
|
+
};
|
|
9216
|
+
});
|
|
9217
|
+
// Return new instance
|
|
9218
|
+
return new SEPADirectDebitPaymentInitiation({
|
|
9219
|
+
messageId: messageId,
|
|
9220
|
+
creationDate: creationDate,
|
|
9221
|
+
initiatingParty: topLevelInitiatingParty,
|
|
9222
|
+
paymentInstructions: paymentInstructions,
|
|
8649
9223
|
});
|
|
8650
9224
|
}
|
|
8651
9225
|
}
|
|
8652
9226
|
|
|
8653
9227
|
const ISO20022Messages = {
|
|
8654
|
-
CAMT_003:
|
|
8655
|
-
CAMT_004:
|
|
8656
|
-
CAMT_005:
|
|
8657
|
-
CAMT_006:
|
|
8658
|
-
CAMT_053:
|
|
8659
|
-
PAIN_001:
|
|
8660
|
-
PAIN_002:
|
|
9228
|
+
CAMT_003: 'CAMT.003',
|
|
9229
|
+
CAMT_004: 'CAMT.004',
|
|
9230
|
+
CAMT_005: 'CAMT.005',
|
|
9231
|
+
CAMT_006: 'CAMT.006',
|
|
9232
|
+
CAMT_053: 'CAMT.053',
|
|
9233
|
+
PAIN_001: 'PAIN.001',
|
|
9234
|
+
PAIN_002: 'PAIN.002',
|
|
8661
9235
|
};
|
|
8662
9236
|
const ISO20022Implementations = new Map();
|
|
8663
9237
|
function registerISO20022Implementation(cl) {
|
|
8664
|
-
cl.supportedMessages().forEach(
|
|
9238
|
+
cl.supportedMessages().forEach(msg => {
|
|
8665
9239
|
ISO20022Implementations.set(msg, cl);
|
|
8666
9240
|
});
|
|
8667
9241
|
}
|
|
@@ -8717,12 +9291,12 @@ class CashManagementGetAccount {
|
|
|
8717
9291
|
static fromDocumentOject(doc) {
|
|
8718
9292
|
const rawHeader = doc.Document?.GetAcct?.MsgHdr;
|
|
8719
9293
|
if (!rawHeader) {
|
|
8720
|
-
throw new InvalidStructureError(
|
|
9294
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing MsgHdr');
|
|
8721
9295
|
}
|
|
8722
9296
|
const header = parseMessageHeader(rawHeader);
|
|
8723
9297
|
const newCrit = doc.Document?.GetAcct?.AcctQryDef?.AcctCrit?.NewCrit;
|
|
8724
9298
|
if (!newCrit) {
|
|
8725
|
-
throw new InvalidStructureError(
|
|
9299
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing GetAcct.AcctQryDef.AcctCrit.NewCrit');
|
|
8726
9300
|
}
|
|
8727
9301
|
const name = newCrit.NewQryNm;
|
|
8728
9302
|
let searchCriteria = [];
|
|
@@ -8732,16 +9306,19 @@ class CashManagementGetAccount {
|
|
|
8732
9306
|
}
|
|
8733
9307
|
rawCriterias = rawCriterias.filter((c) => !!c);
|
|
8734
9308
|
if (rawCriterias.length === 0) {
|
|
8735
|
-
throw new InvalidStructureError(
|
|
9309
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing search criteria');
|
|
8736
9310
|
}
|
|
8737
9311
|
for (const rawCriterium of rawCriterias) {
|
|
8738
9312
|
const crit = {};
|
|
8739
9313
|
// search on Ids, only one criterium supported for now
|
|
8740
9314
|
if (rawCriterium.AcctId) {
|
|
8741
|
-
if (Array.isArray(rawCriterium.AcctId) &&
|
|
8742
|
-
|
|
9315
|
+
if (Array.isArray(rawCriterium.AcctId) &&
|
|
9316
|
+
rawCriterium.AcctId.length > 1) {
|
|
9317
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple AcctId criterium not supported');
|
|
8743
9318
|
}
|
|
8744
|
-
const acctId = Array.isArray(rawCriterium.AcctId)
|
|
9319
|
+
const acctId = Array.isArray(rawCriterium.AcctId)
|
|
9320
|
+
? rawCriterium.AcctId[0]
|
|
9321
|
+
: rawCriterium.AcctId;
|
|
8745
9322
|
if (acctId.CTTxt) {
|
|
8746
9323
|
crit.accountRegExp = `.*${acctId.CTTxt}.*`; // contains
|
|
8747
9324
|
}
|
|
@@ -8755,19 +9332,23 @@ class CashManagementGetAccount {
|
|
|
8755
9332
|
// search on currency
|
|
8756
9333
|
if (rawCriterium.Ccy) {
|
|
8757
9334
|
if (Array.isArray(rawCriterium.Ccy) && rawCriterium.Ccy.length > 1) {
|
|
8758
|
-
throw new InvalidStructureError(
|
|
9335
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple Ccy criterium not supported');
|
|
8759
9336
|
}
|
|
8760
|
-
const ccy = Array.isArray(rawCriterium.Ccy)
|
|
9337
|
+
const ccy = Array.isArray(rawCriterium.Ccy)
|
|
9338
|
+
? rawCriterium.Ccy[0]
|
|
9339
|
+
: rawCriterium.Ccy;
|
|
8761
9340
|
crit.currencyEqualTo = ccy;
|
|
8762
9341
|
}
|
|
8763
9342
|
// search on balance as of date
|
|
8764
9343
|
if (rawCriterium.Bal) {
|
|
8765
9344
|
if (Array.isArray(rawCriterium.Bal) && rawCriterium.Bal.length > 1) {
|
|
8766
|
-
throw new InvalidStructureError(
|
|
9345
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple Bal criterium not supported');
|
|
8767
9346
|
}
|
|
8768
|
-
const bal = Array.isArray(rawCriterium.Bal)
|
|
9347
|
+
const bal = Array.isArray(rawCriterium.Bal)
|
|
9348
|
+
? rawCriterium.Bal[0]
|
|
9349
|
+
: rawCriterium.Bal;
|
|
8769
9350
|
if (bal?.ValDt && Array.isArray(bal.ValDt) && bal.ValDt.length > 1) {
|
|
8770
|
-
throw new InvalidStructureError(
|
|
9351
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple ValDt criterium not supported');
|
|
8771
9352
|
}
|
|
8772
9353
|
const valDt = Array.isArray(bal?.ValDt) ? bal.ValDt[0] : bal?.ValDt;
|
|
8773
9354
|
if (valDt?.Dt?.EQDt) {
|
|
@@ -8788,9 +9369,10 @@ class CashManagementGetAccount {
|
|
|
8788
9369
|
const parser = XML.getParser();
|
|
8789
9370
|
const doc = parser.parse(xml);
|
|
8790
9371
|
if (!doc.Document) {
|
|
8791
|
-
throw new Error(
|
|
9372
|
+
throw new Error('Invalid XML format');
|
|
8792
9373
|
}
|
|
8793
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
9374
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
9375
|
+
doc.Document['@_Xmlns']);
|
|
8794
9376
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.003.001.')) {
|
|
8795
9377
|
throw new InvalidXmlNamespaceError('Invalid CAMT.003 namespace');
|
|
8796
9378
|
}
|
|
@@ -8799,7 +9381,7 @@ class CashManagementGetAccount {
|
|
|
8799
9381
|
static fromJSON(json) {
|
|
8800
9382
|
const obj = JSON.parse(json);
|
|
8801
9383
|
if (!obj.Document) {
|
|
8802
|
-
throw new Error(
|
|
9384
|
+
throw new Error('Invalid JSON format');
|
|
8803
9385
|
}
|
|
8804
9386
|
return CashManagementGetAccount.fromDocumentOject(obj);
|
|
8805
9387
|
}
|
|
@@ -8819,39 +9401,55 @@ class CashManagementGetAccount {
|
|
|
8819
9401
|
AcctCrit: {
|
|
8820
9402
|
NewCrit: {
|
|
8821
9403
|
NewQryNm: this._data.newCriteria?.name,
|
|
8822
|
-
SchCrit: this._data.newCriteria?.searchCriteria.map(
|
|
9404
|
+
SchCrit: this._data.newCriteria?.searchCriteria.map(c => {
|
|
8823
9405
|
const obj = {};
|
|
8824
9406
|
if (c.accountRegExp) {
|
|
8825
|
-
if (c.accountRegExp.startsWith('.*') &&
|
|
8826
|
-
|
|
9407
|
+
if (c.accountRegExp.startsWith('.*') &&
|
|
9408
|
+
c.accountRegExp.endsWith('.*')) {
|
|
9409
|
+
obj.AcctId = {
|
|
9410
|
+
CTTxt: c.accountRegExp
|
|
9411
|
+
.replace(/^\.\*/, '')
|
|
9412
|
+
.replace(/\.\*$/, ''),
|
|
9413
|
+
}; // contains
|
|
8827
9414
|
}
|
|
8828
|
-
else if (c.accountRegExp.startsWith('^((?!') &&
|
|
8829
|
-
|
|
9415
|
+
else if (c.accountRegExp.startsWith('^((?!') &&
|
|
9416
|
+
c.accountRegExp.endsWith(').)*$')) {
|
|
9417
|
+
obj.AcctId = {
|
|
9418
|
+
NCTTxt: c.accountRegExp
|
|
9419
|
+
.replace(/^\^\(\(\!\(/, '')
|
|
9420
|
+
.replace(/\)\.\)\*\$$/, ''),
|
|
9421
|
+
}; // does not contain
|
|
8830
9422
|
}
|
|
8831
9423
|
}
|
|
8832
9424
|
else if (c.accountEqualTo) {
|
|
8833
9425
|
obj.AcctId = {
|
|
8834
|
-
EQ: exportAccountIdentification(c.accountEqualTo)
|
|
9426
|
+
EQ: exportAccountIdentification(c.accountEqualTo),
|
|
8835
9427
|
};
|
|
8836
9428
|
}
|
|
8837
9429
|
if (c.currencyEqualTo) {
|
|
8838
9430
|
obj.Ccy = [c.currencyEqualTo];
|
|
8839
9431
|
}
|
|
8840
9432
|
if (c.balanceAsOfDateEqualTo) {
|
|
8841
|
-
obj.Bal = [
|
|
8842
|
-
|
|
9433
|
+
obj.Bal = [
|
|
9434
|
+
{
|
|
9435
|
+
ValDt: [
|
|
9436
|
+
{
|
|
8843
9437
|
Dt: {
|
|
8844
|
-
EQDt: c.balanceAsOfDateEqualTo
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
9438
|
+
EQDt: c.balanceAsOfDateEqualTo
|
|
9439
|
+
.toISOString()
|
|
9440
|
+
.slice(0, 10),
|
|
9441
|
+
},
|
|
9442
|
+
},
|
|
9443
|
+
],
|
|
9444
|
+
},
|
|
9445
|
+
];
|
|
8848
9446
|
}
|
|
8849
9447
|
return obj;
|
|
8850
9448
|
}),
|
|
8851
|
-
}
|
|
8852
|
-
}
|
|
8853
|
-
}
|
|
8854
|
-
}
|
|
9449
|
+
},
|
|
9450
|
+
},
|
|
9451
|
+
},
|
|
9452
|
+
},
|
|
8855
9453
|
};
|
|
8856
9454
|
return { Document };
|
|
8857
9455
|
}
|
|
@@ -8951,10 +9549,10 @@ const exportStatement = (stmt) => {
|
|
|
8951
9549
|
},
|
|
8952
9550
|
Acct: {
|
|
8953
9551
|
...exportAccount(stmt.account),
|
|
8954
|
-
Svcr: exportAgent(stmt.agent)
|
|
9552
|
+
Svcr: exportAgent(stmt.agent),
|
|
8955
9553
|
},
|
|
8956
|
-
Bal: stmt.balances.map(
|
|
8957
|
-
Ntry: stmt.entries.map(
|
|
9554
|
+
Bal: stmt.balances.map(bal => exportBalance(bal)),
|
|
9555
|
+
Ntry: stmt.entries.map(entry => exportEntry(entry)),
|
|
8958
9556
|
};
|
|
8959
9557
|
return obj;
|
|
8960
9558
|
};
|
|
@@ -9078,7 +9676,9 @@ const exportEntry = (entry) => {
|
|
|
9078
9676
|
BkTxCd: exportBankTransactionCode(entry.bankTransactionCode, entry.proprietaryCode),
|
|
9079
9677
|
AddtlNtryInf: entry.additionalInformation,
|
|
9080
9678
|
AcctSvcrRef: entry.accountServicerReferenceId,
|
|
9081
|
-
NtryDtls: entry.transactions.map(
|
|
9679
|
+
NtryDtls: entry.transactions.map(tx => ({
|
|
9680
|
+
TxDtls: exportTransactionDetails(tx),
|
|
9681
|
+
})),
|
|
9082
9682
|
};
|
|
9083
9683
|
return obj;
|
|
9084
9684
|
};
|
|
@@ -9171,7 +9771,9 @@ const exportTransactionDetails = (tx) => {
|
|
|
9171
9771
|
Dbtr: {
|
|
9172
9772
|
Nm: tx.debtor.name,
|
|
9173
9773
|
},
|
|
9174
|
-
DbtrAcct: tx.debtor.account
|
|
9774
|
+
DbtrAcct: tx.debtor.account
|
|
9775
|
+
? exportAccount(tx.debtor.account)
|
|
9776
|
+
: undefined,
|
|
9175
9777
|
};
|
|
9176
9778
|
obj.RltdAgts = {
|
|
9177
9779
|
DbtrAgt: tx.debtor.agent ? exportAgent(tx.debtor.agent) : undefined,
|
|
@@ -9183,7 +9785,9 @@ const exportTransactionDetails = (tx) => {
|
|
|
9183
9785
|
Cdtr: {
|
|
9184
9786
|
Nm: tx.creditor.name,
|
|
9185
9787
|
},
|
|
9186
|
-
CdtrAcct: tx.creditor.account
|
|
9788
|
+
CdtrAcct: tx.creditor.account
|
|
9789
|
+
? exportAccount(tx.creditor.account)
|
|
9790
|
+
: undefined,
|
|
9187
9791
|
};
|
|
9188
9792
|
obj.RltdAgts = {
|
|
9189
9793
|
CdtrAgt: tx.creditor.agent ? exportAgent(tx.creditor.agent) : undefined,
|
|
@@ -9228,7 +9832,7 @@ const exportBankTransactionCode = (bankTransactionCode, proprietaryCode) => {
|
|
|
9228
9832
|
return obj;
|
|
9229
9833
|
};
|
|
9230
9834
|
const parseBusinessError = (bizErr) => {
|
|
9231
|
-
const code = bizErr.Err?.Cd || bizErr.Err?.Prtry ||
|
|
9835
|
+
const code = bizErr.Err?.Cd || bizErr.Err?.Prtry || 'UKNW';
|
|
9232
9836
|
const description = bizErr.Desc;
|
|
9233
9837
|
return {
|
|
9234
9838
|
code,
|
|
@@ -9259,7 +9863,7 @@ class CashManagementReturnAccount {
|
|
|
9259
9863
|
static fromDocumentOject(doc) {
|
|
9260
9864
|
const rawHeader = doc.Document?.RtrAcct?.MsgHdr;
|
|
9261
9865
|
if (!rawHeader) {
|
|
9262
|
-
throw new InvalidStructureError(
|
|
9866
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing MsgHdr');
|
|
9263
9867
|
}
|
|
9264
9868
|
const header = parseMessageHeader(rawHeader);
|
|
9265
9869
|
// interpret the report
|
|
@@ -9274,7 +9878,7 @@ class CashManagementReturnAccount {
|
|
|
9274
9878
|
if (r.AcctOrErr?.Acct) {
|
|
9275
9879
|
// report
|
|
9276
9880
|
if (!r.AcctOrErr.Acct.Ccy) {
|
|
9277
|
-
throw new InvalidStructureError(
|
|
9881
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing Ccy in Acct');
|
|
9278
9882
|
}
|
|
9279
9883
|
let rawMulBal = r.AcctOrErr.Acct.MulBal;
|
|
9280
9884
|
if (!Array.isArray(rawMulBal))
|
|
@@ -9287,7 +9891,7 @@ class CashManagementReturnAccount {
|
|
|
9287
9891
|
balances: rawMulBal.map((bal) => parseBalanceReport(r.AcctOrErr.Acct.Ccy, bal)),
|
|
9288
9892
|
};
|
|
9289
9893
|
if (report.balances.length === 0) {
|
|
9290
|
-
throw new InvalidStructureError(
|
|
9894
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing MulBal in Acct');
|
|
9291
9895
|
}
|
|
9292
9896
|
}
|
|
9293
9897
|
else if (r.AcctOrErr?.BizErr) {
|
|
@@ -9295,7 +9899,7 @@ class CashManagementReturnAccount {
|
|
|
9295
9899
|
error = parseBusinessError(r.AcctOrErr.BizErr);
|
|
9296
9900
|
}
|
|
9297
9901
|
else {
|
|
9298
|
-
throw new InvalidStructureError(
|
|
9902
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing AcctOrErr');
|
|
9299
9903
|
}
|
|
9300
9904
|
return { accountId, report, error };
|
|
9301
9905
|
});
|
|
@@ -9308,9 +9912,10 @@ class CashManagementReturnAccount {
|
|
|
9308
9912
|
const parser = XML.getParser();
|
|
9309
9913
|
const doc = parser.parse(xml);
|
|
9310
9914
|
if (!doc.Document) {
|
|
9311
|
-
throw new Error(
|
|
9915
|
+
throw new Error('Invalid XML format');
|
|
9312
9916
|
}
|
|
9313
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
9917
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
9918
|
+
doc.Document['@_Xmlns']);
|
|
9314
9919
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.004.001.')) {
|
|
9315
9920
|
throw new InvalidXmlNamespaceError('Invalid CAMT.004 namespace');
|
|
9316
9921
|
}
|
|
@@ -9319,7 +9924,7 @@ class CashManagementReturnAccount {
|
|
|
9319
9924
|
static fromJSON(json) {
|
|
9320
9925
|
const obj = JSON.parse(json);
|
|
9321
9926
|
if (!obj.Document) {
|
|
9322
|
-
throw new Error(
|
|
9927
|
+
throw new Error('Invalid JSON format');
|
|
9323
9928
|
}
|
|
9324
9929
|
return CashManagementReturnAccount.fromDocumentOject(obj);
|
|
9325
9930
|
}
|
|
@@ -9336,7 +9941,7 @@ class CashManagementReturnAccount {
|
|
|
9336
9941
|
RtrAcct: {
|
|
9337
9942
|
MsgHdr: exportMessageHeader(this._data.header),
|
|
9338
9943
|
RptOrErr: {
|
|
9339
|
-
AcctRpt: this._data.reports.map(
|
|
9944
|
+
AcctRpt: this._data.reports.map(report => {
|
|
9340
9945
|
const obj = {
|
|
9341
9946
|
AcctId: exportAccountIdentification(report.accountId),
|
|
9342
9947
|
AcctOrErr: {}, // filled below
|
|
@@ -9346,16 +9951,16 @@ class CashManagementReturnAccount {
|
|
|
9346
9951
|
Ccy: report.report.currency,
|
|
9347
9952
|
Nm: report.report.name,
|
|
9348
9953
|
Tp: { Cd: report.report.type }, // TODO add Prtry handling
|
|
9349
|
-
MulBal: report.report.balances.map(
|
|
9954
|
+
MulBal: report.report.balances.map(bal => exportBalanceReport(report.report.currency, bal)),
|
|
9350
9955
|
};
|
|
9351
9956
|
}
|
|
9352
9957
|
else if (report.error) {
|
|
9353
9958
|
obj.AcctOrErr.BizErr = exportBusinessError(report.error);
|
|
9354
9959
|
}
|
|
9355
9960
|
return obj;
|
|
9356
|
-
})
|
|
9357
|
-
}
|
|
9358
|
-
}
|
|
9961
|
+
}),
|
|
9962
|
+
},
|
|
9963
|
+
},
|
|
9359
9964
|
};
|
|
9360
9965
|
return { Document };
|
|
9361
9966
|
}
|
|
@@ -9376,12 +9981,12 @@ class CashManagementGetTransaction {
|
|
|
9376
9981
|
static fromDocumentOject(doc) {
|
|
9377
9982
|
const rawHeader = doc.Document?.GetTx?.MsgHdr;
|
|
9378
9983
|
if (!rawHeader) {
|
|
9379
|
-
throw new InvalidStructureError(
|
|
9984
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing MsgHdr');
|
|
9380
9985
|
}
|
|
9381
9986
|
const header = parseMessageHeader(rawHeader);
|
|
9382
9987
|
const newCrit = doc.Document?.GetTx?.TxQryDef?.TxCrit?.NewCrit;
|
|
9383
9988
|
if (!newCrit) {
|
|
9384
|
-
throw new InvalidStructureError(
|
|
9989
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing GetTx.TxQryDef.TxCrit.NewCrit');
|
|
9385
9990
|
}
|
|
9386
9991
|
const name = newCrit.NewQryNm;
|
|
9387
9992
|
let searchCriteria = [];
|
|
@@ -9391,35 +9996,42 @@ class CashManagementGetTransaction {
|
|
|
9391
9996
|
}
|
|
9392
9997
|
rawCriterias = rawCriterias.filter((c) => !!c);
|
|
9393
9998
|
if (rawCriterias.length === 0) {
|
|
9394
|
-
throw new InvalidStructureError(
|
|
9999
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing search criteria');
|
|
9395
10000
|
}
|
|
9396
10001
|
for (const rawCriterium of rawCriterias) {
|
|
9397
10002
|
// search on Ids
|
|
9398
10003
|
if (rawCriterium.PmtSch.MsgId) {
|
|
9399
10004
|
searchCriteria.push({
|
|
9400
|
-
type:
|
|
9401
|
-
msgIdsEqualTo: Array.isArray(rawCriterium.PmtSch.MsgId)
|
|
10005
|
+
type: 'PmtSch.MsgId',
|
|
10006
|
+
msgIdsEqualTo: Array.isArray(rawCriterium.PmtSch.MsgId)
|
|
10007
|
+
? rawCriterium.PmtSch.MsgId
|
|
10008
|
+
: [rawCriterium.PmtSch.MsgId],
|
|
9402
10009
|
});
|
|
9403
10010
|
}
|
|
9404
10011
|
// seach on date
|
|
9405
10012
|
if (rawCriterium.PmtSch.ReqdExctnDt) {
|
|
9406
|
-
if (Array.isArray(rawCriterium.PmtSch.ReqdExctnDt) &&
|
|
9407
|
-
|
|
10013
|
+
if (Array.isArray(rawCriterium.PmtSch.ReqdExctnDt) &&
|
|
10014
|
+
rawCriterium.PmtSch.ReqdExctnDt.length > 1) {
|
|
10015
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: multiple ReqdExctnDt criterium not supported');
|
|
9408
10016
|
}
|
|
9409
|
-
const criterium = Array.isArray(rawCriterium.PmtSch.ReqdExctnDt)
|
|
10017
|
+
const criterium = Array.isArray(rawCriterium.PmtSch.ReqdExctnDt)
|
|
10018
|
+
? rawCriterium.PmtSch.ReqdExctnDt[0]
|
|
10019
|
+
: rawCriterium.PmtSch.ReqdExctnDt;
|
|
9410
10020
|
if (criterium?.DtSch?.EQDt) {
|
|
9411
10021
|
searchCriteria.push({
|
|
9412
|
-
type:
|
|
10022
|
+
type: 'PmtSch.ReqdExctnDt',
|
|
9413
10023
|
dateEqualTo: parseDate(criterium.DtSch.EQDt),
|
|
9414
10024
|
});
|
|
9415
10025
|
}
|
|
9416
10026
|
}
|
|
9417
|
-
let pmtIds = Array.isArray(rawCriterium.PmtSch.PmtId)
|
|
9418
|
-
|
|
10027
|
+
let pmtIds = Array.isArray(rawCriterium.PmtSch.PmtId)
|
|
10028
|
+
? rawCriterium.PmtSch.PmtId
|
|
10029
|
+
: [rawCriterium.PmtSch.PmtId];
|
|
10030
|
+
pmtIds = pmtIds.filter(p => !!p && p.LngBizId?.EndToEndId);
|
|
9419
10031
|
if (pmtIds.length > 0) {
|
|
9420
10032
|
searchCriteria.push({
|
|
9421
|
-
type:
|
|
9422
|
-
endToEndIdEqualTo: pmtIds.map(
|
|
10033
|
+
type: 'PmtSch.PmtId.LngBizId.EndToEndId',
|
|
10034
|
+
endToEndIdEqualTo: pmtIds.map(id => id.LngBizId.EndToEndId),
|
|
9423
10035
|
});
|
|
9424
10036
|
}
|
|
9425
10037
|
}
|
|
@@ -9435,9 +10047,10 @@ class CashManagementGetTransaction {
|
|
|
9435
10047
|
const parser = XML.getParser();
|
|
9436
10048
|
const doc = parser.parse(xml);
|
|
9437
10049
|
if (!doc.Document) {
|
|
9438
|
-
throw new Error(
|
|
10050
|
+
throw new Error('Invalid XML format');
|
|
9439
10051
|
}
|
|
9440
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
10052
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
10053
|
+
doc.Document['@_Xmlns']);
|
|
9441
10054
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.005.001.')) {
|
|
9442
10055
|
throw new InvalidXmlNamespaceError('Invalid CAMT.005 namespace');
|
|
9443
10056
|
}
|
|
@@ -9446,7 +10059,7 @@ class CashManagementGetTransaction {
|
|
|
9446
10059
|
static fromJSON(json) {
|
|
9447
10060
|
const obj = JSON.parse(json);
|
|
9448
10061
|
if (!obj.Document) {
|
|
9449
|
-
throw new Error(
|
|
10062
|
+
throw new Error('Invalid JSON format');
|
|
9450
10063
|
}
|
|
9451
10064
|
return CashManagementGetTransaction.fromDocumentOject(obj);
|
|
9452
10065
|
}
|
|
@@ -9466,37 +10079,38 @@ class CashManagementGetTransaction {
|
|
|
9466
10079
|
TxCrit: {
|
|
9467
10080
|
NewCrit: {
|
|
9468
10081
|
NewQryNm: this._data.newCriteria?.name,
|
|
9469
|
-
SchCrit: this._data.newCriteria?.searchCriteria.map(
|
|
10082
|
+
SchCrit: this._data.newCriteria?.searchCriteria.map(c => {
|
|
9470
10083
|
const obj = {};
|
|
9471
|
-
if (c.type ===
|
|
10084
|
+
if (c.type === 'PmtSch.MsgId' && c.msgIdsEqualTo) {
|
|
9472
10085
|
obj.PmtSch = {
|
|
9473
|
-
MsgId: c.msgIdsEqualTo
|
|
10086
|
+
MsgId: c.msgIdsEqualTo,
|
|
9474
10087
|
};
|
|
9475
10088
|
}
|
|
9476
|
-
if (c.type ===
|
|
10089
|
+
if (c.type === 'PmtSch.ReqdExctnDt' && c.dateEqualTo) {
|
|
9477
10090
|
obj.PmtSch = {
|
|
9478
10091
|
ReqdExctnDt: {
|
|
9479
10092
|
DtSch: {
|
|
9480
10093
|
EQDt: c.dateEqualTo.toISOString().slice(0, 10),
|
|
9481
|
-
}
|
|
9482
|
-
}
|
|
10094
|
+
},
|
|
10095
|
+
},
|
|
9483
10096
|
};
|
|
9484
10097
|
}
|
|
9485
|
-
if (c.type ===
|
|
10098
|
+
if (c.type === 'PmtSch.PmtId.LngBizId.EndToEndId' &&
|
|
10099
|
+
c.endToEndIdEqualTo) {
|
|
9486
10100
|
obj.PmtSch = {
|
|
9487
|
-
PmtId: c.endToEndIdEqualTo.map(
|
|
10101
|
+
PmtId: c.endToEndIdEqualTo.map(id => ({
|
|
9488
10102
|
LngBizId: {
|
|
9489
10103
|
EndToEndId: id,
|
|
9490
|
-
}
|
|
9491
|
-
}))
|
|
10104
|
+
},
|
|
10105
|
+
})),
|
|
9492
10106
|
};
|
|
9493
10107
|
}
|
|
9494
10108
|
return obj;
|
|
9495
10109
|
}),
|
|
9496
|
-
}
|
|
9497
|
-
}
|
|
9498
|
-
}
|
|
9499
|
-
}
|
|
10110
|
+
},
|
|
10111
|
+
},
|
|
10112
|
+
},
|
|
10113
|
+
},
|
|
9500
10114
|
};
|
|
9501
10115
|
return { Document };
|
|
9502
10116
|
}
|
|
@@ -9517,7 +10131,7 @@ class CashManagementReturnTransaction {
|
|
|
9517
10131
|
static fromDocumentOject(doc) {
|
|
9518
10132
|
const rawHeader = doc.Document?.RtrTx?.MsgHdr;
|
|
9519
10133
|
if (!rawHeader) {
|
|
9520
|
-
throw new InvalidStructureError(
|
|
10134
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing MsgHdr');
|
|
9521
10135
|
}
|
|
9522
10136
|
const header = parseMessageHeader(rawHeader);
|
|
9523
10137
|
// interpret the report
|
|
@@ -9526,7 +10140,8 @@ class CashManagementReturnTransaction {
|
|
|
9526
10140
|
rawReports = [rawReports];
|
|
9527
10141
|
rawReports = rawReports.filter((r) => !!r); // remove null/undefined
|
|
9528
10142
|
const reports = rawReports.map((r) => {
|
|
9529
|
-
const rawAmount = r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Amt ||
|
|
10143
|
+
const rawAmount = r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Amt ||
|
|
10144
|
+
r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Amount; // some implementations use Amount instead of Amt
|
|
9530
10145
|
const paymentId = {
|
|
9531
10146
|
currency: r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Ccy,
|
|
9532
10147
|
amount: parseAmountToMinorUnits(rawAmount, r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Ccy),
|
|
@@ -9536,20 +10151,24 @@ class CashManagementReturnTransaction {
|
|
|
9536
10151
|
};
|
|
9537
10152
|
// check required fields
|
|
9538
10153
|
if (!paymentId.currency) {
|
|
9539
|
-
throw new InvalidStructureError(
|
|
10154
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Ccy in PmtId.LngBizId.IntrBkSttlmAmt');
|
|
9540
10155
|
}
|
|
9541
|
-
if (paymentId.amount === undefined ||
|
|
9542
|
-
|
|
10156
|
+
if (paymentId.amount === undefined ||
|
|
10157
|
+
paymentId.amount === null ||
|
|
10158
|
+
isNaN(paymentId.amount)) {
|
|
10159
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing or invalid Amt in PmtId.LngBizId.IntrBkSttlmAmt');
|
|
9543
10160
|
}
|
|
9544
10161
|
if (!paymentId.endToEndId) {
|
|
9545
|
-
throw new InvalidStructureError(
|
|
10162
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing EndToEndId in PmtId.LngBizId');
|
|
9546
10163
|
}
|
|
9547
10164
|
let report = undefined;
|
|
9548
10165
|
let error = undefined;
|
|
9549
10166
|
if (r.TxOrErr?.Tx) {
|
|
9550
10167
|
// report
|
|
9551
10168
|
const msgId = r.TxOrErr.Tx.Pmt?.MsgId;
|
|
9552
|
-
const reqExecutionDate = r.TxOrErr.Tx.Pmt?.ReqdExctnDt?.Dt
|
|
10169
|
+
const reqExecutionDate = r.TxOrErr.Tx.Pmt?.ReqdExctnDt?.Dt
|
|
10170
|
+
? parseDate(r.TxOrErr.Tx.Pmt.ReqdExctnDt)
|
|
10171
|
+
: undefined;
|
|
9553
10172
|
const status = ((sts) => {
|
|
9554
10173
|
if (!sts)
|
|
9555
10174
|
return undefined;
|
|
@@ -9557,9 +10176,13 @@ class CashManagementReturnTransaction {
|
|
|
9557
10176
|
return undefined;
|
|
9558
10177
|
if (Array.isArray(sts))
|
|
9559
10178
|
sts = sts[0]; // take the first one only
|
|
9560
|
-
let code = sts.Cd?.Pdg ||
|
|
10179
|
+
let code = sts.Cd?.Pdg ||
|
|
10180
|
+
sts.Cd?.Fnl ||
|
|
10181
|
+
sts.Cd?.RTGS ||
|
|
10182
|
+
sts.Cd?.Sttlm ||
|
|
10183
|
+
sts.Cd?.Prtly;
|
|
9561
10184
|
if (code)
|
|
9562
|
-
code = Object.keys(sts.Cd)[0] +
|
|
10185
|
+
code = Object.keys(sts.Cd)[0] + ':' + code; // prefix with the type of code
|
|
9563
10186
|
else
|
|
9564
10187
|
return undefined;
|
|
9565
10188
|
const reason = sts.Rsn?.Prtry;
|
|
@@ -9574,7 +10197,7 @@ class CashManagementReturnTransaction {
|
|
|
9574
10197
|
}
|
|
9575
10198
|
function parseAgent(agent) {
|
|
9576
10199
|
if (!agent)
|
|
9577
|
-
return { bic:
|
|
10200
|
+
return { bic: '' };
|
|
9578
10201
|
return { bic: agent?.FinInstnId?.BICFI };
|
|
9579
10202
|
}
|
|
9580
10203
|
report = {
|
|
@@ -9588,10 +10211,10 @@ class CashManagementReturnTransaction {
|
|
|
9588
10211
|
};
|
|
9589
10212
|
// check the debtor and creditor required fields
|
|
9590
10213
|
if (!report.debtor.id) {
|
|
9591
|
-
throw new InvalidStructureError(
|
|
10214
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Id in TxOrErr.Tx.Dbtr.Pty');
|
|
9592
10215
|
}
|
|
9593
10216
|
if (!report.creditor.id) {
|
|
9594
|
-
throw new InvalidStructureError(
|
|
10217
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Id in TxOrErr.Tx.Cdtr.Pty');
|
|
9595
10218
|
}
|
|
9596
10219
|
}
|
|
9597
10220
|
else if (r.TxOrErr?.BizErr) {
|
|
@@ -9599,7 +10222,7 @@ class CashManagementReturnTransaction {
|
|
|
9599
10222
|
error = parseBusinessError(r.TxOrErr.BizErr);
|
|
9600
10223
|
}
|
|
9601
10224
|
else {
|
|
9602
|
-
throw new InvalidStructureError(
|
|
10225
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing TxOrErr');
|
|
9603
10226
|
}
|
|
9604
10227
|
return { paymentId, report, error };
|
|
9605
10228
|
});
|
|
@@ -9612,9 +10235,10 @@ class CashManagementReturnTransaction {
|
|
|
9612
10235
|
const parser = XML.getParser();
|
|
9613
10236
|
const doc = parser.parse(xml);
|
|
9614
10237
|
if (!doc.Document) {
|
|
9615
|
-
throw new Error(
|
|
10238
|
+
throw new Error('Invalid XML format');
|
|
9616
10239
|
}
|
|
9617
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
10240
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
10241
|
+
doc.Document['@_Xmlns']);
|
|
9618
10242
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.004.001.')) {
|
|
9619
10243
|
throw new InvalidXmlNamespaceError('Invalid CAMT.004 namespace');
|
|
9620
10244
|
}
|
|
@@ -9623,7 +10247,7 @@ class CashManagementReturnTransaction {
|
|
|
9623
10247
|
static fromJSON(json) {
|
|
9624
10248
|
const obj = JSON.parse(json);
|
|
9625
10249
|
if (!obj.Document) {
|
|
9626
|
-
throw new Error(
|
|
10250
|
+
throw new Error('Invalid JSON format');
|
|
9627
10251
|
}
|
|
9628
10252
|
return CashManagementReturnTransaction.fromDocumentOject(obj);
|
|
9629
10253
|
}
|
|
@@ -9641,7 +10265,7 @@ class CashManagementReturnTransaction {
|
|
|
9641
10265
|
MsgHdr: exportMessageHeader(this._data.header),
|
|
9642
10266
|
RptOrErr: {
|
|
9643
10267
|
BizRpt: {
|
|
9644
|
-
TxRpt: this._data.reports.map(
|
|
10268
|
+
TxRpt: this._data.reports.map(report => {
|
|
9645
10269
|
const obj = {
|
|
9646
10270
|
PmtId: {
|
|
9647
10271
|
LngBizId: {
|
|
@@ -9653,7 +10277,7 @@ class CashManagementReturnTransaction {
|
|
|
9653
10277
|
UETR: report.paymentId.uetr,
|
|
9654
10278
|
TxId: report.paymentId.transactionId,
|
|
9655
10279
|
EndToEndId: report.paymentId.endToEndId,
|
|
9656
|
-
}
|
|
10280
|
+
},
|
|
9657
10281
|
},
|
|
9658
10282
|
TxOrErr: {}, // filled below
|
|
9659
10283
|
};
|
|
@@ -9672,38 +10296,46 @@ class CashManagementReturnTransaction {
|
|
|
9672
10296
|
function exportAgent(a) {
|
|
9673
10297
|
if (!a)
|
|
9674
10298
|
return undefined;
|
|
9675
|
-
if (
|
|
10299
|
+
if ('bic' in a && a.bic)
|
|
9676
10300
|
return { FinInstnId: { BICFI: a.bic } };
|
|
9677
|
-
if (
|
|
10301
|
+
if ('abaRoutingNumber' in a && a.abaRoutingNumber)
|
|
9678
10302
|
return { FinInstId: { Othr: { Id: a.abaRoutingNumber } } };
|
|
9679
10303
|
return undefined;
|
|
9680
10304
|
}
|
|
9681
|
-
const [codeType, code] = report.report.status
|
|
10305
|
+
const [codeType, code] = report.report.status
|
|
10306
|
+
? report.report.status.code.split(':')
|
|
10307
|
+
: [undefined, undefined];
|
|
9682
10308
|
obj.TxOrErr.Tx = {
|
|
9683
10309
|
Pmt: {
|
|
9684
10310
|
MsgId: report.report.msgId,
|
|
9685
|
-
ReqdExctnDt: {
|
|
10311
|
+
ReqdExctnDt: {
|
|
10312
|
+
Dt: report.report.reqExecutionDate
|
|
10313
|
+
?.toISOString()
|
|
10314
|
+
?.slice(0, 10),
|
|
10315
|
+
},
|
|
9686
10316
|
Sts: {
|
|
9687
10317
|
Cd: codeType ? { [codeType]: code } : undefined,
|
|
9688
|
-
Rsn: report.report.status?.reason
|
|
10318
|
+
Rsn: report.report.status?.reason
|
|
10319
|
+
? { Prtry: report.report.status.reason }
|
|
10320
|
+
: undefined,
|
|
9689
10321
|
},
|
|
9690
10322
|
Pties: {
|
|
9691
10323
|
Dbtr: exportParty(report.report.debtor),
|
|
9692
10324
|
DbtrAgt: exportAgent(report.report.debtorAgent),
|
|
9693
10325
|
Cdtr: exportParty(report.report.creditor),
|
|
9694
10326
|
CdtrAgt: exportAgent(report.report.creditorAgent),
|
|
9695
|
-
}
|
|
9696
|
-
}
|
|
10327
|
+
},
|
|
10328
|
+
},
|
|
9697
10329
|
};
|
|
9698
10330
|
}
|
|
9699
10331
|
else if (report.error) {
|
|
9700
10332
|
obj.TxOrErr.BizErr = exportBusinessError(report.error);
|
|
9701
10333
|
}
|
|
9702
10334
|
return obj;
|
|
9703
|
-
})
|
|
9704
|
-
}
|
|
9705
|
-
}
|
|
9706
|
-
}
|
|
10335
|
+
}),
|
|
10336
|
+
},
|
|
10337
|
+
},
|
|
10338
|
+
},
|
|
9707
10339
|
};
|
|
9708
10340
|
return { Document };
|
|
9709
10341
|
}
|
|
@@ -9743,17 +10375,17 @@ const BalanceTypeCode = {
|
|
|
9743
10375
|
* Description mapping of BalanceTypeCode values to their names.
|
|
9744
10376
|
*/
|
|
9745
10377
|
const BalanceTypeCodeDescriptionMap = {
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
10378
|
+
CLAV: 'Closing Available',
|
|
10379
|
+
CLBD: 'Closing Booked',
|
|
10380
|
+
FWAV: 'Forward Available',
|
|
10381
|
+
INFO: 'Information',
|
|
10382
|
+
ITAV: 'Interim Available',
|
|
10383
|
+
ITBD: 'Interim Booked',
|
|
10384
|
+
OPAV: 'Opening Available',
|
|
10385
|
+
OPBD: 'Opening Booked',
|
|
10386
|
+
PRCD: 'Previously Closed Booked',
|
|
10387
|
+
XPCD: 'Expected',
|
|
10388
|
+
ABRR: 'Additional Balance Reserve Requirement',
|
|
9757
10389
|
};
|
|
9758
10390
|
|
|
9759
10391
|
/**
|
|
@@ -9976,6 +10608,64 @@ class ISO20022 {
|
|
|
9976
10608
|
creationDate: config.creationDate,
|
|
9977
10609
|
});
|
|
9978
10610
|
}
|
|
10611
|
+
/**
|
|
10612
|
+
* Creates a SEPA Direct Debit Payment Initiation message.
|
|
10613
|
+
* @param {SEPADirectDebitPaymentInitiationConfig} config - Configuration containing payment instruction groups and optional parameters.
|
|
10614
|
+
* @example
|
|
10615
|
+
* const payment = iso20022.createSEPADirectDebitPaymentInitiation({
|
|
10616
|
+
* paymentInstructions: [
|
|
10617
|
+
* {
|
|
10618
|
+
* creditor: {
|
|
10619
|
+
* name: 'Landlord Company Ltd',
|
|
10620
|
+
* account: {
|
|
10621
|
+
* iban: 'DE54120300001030860744',
|
|
10622
|
+
* },
|
|
10623
|
+
* agent: {
|
|
10624
|
+
* bic: 'BYLADEM1001',
|
|
10625
|
+
* },
|
|
10626
|
+
* },
|
|
10627
|
+
* creditorSchemeId: 'DE96ZZZ00000345986',
|
|
10628
|
+
* requestedCollectionDate: new Date('2025-11-22'),
|
|
10629
|
+
* sequenceType: 'RCUR',
|
|
10630
|
+
* payments: [
|
|
10631
|
+
* {
|
|
10632
|
+
* type: 'sepa',
|
|
10633
|
+
* direction: 'debit',
|
|
10634
|
+
* amount: 31700, // €317.00 Euros
|
|
10635
|
+
* currency: 'EUR',
|
|
10636
|
+
* debtor: {
|
|
10637
|
+
* name: 'John Doe',
|
|
10638
|
+
* account: {
|
|
10639
|
+
* iban: 'DE20120300001088243355',
|
|
10640
|
+
* },
|
|
10641
|
+
* agent: {
|
|
10642
|
+
* bic: 'BYLADEM1001',
|
|
10643
|
+
* },
|
|
10644
|
+
* },
|
|
10645
|
+
* mandate: {
|
|
10646
|
+
* mandateId: 'MR-12345-001',
|
|
10647
|
+
* dateOfSignature: new Date('2024-01-15'),
|
|
10648
|
+
* amendmentIndicator: false,
|
|
10649
|
+
* },
|
|
10650
|
+
* remittanceInformation: 'Rent payment November 2024',
|
|
10651
|
+
* },
|
|
10652
|
+
* ],
|
|
10653
|
+
* localInstrument: 'CORE', // Optional
|
|
10654
|
+
* },
|
|
10655
|
+
* ],
|
|
10656
|
+
* messageId: 'DD-MSG-001', // Optional
|
|
10657
|
+
* creationDate: new Date('2025-03-01'), // Optional
|
|
10658
|
+
* });
|
|
10659
|
+
* @returns {SEPADirectDebitPaymentInitiation} A new SEPA Direct Debit Payment Initiation object.
|
|
10660
|
+
*/
|
|
10661
|
+
createSEPADirectDebitPaymentInitiation(config) {
|
|
10662
|
+
return new SEPADirectDebitPaymentInitiation({
|
|
10663
|
+
initiatingParty: this.initiatingParty,
|
|
10664
|
+
paymentInstructions: config.paymentInstructions,
|
|
10665
|
+
messageId: config.messageId,
|
|
10666
|
+
creationDate: config.creationDate,
|
|
10667
|
+
});
|
|
10668
|
+
}
|
|
9979
10669
|
/** Create a message CAMT or other */
|
|
9980
10670
|
createMessage(type, config) {
|
|
9981
10671
|
const implementation = getISO20022Implementation(type);
|
|
@@ -10264,9 +10954,10 @@ class CashManagementEndOfDayReport {
|
|
|
10264
10954
|
const parser = XML.getParser();
|
|
10265
10955
|
const xml = parser.parse(rawXml);
|
|
10266
10956
|
if (!xml.Document) {
|
|
10267
|
-
throw new InvalidXmlError(
|
|
10957
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
10268
10958
|
}
|
|
10269
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
10959
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
10960
|
+
xml.Document['@_Xmlns']);
|
|
10270
10961
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.053.001.')) {
|
|
10271
10962
|
throw new InvalidXmlNamespaceError('Invalid CAMT.053 namespace');
|
|
10272
10963
|
}
|
|
@@ -10281,7 +10972,7 @@ class CashManagementEndOfDayReport {
|
|
|
10281
10972
|
static fromJSON(json) {
|
|
10282
10973
|
const obj = JSON.parse(json);
|
|
10283
10974
|
if (!obj.Document) {
|
|
10284
|
-
throw new InvalidXmlError(
|
|
10975
|
+
throw new InvalidXmlError('Invalid JSON format');
|
|
10285
10976
|
}
|
|
10286
10977
|
return CashManagementEndOfDayReport.fromDocumentObject(obj);
|
|
10287
10978
|
}
|
|
@@ -10291,10 +10982,12 @@ class CashManagementEndOfDayReport {
|
|
|
10291
10982
|
GrpHdr: {
|
|
10292
10983
|
MsgId: this._messageId,
|
|
10293
10984
|
CreDtTm: this._creationDate.toISOString(),
|
|
10294
|
-
MsgRcpt: this._recipient
|
|
10985
|
+
MsgRcpt: this._recipient
|
|
10986
|
+
? exportRecipient(this._recipient)
|
|
10987
|
+
: undefined,
|
|
10295
10988
|
},
|
|
10296
|
-
Stmt: this._statements.map(
|
|
10297
|
-
}
|
|
10989
|
+
Stmt: this._statements.map(stmt => exportStatement(stmt)),
|
|
10990
|
+
},
|
|
10298
10991
|
};
|
|
10299
10992
|
return { Document };
|
|
10300
10993
|
}
|
|
@@ -10359,4 +11052,4 @@ class CashManagementEndOfDayReport {
|
|
|
10359
11052
|
}
|
|
10360
11053
|
registerISO20022Implementation(CashManagementEndOfDayReport);
|
|
10361
11054
|
|
|
10362
|
-
export { ACHCreditPaymentInitiation, ACHLocalInstrumentCode, ACHLocalInstrumentCodeDescriptionMap, BalanceTypeCode, BalanceTypeCodeDescriptionMap, CashManagementEndOfDayReport, ISO20022, InvalidXmlError, InvalidXmlNamespaceError, Iso20022JsError, PaymentStatusCode, PaymentStatusReport, RTPCreditPaymentInitiation, SEPACreditPaymentInitiation, SEPAMultiCreditPaymentInitiation, SWIFTCreditPaymentInitiation };
|
|
11055
|
+
export { ACHCreditPaymentInitiation, ACHLocalInstrumentCode, ACHLocalInstrumentCodeDescriptionMap, BalanceTypeCode, BalanceTypeCodeDescriptionMap, CashManagementEndOfDayReport, ISO20022, InvalidXmlError, InvalidXmlNamespaceError, Iso20022JsError, PaymentStatusCode, PaymentStatusReport, RTPCreditPaymentInitiation, SEPACreditPaymentInitiation, SEPADirectDebitPaymentInitiation, SEPAMultiCreditPaymentInitiation, SWIFTCreditPaymentInitiation };
|