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