@lucianpacurar/iso20022.js 0.2.10 → 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 +978 -283
- package/dist/index.mjs +978 -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,11 +8079,12 @@ 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',
|
|
8018
8086
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
8087
|
+
'@xsi:schemaLocation': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd',
|
|
8019
8088
|
CstmrCdtTrfInitn: {
|
|
8020
8089
|
GrpHdr: {
|
|
8021
8090
|
MsgId: this.messageId,
|
|
@@ -8036,7 +8105,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8036
8105
|
},
|
|
8037
8106
|
},
|
|
8038
8107
|
PmtInf: paymentInfoEntries,
|
|
8039
|
-
}
|
|
8108
|
+
},
|
|
8040
8109
|
},
|
|
8041
8110
|
};
|
|
8042
8111
|
return builder.build(xml);
|
|
@@ -8054,10 +8123,11 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8054
8123
|
const xml = parser.parse(rawXml);
|
|
8055
8124
|
// Validate XML structure
|
|
8056
8125
|
if (!xml.Document) {
|
|
8057
|
-
throw new InvalidXmlError(
|
|
8126
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8058
8127
|
}
|
|
8059
8128
|
// Validate namespace
|
|
8060
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8129
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8130
|
+
xml.Document['@_Xmlns']);
|
|
8061
8131
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8062
8132
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8063
8133
|
}
|
|
@@ -8067,7 +8137,8 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8067
8137
|
// Extract top-level initiating party from GrpHdr
|
|
8068
8138
|
const topLevelInitiatingParty = {
|
|
8069
8139
|
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm,
|
|
8070
|
-
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
8140
|
+
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
8141
|
+
?.Id,
|
|
8071
8142
|
};
|
|
8072
8143
|
// Normalize PmtInf to array (handle both single object and array cases)
|
|
8073
8144
|
const rawPmtInf = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf)
|
|
@@ -8085,7 +8156,9 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8085
8156
|
// Extract optional category purpose
|
|
8086
8157
|
const categoryPurpose = pmtInf.PmtTpInf?.CtgyPurp?.Cd;
|
|
8087
8158
|
// Extract requested execution date
|
|
8088
|
-
const requestedExecutionDate = pmtInf.ReqdExctnDt
|
|
8159
|
+
const requestedExecutionDate = pmtInf.ReqdExctnDt
|
|
8160
|
+
? new Date(pmtInf.ReqdExctnDt)
|
|
8161
|
+
: undefined;
|
|
8089
8162
|
// Normalize CdtTrfTxInf to array
|
|
8090
8163
|
const rawInstructions = Array.isArray(pmtInf.CdtTrfTxInf)
|
|
8091
8164
|
? pmtInf.CdtTrfTxInf
|
|
@@ -8096,29 +8169,56 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8096
8169
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8097
8170
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8098
8171
|
return {
|
|
8099
|
-
...(inst.PmtId.InstrId && {
|
|
8100
|
-
|
|
8172
|
+
...(inst.PmtId.InstrId && {
|
|
8173
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8174
|
+
}),
|
|
8175
|
+
...(inst.PmtId.EndToEndId && {
|
|
8176
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8177
|
+
}),
|
|
8101
8178
|
type: 'sepa',
|
|
8102
8179
|
direction: 'credit',
|
|
8103
8180
|
amount: amount,
|
|
8104
8181
|
currency: currency,
|
|
8105
|
-
...(requestedExecutionDate && {
|
|
8182
|
+
...(requestedExecutionDate && {
|
|
8183
|
+
requestedPaymentExecutionDate: requestedExecutionDate,
|
|
8184
|
+
}),
|
|
8106
8185
|
creditor: {
|
|
8107
8186
|
name: inst.Cdtr?.Nm,
|
|
8108
8187
|
agent: parseAgent(inst.CdtrAgt),
|
|
8109
8188
|
account: parseAccount(inst.CdtrAcct),
|
|
8110
|
-
...(
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
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
|
+
},
|
|
8118
8216
|
}
|
|
8119
|
-
|
|
8217
|
+
: {}),
|
|
8120
8218
|
},
|
|
8121
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8219
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8220
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8221
|
+
}),
|
|
8122
8222
|
};
|
|
8123
8223
|
});
|
|
8124
8224
|
return {
|
|
@@ -8166,7 +8266,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8166
8266
|
paymentInformationId;
|
|
8167
8267
|
formattedPaymentSum;
|
|
8168
8268
|
constructor(config) {
|
|
8169
|
-
super({ type:
|
|
8269
|
+
super({ type: 'rtp' });
|
|
8170
8270
|
this.initiatingParty = config.initiatingParty;
|
|
8171
8271
|
this.paymentInstructions = config.paymentInstructions;
|
|
8172
8272
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
@@ -8184,9 +8284,11 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8184
8284
|
*/
|
|
8185
8285
|
sumPaymentInstructions(instructions) {
|
|
8186
8286
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
8187
|
-
return instructionDineros
|
|
8287
|
+
return instructionDineros
|
|
8288
|
+
.reduce((acc, next) => {
|
|
8188
8289
|
return acc.add(next);
|
|
8189
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8290
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8291
|
+
.toFormat('0.00');
|
|
8190
8292
|
}
|
|
8191
8293
|
/**
|
|
8192
8294
|
* Validates the payment initiation data according to SEPA requirements.
|
|
@@ -8208,7 +8310,10 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8208
8310
|
creditTransfer(instruction) {
|
|
8209
8311
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
8210
8312
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8211
|
-
const dinero = Dinero({
|
|
8313
|
+
const dinero = Dinero({
|
|
8314
|
+
amount: instruction.amount,
|
|
8315
|
+
currency: instruction.currency,
|
|
8316
|
+
});
|
|
8212
8317
|
return {
|
|
8213
8318
|
PmtId: {
|
|
8214
8319
|
InstrId: paymentInstructionId,
|
|
@@ -8229,9 +8334,11 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8229
8334
|
},
|
|
8230
8335
|
},
|
|
8231
8336
|
},
|
|
8232
|
-
RmtInf: instruction.remittanceInformation
|
|
8233
|
-
|
|
8234
|
-
|
|
8337
|
+
RmtInf: instruction.remittanceInformation
|
|
8338
|
+
? {
|
|
8339
|
+
Ustrd: instruction.remittanceInformation,
|
|
8340
|
+
}
|
|
8341
|
+
: undefined,
|
|
8235
8342
|
};
|
|
8236
8343
|
}
|
|
8237
8344
|
/**
|
|
@@ -8243,7 +8350,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8243
8350
|
const xml = {
|
|
8244
8351
|
'?xml': {
|
|
8245
8352
|
'@version': '1.0',
|
|
8246
|
-
'@encoding': 'UTF-8'
|
|
8353
|
+
'@encoding': 'UTF-8',
|
|
8247
8354
|
},
|
|
8248
8355
|
Document: {
|
|
8249
8356
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8272,7 +8379,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8272
8379
|
CtrlSum: this.formattedPaymentSum,
|
|
8273
8380
|
PmtTpInf: {
|
|
8274
8381
|
SvcLvl: { Cd: 'URNS' },
|
|
8275
|
-
LclInstrm: { Prtry:
|
|
8382
|
+
LclInstrm: { Prtry: 'RTP' },
|
|
8276
8383
|
},
|
|
8277
8384
|
ReqdExctnDt: this.creationDate.toISOString().split('T').at(0),
|
|
8278
8385
|
Dbtr: this.party(this.initiatingParty),
|
|
@@ -8281,8 +8388,8 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8281
8388
|
ChrgBr: 'SLEV',
|
|
8282
8389
|
// payments[]
|
|
8283
8390
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
8284
|
-
}
|
|
8285
|
-
}
|
|
8391
|
+
},
|
|
8392
|
+
},
|
|
8286
8393
|
},
|
|
8287
8394
|
};
|
|
8288
8395
|
return builder.build(xml);
|
|
@@ -8291,9 +8398,10 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8291
8398
|
const parser = new fxp.XMLParser({ ignoreAttributes: false });
|
|
8292
8399
|
const xml = parser.parse(rawXml);
|
|
8293
8400
|
if (!xml.Document) {
|
|
8294
|
-
throw new InvalidXmlError(
|
|
8401
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8295
8402
|
}
|
|
8296
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8403
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8404
|
+
xml.Document['@_Xmlns']);
|
|
8297
8405
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8298
8406
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8299
8407
|
}
|
|
@@ -8304,19 +8412,27 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8304
8412
|
}
|
|
8305
8413
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
8306
8414
|
const initiatingParty = {
|
|
8307
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8308
|
-
|
|
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,
|
|
8309
8419
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
8310
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
8420
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
8311
8421
|
};
|
|
8312
|
-
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];
|
|
8313
8425
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
8314
8426
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
8315
8427
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8316
8428
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8317
8429
|
return {
|
|
8318
|
-
...(inst.PmtId.InstrId && {
|
|
8319
|
-
|
|
8430
|
+
...(inst.PmtId.InstrId && {
|
|
8431
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8432
|
+
}),
|
|
8433
|
+
...(inst.PmtId.EndToEndId && {
|
|
8434
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8435
|
+
}),
|
|
8320
8436
|
type: 'sepa',
|
|
8321
8437
|
direction: 'credit',
|
|
8322
8438
|
amount: amount,
|
|
@@ -8325,25 +8441,46 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8325
8441
|
name: inst.Cdtr?.Nm,
|
|
8326
8442
|
agent: parseAgent(inst.CdtrAgt),
|
|
8327
8443
|
account: parseAccount(inst.CdtrAcct),
|
|
8328
|
-
...(
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
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
|
+
},
|
|
8336
8471
|
}
|
|
8337
|
-
|
|
8472
|
+
: {}),
|
|
8338
8473
|
},
|
|
8339
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8474
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8475
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8476
|
+
}),
|
|
8340
8477
|
};
|
|
8341
8478
|
});
|
|
8342
8479
|
return new RTPCreditPaymentInitiation({
|
|
8343
8480
|
messageId: messageId,
|
|
8344
8481
|
creationDate: creationDate,
|
|
8345
8482
|
initiatingParty: initiatingParty,
|
|
8346
|
-
paymentInstructions: paymentInstructions
|
|
8483
|
+
paymentInstructions: paymentInstructions,
|
|
8347
8484
|
});
|
|
8348
8485
|
}
|
|
8349
8486
|
}
|
|
@@ -8378,14 +8515,14 @@ const ACHLocalInstrumentCode = {
|
|
|
8378
8515
|
RepresentedCheck: 'RCK',
|
|
8379
8516
|
};
|
|
8380
8517
|
const ACHLocalInstrumentCodeDescriptionMap = {
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
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',
|
|
8389
8526
|
};
|
|
8390
8527
|
|
|
8391
8528
|
/**
|
|
@@ -8444,13 +8581,14 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8444
8581
|
instructionPriority;
|
|
8445
8582
|
formattedPaymentSum;
|
|
8446
8583
|
constructor(config) {
|
|
8447
|
-
super({ type:
|
|
8584
|
+
super({ type: 'ach' });
|
|
8448
8585
|
this.initiatingParty = config.initiatingParty;
|
|
8449
8586
|
this.paymentInstructions = config.paymentInstructions;
|
|
8450
8587
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
8451
8588
|
this.creationDate = config.creationDate || new Date();
|
|
8452
8589
|
this.paymentInformationId = sanitize(v4(), 35);
|
|
8453
|
-
this.localInstrument =
|
|
8590
|
+
this.localInstrument =
|
|
8591
|
+
config.localInstrument || ACHLocalInstrumentCode.CorporateCreditDebit;
|
|
8454
8592
|
this.serviceLevel = 'NURG'; // Normal Urgency
|
|
8455
8593
|
this.instructionPriority = 'NORM'; // Normal Priority
|
|
8456
8594
|
this.formattedPaymentSum = this.sumPaymentInstructions(this.paymentInstructions);
|
|
@@ -8465,9 +8603,11 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8465
8603
|
*/
|
|
8466
8604
|
sumPaymentInstructions(instructions) {
|
|
8467
8605
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
8468
|
-
return instructionDineros
|
|
8606
|
+
return instructionDineros
|
|
8607
|
+
.reduce((acc, next) => {
|
|
8469
8608
|
return acc.add(next);
|
|
8470
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8609
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8610
|
+
.toFormat('0.00');
|
|
8471
8611
|
}
|
|
8472
8612
|
/**
|
|
8473
8613
|
* Validates the payment initiation data according to ACH requirements.
|
|
@@ -8495,7 +8635,10 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8495
8635
|
creditTransfer(instruction) {
|
|
8496
8636
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
8497
8637
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8498
|
-
const dinero = Dinero({
|
|
8638
|
+
const dinero = Dinero({
|
|
8639
|
+
amount: instruction.amount,
|
|
8640
|
+
currency: instruction.currency,
|
|
8641
|
+
});
|
|
8499
8642
|
return {
|
|
8500
8643
|
PmtId: {
|
|
8501
8644
|
InstrId: paymentInstructionId,
|
|
@@ -8520,9 +8663,11 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8520
8663
|
},
|
|
8521
8664
|
Ccy: instruction.currency,
|
|
8522
8665
|
},
|
|
8523
|
-
RmtInf: instruction.remittanceInformation
|
|
8524
|
-
|
|
8525
|
-
|
|
8666
|
+
RmtInf: instruction.remittanceInformation
|
|
8667
|
+
? {
|
|
8668
|
+
Ustrd: instruction.remittanceInformation,
|
|
8669
|
+
}
|
|
8670
|
+
: undefined,
|
|
8526
8671
|
};
|
|
8527
8672
|
}
|
|
8528
8673
|
/**
|
|
@@ -8534,7 +8679,7 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8534
8679
|
const xml = {
|
|
8535
8680
|
'?xml': {
|
|
8536
8681
|
'@version': '1.0',
|
|
8537
|
-
'@encoding': 'UTF-8'
|
|
8682
|
+
'@encoding': 'UTF-8',
|
|
8538
8683
|
},
|
|
8539
8684
|
Document: {
|
|
8540
8685
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8572,8 +8717,8 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8572
8717
|
ChrgBr: 'SHAR',
|
|
8573
8718
|
// payments[]
|
|
8574
8719
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
8575
|
-
}
|
|
8576
|
-
}
|
|
8720
|
+
},
|
|
8721
|
+
},
|
|
8577
8722
|
},
|
|
8578
8723
|
};
|
|
8579
8724
|
return builder.build(xml);
|
|
@@ -8587,12 +8732,17 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8587
8732
|
* @throws {Error} If multiple payment information blocks are found.
|
|
8588
8733
|
*/
|
|
8589
8734
|
static fromXML(rawXml) {
|
|
8590
|
-
const parser = new fxp.XMLParser({
|
|
8735
|
+
const parser = new fxp.XMLParser({
|
|
8736
|
+
ignoreAttributes: false,
|
|
8737
|
+
attributeNamePrefix: '@_',
|
|
8738
|
+
textNodeName: '#text',
|
|
8739
|
+
});
|
|
8591
8740
|
const xml = parser.parse(rawXml);
|
|
8592
8741
|
if (!xml.Document) {
|
|
8593
|
-
throw new InvalidXmlError(
|
|
8742
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8594
8743
|
}
|
|
8595
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8744
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8745
|
+
xml.Document['@_Xmlns']);
|
|
8596
8746
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8597
8747
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8598
8748
|
}
|
|
@@ -8605,19 +8755,27 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8605
8755
|
xml.Document.CstmrCdtTrfInitn.PmtInf.PmtTpInf;
|
|
8606
8756
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
8607
8757
|
const initiatingParty = {
|
|
8608
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8609
|
-
|
|
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,
|
|
8610
8762
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
8611
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
8763
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
8612
8764
|
};
|
|
8613
|
-
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];
|
|
8614
8768
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
8615
8769
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
8616
8770
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8617
8771
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8618
8772
|
return {
|
|
8619
|
-
...(inst.PmtId.InstrId && {
|
|
8620
|
-
|
|
8773
|
+
...(inst.PmtId.InstrId && {
|
|
8774
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8775
|
+
}),
|
|
8776
|
+
...(inst.PmtId.EndToEndId && {
|
|
8777
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8778
|
+
}),
|
|
8621
8779
|
type: 'ach',
|
|
8622
8780
|
direction: 'credit',
|
|
8623
8781
|
amount: amount,
|
|
@@ -8626,41 +8784,458 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8626
8784
|
name: inst.Cdtr?.Nm,
|
|
8627
8785
|
agent: parseAgent(inst.CdtrAgt),
|
|
8628
8786
|
account: parseAccount(inst.CdtrAcct),
|
|
8629
|
-
...(
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
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
|
+
},
|
|
8637
8814
|
}
|
|
8638
|
-
|
|
8815
|
+
: {}),
|
|
8639
8816
|
},
|
|
8640
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8817
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8818
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8819
|
+
}),
|
|
8641
8820
|
};
|
|
8642
8821
|
});
|
|
8643
8822
|
return new ACHCreditPaymentInitiation({
|
|
8644
8823
|
messageId: messageId,
|
|
8645
8824
|
creationDate: creationDate,
|
|
8646
8825
|
initiatingParty: initiatingParty,
|
|
8647
|
-
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,
|
|
8648
9223
|
});
|
|
8649
9224
|
}
|
|
8650
9225
|
}
|
|
8651
9226
|
|
|
8652
9227
|
const ISO20022Messages = {
|
|
8653
|
-
CAMT_003:
|
|
8654
|
-
CAMT_004:
|
|
8655
|
-
CAMT_005:
|
|
8656
|
-
CAMT_006:
|
|
8657
|
-
CAMT_053:
|
|
8658
|
-
PAIN_001:
|
|
8659
|
-
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',
|
|
8660
9235
|
};
|
|
8661
9236
|
const ISO20022Implementations = new Map();
|
|
8662
9237
|
function registerISO20022Implementation(cl) {
|
|
8663
|
-
cl.supportedMessages().forEach(
|
|
9238
|
+
cl.supportedMessages().forEach(msg => {
|
|
8664
9239
|
ISO20022Implementations.set(msg, cl);
|
|
8665
9240
|
});
|
|
8666
9241
|
}
|
|
@@ -8716,12 +9291,12 @@ class CashManagementGetAccount {
|
|
|
8716
9291
|
static fromDocumentOject(doc) {
|
|
8717
9292
|
const rawHeader = doc.Document?.GetAcct?.MsgHdr;
|
|
8718
9293
|
if (!rawHeader) {
|
|
8719
|
-
throw new InvalidStructureError(
|
|
9294
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing MsgHdr');
|
|
8720
9295
|
}
|
|
8721
9296
|
const header = parseMessageHeader(rawHeader);
|
|
8722
9297
|
const newCrit = doc.Document?.GetAcct?.AcctQryDef?.AcctCrit?.NewCrit;
|
|
8723
9298
|
if (!newCrit) {
|
|
8724
|
-
throw new InvalidStructureError(
|
|
9299
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing GetAcct.AcctQryDef.AcctCrit.NewCrit');
|
|
8725
9300
|
}
|
|
8726
9301
|
const name = newCrit.NewQryNm;
|
|
8727
9302
|
let searchCriteria = [];
|
|
@@ -8731,16 +9306,19 @@ class CashManagementGetAccount {
|
|
|
8731
9306
|
}
|
|
8732
9307
|
rawCriterias = rawCriterias.filter((c) => !!c);
|
|
8733
9308
|
if (rawCriterias.length === 0) {
|
|
8734
|
-
throw new InvalidStructureError(
|
|
9309
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing search criteria');
|
|
8735
9310
|
}
|
|
8736
9311
|
for (const rawCriterium of rawCriterias) {
|
|
8737
9312
|
const crit = {};
|
|
8738
9313
|
// search on Ids, only one criterium supported for now
|
|
8739
9314
|
if (rawCriterium.AcctId) {
|
|
8740
|
-
if (Array.isArray(rawCriterium.AcctId) &&
|
|
8741
|
-
|
|
9315
|
+
if (Array.isArray(rawCriterium.AcctId) &&
|
|
9316
|
+
rawCriterium.AcctId.length > 1) {
|
|
9317
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple AcctId criterium not supported');
|
|
8742
9318
|
}
|
|
8743
|
-
const acctId = Array.isArray(rawCriterium.AcctId)
|
|
9319
|
+
const acctId = Array.isArray(rawCriterium.AcctId)
|
|
9320
|
+
? rawCriterium.AcctId[0]
|
|
9321
|
+
: rawCriterium.AcctId;
|
|
8744
9322
|
if (acctId.CTTxt) {
|
|
8745
9323
|
crit.accountRegExp = `.*${acctId.CTTxt}.*`; // contains
|
|
8746
9324
|
}
|
|
@@ -8754,19 +9332,23 @@ class CashManagementGetAccount {
|
|
|
8754
9332
|
// search on currency
|
|
8755
9333
|
if (rawCriterium.Ccy) {
|
|
8756
9334
|
if (Array.isArray(rawCriterium.Ccy) && rawCriterium.Ccy.length > 1) {
|
|
8757
|
-
throw new InvalidStructureError(
|
|
9335
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple Ccy criterium not supported');
|
|
8758
9336
|
}
|
|
8759
|
-
const ccy = Array.isArray(rawCriterium.Ccy)
|
|
9337
|
+
const ccy = Array.isArray(rawCriterium.Ccy)
|
|
9338
|
+
? rawCriterium.Ccy[0]
|
|
9339
|
+
: rawCriterium.Ccy;
|
|
8760
9340
|
crit.currencyEqualTo = ccy;
|
|
8761
9341
|
}
|
|
8762
9342
|
// search on balance as of date
|
|
8763
9343
|
if (rawCriterium.Bal) {
|
|
8764
9344
|
if (Array.isArray(rawCriterium.Bal) && rawCriterium.Bal.length > 1) {
|
|
8765
|
-
throw new InvalidStructureError(
|
|
9345
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple Bal criterium not supported');
|
|
8766
9346
|
}
|
|
8767
|
-
const bal = Array.isArray(rawCriterium.Bal)
|
|
9347
|
+
const bal = Array.isArray(rawCriterium.Bal)
|
|
9348
|
+
? rawCriterium.Bal[0]
|
|
9349
|
+
: rawCriterium.Bal;
|
|
8768
9350
|
if (bal?.ValDt && Array.isArray(bal.ValDt) && bal.ValDt.length > 1) {
|
|
8769
|
-
throw new InvalidStructureError(
|
|
9351
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple ValDt criterium not supported');
|
|
8770
9352
|
}
|
|
8771
9353
|
const valDt = Array.isArray(bal?.ValDt) ? bal.ValDt[0] : bal?.ValDt;
|
|
8772
9354
|
if (valDt?.Dt?.EQDt) {
|
|
@@ -8787,9 +9369,10 @@ class CashManagementGetAccount {
|
|
|
8787
9369
|
const parser = XML.getParser();
|
|
8788
9370
|
const doc = parser.parse(xml);
|
|
8789
9371
|
if (!doc.Document) {
|
|
8790
|
-
throw new Error(
|
|
9372
|
+
throw new Error('Invalid XML format');
|
|
8791
9373
|
}
|
|
8792
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
9374
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
9375
|
+
doc.Document['@_Xmlns']);
|
|
8793
9376
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.003.001.')) {
|
|
8794
9377
|
throw new InvalidXmlNamespaceError('Invalid CAMT.003 namespace');
|
|
8795
9378
|
}
|
|
@@ -8798,7 +9381,7 @@ class CashManagementGetAccount {
|
|
|
8798
9381
|
static fromJSON(json) {
|
|
8799
9382
|
const obj = JSON.parse(json);
|
|
8800
9383
|
if (!obj.Document) {
|
|
8801
|
-
throw new Error(
|
|
9384
|
+
throw new Error('Invalid JSON format');
|
|
8802
9385
|
}
|
|
8803
9386
|
return CashManagementGetAccount.fromDocumentOject(obj);
|
|
8804
9387
|
}
|
|
@@ -8818,39 +9401,55 @@ class CashManagementGetAccount {
|
|
|
8818
9401
|
AcctCrit: {
|
|
8819
9402
|
NewCrit: {
|
|
8820
9403
|
NewQryNm: this._data.newCriteria?.name,
|
|
8821
|
-
SchCrit: this._data.newCriteria?.searchCriteria.map(
|
|
9404
|
+
SchCrit: this._data.newCriteria?.searchCriteria.map(c => {
|
|
8822
9405
|
const obj = {};
|
|
8823
9406
|
if (c.accountRegExp) {
|
|
8824
|
-
if (c.accountRegExp.startsWith('.*') &&
|
|
8825
|
-
|
|
9407
|
+
if (c.accountRegExp.startsWith('.*') &&
|
|
9408
|
+
c.accountRegExp.endsWith('.*')) {
|
|
9409
|
+
obj.AcctId = {
|
|
9410
|
+
CTTxt: c.accountRegExp
|
|
9411
|
+
.replace(/^\.\*/, '')
|
|
9412
|
+
.replace(/\.\*$/, ''),
|
|
9413
|
+
}; // contains
|
|
8826
9414
|
}
|
|
8827
|
-
else if (c.accountRegExp.startsWith('^((?!') &&
|
|
8828
|
-
|
|
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
|
|
8829
9422
|
}
|
|
8830
9423
|
}
|
|
8831
9424
|
else if (c.accountEqualTo) {
|
|
8832
9425
|
obj.AcctId = {
|
|
8833
|
-
EQ: exportAccountIdentification(c.accountEqualTo)
|
|
9426
|
+
EQ: exportAccountIdentification(c.accountEqualTo),
|
|
8834
9427
|
};
|
|
8835
9428
|
}
|
|
8836
9429
|
if (c.currencyEqualTo) {
|
|
8837
9430
|
obj.Ccy = [c.currencyEqualTo];
|
|
8838
9431
|
}
|
|
8839
9432
|
if (c.balanceAsOfDateEqualTo) {
|
|
8840
|
-
obj.Bal = [
|
|
8841
|
-
|
|
9433
|
+
obj.Bal = [
|
|
9434
|
+
{
|
|
9435
|
+
ValDt: [
|
|
9436
|
+
{
|
|
8842
9437
|
Dt: {
|
|
8843
|
-
EQDt: c.balanceAsOfDateEqualTo
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
9438
|
+
EQDt: c.balanceAsOfDateEqualTo
|
|
9439
|
+
.toISOString()
|
|
9440
|
+
.slice(0, 10),
|
|
9441
|
+
},
|
|
9442
|
+
},
|
|
9443
|
+
],
|
|
9444
|
+
},
|
|
9445
|
+
];
|
|
8847
9446
|
}
|
|
8848
9447
|
return obj;
|
|
8849
9448
|
}),
|
|
8850
|
-
}
|
|
8851
|
-
}
|
|
8852
|
-
}
|
|
8853
|
-
}
|
|
9449
|
+
},
|
|
9450
|
+
},
|
|
9451
|
+
},
|
|
9452
|
+
},
|
|
8854
9453
|
};
|
|
8855
9454
|
return { Document };
|
|
8856
9455
|
}
|
|
@@ -8950,10 +9549,10 @@ const exportStatement = (stmt) => {
|
|
|
8950
9549
|
},
|
|
8951
9550
|
Acct: {
|
|
8952
9551
|
...exportAccount(stmt.account),
|
|
8953
|
-
Svcr: exportAgent(stmt.agent)
|
|
9552
|
+
Svcr: exportAgent(stmt.agent),
|
|
8954
9553
|
},
|
|
8955
|
-
Bal: stmt.balances.map(
|
|
8956
|
-
Ntry: stmt.entries.map(
|
|
9554
|
+
Bal: stmt.balances.map(bal => exportBalance(bal)),
|
|
9555
|
+
Ntry: stmt.entries.map(entry => exportEntry(entry)),
|
|
8957
9556
|
};
|
|
8958
9557
|
return obj;
|
|
8959
9558
|
};
|
|
@@ -9077,7 +9676,9 @@ const exportEntry = (entry) => {
|
|
|
9077
9676
|
BkTxCd: exportBankTransactionCode(entry.bankTransactionCode, entry.proprietaryCode),
|
|
9078
9677
|
AddtlNtryInf: entry.additionalInformation,
|
|
9079
9678
|
AcctSvcrRef: entry.accountServicerReferenceId,
|
|
9080
|
-
NtryDtls: entry.transactions.map(
|
|
9679
|
+
NtryDtls: entry.transactions.map(tx => ({
|
|
9680
|
+
TxDtls: exportTransactionDetails(tx),
|
|
9681
|
+
})),
|
|
9081
9682
|
};
|
|
9082
9683
|
return obj;
|
|
9083
9684
|
};
|
|
@@ -9170,7 +9771,9 @@ const exportTransactionDetails = (tx) => {
|
|
|
9170
9771
|
Dbtr: {
|
|
9171
9772
|
Nm: tx.debtor.name,
|
|
9172
9773
|
},
|
|
9173
|
-
DbtrAcct: tx.debtor.account
|
|
9774
|
+
DbtrAcct: tx.debtor.account
|
|
9775
|
+
? exportAccount(tx.debtor.account)
|
|
9776
|
+
: undefined,
|
|
9174
9777
|
};
|
|
9175
9778
|
obj.RltdAgts = {
|
|
9176
9779
|
DbtrAgt: tx.debtor.agent ? exportAgent(tx.debtor.agent) : undefined,
|
|
@@ -9182,7 +9785,9 @@ const exportTransactionDetails = (tx) => {
|
|
|
9182
9785
|
Cdtr: {
|
|
9183
9786
|
Nm: tx.creditor.name,
|
|
9184
9787
|
},
|
|
9185
|
-
CdtrAcct: tx.creditor.account
|
|
9788
|
+
CdtrAcct: tx.creditor.account
|
|
9789
|
+
? exportAccount(tx.creditor.account)
|
|
9790
|
+
: undefined,
|
|
9186
9791
|
};
|
|
9187
9792
|
obj.RltdAgts = {
|
|
9188
9793
|
CdtrAgt: tx.creditor.agent ? exportAgent(tx.creditor.agent) : undefined,
|
|
@@ -9227,7 +9832,7 @@ const exportBankTransactionCode = (bankTransactionCode, proprietaryCode) => {
|
|
|
9227
9832
|
return obj;
|
|
9228
9833
|
};
|
|
9229
9834
|
const parseBusinessError = (bizErr) => {
|
|
9230
|
-
const code = bizErr.Err?.Cd || bizErr.Err?.Prtry ||
|
|
9835
|
+
const code = bizErr.Err?.Cd || bizErr.Err?.Prtry || 'UKNW';
|
|
9231
9836
|
const description = bizErr.Desc;
|
|
9232
9837
|
return {
|
|
9233
9838
|
code,
|
|
@@ -9258,7 +9863,7 @@ class CashManagementReturnAccount {
|
|
|
9258
9863
|
static fromDocumentOject(doc) {
|
|
9259
9864
|
const rawHeader = doc.Document?.RtrAcct?.MsgHdr;
|
|
9260
9865
|
if (!rawHeader) {
|
|
9261
|
-
throw new InvalidStructureError(
|
|
9866
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing MsgHdr');
|
|
9262
9867
|
}
|
|
9263
9868
|
const header = parseMessageHeader(rawHeader);
|
|
9264
9869
|
// interpret the report
|
|
@@ -9273,7 +9878,7 @@ class CashManagementReturnAccount {
|
|
|
9273
9878
|
if (r.AcctOrErr?.Acct) {
|
|
9274
9879
|
// report
|
|
9275
9880
|
if (!r.AcctOrErr.Acct.Ccy) {
|
|
9276
|
-
throw new InvalidStructureError(
|
|
9881
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing Ccy in Acct');
|
|
9277
9882
|
}
|
|
9278
9883
|
let rawMulBal = r.AcctOrErr.Acct.MulBal;
|
|
9279
9884
|
if (!Array.isArray(rawMulBal))
|
|
@@ -9286,7 +9891,7 @@ class CashManagementReturnAccount {
|
|
|
9286
9891
|
balances: rawMulBal.map((bal) => parseBalanceReport(r.AcctOrErr.Acct.Ccy, bal)),
|
|
9287
9892
|
};
|
|
9288
9893
|
if (report.balances.length === 0) {
|
|
9289
|
-
throw new InvalidStructureError(
|
|
9894
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing MulBal in Acct');
|
|
9290
9895
|
}
|
|
9291
9896
|
}
|
|
9292
9897
|
else if (r.AcctOrErr?.BizErr) {
|
|
@@ -9294,7 +9899,7 @@ class CashManagementReturnAccount {
|
|
|
9294
9899
|
error = parseBusinessError(r.AcctOrErr.BizErr);
|
|
9295
9900
|
}
|
|
9296
9901
|
else {
|
|
9297
|
-
throw new InvalidStructureError(
|
|
9902
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing AcctOrErr');
|
|
9298
9903
|
}
|
|
9299
9904
|
return { accountId, report, error };
|
|
9300
9905
|
});
|
|
@@ -9307,9 +9912,10 @@ class CashManagementReturnAccount {
|
|
|
9307
9912
|
const parser = XML.getParser();
|
|
9308
9913
|
const doc = parser.parse(xml);
|
|
9309
9914
|
if (!doc.Document) {
|
|
9310
|
-
throw new Error(
|
|
9915
|
+
throw new Error('Invalid XML format');
|
|
9311
9916
|
}
|
|
9312
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
9917
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
9918
|
+
doc.Document['@_Xmlns']);
|
|
9313
9919
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.004.001.')) {
|
|
9314
9920
|
throw new InvalidXmlNamespaceError('Invalid CAMT.004 namespace');
|
|
9315
9921
|
}
|
|
@@ -9318,7 +9924,7 @@ class CashManagementReturnAccount {
|
|
|
9318
9924
|
static fromJSON(json) {
|
|
9319
9925
|
const obj = JSON.parse(json);
|
|
9320
9926
|
if (!obj.Document) {
|
|
9321
|
-
throw new Error(
|
|
9927
|
+
throw new Error('Invalid JSON format');
|
|
9322
9928
|
}
|
|
9323
9929
|
return CashManagementReturnAccount.fromDocumentOject(obj);
|
|
9324
9930
|
}
|
|
@@ -9335,7 +9941,7 @@ class CashManagementReturnAccount {
|
|
|
9335
9941
|
RtrAcct: {
|
|
9336
9942
|
MsgHdr: exportMessageHeader(this._data.header),
|
|
9337
9943
|
RptOrErr: {
|
|
9338
|
-
AcctRpt: this._data.reports.map(
|
|
9944
|
+
AcctRpt: this._data.reports.map(report => {
|
|
9339
9945
|
const obj = {
|
|
9340
9946
|
AcctId: exportAccountIdentification(report.accountId),
|
|
9341
9947
|
AcctOrErr: {}, // filled below
|
|
@@ -9345,16 +9951,16 @@ class CashManagementReturnAccount {
|
|
|
9345
9951
|
Ccy: report.report.currency,
|
|
9346
9952
|
Nm: report.report.name,
|
|
9347
9953
|
Tp: { Cd: report.report.type }, // TODO add Prtry handling
|
|
9348
|
-
MulBal: report.report.balances.map(
|
|
9954
|
+
MulBal: report.report.balances.map(bal => exportBalanceReport(report.report.currency, bal)),
|
|
9349
9955
|
};
|
|
9350
9956
|
}
|
|
9351
9957
|
else if (report.error) {
|
|
9352
9958
|
obj.AcctOrErr.BizErr = exportBusinessError(report.error);
|
|
9353
9959
|
}
|
|
9354
9960
|
return obj;
|
|
9355
|
-
})
|
|
9356
|
-
}
|
|
9357
|
-
}
|
|
9961
|
+
}),
|
|
9962
|
+
},
|
|
9963
|
+
},
|
|
9358
9964
|
};
|
|
9359
9965
|
return { Document };
|
|
9360
9966
|
}
|
|
@@ -9375,12 +9981,12 @@ class CashManagementGetTransaction {
|
|
|
9375
9981
|
static fromDocumentOject(doc) {
|
|
9376
9982
|
const rawHeader = doc.Document?.GetTx?.MsgHdr;
|
|
9377
9983
|
if (!rawHeader) {
|
|
9378
|
-
throw new InvalidStructureError(
|
|
9984
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing MsgHdr');
|
|
9379
9985
|
}
|
|
9380
9986
|
const header = parseMessageHeader(rawHeader);
|
|
9381
9987
|
const newCrit = doc.Document?.GetTx?.TxQryDef?.TxCrit?.NewCrit;
|
|
9382
9988
|
if (!newCrit) {
|
|
9383
|
-
throw new InvalidStructureError(
|
|
9989
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing GetTx.TxQryDef.TxCrit.NewCrit');
|
|
9384
9990
|
}
|
|
9385
9991
|
const name = newCrit.NewQryNm;
|
|
9386
9992
|
let searchCriteria = [];
|
|
@@ -9390,35 +9996,42 @@ class CashManagementGetTransaction {
|
|
|
9390
9996
|
}
|
|
9391
9997
|
rawCriterias = rawCriterias.filter((c) => !!c);
|
|
9392
9998
|
if (rawCriterias.length === 0) {
|
|
9393
|
-
throw new InvalidStructureError(
|
|
9999
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing search criteria');
|
|
9394
10000
|
}
|
|
9395
10001
|
for (const rawCriterium of rawCriterias) {
|
|
9396
10002
|
// search on Ids
|
|
9397
10003
|
if (rawCriterium.PmtSch.MsgId) {
|
|
9398
10004
|
searchCriteria.push({
|
|
9399
|
-
type:
|
|
9400
|
-
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],
|
|
9401
10009
|
});
|
|
9402
10010
|
}
|
|
9403
10011
|
// seach on date
|
|
9404
10012
|
if (rawCriterium.PmtSch.ReqdExctnDt) {
|
|
9405
|
-
if (Array.isArray(rawCriterium.PmtSch.ReqdExctnDt) &&
|
|
9406
|
-
|
|
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');
|
|
9407
10016
|
}
|
|
9408
|
-
const criterium = Array.isArray(rawCriterium.PmtSch.ReqdExctnDt)
|
|
10017
|
+
const criterium = Array.isArray(rawCriterium.PmtSch.ReqdExctnDt)
|
|
10018
|
+
? rawCriterium.PmtSch.ReqdExctnDt[0]
|
|
10019
|
+
: rawCriterium.PmtSch.ReqdExctnDt;
|
|
9409
10020
|
if (criterium?.DtSch?.EQDt) {
|
|
9410
10021
|
searchCriteria.push({
|
|
9411
|
-
type:
|
|
10022
|
+
type: 'PmtSch.ReqdExctnDt',
|
|
9412
10023
|
dateEqualTo: parseDate(criterium.DtSch.EQDt),
|
|
9413
10024
|
});
|
|
9414
10025
|
}
|
|
9415
10026
|
}
|
|
9416
|
-
let pmtIds = Array.isArray(rawCriterium.PmtSch.PmtId)
|
|
9417
|
-
|
|
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);
|
|
9418
10031
|
if (pmtIds.length > 0) {
|
|
9419
10032
|
searchCriteria.push({
|
|
9420
|
-
type:
|
|
9421
|
-
endToEndIdEqualTo: pmtIds.map(
|
|
10033
|
+
type: 'PmtSch.PmtId.LngBizId.EndToEndId',
|
|
10034
|
+
endToEndIdEqualTo: pmtIds.map(id => id.LngBizId.EndToEndId),
|
|
9422
10035
|
});
|
|
9423
10036
|
}
|
|
9424
10037
|
}
|
|
@@ -9434,9 +10047,10 @@ class CashManagementGetTransaction {
|
|
|
9434
10047
|
const parser = XML.getParser();
|
|
9435
10048
|
const doc = parser.parse(xml);
|
|
9436
10049
|
if (!doc.Document) {
|
|
9437
|
-
throw new Error(
|
|
10050
|
+
throw new Error('Invalid XML format');
|
|
9438
10051
|
}
|
|
9439
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
10052
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
10053
|
+
doc.Document['@_Xmlns']);
|
|
9440
10054
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.005.001.')) {
|
|
9441
10055
|
throw new InvalidXmlNamespaceError('Invalid CAMT.005 namespace');
|
|
9442
10056
|
}
|
|
@@ -9445,7 +10059,7 @@ class CashManagementGetTransaction {
|
|
|
9445
10059
|
static fromJSON(json) {
|
|
9446
10060
|
const obj = JSON.parse(json);
|
|
9447
10061
|
if (!obj.Document) {
|
|
9448
|
-
throw new Error(
|
|
10062
|
+
throw new Error('Invalid JSON format');
|
|
9449
10063
|
}
|
|
9450
10064
|
return CashManagementGetTransaction.fromDocumentOject(obj);
|
|
9451
10065
|
}
|
|
@@ -9465,37 +10079,38 @@ class CashManagementGetTransaction {
|
|
|
9465
10079
|
TxCrit: {
|
|
9466
10080
|
NewCrit: {
|
|
9467
10081
|
NewQryNm: this._data.newCriteria?.name,
|
|
9468
|
-
SchCrit: this._data.newCriteria?.searchCriteria.map(
|
|
10082
|
+
SchCrit: this._data.newCriteria?.searchCriteria.map(c => {
|
|
9469
10083
|
const obj = {};
|
|
9470
|
-
if (c.type ===
|
|
10084
|
+
if (c.type === 'PmtSch.MsgId' && c.msgIdsEqualTo) {
|
|
9471
10085
|
obj.PmtSch = {
|
|
9472
|
-
MsgId: c.msgIdsEqualTo
|
|
10086
|
+
MsgId: c.msgIdsEqualTo,
|
|
9473
10087
|
};
|
|
9474
10088
|
}
|
|
9475
|
-
if (c.type ===
|
|
10089
|
+
if (c.type === 'PmtSch.ReqdExctnDt' && c.dateEqualTo) {
|
|
9476
10090
|
obj.PmtSch = {
|
|
9477
10091
|
ReqdExctnDt: {
|
|
9478
10092
|
DtSch: {
|
|
9479
10093
|
EQDt: c.dateEqualTo.toISOString().slice(0, 10),
|
|
9480
|
-
}
|
|
9481
|
-
}
|
|
10094
|
+
},
|
|
10095
|
+
},
|
|
9482
10096
|
};
|
|
9483
10097
|
}
|
|
9484
|
-
if (c.type ===
|
|
10098
|
+
if (c.type === 'PmtSch.PmtId.LngBizId.EndToEndId' &&
|
|
10099
|
+
c.endToEndIdEqualTo) {
|
|
9485
10100
|
obj.PmtSch = {
|
|
9486
|
-
PmtId: c.endToEndIdEqualTo.map(
|
|
10101
|
+
PmtId: c.endToEndIdEqualTo.map(id => ({
|
|
9487
10102
|
LngBizId: {
|
|
9488
10103
|
EndToEndId: id,
|
|
9489
|
-
}
|
|
9490
|
-
}))
|
|
10104
|
+
},
|
|
10105
|
+
})),
|
|
9491
10106
|
};
|
|
9492
10107
|
}
|
|
9493
10108
|
return obj;
|
|
9494
10109
|
}),
|
|
9495
|
-
}
|
|
9496
|
-
}
|
|
9497
|
-
}
|
|
9498
|
-
}
|
|
10110
|
+
},
|
|
10111
|
+
},
|
|
10112
|
+
},
|
|
10113
|
+
},
|
|
9499
10114
|
};
|
|
9500
10115
|
return { Document };
|
|
9501
10116
|
}
|
|
@@ -9516,7 +10131,7 @@ class CashManagementReturnTransaction {
|
|
|
9516
10131
|
static fromDocumentOject(doc) {
|
|
9517
10132
|
const rawHeader = doc.Document?.RtrTx?.MsgHdr;
|
|
9518
10133
|
if (!rawHeader) {
|
|
9519
|
-
throw new InvalidStructureError(
|
|
10134
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing MsgHdr');
|
|
9520
10135
|
}
|
|
9521
10136
|
const header = parseMessageHeader(rawHeader);
|
|
9522
10137
|
// interpret the report
|
|
@@ -9525,7 +10140,8 @@ class CashManagementReturnTransaction {
|
|
|
9525
10140
|
rawReports = [rawReports];
|
|
9526
10141
|
rawReports = rawReports.filter((r) => !!r); // remove null/undefined
|
|
9527
10142
|
const reports = rawReports.map((r) => {
|
|
9528
|
-
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
|
|
9529
10145
|
const paymentId = {
|
|
9530
10146
|
currency: r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Ccy,
|
|
9531
10147
|
amount: parseAmountToMinorUnits(rawAmount, r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Ccy),
|
|
@@ -9535,20 +10151,24 @@ class CashManagementReturnTransaction {
|
|
|
9535
10151
|
};
|
|
9536
10152
|
// check required fields
|
|
9537
10153
|
if (!paymentId.currency) {
|
|
9538
|
-
throw new InvalidStructureError(
|
|
10154
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Ccy in PmtId.LngBizId.IntrBkSttlmAmt');
|
|
9539
10155
|
}
|
|
9540
|
-
if (paymentId.amount === undefined ||
|
|
9541
|
-
|
|
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');
|
|
9542
10160
|
}
|
|
9543
10161
|
if (!paymentId.endToEndId) {
|
|
9544
|
-
throw new InvalidStructureError(
|
|
10162
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing EndToEndId in PmtId.LngBizId');
|
|
9545
10163
|
}
|
|
9546
10164
|
let report = undefined;
|
|
9547
10165
|
let error = undefined;
|
|
9548
10166
|
if (r.TxOrErr?.Tx) {
|
|
9549
10167
|
// report
|
|
9550
10168
|
const msgId = r.TxOrErr.Tx.Pmt?.MsgId;
|
|
9551
|
-
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;
|
|
9552
10172
|
const status = ((sts) => {
|
|
9553
10173
|
if (!sts)
|
|
9554
10174
|
return undefined;
|
|
@@ -9556,9 +10176,13 @@ class CashManagementReturnTransaction {
|
|
|
9556
10176
|
return undefined;
|
|
9557
10177
|
if (Array.isArray(sts))
|
|
9558
10178
|
sts = sts[0]; // take the first one only
|
|
9559
|
-
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;
|
|
9560
10184
|
if (code)
|
|
9561
|
-
code = Object.keys(sts.Cd)[0] +
|
|
10185
|
+
code = Object.keys(sts.Cd)[0] + ':' + code; // prefix with the type of code
|
|
9562
10186
|
else
|
|
9563
10187
|
return undefined;
|
|
9564
10188
|
const reason = sts.Rsn?.Prtry;
|
|
@@ -9573,7 +10197,7 @@ class CashManagementReturnTransaction {
|
|
|
9573
10197
|
}
|
|
9574
10198
|
function parseAgent(agent) {
|
|
9575
10199
|
if (!agent)
|
|
9576
|
-
return { bic:
|
|
10200
|
+
return { bic: '' };
|
|
9577
10201
|
return { bic: agent?.FinInstnId?.BICFI };
|
|
9578
10202
|
}
|
|
9579
10203
|
report = {
|
|
@@ -9587,10 +10211,10 @@ class CashManagementReturnTransaction {
|
|
|
9587
10211
|
};
|
|
9588
10212
|
// check the debtor and creditor required fields
|
|
9589
10213
|
if (!report.debtor.id) {
|
|
9590
|
-
throw new InvalidStructureError(
|
|
10214
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Id in TxOrErr.Tx.Dbtr.Pty');
|
|
9591
10215
|
}
|
|
9592
10216
|
if (!report.creditor.id) {
|
|
9593
|
-
throw new InvalidStructureError(
|
|
10217
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Id in TxOrErr.Tx.Cdtr.Pty');
|
|
9594
10218
|
}
|
|
9595
10219
|
}
|
|
9596
10220
|
else if (r.TxOrErr?.BizErr) {
|
|
@@ -9598,7 +10222,7 @@ class CashManagementReturnTransaction {
|
|
|
9598
10222
|
error = parseBusinessError(r.TxOrErr.BizErr);
|
|
9599
10223
|
}
|
|
9600
10224
|
else {
|
|
9601
|
-
throw new InvalidStructureError(
|
|
10225
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing TxOrErr');
|
|
9602
10226
|
}
|
|
9603
10227
|
return { paymentId, report, error };
|
|
9604
10228
|
});
|
|
@@ -9611,9 +10235,10 @@ class CashManagementReturnTransaction {
|
|
|
9611
10235
|
const parser = XML.getParser();
|
|
9612
10236
|
const doc = parser.parse(xml);
|
|
9613
10237
|
if (!doc.Document) {
|
|
9614
|
-
throw new Error(
|
|
10238
|
+
throw new Error('Invalid XML format');
|
|
9615
10239
|
}
|
|
9616
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
10240
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
10241
|
+
doc.Document['@_Xmlns']);
|
|
9617
10242
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.004.001.')) {
|
|
9618
10243
|
throw new InvalidXmlNamespaceError('Invalid CAMT.004 namespace');
|
|
9619
10244
|
}
|
|
@@ -9622,7 +10247,7 @@ class CashManagementReturnTransaction {
|
|
|
9622
10247
|
static fromJSON(json) {
|
|
9623
10248
|
const obj = JSON.parse(json);
|
|
9624
10249
|
if (!obj.Document) {
|
|
9625
|
-
throw new Error(
|
|
10250
|
+
throw new Error('Invalid JSON format');
|
|
9626
10251
|
}
|
|
9627
10252
|
return CashManagementReturnTransaction.fromDocumentOject(obj);
|
|
9628
10253
|
}
|
|
@@ -9640,7 +10265,7 @@ class CashManagementReturnTransaction {
|
|
|
9640
10265
|
MsgHdr: exportMessageHeader(this._data.header),
|
|
9641
10266
|
RptOrErr: {
|
|
9642
10267
|
BizRpt: {
|
|
9643
|
-
TxRpt: this._data.reports.map(
|
|
10268
|
+
TxRpt: this._data.reports.map(report => {
|
|
9644
10269
|
const obj = {
|
|
9645
10270
|
PmtId: {
|
|
9646
10271
|
LngBizId: {
|
|
@@ -9652,7 +10277,7 @@ class CashManagementReturnTransaction {
|
|
|
9652
10277
|
UETR: report.paymentId.uetr,
|
|
9653
10278
|
TxId: report.paymentId.transactionId,
|
|
9654
10279
|
EndToEndId: report.paymentId.endToEndId,
|
|
9655
|
-
}
|
|
10280
|
+
},
|
|
9656
10281
|
},
|
|
9657
10282
|
TxOrErr: {}, // filled below
|
|
9658
10283
|
};
|
|
@@ -9671,38 +10296,46 @@ class CashManagementReturnTransaction {
|
|
|
9671
10296
|
function exportAgent(a) {
|
|
9672
10297
|
if (!a)
|
|
9673
10298
|
return undefined;
|
|
9674
|
-
if (
|
|
10299
|
+
if ('bic' in a && a.bic)
|
|
9675
10300
|
return { FinInstnId: { BICFI: a.bic } };
|
|
9676
|
-
if (
|
|
10301
|
+
if ('abaRoutingNumber' in a && a.abaRoutingNumber)
|
|
9677
10302
|
return { FinInstId: { Othr: { Id: a.abaRoutingNumber } } };
|
|
9678
10303
|
return undefined;
|
|
9679
10304
|
}
|
|
9680
|
-
const [codeType, code] = report.report.status
|
|
10305
|
+
const [codeType, code] = report.report.status
|
|
10306
|
+
? report.report.status.code.split(':')
|
|
10307
|
+
: [undefined, undefined];
|
|
9681
10308
|
obj.TxOrErr.Tx = {
|
|
9682
10309
|
Pmt: {
|
|
9683
10310
|
MsgId: report.report.msgId,
|
|
9684
|
-
ReqdExctnDt: {
|
|
10311
|
+
ReqdExctnDt: {
|
|
10312
|
+
Dt: report.report.reqExecutionDate
|
|
10313
|
+
?.toISOString()
|
|
10314
|
+
?.slice(0, 10),
|
|
10315
|
+
},
|
|
9685
10316
|
Sts: {
|
|
9686
10317
|
Cd: codeType ? { [codeType]: code } : undefined,
|
|
9687
|
-
Rsn: report.report.status?.reason
|
|
10318
|
+
Rsn: report.report.status?.reason
|
|
10319
|
+
? { Prtry: report.report.status.reason }
|
|
10320
|
+
: undefined,
|
|
9688
10321
|
},
|
|
9689
10322
|
Pties: {
|
|
9690
10323
|
Dbtr: exportParty(report.report.debtor),
|
|
9691
10324
|
DbtrAgt: exportAgent(report.report.debtorAgent),
|
|
9692
10325
|
Cdtr: exportParty(report.report.creditor),
|
|
9693
10326
|
CdtrAgt: exportAgent(report.report.creditorAgent),
|
|
9694
|
-
}
|
|
9695
|
-
}
|
|
10327
|
+
},
|
|
10328
|
+
},
|
|
9696
10329
|
};
|
|
9697
10330
|
}
|
|
9698
10331
|
else if (report.error) {
|
|
9699
10332
|
obj.TxOrErr.BizErr = exportBusinessError(report.error);
|
|
9700
10333
|
}
|
|
9701
10334
|
return obj;
|
|
9702
|
-
})
|
|
9703
|
-
}
|
|
9704
|
-
}
|
|
9705
|
-
}
|
|
10335
|
+
}),
|
|
10336
|
+
},
|
|
10337
|
+
},
|
|
10338
|
+
},
|
|
9706
10339
|
};
|
|
9707
10340
|
return { Document };
|
|
9708
10341
|
}
|
|
@@ -9742,17 +10375,17 @@ const BalanceTypeCode = {
|
|
|
9742
10375
|
* Description mapping of BalanceTypeCode values to their names.
|
|
9743
10376
|
*/
|
|
9744
10377
|
const BalanceTypeCodeDescriptionMap = {
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
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',
|
|
9756
10389
|
};
|
|
9757
10390
|
|
|
9758
10391
|
/**
|
|
@@ -9975,6 +10608,64 @@ class ISO20022 {
|
|
|
9975
10608
|
creationDate: config.creationDate,
|
|
9976
10609
|
});
|
|
9977
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
|
+
}
|
|
9978
10669
|
/** Create a message CAMT or other */
|
|
9979
10670
|
createMessage(type, config) {
|
|
9980
10671
|
const implementation = getISO20022Implementation(type);
|
|
@@ -10263,9 +10954,10 @@ class CashManagementEndOfDayReport {
|
|
|
10263
10954
|
const parser = XML.getParser();
|
|
10264
10955
|
const xml = parser.parse(rawXml);
|
|
10265
10956
|
if (!xml.Document) {
|
|
10266
|
-
throw new InvalidXmlError(
|
|
10957
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
10267
10958
|
}
|
|
10268
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
10959
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
10960
|
+
xml.Document['@_Xmlns']);
|
|
10269
10961
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.053.001.')) {
|
|
10270
10962
|
throw new InvalidXmlNamespaceError('Invalid CAMT.053 namespace');
|
|
10271
10963
|
}
|
|
@@ -10280,7 +10972,7 @@ class CashManagementEndOfDayReport {
|
|
|
10280
10972
|
static fromJSON(json) {
|
|
10281
10973
|
const obj = JSON.parse(json);
|
|
10282
10974
|
if (!obj.Document) {
|
|
10283
|
-
throw new InvalidXmlError(
|
|
10975
|
+
throw new InvalidXmlError('Invalid JSON format');
|
|
10284
10976
|
}
|
|
10285
10977
|
return CashManagementEndOfDayReport.fromDocumentObject(obj);
|
|
10286
10978
|
}
|
|
@@ -10290,10 +10982,12 @@ class CashManagementEndOfDayReport {
|
|
|
10290
10982
|
GrpHdr: {
|
|
10291
10983
|
MsgId: this._messageId,
|
|
10292
10984
|
CreDtTm: this._creationDate.toISOString(),
|
|
10293
|
-
MsgRcpt: this._recipient
|
|
10985
|
+
MsgRcpt: this._recipient
|
|
10986
|
+
? exportRecipient(this._recipient)
|
|
10987
|
+
: undefined,
|
|
10294
10988
|
},
|
|
10295
|
-
Stmt: this._statements.map(
|
|
10296
|
-
}
|
|
10989
|
+
Stmt: this._statements.map(stmt => exportStatement(stmt)),
|
|
10990
|
+
},
|
|
10297
10991
|
};
|
|
10298
10992
|
return { Document };
|
|
10299
10993
|
}
|
|
@@ -10358,4 +11052,4 @@ class CashManagementEndOfDayReport {
|
|
|
10358
11052
|
}
|
|
10359
11053
|
registerISO20022Implementation(CashManagementEndOfDayReport);
|
|
10360
11054
|
|
|
10361
|
-
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 };
|