@lucianpacurar/iso20022.js 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +387 -18
- package/dist/index.js +978 -283
- package/dist/index.mjs +978 -284
- package/dist/src/camt/003/cash-management-get-account.d.ts +2 -2
- package/dist/src/camt/004/cash-management-return-account.d.ts +4 -4
- package/dist/src/camt/005/cash-management-get-transaction.d.ts +2 -2
- package/dist/src/camt/006/cash-management-return-transaction.d.ts +4 -4
- package/dist/src/camt/index.d.ts +5 -5
- package/dist/src/index.d.ts +5 -3
- package/dist/src/iso20022.d.ts +118 -0
- package/dist/src/lib/types.d.ts +56 -0
- package/dist/src/pain/001/payment-initiation.d.ts +2 -2
- package/dist/src/pain/001/sepa-credit-payment-initiation.d.ts +1 -1
- package/dist/src/pain/001/sepa-multi-credit-payment-initiation.d.ts +1 -1
- package/dist/src/pain/008/index.d.ts +2 -0
- package/dist/src/pain/008/sepa-direct-debit-payment-initiation.d.ts +198 -0
- package/package.json +1 -1
package/dist/index.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,11 +8081,12 @@ 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',
|
|
8020
8088
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
8089
|
+
'@xsi:schemaLocation': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd',
|
|
8021
8090
|
CstmrCdtTrfInitn: {
|
|
8022
8091
|
GrpHdr: {
|
|
8023
8092
|
MsgId: this.messageId,
|
|
@@ -8038,7 +8107,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8038
8107
|
},
|
|
8039
8108
|
},
|
|
8040
8109
|
PmtInf: paymentInfoEntries,
|
|
8041
|
-
}
|
|
8110
|
+
},
|
|
8042
8111
|
},
|
|
8043
8112
|
};
|
|
8044
8113
|
return builder.build(xml);
|
|
@@ -8056,10 +8125,11 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8056
8125
|
const xml = parser.parse(rawXml);
|
|
8057
8126
|
// Validate XML structure
|
|
8058
8127
|
if (!xml.Document) {
|
|
8059
|
-
throw new InvalidXmlError(
|
|
8128
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8060
8129
|
}
|
|
8061
8130
|
// Validate namespace
|
|
8062
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8131
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8132
|
+
xml.Document['@_Xmlns']);
|
|
8063
8133
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8064
8134
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8065
8135
|
}
|
|
@@ -8069,7 +8139,8 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8069
8139
|
// Extract top-level initiating party from GrpHdr
|
|
8070
8140
|
const topLevelInitiatingParty = {
|
|
8071
8141
|
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm,
|
|
8072
|
-
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
8142
|
+
id: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id?.OrgId?.Othr
|
|
8143
|
+
?.Id,
|
|
8073
8144
|
};
|
|
8074
8145
|
// Normalize PmtInf to array (handle both single object and array cases)
|
|
8075
8146
|
const rawPmtInf = Array.isArray(xml.Document.CstmrCdtTrfInitn.PmtInf)
|
|
@@ -8087,7 +8158,9 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8087
8158
|
// Extract optional category purpose
|
|
8088
8159
|
const categoryPurpose = pmtInf.PmtTpInf?.CtgyPurp?.Cd;
|
|
8089
8160
|
// Extract requested execution date
|
|
8090
|
-
const requestedExecutionDate = pmtInf.ReqdExctnDt
|
|
8161
|
+
const requestedExecutionDate = pmtInf.ReqdExctnDt
|
|
8162
|
+
? new Date(pmtInf.ReqdExctnDt)
|
|
8163
|
+
: undefined;
|
|
8091
8164
|
// Normalize CdtTrfTxInf to array
|
|
8092
8165
|
const rawInstructions = Array.isArray(pmtInf.CdtTrfTxInf)
|
|
8093
8166
|
? pmtInf.CdtTrfTxInf
|
|
@@ -8098,29 +8171,56 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8098
8171
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8099
8172
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8100
8173
|
return {
|
|
8101
|
-
...(inst.PmtId.InstrId && {
|
|
8102
|
-
|
|
8174
|
+
...(inst.PmtId.InstrId && {
|
|
8175
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8176
|
+
}),
|
|
8177
|
+
...(inst.PmtId.EndToEndId && {
|
|
8178
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8179
|
+
}),
|
|
8103
8180
|
type: 'sepa',
|
|
8104
8181
|
direction: 'credit',
|
|
8105
8182
|
amount: amount,
|
|
8106
8183
|
currency: currency,
|
|
8107
|
-
...(requestedExecutionDate && {
|
|
8184
|
+
...(requestedExecutionDate && {
|
|
8185
|
+
requestedPaymentExecutionDate: requestedExecutionDate,
|
|
8186
|
+
}),
|
|
8108
8187
|
creditor: {
|
|
8109
8188
|
name: inst.Cdtr?.Nm,
|
|
8110
8189
|
agent: parseAgent(inst.CdtrAgt),
|
|
8111
8190
|
account: parseAccount(inst.CdtrAcct),
|
|
8112
|
-
...(
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
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
|
+
},
|
|
8120
8218
|
}
|
|
8121
|
-
|
|
8219
|
+
: {}),
|
|
8122
8220
|
},
|
|
8123
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8221
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8222
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8223
|
+
}),
|
|
8124
8224
|
};
|
|
8125
8225
|
});
|
|
8126
8226
|
return {
|
|
@@ -8168,7 +8268,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8168
8268
|
paymentInformationId;
|
|
8169
8269
|
formattedPaymentSum;
|
|
8170
8270
|
constructor(config) {
|
|
8171
|
-
super({ type:
|
|
8271
|
+
super({ type: 'rtp' });
|
|
8172
8272
|
this.initiatingParty = config.initiatingParty;
|
|
8173
8273
|
this.paymentInstructions = config.paymentInstructions;
|
|
8174
8274
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
@@ -8186,9 +8286,11 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8186
8286
|
*/
|
|
8187
8287
|
sumPaymentInstructions(instructions) {
|
|
8188
8288
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
8189
|
-
return instructionDineros
|
|
8289
|
+
return instructionDineros
|
|
8290
|
+
.reduce((acc, next) => {
|
|
8190
8291
|
return acc.add(next);
|
|
8191
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8292
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8293
|
+
.toFormat('0.00');
|
|
8192
8294
|
}
|
|
8193
8295
|
/**
|
|
8194
8296
|
* Validates the payment initiation data according to SEPA requirements.
|
|
@@ -8210,7 +8312,10 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8210
8312
|
creditTransfer(instruction) {
|
|
8211
8313
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
8212
8314
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8213
|
-
const dinero = Dinero({
|
|
8315
|
+
const dinero = Dinero({
|
|
8316
|
+
amount: instruction.amount,
|
|
8317
|
+
currency: instruction.currency,
|
|
8318
|
+
});
|
|
8214
8319
|
return {
|
|
8215
8320
|
PmtId: {
|
|
8216
8321
|
InstrId: paymentInstructionId,
|
|
@@ -8231,9 +8336,11 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8231
8336
|
},
|
|
8232
8337
|
},
|
|
8233
8338
|
},
|
|
8234
|
-
RmtInf: instruction.remittanceInformation
|
|
8235
|
-
|
|
8236
|
-
|
|
8339
|
+
RmtInf: instruction.remittanceInformation
|
|
8340
|
+
? {
|
|
8341
|
+
Ustrd: instruction.remittanceInformation,
|
|
8342
|
+
}
|
|
8343
|
+
: undefined,
|
|
8237
8344
|
};
|
|
8238
8345
|
}
|
|
8239
8346
|
/**
|
|
@@ -8245,7 +8352,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8245
8352
|
const xml = {
|
|
8246
8353
|
'?xml': {
|
|
8247
8354
|
'@version': '1.0',
|
|
8248
|
-
'@encoding': 'UTF-8'
|
|
8355
|
+
'@encoding': 'UTF-8',
|
|
8249
8356
|
},
|
|
8250
8357
|
Document: {
|
|
8251
8358
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8274,7 +8381,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8274
8381
|
CtrlSum: this.formattedPaymentSum,
|
|
8275
8382
|
PmtTpInf: {
|
|
8276
8383
|
SvcLvl: { Cd: 'URNS' },
|
|
8277
|
-
LclInstrm: { Prtry:
|
|
8384
|
+
LclInstrm: { Prtry: 'RTP' },
|
|
8278
8385
|
},
|
|
8279
8386
|
ReqdExctnDt: this.creationDate.toISOString().split('T').at(0),
|
|
8280
8387
|
Dbtr: this.party(this.initiatingParty),
|
|
@@ -8283,8 +8390,8 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8283
8390
|
ChrgBr: 'SLEV',
|
|
8284
8391
|
// payments[]
|
|
8285
8392
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
8286
|
-
}
|
|
8287
|
-
}
|
|
8393
|
+
},
|
|
8394
|
+
},
|
|
8288
8395
|
},
|
|
8289
8396
|
};
|
|
8290
8397
|
return builder.build(xml);
|
|
@@ -8293,9 +8400,10 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8293
8400
|
const parser = new fxp.XMLParser({ ignoreAttributes: false });
|
|
8294
8401
|
const xml = parser.parse(rawXml);
|
|
8295
8402
|
if (!xml.Document) {
|
|
8296
|
-
throw new InvalidXmlError(
|
|
8403
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8297
8404
|
}
|
|
8298
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8405
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8406
|
+
xml.Document['@_Xmlns']);
|
|
8299
8407
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8300
8408
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8301
8409
|
}
|
|
@@ -8306,19 +8414,27 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8306
8414
|
}
|
|
8307
8415
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
8308
8416
|
const initiatingParty = {
|
|
8309
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8310
|
-
|
|
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,
|
|
8311
8421
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
8312
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
8422
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
8313
8423
|
};
|
|
8314
|
-
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];
|
|
8315
8427
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
8316
8428
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
8317
8429
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8318
8430
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8319
8431
|
return {
|
|
8320
|
-
...(inst.PmtId.InstrId && {
|
|
8321
|
-
|
|
8432
|
+
...(inst.PmtId.InstrId && {
|
|
8433
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8434
|
+
}),
|
|
8435
|
+
...(inst.PmtId.EndToEndId && {
|
|
8436
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8437
|
+
}),
|
|
8322
8438
|
type: 'sepa',
|
|
8323
8439
|
direction: 'credit',
|
|
8324
8440
|
amount: amount,
|
|
@@ -8327,25 +8443,46 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8327
8443
|
name: inst.Cdtr?.Nm,
|
|
8328
8444
|
agent: parseAgent(inst.CdtrAgt),
|
|
8329
8445
|
account: parseAccount(inst.CdtrAcct),
|
|
8330
|
-
...(
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
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
|
+
},
|
|
8338
8473
|
}
|
|
8339
|
-
|
|
8474
|
+
: {}),
|
|
8340
8475
|
},
|
|
8341
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8476
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8477
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8478
|
+
}),
|
|
8342
8479
|
};
|
|
8343
8480
|
});
|
|
8344
8481
|
return new RTPCreditPaymentInitiation({
|
|
8345
8482
|
messageId: messageId,
|
|
8346
8483
|
creationDate: creationDate,
|
|
8347
8484
|
initiatingParty: initiatingParty,
|
|
8348
|
-
paymentInstructions: paymentInstructions
|
|
8485
|
+
paymentInstructions: paymentInstructions,
|
|
8349
8486
|
});
|
|
8350
8487
|
}
|
|
8351
8488
|
}
|
|
@@ -8380,14 +8517,14 @@ const ACHLocalInstrumentCode = {
|
|
|
8380
8517
|
RepresentedCheck: 'RCK',
|
|
8381
8518
|
};
|
|
8382
8519
|
const ACHLocalInstrumentCodeDescriptionMap = {
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
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',
|
|
8391
8528
|
};
|
|
8392
8529
|
|
|
8393
8530
|
/**
|
|
@@ -8446,13 +8583,14 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8446
8583
|
instructionPriority;
|
|
8447
8584
|
formattedPaymentSum;
|
|
8448
8585
|
constructor(config) {
|
|
8449
|
-
super({ type:
|
|
8586
|
+
super({ type: 'ach' });
|
|
8450
8587
|
this.initiatingParty = config.initiatingParty;
|
|
8451
8588
|
this.paymentInstructions = config.paymentInstructions;
|
|
8452
8589
|
this.messageId = config.messageId || v4().replace(/-/g, '');
|
|
8453
8590
|
this.creationDate = config.creationDate || new Date();
|
|
8454
8591
|
this.paymentInformationId = sanitize(v4(), 35);
|
|
8455
|
-
this.localInstrument =
|
|
8592
|
+
this.localInstrument =
|
|
8593
|
+
config.localInstrument || ACHLocalInstrumentCode.CorporateCreditDebit;
|
|
8456
8594
|
this.serviceLevel = 'NURG'; // Normal Urgency
|
|
8457
8595
|
this.instructionPriority = 'NORM'; // Normal Priority
|
|
8458
8596
|
this.formattedPaymentSum = this.sumPaymentInstructions(this.paymentInstructions);
|
|
@@ -8467,9 +8605,11 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8467
8605
|
*/
|
|
8468
8606
|
sumPaymentInstructions(instructions) {
|
|
8469
8607
|
const instructionDineros = instructions.map(instruction => Dinero({ amount: instruction.amount, currency: instruction.currency }));
|
|
8470
|
-
return instructionDineros
|
|
8608
|
+
return instructionDineros
|
|
8609
|
+
.reduce((acc, next) => {
|
|
8471
8610
|
return acc.add(next);
|
|
8472
|
-
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8611
|
+
}, Dinero({ amount: 0, currency: instructions[0].currency }))
|
|
8612
|
+
.toFormat('0.00');
|
|
8473
8613
|
}
|
|
8474
8614
|
/**
|
|
8475
8615
|
* Validates the payment initiation data according to ACH requirements.
|
|
@@ -8497,7 +8637,10 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8497
8637
|
creditTransfer(instruction) {
|
|
8498
8638
|
const paymentInstructionId = sanitize(instruction.id || v4(), 35);
|
|
8499
8639
|
const endToEndId = sanitize(instruction.endToEndId || instruction.id || v4(), 35);
|
|
8500
|
-
const dinero = Dinero({
|
|
8640
|
+
const dinero = Dinero({
|
|
8641
|
+
amount: instruction.amount,
|
|
8642
|
+
currency: instruction.currency,
|
|
8643
|
+
});
|
|
8501
8644
|
return {
|
|
8502
8645
|
PmtId: {
|
|
8503
8646
|
InstrId: paymentInstructionId,
|
|
@@ -8522,9 +8665,11 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8522
8665
|
},
|
|
8523
8666
|
Ccy: instruction.currency,
|
|
8524
8667
|
},
|
|
8525
|
-
RmtInf: instruction.remittanceInformation
|
|
8526
|
-
|
|
8527
|
-
|
|
8668
|
+
RmtInf: instruction.remittanceInformation
|
|
8669
|
+
? {
|
|
8670
|
+
Ustrd: instruction.remittanceInformation,
|
|
8671
|
+
}
|
|
8672
|
+
: undefined,
|
|
8528
8673
|
};
|
|
8529
8674
|
}
|
|
8530
8675
|
/**
|
|
@@ -8536,7 +8681,7 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8536
8681
|
const xml = {
|
|
8537
8682
|
'?xml': {
|
|
8538
8683
|
'@version': '1.0',
|
|
8539
|
-
'@encoding': 'UTF-8'
|
|
8684
|
+
'@encoding': 'UTF-8',
|
|
8540
8685
|
},
|
|
8541
8686
|
Document: {
|
|
8542
8687
|
'@xmlns': 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03',
|
|
@@ -8574,8 +8719,8 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8574
8719
|
ChrgBr: 'SHAR',
|
|
8575
8720
|
// payments[]
|
|
8576
8721
|
CdtTrfTxInf: this.paymentInstructions.map(p => this.creditTransfer(p)),
|
|
8577
|
-
}
|
|
8578
|
-
}
|
|
8722
|
+
},
|
|
8723
|
+
},
|
|
8579
8724
|
},
|
|
8580
8725
|
};
|
|
8581
8726
|
return builder.build(xml);
|
|
@@ -8589,12 +8734,17 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8589
8734
|
* @throws {Error} If multiple payment information blocks are found.
|
|
8590
8735
|
*/
|
|
8591
8736
|
static fromXML(rawXml) {
|
|
8592
|
-
const parser = new fxp.XMLParser({
|
|
8737
|
+
const parser = new fxp.XMLParser({
|
|
8738
|
+
ignoreAttributes: false,
|
|
8739
|
+
attributeNamePrefix: '@_',
|
|
8740
|
+
textNodeName: '#text',
|
|
8741
|
+
});
|
|
8593
8742
|
const xml = parser.parse(rawXml);
|
|
8594
8743
|
if (!xml.Document) {
|
|
8595
|
-
throw new InvalidXmlError(
|
|
8744
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
8596
8745
|
}
|
|
8597
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
8746
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
8747
|
+
xml.Document['@_Xmlns']);
|
|
8598
8748
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:pain.001.001.03')) {
|
|
8599
8749
|
throw new InvalidXmlNamespaceError('Invalid PAIN.001 namespace');
|
|
8600
8750
|
}
|
|
@@ -8607,19 +8757,27 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8607
8757
|
xml.Document.CstmrCdtTrfInitn.PmtInf.PmtTpInf;
|
|
8608
8758
|
// Assuming we have one PmtInf / one Debtor, we can hack together this information from InitgPty / Dbtr
|
|
8609
8759
|
const initiatingParty = {
|
|
8610
|
-
name: xml.Document.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm ||
|
|
8611
|
-
|
|
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,
|
|
8612
8764
|
agent: parseAgent(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAgt),
|
|
8613
|
-
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct)
|
|
8765
|
+
account: parseAccount(xml.Document.CstmrCdtTrfInitn.PmtInf.DbtrAcct),
|
|
8614
8766
|
};
|
|
8615
|
-
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];
|
|
8616
8770
|
const paymentInstructions = rawInstructions.map((inst) => {
|
|
8617
8771
|
const currency = inst.Amt.InstdAmt['@_Ccy'];
|
|
8618
8772
|
const amount = parseAmountToMinorUnits(Number(inst.Amt.InstdAmt['#text']), currency);
|
|
8619
8773
|
const rawPostalAddress = inst.Cdtr.PstlAdr;
|
|
8620
8774
|
return {
|
|
8621
|
-
...(inst.PmtId.InstrId && {
|
|
8622
|
-
|
|
8775
|
+
...(inst.PmtId.InstrId && {
|
|
8776
|
+
id: inst.PmtId.InstrId.toString(),
|
|
8777
|
+
}),
|
|
8778
|
+
...(inst.PmtId.EndToEndId && {
|
|
8779
|
+
endToEndId: inst.PmtId.EndToEndId.toString(),
|
|
8780
|
+
}),
|
|
8623
8781
|
type: 'ach',
|
|
8624
8782
|
direction: 'credit',
|
|
8625
8783
|
amount: amount,
|
|
@@ -8628,41 +8786,458 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
|
|
|
8628
8786
|
name: inst.Cdtr?.Nm,
|
|
8629
8787
|
agent: parseAgent(inst.CdtrAgt),
|
|
8630
8788
|
account: parseAccount(inst.CdtrAcct),
|
|
8631
|
-
...(
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
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
|
+
},
|
|
8639
8816
|
}
|
|
8640
|
-
|
|
8817
|
+
: {}),
|
|
8641
8818
|
},
|
|
8642
|
-
...(inst.RmtInf?.Ustrd && {
|
|
8819
|
+
...(inst.RmtInf?.Ustrd && {
|
|
8820
|
+
remittanceInformation: inst.RmtInf.Ustrd.toString(),
|
|
8821
|
+
}),
|
|
8643
8822
|
};
|
|
8644
8823
|
});
|
|
8645
8824
|
return new ACHCreditPaymentInitiation({
|
|
8646
8825
|
messageId: messageId,
|
|
8647
8826
|
creationDate: creationDate,
|
|
8648
8827
|
initiatingParty: initiatingParty,
|
|
8649
|
-
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,
|
|
8650
9225
|
});
|
|
8651
9226
|
}
|
|
8652
9227
|
}
|
|
8653
9228
|
|
|
8654
9229
|
const ISO20022Messages = {
|
|
8655
|
-
CAMT_003:
|
|
8656
|
-
CAMT_004:
|
|
8657
|
-
CAMT_005:
|
|
8658
|
-
CAMT_006:
|
|
8659
|
-
CAMT_053:
|
|
8660
|
-
PAIN_001:
|
|
8661
|
-
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',
|
|
8662
9237
|
};
|
|
8663
9238
|
const ISO20022Implementations = new Map();
|
|
8664
9239
|
function registerISO20022Implementation(cl) {
|
|
8665
|
-
cl.supportedMessages().forEach(
|
|
9240
|
+
cl.supportedMessages().forEach(msg => {
|
|
8666
9241
|
ISO20022Implementations.set(msg, cl);
|
|
8667
9242
|
});
|
|
8668
9243
|
}
|
|
@@ -8718,12 +9293,12 @@ class CashManagementGetAccount {
|
|
|
8718
9293
|
static fromDocumentOject(doc) {
|
|
8719
9294
|
const rawHeader = doc.Document?.GetAcct?.MsgHdr;
|
|
8720
9295
|
if (!rawHeader) {
|
|
8721
|
-
throw new InvalidStructureError(
|
|
9296
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing MsgHdr');
|
|
8722
9297
|
}
|
|
8723
9298
|
const header = parseMessageHeader(rawHeader);
|
|
8724
9299
|
const newCrit = doc.Document?.GetAcct?.AcctQryDef?.AcctCrit?.NewCrit;
|
|
8725
9300
|
if (!newCrit) {
|
|
8726
|
-
throw new InvalidStructureError(
|
|
9301
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing GetAcct.AcctQryDef.AcctCrit.NewCrit');
|
|
8727
9302
|
}
|
|
8728
9303
|
const name = newCrit.NewQryNm;
|
|
8729
9304
|
let searchCriteria = [];
|
|
@@ -8733,16 +9308,19 @@ class CashManagementGetAccount {
|
|
|
8733
9308
|
}
|
|
8734
9309
|
rawCriterias = rawCriterias.filter((c) => !!c);
|
|
8735
9310
|
if (rawCriterias.length === 0) {
|
|
8736
|
-
throw new InvalidStructureError(
|
|
9311
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: missing search criteria');
|
|
8737
9312
|
}
|
|
8738
9313
|
for (const rawCriterium of rawCriterias) {
|
|
8739
9314
|
const crit = {};
|
|
8740
9315
|
// search on Ids, only one criterium supported for now
|
|
8741
9316
|
if (rawCriterium.AcctId) {
|
|
8742
|
-
if (Array.isArray(rawCriterium.AcctId) &&
|
|
8743
|
-
|
|
9317
|
+
if (Array.isArray(rawCriterium.AcctId) &&
|
|
9318
|
+
rawCriterium.AcctId.length > 1) {
|
|
9319
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple AcctId criterium not supported');
|
|
8744
9320
|
}
|
|
8745
|
-
const acctId = Array.isArray(rawCriterium.AcctId)
|
|
9321
|
+
const acctId = Array.isArray(rawCriterium.AcctId)
|
|
9322
|
+
? rawCriterium.AcctId[0]
|
|
9323
|
+
: rawCriterium.AcctId;
|
|
8746
9324
|
if (acctId.CTTxt) {
|
|
8747
9325
|
crit.accountRegExp = `.*${acctId.CTTxt}.*`; // contains
|
|
8748
9326
|
}
|
|
@@ -8756,19 +9334,23 @@ class CashManagementGetAccount {
|
|
|
8756
9334
|
// search on currency
|
|
8757
9335
|
if (rawCriterium.Ccy) {
|
|
8758
9336
|
if (Array.isArray(rawCriterium.Ccy) && rawCriterium.Ccy.length > 1) {
|
|
8759
|
-
throw new InvalidStructureError(
|
|
9337
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple Ccy criterium not supported');
|
|
8760
9338
|
}
|
|
8761
|
-
const ccy = Array.isArray(rawCriterium.Ccy)
|
|
9339
|
+
const ccy = Array.isArray(rawCriterium.Ccy)
|
|
9340
|
+
? rawCriterium.Ccy[0]
|
|
9341
|
+
: rawCriterium.Ccy;
|
|
8762
9342
|
crit.currencyEqualTo = ccy;
|
|
8763
9343
|
}
|
|
8764
9344
|
// search on balance as of date
|
|
8765
9345
|
if (rawCriterium.Bal) {
|
|
8766
9346
|
if (Array.isArray(rawCriterium.Bal) && rawCriterium.Bal.length > 1) {
|
|
8767
|
-
throw new InvalidStructureError(
|
|
9347
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple Bal criterium not supported');
|
|
8768
9348
|
}
|
|
8769
|
-
const bal = Array.isArray(rawCriterium.Bal)
|
|
9349
|
+
const bal = Array.isArray(rawCriterium.Bal)
|
|
9350
|
+
? rawCriterium.Bal[0]
|
|
9351
|
+
: rawCriterium.Bal;
|
|
8770
9352
|
if (bal?.ValDt && Array.isArray(bal.ValDt) && bal.ValDt.length > 1) {
|
|
8771
|
-
throw new InvalidStructureError(
|
|
9353
|
+
throw new InvalidStructureError('Invalid CAMT.003 document: multiple ValDt criterium not supported');
|
|
8772
9354
|
}
|
|
8773
9355
|
const valDt = Array.isArray(bal?.ValDt) ? bal.ValDt[0] : bal?.ValDt;
|
|
8774
9356
|
if (valDt?.Dt?.EQDt) {
|
|
@@ -8789,9 +9371,10 @@ class CashManagementGetAccount {
|
|
|
8789
9371
|
const parser = XML.getParser();
|
|
8790
9372
|
const doc = parser.parse(xml);
|
|
8791
9373
|
if (!doc.Document) {
|
|
8792
|
-
throw new Error(
|
|
9374
|
+
throw new Error('Invalid XML format');
|
|
8793
9375
|
}
|
|
8794
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
9376
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
9377
|
+
doc.Document['@_Xmlns']);
|
|
8795
9378
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.003.001.')) {
|
|
8796
9379
|
throw new InvalidXmlNamespaceError('Invalid CAMT.003 namespace');
|
|
8797
9380
|
}
|
|
@@ -8800,7 +9383,7 @@ class CashManagementGetAccount {
|
|
|
8800
9383
|
static fromJSON(json) {
|
|
8801
9384
|
const obj = JSON.parse(json);
|
|
8802
9385
|
if (!obj.Document) {
|
|
8803
|
-
throw new Error(
|
|
9386
|
+
throw new Error('Invalid JSON format');
|
|
8804
9387
|
}
|
|
8805
9388
|
return CashManagementGetAccount.fromDocumentOject(obj);
|
|
8806
9389
|
}
|
|
@@ -8820,39 +9403,55 @@ class CashManagementGetAccount {
|
|
|
8820
9403
|
AcctCrit: {
|
|
8821
9404
|
NewCrit: {
|
|
8822
9405
|
NewQryNm: this._data.newCriteria?.name,
|
|
8823
|
-
SchCrit: this._data.newCriteria?.searchCriteria.map(
|
|
9406
|
+
SchCrit: this._data.newCriteria?.searchCriteria.map(c => {
|
|
8824
9407
|
const obj = {};
|
|
8825
9408
|
if (c.accountRegExp) {
|
|
8826
|
-
if (c.accountRegExp.startsWith('.*') &&
|
|
8827
|
-
|
|
9409
|
+
if (c.accountRegExp.startsWith('.*') &&
|
|
9410
|
+
c.accountRegExp.endsWith('.*')) {
|
|
9411
|
+
obj.AcctId = {
|
|
9412
|
+
CTTxt: c.accountRegExp
|
|
9413
|
+
.replace(/^\.\*/, '')
|
|
9414
|
+
.replace(/\.\*$/, ''),
|
|
9415
|
+
}; // contains
|
|
8828
9416
|
}
|
|
8829
|
-
else if (c.accountRegExp.startsWith('^((?!') &&
|
|
8830
|
-
|
|
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
|
|
8831
9424
|
}
|
|
8832
9425
|
}
|
|
8833
9426
|
else if (c.accountEqualTo) {
|
|
8834
9427
|
obj.AcctId = {
|
|
8835
|
-
EQ: exportAccountIdentification(c.accountEqualTo)
|
|
9428
|
+
EQ: exportAccountIdentification(c.accountEqualTo),
|
|
8836
9429
|
};
|
|
8837
9430
|
}
|
|
8838
9431
|
if (c.currencyEqualTo) {
|
|
8839
9432
|
obj.Ccy = [c.currencyEqualTo];
|
|
8840
9433
|
}
|
|
8841
9434
|
if (c.balanceAsOfDateEqualTo) {
|
|
8842
|
-
obj.Bal = [
|
|
8843
|
-
|
|
9435
|
+
obj.Bal = [
|
|
9436
|
+
{
|
|
9437
|
+
ValDt: [
|
|
9438
|
+
{
|
|
8844
9439
|
Dt: {
|
|
8845
|
-
EQDt: c.balanceAsOfDateEqualTo
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
9440
|
+
EQDt: c.balanceAsOfDateEqualTo
|
|
9441
|
+
.toISOString()
|
|
9442
|
+
.slice(0, 10),
|
|
9443
|
+
},
|
|
9444
|
+
},
|
|
9445
|
+
],
|
|
9446
|
+
},
|
|
9447
|
+
];
|
|
8849
9448
|
}
|
|
8850
9449
|
return obj;
|
|
8851
9450
|
}),
|
|
8852
|
-
}
|
|
8853
|
-
}
|
|
8854
|
-
}
|
|
8855
|
-
}
|
|
9451
|
+
},
|
|
9452
|
+
},
|
|
9453
|
+
},
|
|
9454
|
+
},
|
|
8856
9455
|
};
|
|
8857
9456
|
return { Document };
|
|
8858
9457
|
}
|
|
@@ -8952,10 +9551,10 @@ const exportStatement = (stmt) => {
|
|
|
8952
9551
|
},
|
|
8953
9552
|
Acct: {
|
|
8954
9553
|
...exportAccount(stmt.account),
|
|
8955
|
-
Svcr: exportAgent(stmt.agent)
|
|
9554
|
+
Svcr: exportAgent(stmt.agent),
|
|
8956
9555
|
},
|
|
8957
|
-
Bal: stmt.balances.map(
|
|
8958
|
-
Ntry: stmt.entries.map(
|
|
9556
|
+
Bal: stmt.balances.map(bal => exportBalance(bal)),
|
|
9557
|
+
Ntry: stmt.entries.map(entry => exportEntry(entry)),
|
|
8959
9558
|
};
|
|
8960
9559
|
return obj;
|
|
8961
9560
|
};
|
|
@@ -9079,7 +9678,9 @@ const exportEntry = (entry) => {
|
|
|
9079
9678
|
BkTxCd: exportBankTransactionCode(entry.bankTransactionCode, entry.proprietaryCode),
|
|
9080
9679
|
AddtlNtryInf: entry.additionalInformation,
|
|
9081
9680
|
AcctSvcrRef: entry.accountServicerReferenceId,
|
|
9082
|
-
NtryDtls: entry.transactions.map(
|
|
9681
|
+
NtryDtls: entry.transactions.map(tx => ({
|
|
9682
|
+
TxDtls: exportTransactionDetails(tx),
|
|
9683
|
+
})),
|
|
9083
9684
|
};
|
|
9084
9685
|
return obj;
|
|
9085
9686
|
};
|
|
@@ -9172,7 +9773,9 @@ const exportTransactionDetails = (tx) => {
|
|
|
9172
9773
|
Dbtr: {
|
|
9173
9774
|
Nm: tx.debtor.name,
|
|
9174
9775
|
},
|
|
9175
|
-
DbtrAcct: tx.debtor.account
|
|
9776
|
+
DbtrAcct: tx.debtor.account
|
|
9777
|
+
? exportAccount(tx.debtor.account)
|
|
9778
|
+
: undefined,
|
|
9176
9779
|
};
|
|
9177
9780
|
obj.RltdAgts = {
|
|
9178
9781
|
DbtrAgt: tx.debtor.agent ? exportAgent(tx.debtor.agent) : undefined,
|
|
@@ -9184,7 +9787,9 @@ const exportTransactionDetails = (tx) => {
|
|
|
9184
9787
|
Cdtr: {
|
|
9185
9788
|
Nm: tx.creditor.name,
|
|
9186
9789
|
},
|
|
9187
|
-
CdtrAcct: tx.creditor.account
|
|
9790
|
+
CdtrAcct: tx.creditor.account
|
|
9791
|
+
? exportAccount(tx.creditor.account)
|
|
9792
|
+
: undefined,
|
|
9188
9793
|
};
|
|
9189
9794
|
obj.RltdAgts = {
|
|
9190
9795
|
CdtrAgt: tx.creditor.agent ? exportAgent(tx.creditor.agent) : undefined,
|
|
@@ -9229,7 +9834,7 @@ const exportBankTransactionCode = (bankTransactionCode, proprietaryCode) => {
|
|
|
9229
9834
|
return obj;
|
|
9230
9835
|
};
|
|
9231
9836
|
const parseBusinessError = (bizErr) => {
|
|
9232
|
-
const code = bizErr.Err?.Cd || bizErr.Err?.Prtry ||
|
|
9837
|
+
const code = bizErr.Err?.Cd || bizErr.Err?.Prtry || 'UKNW';
|
|
9233
9838
|
const description = bizErr.Desc;
|
|
9234
9839
|
return {
|
|
9235
9840
|
code,
|
|
@@ -9260,7 +9865,7 @@ class CashManagementReturnAccount {
|
|
|
9260
9865
|
static fromDocumentOject(doc) {
|
|
9261
9866
|
const rawHeader = doc.Document?.RtrAcct?.MsgHdr;
|
|
9262
9867
|
if (!rawHeader) {
|
|
9263
|
-
throw new InvalidStructureError(
|
|
9868
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing MsgHdr');
|
|
9264
9869
|
}
|
|
9265
9870
|
const header = parseMessageHeader(rawHeader);
|
|
9266
9871
|
// interpret the report
|
|
@@ -9275,7 +9880,7 @@ class CashManagementReturnAccount {
|
|
|
9275
9880
|
if (r.AcctOrErr?.Acct) {
|
|
9276
9881
|
// report
|
|
9277
9882
|
if (!r.AcctOrErr.Acct.Ccy) {
|
|
9278
|
-
throw new InvalidStructureError(
|
|
9883
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing Ccy in Acct');
|
|
9279
9884
|
}
|
|
9280
9885
|
let rawMulBal = r.AcctOrErr.Acct.MulBal;
|
|
9281
9886
|
if (!Array.isArray(rawMulBal))
|
|
@@ -9288,7 +9893,7 @@ class CashManagementReturnAccount {
|
|
|
9288
9893
|
balances: rawMulBal.map((bal) => parseBalanceReport(r.AcctOrErr.Acct.Ccy, bal)),
|
|
9289
9894
|
};
|
|
9290
9895
|
if (report.balances.length === 0) {
|
|
9291
|
-
throw new InvalidStructureError(
|
|
9896
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing MulBal in Acct');
|
|
9292
9897
|
}
|
|
9293
9898
|
}
|
|
9294
9899
|
else if (r.AcctOrErr?.BizErr) {
|
|
@@ -9296,7 +9901,7 @@ class CashManagementReturnAccount {
|
|
|
9296
9901
|
error = parseBusinessError(r.AcctOrErr.BizErr);
|
|
9297
9902
|
}
|
|
9298
9903
|
else {
|
|
9299
|
-
throw new InvalidStructureError(
|
|
9904
|
+
throw new InvalidStructureError('Invalid CAMT.004 document: missing AcctOrErr');
|
|
9300
9905
|
}
|
|
9301
9906
|
return { accountId, report, error };
|
|
9302
9907
|
});
|
|
@@ -9309,9 +9914,10 @@ class CashManagementReturnAccount {
|
|
|
9309
9914
|
const parser = XML.getParser();
|
|
9310
9915
|
const doc = parser.parse(xml);
|
|
9311
9916
|
if (!doc.Document) {
|
|
9312
|
-
throw new Error(
|
|
9917
|
+
throw new Error('Invalid XML format');
|
|
9313
9918
|
}
|
|
9314
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
9919
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
9920
|
+
doc.Document['@_Xmlns']);
|
|
9315
9921
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.004.001.')) {
|
|
9316
9922
|
throw new InvalidXmlNamespaceError('Invalid CAMT.004 namespace');
|
|
9317
9923
|
}
|
|
@@ -9320,7 +9926,7 @@ class CashManagementReturnAccount {
|
|
|
9320
9926
|
static fromJSON(json) {
|
|
9321
9927
|
const obj = JSON.parse(json);
|
|
9322
9928
|
if (!obj.Document) {
|
|
9323
|
-
throw new Error(
|
|
9929
|
+
throw new Error('Invalid JSON format');
|
|
9324
9930
|
}
|
|
9325
9931
|
return CashManagementReturnAccount.fromDocumentOject(obj);
|
|
9326
9932
|
}
|
|
@@ -9337,7 +9943,7 @@ class CashManagementReturnAccount {
|
|
|
9337
9943
|
RtrAcct: {
|
|
9338
9944
|
MsgHdr: exportMessageHeader(this._data.header),
|
|
9339
9945
|
RptOrErr: {
|
|
9340
|
-
AcctRpt: this._data.reports.map(
|
|
9946
|
+
AcctRpt: this._data.reports.map(report => {
|
|
9341
9947
|
const obj = {
|
|
9342
9948
|
AcctId: exportAccountIdentification(report.accountId),
|
|
9343
9949
|
AcctOrErr: {}, // filled below
|
|
@@ -9347,16 +9953,16 @@ class CashManagementReturnAccount {
|
|
|
9347
9953
|
Ccy: report.report.currency,
|
|
9348
9954
|
Nm: report.report.name,
|
|
9349
9955
|
Tp: { Cd: report.report.type }, // TODO add Prtry handling
|
|
9350
|
-
MulBal: report.report.balances.map(
|
|
9956
|
+
MulBal: report.report.balances.map(bal => exportBalanceReport(report.report.currency, bal)),
|
|
9351
9957
|
};
|
|
9352
9958
|
}
|
|
9353
9959
|
else if (report.error) {
|
|
9354
9960
|
obj.AcctOrErr.BizErr = exportBusinessError(report.error);
|
|
9355
9961
|
}
|
|
9356
9962
|
return obj;
|
|
9357
|
-
})
|
|
9358
|
-
}
|
|
9359
|
-
}
|
|
9963
|
+
}),
|
|
9964
|
+
},
|
|
9965
|
+
},
|
|
9360
9966
|
};
|
|
9361
9967
|
return { Document };
|
|
9362
9968
|
}
|
|
@@ -9377,12 +9983,12 @@ class CashManagementGetTransaction {
|
|
|
9377
9983
|
static fromDocumentOject(doc) {
|
|
9378
9984
|
const rawHeader = doc.Document?.GetTx?.MsgHdr;
|
|
9379
9985
|
if (!rawHeader) {
|
|
9380
|
-
throw new InvalidStructureError(
|
|
9986
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing MsgHdr');
|
|
9381
9987
|
}
|
|
9382
9988
|
const header = parseMessageHeader(rawHeader);
|
|
9383
9989
|
const newCrit = doc.Document?.GetTx?.TxQryDef?.TxCrit?.NewCrit;
|
|
9384
9990
|
if (!newCrit) {
|
|
9385
|
-
throw new InvalidStructureError(
|
|
9991
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing GetTx.TxQryDef.TxCrit.NewCrit');
|
|
9386
9992
|
}
|
|
9387
9993
|
const name = newCrit.NewQryNm;
|
|
9388
9994
|
let searchCriteria = [];
|
|
@@ -9392,35 +9998,42 @@ class CashManagementGetTransaction {
|
|
|
9392
9998
|
}
|
|
9393
9999
|
rawCriterias = rawCriterias.filter((c) => !!c);
|
|
9394
10000
|
if (rawCriterias.length === 0) {
|
|
9395
|
-
throw new InvalidStructureError(
|
|
10001
|
+
throw new InvalidStructureError('Invalid CAMT.005 document: missing search criteria');
|
|
9396
10002
|
}
|
|
9397
10003
|
for (const rawCriterium of rawCriterias) {
|
|
9398
10004
|
// search on Ids
|
|
9399
10005
|
if (rawCriterium.PmtSch.MsgId) {
|
|
9400
10006
|
searchCriteria.push({
|
|
9401
|
-
type:
|
|
9402
|
-
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],
|
|
9403
10011
|
});
|
|
9404
10012
|
}
|
|
9405
10013
|
// seach on date
|
|
9406
10014
|
if (rawCriterium.PmtSch.ReqdExctnDt) {
|
|
9407
|
-
if (Array.isArray(rawCriterium.PmtSch.ReqdExctnDt) &&
|
|
9408
|
-
|
|
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');
|
|
9409
10018
|
}
|
|
9410
|
-
const criterium = Array.isArray(rawCriterium.PmtSch.ReqdExctnDt)
|
|
10019
|
+
const criterium = Array.isArray(rawCriterium.PmtSch.ReqdExctnDt)
|
|
10020
|
+
? rawCriterium.PmtSch.ReqdExctnDt[0]
|
|
10021
|
+
: rawCriterium.PmtSch.ReqdExctnDt;
|
|
9411
10022
|
if (criterium?.DtSch?.EQDt) {
|
|
9412
10023
|
searchCriteria.push({
|
|
9413
|
-
type:
|
|
10024
|
+
type: 'PmtSch.ReqdExctnDt',
|
|
9414
10025
|
dateEqualTo: parseDate(criterium.DtSch.EQDt),
|
|
9415
10026
|
});
|
|
9416
10027
|
}
|
|
9417
10028
|
}
|
|
9418
|
-
let pmtIds = Array.isArray(rawCriterium.PmtSch.PmtId)
|
|
9419
|
-
|
|
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);
|
|
9420
10033
|
if (pmtIds.length > 0) {
|
|
9421
10034
|
searchCriteria.push({
|
|
9422
|
-
type:
|
|
9423
|
-
endToEndIdEqualTo: pmtIds.map(
|
|
10035
|
+
type: 'PmtSch.PmtId.LngBizId.EndToEndId',
|
|
10036
|
+
endToEndIdEqualTo: pmtIds.map(id => id.LngBizId.EndToEndId),
|
|
9424
10037
|
});
|
|
9425
10038
|
}
|
|
9426
10039
|
}
|
|
@@ -9436,9 +10049,10 @@ class CashManagementGetTransaction {
|
|
|
9436
10049
|
const parser = XML.getParser();
|
|
9437
10050
|
const doc = parser.parse(xml);
|
|
9438
10051
|
if (!doc.Document) {
|
|
9439
|
-
throw new Error(
|
|
10052
|
+
throw new Error('Invalid XML format');
|
|
9440
10053
|
}
|
|
9441
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
10054
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
10055
|
+
doc.Document['@_Xmlns']);
|
|
9442
10056
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.005.001.')) {
|
|
9443
10057
|
throw new InvalidXmlNamespaceError('Invalid CAMT.005 namespace');
|
|
9444
10058
|
}
|
|
@@ -9447,7 +10061,7 @@ class CashManagementGetTransaction {
|
|
|
9447
10061
|
static fromJSON(json) {
|
|
9448
10062
|
const obj = JSON.parse(json);
|
|
9449
10063
|
if (!obj.Document) {
|
|
9450
|
-
throw new Error(
|
|
10064
|
+
throw new Error('Invalid JSON format');
|
|
9451
10065
|
}
|
|
9452
10066
|
return CashManagementGetTransaction.fromDocumentOject(obj);
|
|
9453
10067
|
}
|
|
@@ -9467,37 +10081,38 @@ class CashManagementGetTransaction {
|
|
|
9467
10081
|
TxCrit: {
|
|
9468
10082
|
NewCrit: {
|
|
9469
10083
|
NewQryNm: this._data.newCriteria?.name,
|
|
9470
|
-
SchCrit: this._data.newCriteria?.searchCriteria.map(
|
|
10084
|
+
SchCrit: this._data.newCriteria?.searchCriteria.map(c => {
|
|
9471
10085
|
const obj = {};
|
|
9472
|
-
if (c.type ===
|
|
10086
|
+
if (c.type === 'PmtSch.MsgId' && c.msgIdsEqualTo) {
|
|
9473
10087
|
obj.PmtSch = {
|
|
9474
|
-
MsgId: c.msgIdsEqualTo
|
|
10088
|
+
MsgId: c.msgIdsEqualTo,
|
|
9475
10089
|
};
|
|
9476
10090
|
}
|
|
9477
|
-
if (c.type ===
|
|
10091
|
+
if (c.type === 'PmtSch.ReqdExctnDt' && c.dateEqualTo) {
|
|
9478
10092
|
obj.PmtSch = {
|
|
9479
10093
|
ReqdExctnDt: {
|
|
9480
10094
|
DtSch: {
|
|
9481
10095
|
EQDt: c.dateEqualTo.toISOString().slice(0, 10),
|
|
9482
|
-
}
|
|
9483
|
-
}
|
|
10096
|
+
},
|
|
10097
|
+
},
|
|
9484
10098
|
};
|
|
9485
10099
|
}
|
|
9486
|
-
if (c.type ===
|
|
10100
|
+
if (c.type === 'PmtSch.PmtId.LngBizId.EndToEndId' &&
|
|
10101
|
+
c.endToEndIdEqualTo) {
|
|
9487
10102
|
obj.PmtSch = {
|
|
9488
|
-
PmtId: c.endToEndIdEqualTo.map(
|
|
10103
|
+
PmtId: c.endToEndIdEqualTo.map(id => ({
|
|
9489
10104
|
LngBizId: {
|
|
9490
10105
|
EndToEndId: id,
|
|
9491
|
-
}
|
|
9492
|
-
}))
|
|
10106
|
+
},
|
|
10107
|
+
})),
|
|
9493
10108
|
};
|
|
9494
10109
|
}
|
|
9495
10110
|
return obj;
|
|
9496
10111
|
}),
|
|
9497
|
-
}
|
|
9498
|
-
}
|
|
9499
|
-
}
|
|
9500
|
-
}
|
|
10112
|
+
},
|
|
10113
|
+
},
|
|
10114
|
+
},
|
|
10115
|
+
},
|
|
9501
10116
|
};
|
|
9502
10117
|
return { Document };
|
|
9503
10118
|
}
|
|
@@ -9518,7 +10133,7 @@ class CashManagementReturnTransaction {
|
|
|
9518
10133
|
static fromDocumentOject(doc) {
|
|
9519
10134
|
const rawHeader = doc.Document?.RtrTx?.MsgHdr;
|
|
9520
10135
|
if (!rawHeader) {
|
|
9521
|
-
throw new InvalidStructureError(
|
|
10136
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing MsgHdr');
|
|
9522
10137
|
}
|
|
9523
10138
|
const header = parseMessageHeader(rawHeader);
|
|
9524
10139
|
// interpret the report
|
|
@@ -9527,7 +10142,8 @@ class CashManagementReturnTransaction {
|
|
|
9527
10142
|
rawReports = [rawReports];
|
|
9528
10143
|
rawReports = rawReports.filter((r) => !!r); // remove null/undefined
|
|
9529
10144
|
const reports = rawReports.map((r) => {
|
|
9530
|
-
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
|
|
9531
10147
|
const paymentId = {
|
|
9532
10148
|
currency: r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Ccy,
|
|
9533
10149
|
amount: parseAmountToMinorUnits(rawAmount, r.PmtId?.LngBizId?.IntrBkSttlmAmt?.Ccy),
|
|
@@ -9537,20 +10153,24 @@ class CashManagementReturnTransaction {
|
|
|
9537
10153
|
};
|
|
9538
10154
|
// check required fields
|
|
9539
10155
|
if (!paymentId.currency) {
|
|
9540
|
-
throw new InvalidStructureError(
|
|
10156
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Ccy in PmtId.LngBizId.IntrBkSttlmAmt');
|
|
9541
10157
|
}
|
|
9542
|
-
if (paymentId.amount === undefined ||
|
|
9543
|
-
|
|
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');
|
|
9544
10162
|
}
|
|
9545
10163
|
if (!paymentId.endToEndId) {
|
|
9546
|
-
throw new InvalidStructureError(
|
|
10164
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing EndToEndId in PmtId.LngBizId');
|
|
9547
10165
|
}
|
|
9548
10166
|
let report = undefined;
|
|
9549
10167
|
let error = undefined;
|
|
9550
10168
|
if (r.TxOrErr?.Tx) {
|
|
9551
10169
|
// report
|
|
9552
10170
|
const msgId = r.TxOrErr.Tx.Pmt?.MsgId;
|
|
9553
|
-
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;
|
|
9554
10174
|
const status = ((sts) => {
|
|
9555
10175
|
if (!sts)
|
|
9556
10176
|
return undefined;
|
|
@@ -9558,9 +10178,13 @@ class CashManagementReturnTransaction {
|
|
|
9558
10178
|
return undefined;
|
|
9559
10179
|
if (Array.isArray(sts))
|
|
9560
10180
|
sts = sts[0]; // take the first one only
|
|
9561
|
-
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;
|
|
9562
10186
|
if (code)
|
|
9563
|
-
code = Object.keys(sts.Cd)[0] +
|
|
10187
|
+
code = Object.keys(sts.Cd)[0] + ':' + code; // prefix with the type of code
|
|
9564
10188
|
else
|
|
9565
10189
|
return undefined;
|
|
9566
10190
|
const reason = sts.Rsn?.Prtry;
|
|
@@ -9575,7 +10199,7 @@ class CashManagementReturnTransaction {
|
|
|
9575
10199
|
}
|
|
9576
10200
|
function parseAgent(agent) {
|
|
9577
10201
|
if (!agent)
|
|
9578
|
-
return { bic:
|
|
10202
|
+
return { bic: '' };
|
|
9579
10203
|
return { bic: agent?.FinInstnId?.BICFI };
|
|
9580
10204
|
}
|
|
9581
10205
|
report = {
|
|
@@ -9589,10 +10213,10 @@ class CashManagementReturnTransaction {
|
|
|
9589
10213
|
};
|
|
9590
10214
|
// check the debtor and creditor required fields
|
|
9591
10215
|
if (!report.debtor.id) {
|
|
9592
|
-
throw new InvalidStructureError(
|
|
10216
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Id in TxOrErr.Tx.Dbtr.Pty');
|
|
9593
10217
|
}
|
|
9594
10218
|
if (!report.creditor.id) {
|
|
9595
|
-
throw new InvalidStructureError(
|
|
10219
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing Id in TxOrErr.Tx.Cdtr.Pty');
|
|
9596
10220
|
}
|
|
9597
10221
|
}
|
|
9598
10222
|
else if (r.TxOrErr?.BizErr) {
|
|
@@ -9600,7 +10224,7 @@ class CashManagementReturnTransaction {
|
|
|
9600
10224
|
error = parseBusinessError(r.TxOrErr.BizErr);
|
|
9601
10225
|
}
|
|
9602
10226
|
else {
|
|
9603
|
-
throw new InvalidStructureError(
|
|
10227
|
+
throw new InvalidStructureError('Invalid CAMT.006 document: missing TxOrErr');
|
|
9604
10228
|
}
|
|
9605
10229
|
return { paymentId, report, error };
|
|
9606
10230
|
});
|
|
@@ -9613,9 +10237,10 @@ class CashManagementReturnTransaction {
|
|
|
9613
10237
|
const parser = XML.getParser();
|
|
9614
10238
|
const doc = parser.parse(xml);
|
|
9615
10239
|
if (!doc.Document) {
|
|
9616
|
-
throw new Error(
|
|
10240
|
+
throw new Error('Invalid XML format');
|
|
9617
10241
|
}
|
|
9618
|
-
const namespace = (doc.Document['@_xmlns'] ||
|
|
10242
|
+
const namespace = (doc.Document['@_xmlns'] ||
|
|
10243
|
+
doc.Document['@_Xmlns']);
|
|
9619
10244
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.004.001.')) {
|
|
9620
10245
|
throw new InvalidXmlNamespaceError('Invalid CAMT.004 namespace');
|
|
9621
10246
|
}
|
|
@@ -9624,7 +10249,7 @@ class CashManagementReturnTransaction {
|
|
|
9624
10249
|
static fromJSON(json) {
|
|
9625
10250
|
const obj = JSON.parse(json);
|
|
9626
10251
|
if (!obj.Document) {
|
|
9627
|
-
throw new Error(
|
|
10252
|
+
throw new Error('Invalid JSON format');
|
|
9628
10253
|
}
|
|
9629
10254
|
return CashManagementReturnTransaction.fromDocumentOject(obj);
|
|
9630
10255
|
}
|
|
@@ -9642,7 +10267,7 @@ class CashManagementReturnTransaction {
|
|
|
9642
10267
|
MsgHdr: exportMessageHeader(this._data.header),
|
|
9643
10268
|
RptOrErr: {
|
|
9644
10269
|
BizRpt: {
|
|
9645
|
-
TxRpt: this._data.reports.map(
|
|
10270
|
+
TxRpt: this._data.reports.map(report => {
|
|
9646
10271
|
const obj = {
|
|
9647
10272
|
PmtId: {
|
|
9648
10273
|
LngBizId: {
|
|
@@ -9654,7 +10279,7 @@ class CashManagementReturnTransaction {
|
|
|
9654
10279
|
UETR: report.paymentId.uetr,
|
|
9655
10280
|
TxId: report.paymentId.transactionId,
|
|
9656
10281
|
EndToEndId: report.paymentId.endToEndId,
|
|
9657
|
-
}
|
|
10282
|
+
},
|
|
9658
10283
|
},
|
|
9659
10284
|
TxOrErr: {}, // filled below
|
|
9660
10285
|
};
|
|
@@ -9673,38 +10298,46 @@ class CashManagementReturnTransaction {
|
|
|
9673
10298
|
function exportAgent(a) {
|
|
9674
10299
|
if (!a)
|
|
9675
10300
|
return undefined;
|
|
9676
|
-
if (
|
|
10301
|
+
if ('bic' in a && a.bic)
|
|
9677
10302
|
return { FinInstnId: { BICFI: a.bic } };
|
|
9678
|
-
if (
|
|
10303
|
+
if ('abaRoutingNumber' in a && a.abaRoutingNumber)
|
|
9679
10304
|
return { FinInstId: { Othr: { Id: a.abaRoutingNumber } } };
|
|
9680
10305
|
return undefined;
|
|
9681
10306
|
}
|
|
9682
|
-
const [codeType, code] = report.report.status
|
|
10307
|
+
const [codeType, code] = report.report.status
|
|
10308
|
+
? report.report.status.code.split(':')
|
|
10309
|
+
: [undefined, undefined];
|
|
9683
10310
|
obj.TxOrErr.Tx = {
|
|
9684
10311
|
Pmt: {
|
|
9685
10312
|
MsgId: report.report.msgId,
|
|
9686
|
-
ReqdExctnDt: {
|
|
10313
|
+
ReqdExctnDt: {
|
|
10314
|
+
Dt: report.report.reqExecutionDate
|
|
10315
|
+
?.toISOString()
|
|
10316
|
+
?.slice(0, 10),
|
|
10317
|
+
},
|
|
9687
10318
|
Sts: {
|
|
9688
10319
|
Cd: codeType ? { [codeType]: code } : undefined,
|
|
9689
|
-
Rsn: report.report.status?.reason
|
|
10320
|
+
Rsn: report.report.status?.reason
|
|
10321
|
+
? { Prtry: report.report.status.reason }
|
|
10322
|
+
: undefined,
|
|
9690
10323
|
},
|
|
9691
10324
|
Pties: {
|
|
9692
10325
|
Dbtr: exportParty(report.report.debtor),
|
|
9693
10326
|
DbtrAgt: exportAgent(report.report.debtorAgent),
|
|
9694
10327
|
Cdtr: exportParty(report.report.creditor),
|
|
9695
10328
|
CdtrAgt: exportAgent(report.report.creditorAgent),
|
|
9696
|
-
}
|
|
9697
|
-
}
|
|
10329
|
+
},
|
|
10330
|
+
},
|
|
9698
10331
|
};
|
|
9699
10332
|
}
|
|
9700
10333
|
else if (report.error) {
|
|
9701
10334
|
obj.TxOrErr.BizErr = exportBusinessError(report.error);
|
|
9702
10335
|
}
|
|
9703
10336
|
return obj;
|
|
9704
|
-
})
|
|
9705
|
-
}
|
|
9706
|
-
}
|
|
9707
|
-
}
|
|
10337
|
+
}),
|
|
10338
|
+
},
|
|
10339
|
+
},
|
|
10340
|
+
},
|
|
9708
10341
|
};
|
|
9709
10342
|
return { Document };
|
|
9710
10343
|
}
|
|
@@ -9744,17 +10377,17 @@ const BalanceTypeCode = {
|
|
|
9744
10377
|
* Description mapping of BalanceTypeCode values to their names.
|
|
9745
10378
|
*/
|
|
9746
10379
|
const BalanceTypeCodeDescriptionMap = {
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
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',
|
|
9758
10391
|
};
|
|
9759
10392
|
|
|
9760
10393
|
/**
|
|
@@ -9977,6 +10610,64 @@ class ISO20022 {
|
|
|
9977
10610
|
creationDate: config.creationDate,
|
|
9978
10611
|
});
|
|
9979
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
|
+
}
|
|
9980
10671
|
/** Create a message CAMT or other */
|
|
9981
10672
|
createMessage(type, config) {
|
|
9982
10673
|
const implementation = getISO20022Implementation(type);
|
|
@@ -10265,9 +10956,10 @@ class CashManagementEndOfDayReport {
|
|
|
10265
10956
|
const parser = XML.getParser();
|
|
10266
10957
|
const xml = parser.parse(rawXml);
|
|
10267
10958
|
if (!xml.Document) {
|
|
10268
|
-
throw new InvalidXmlError(
|
|
10959
|
+
throw new InvalidXmlError('Invalid XML format');
|
|
10269
10960
|
}
|
|
10270
|
-
const namespace = (xml.Document['@_xmlns'] ||
|
|
10961
|
+
const namespace = (xml.Document['@_xmlns'] ||
|
|
10962
|
+
xml.Document['@_Xmlns']);
|
|
10271
10963
|
if (!namespace.startsWith('urn:iso:std:iso:20022:tech:xsd:camt.053.001.')) {
|
|
10272
10964
|
throw new InvalidXmlNamespaceError('Invalid CAMT.053 namespace');
|
|
10273
10965
|
}
|
|
@@ -10282,7 +10974,7 @@ class CashManagementEndOfDayReport {
|
|
|
10282
10974
|
static fromJSON(json) {
|
|
10283
10975
|
const obj = JSON.parse(json);
|
|
10284
10976
|
if (!obj.Document) {
|
|
10285
|
-
throw new InvalidXmlError(
|
|
10977
|
+
throw new InvalidXmlError('Invalid JSON format');
|
|
10286
10978
|
}
|
|
10287
10979
|
return CashManagementEndOfDayReport.fromDocumentObject(obj);
|
|
10288
10980
|
}
|
|
@@ -10292,10 +10984,12 @@ class CashManagementEndOfDayReport {
|
|
|
10292
10984
|
GrpHdr: {
|
|
10293
10985
|
MsgId: this._messageId,
|
|
10294
10986
|
CreDtTm: this._creationDate.toISOString(),
|
|
10295
|
-
MsgRcpt: this._recipient
|
|
10987
|
+
MsgRcpt: this._recipient
|
|
10988
|
+
? exportRecipient(this._recipient)
|
|
10989
|
+
: undefined,
|
|
10296
10990
|
},
|
|
10297
|
-
Stmt: this._statements.map(
|
|
10298
|
-
}
|
|
10991
|
+
Stmt: this._statements.map(stmt => exportStatement(stmt)),
|
|
10992
|
+
},
|
|
10299
10993
|
};
|
|
10300
10994
|
return { Document };
|
|
10301
10995
|
}
|
|
@@ -10374,5 +11068,6 @@ exports.PaymentStatusCode = PaymentStatusCode;
|
|
|
10374
11068
|
exports.PaymentStatusReport = PaymentStatusReport;
|
|
10375
11069
|
exports.RTPCreditPaymentInitiation = RTPCreditPaymentInitiation;
|
|
10376
11070
|
exports.SEPACreditPaymentInitiation = SEPACreditPaymentInitiation;
|
|
11071
|
+
exports.SEPADirectDebitPaymentInitiation = SEPADirectDebitPaymentInitiation;
|
|
10377
11072
|
exports.SEPAMultiCreditPaymentInitiation = SEPAMultiCreditPaymentInitiation;
|
|
10378
11073
|
exports.SWIFTCreditPaymentInitiation = SWIFTCreditPaymentInitiation;
|