commit b775d8ce5f71816c0e39aab8d19af88ce4588bc2 parent 5ab0c9384cb7862c08a96c9f4c07f1f4bb954038 Author: Antoine A <> Date: Fri, 6 Sep 2024 12:47:13 +0200 nexus: refactor iso20022 logic Diffstat:
24 files changed, 1811 insertions(+), 1702 deletions(-)
diff --git a/nexus/codegen.py b/nexus/codegen.py @@ -62,7 +62,7 @@ def iso20022codegenExternalCodeSet(): // THIS FILE IS GENERATED, DO NOT EDIT -package tech.libeufin.nexus +package tech.libeufin.nexus.iso20022 {extractCodeSet("ExternalStatusReason1Code", "ExternalStatusReasonCode")} @@ -74,7 +74,7 @@ package tech.libeufin.nexus """ with open( - "src/main/kotlin/tech/libeufin/nexus/Iso20022ExternalCodeSets.kt", "w" + "src/main/kotlin/tech/libeufin/nexus/iso20022/ExternalCodeSets.kt", "w" ) as file1: file1.write(kt) @@ -139,7 +139,7 @@ def iso20022codegenBankTransactionCode(): // THIS FILE IS GENERATED, DO NOT EDIT -package tech.libeufin.nexus +package tech.libeufin.nexus.iso20022 {extractCodeSet("Domain", "ExternalBankTransactionDomainCode")} @@ -149,7 +149,7 @@ package tech.libeufin.nexus """ with open( - "src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt", "w" + "src/main/kotlin/tech/libeufin/nexus/iso20022/BankTransactionCode.kt", "w" ) as file1: file1.write(kt) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -1,816 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ -package tech.libeufin.nexus - -import tech.libeufin.common.* -import tech.libeufin.nexus.ebics.Dialect -import java.io.InputStream -import java.time.Instant -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -/** String representation of a Taler [amount] compatible with EBICS */ -fun getAmountNoCurrency(amount: TalerAmount): String { - if (amount.isSubCent()) { - throw Exception("Sub-cent amounts not supported") - } - return amount.number().toString() -} - -data class Pain001Tx( - val creditor: IbanAccountMetadata, - val amount: TalerAmount, - val subject: String, - val endToEndId: String -) - -data class Pain001Msg( - val messageId: String, - val timestamp: Instant, - val debtor: IbanAccountMetadata, - val sum: TalerAmount, - val txs: List<Pain001Tx> -) - -/** Create a pain.001 XML document [msg] valid for [dialect] */ -fun createPain001( - msg: Pain001Msg, - dialect: Dialect -): ByteArray { - val version = "09" - val zonedTimestamp = ZonedDateTime.ofInstant(msg.timestamp, ZoneId.of("UTC")) - val totalAmount = getAmountNoCurrency(msg.sum) - return XmlBuilder.toBytes("Document") { - attr("xmlns", "urn:iso:std:iso:20022:tech:xsd:pain.001.001.$version") - attr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") - attr("xsi:schemaLocation", "urn:iso:std:iso:20022:tech:xsd:pain.001.001.$version pain.001.001.$version.xsd") - el("CstmrCdtTrfInitn") { - el("GrpHdr") { - // Use for idempotency as banks will refuse to process EBICS request with the same MsgId for a pre- agreed period - el("MsgId", msg.messageId) - el("CreDtTm", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedTimestamp)) - el("NbOfTxs", msg.txs.size.toString()) - el("CtrlSum", totalAmount) - el("InitgPty/Nm", msg.debtor.name) - } - el("PmtInf") { - el("PmtInfId", "NOTPROVIDED") - el("PmtMtd", "TRF") - el("BtchBookg", "false") - el("NbOfTxs", msg.txs.size.toString()) - el("CtrlSum", totalAmount) - el("PmtTpInf/SvcLvl/Cd", - when (dialect) { - Dialect.postfinance -> "SDVA" - Dialect.gls -> "SEPA" - } - ) - el("ReqdExctnDt/Dt", DateTimeFormatter.ISO_DATE.format(zonedTimestamp)) - el("Dbtr/Nm", msg.debtor.name) - el("DbtrAcct/Id/IBAN", msg.debtor.iban) - el("DbtrAgt/FinInstnId") { - if (msg.debtor.bic != null) { - el("BICFI", msg.debtor.bic) - } else { - el("Othr/Id", "NOTPROVIDED") - } - } - el("ChrgBr", "SLEV") - for (tx in msg.txs) { - el("CdtTrfTxInf") { - el("PmtId") { - el("InstrId", tx.endToEndId) - // Used to identify this transaction in CAMT files when MsgId is not present - el("EndToEndId", tx.endToEndId) - } - el("Amt/InstdAmt") { - attr("Ccy", tx.amount.currency) - text(getAmountNoCurrency(tx.amount)) - } - if (tx.creditor.bic != null) el("CdtrAgt/FinInstnId/BICFI", tx.creditor.bic) - el("Cdtr") { - el("Nm", tx.creditor.name) - // Addr might become a requirement in the future - /*el("PstlAdr") { - el("TwnNm", "Bochum") - el("Ctry", "DE") - }*/ - } - el("CdtrAcct/Id/IBAN", tx.creditor.iban) - el("RmtInf/Ustrd", tx.subject) - } - } - } - } - } -} - -data class CustomerAck( - val actionType: HacAction, - val orderId: String?, - val code: ExternalStatusReasonCode?, - val info: String, - val timestamp: Instant -) { - fun msg(): String = buildString { - append("$actionType") - if (code != null) append(" ${code.isoCode}") - append(" - '${actionType.description}'") - if (code != null) append(" '${code.description}'") - if (info != "") append(" - '$info'") - } - - override fun toString(): String = buildString { - append(timestamp.fmtDateTime()) - if (orderId != null) append(" $orderId") - append(" ${msg()}") - } -} - -/** Parse HAC pain.002 XML file */ -fun parseCustomerAck(xml: InputStream): List<CustomerAck> { - return XmlDestructor.fromStream(xml, "Document") { - one("CstmrPmtStsRpt").map("OrgnlPmtInfAndSts") { - val actionType = one("OrgnlPmtInfId").enum<HacAction>() - one("StsRsnInf") { - var timestamp: Instant? = null - var orderId: String? = null - one("Orgtr").one("Id").one("OrgId").each("Othr") { - val value = one("Id") - val key = one("SchmeNm").one("Prtry").text() - when (key) { - "TimeStamp" -> { - timestamp = value.dateTime().toInstant(ZoneOffset.UTC) - } - "OrderID" -> orderId = value.text() - } - } - val code = opt("Rsn")?.one("Cd")?.enum<ExternalStatusReasonCode>() - val info = map("AddtlInf") { text() }.joinToString("") - CustomerAck(actionType, orderId, code, info, timestamp!!) - } - } - } -} - -private fun fmtMsg(code: String?, description: String?, reasons: List<Reason>) = buildString { - if (code != null) { - append(code) - append(" ") - if (description != null) { - append("'") - append(description) - append("'") - } - if (reasons.isNotEmpty()) { - append(":") - } - } - for (reason in reasons) { - append(" ") - append(reason.code.isoCode) - append(" '") - append(reason.code.description) - append("'") - if (reason.information.isNotEmpty()) { - append(" '") - append(reason.information) - append("'") - } - } -} - -data class MsgStatus( - val id: String, - val code: ExternalPaymentGroupStatusCode?, - val reasons: List<Reason>, - val payments: List<PmtStatus> -) { - fun msg() = fmtMsg(code?.isoCode, code?.description, reasons) - override fun toString() = buildString { - append(id) - val msg = msg() - if (msg.isNotEmpty()) { - append(" ") - append(msg) - } - for (pmt in payments) { - append("\n>") - append(pmt.id) - val msg = pmt.msg() - if (msg.isNotEmpty()) { - append(" ") - append(msg) - } - - for (tx in pmt.transactions) { - append("\n>>") - if (tx.id != tx.endToEndId) { - append(tx.id) - append(" ") - } - append(tx.endToEndId) - val msg = tx.msg() - if (msg.isNotEmpty()) { - append(" ") - append(msg) - } - } - } - } -} - -data class PmtStatus( - val id: String, - val code: ExternalPaymentGroupStatusCode?, - val reasons: List<Reason>, - val transactions: List<TxStatus> -) { - fun msg() = fmtMsg(code?.isoCode, code?.description, reasons) -} - -data class TxStatus( - val id: String, - val endToEndId: String, - val code: ExternalPaymentTransactionStatusCode, - val reasons: List<Reason> -) { - fun msg() = fmtMsg(code?.isoCode, code?.description, reasons) -} - -data class Reason ( - val code: ExternalStatusReasonCode, - val information: String -) - -/** Parse pain.002 XML file */ -fun parseCustomerPaymentStatusReport(xml: InputStream): MsgStatus { - fun XmlDestructor.reasons(): List<Reason> { - return map("StsRsnInf") { - val code = one("Rsn").one("Cd").enum<ExternalStatusReasonCode>() - val info = map("AddtlInf") { text() }.joinToString("") - Reason(code, info) - } - } - - return XmlDestructor.fromStream(xml, "Document") { - one("CstmrPmtStsRpt") { - val (id, code, reasons) = one("OrgnlGrpInfAndSts") { - val id = one("OrgnlMsgId").text() - val code = opt("GrpSts")?.enum<ExternalPaymentGroupStatusCode>() - val reasons = reasons() - Triple(id, code, reasons) - } - val payments = map("OrgnlPmtInfAndSts") { - val id = one("OrgnlPmtInfId").text() - val code = opt("PmtInfSts")?.enum<ExternalPaymentGroupStatusCode>() - val reasons = reasons() - val transactions = map("TxInfAndSts") { - val id = one("OrgnlInstrId").text() - val endToEndId = one("OrgnlEndToEndId").text() - val code = one("TxSts").enum<ExternalPaymentTransactionStatusCode>() - val reasons = reasons() - TxStatus(id, endToEndId, code, reasons) - } - PmtStatus(id, code, reasons, transactions) - } - MsgStatus(id, code, reasons, payments) - } - } -} - -sealed interface TxNotification { - val executionTime: Instant -} - -/** ISO20022 incoming payment */ -data class IncomingPayment( - /** ISO20022 UETR or TxID */ - val bankId: String? = null, // Null when TxID is wrong with Atruvia's implementation of instant transactions - val amount: TalerAmount, - val wireTransferSubject: String, - override val executionTime: Instant, - val debitPaytoUri: String -): TxNotification { - override fun toString(): String { - return "IN ${executionTime.fmtDate()} $amount $bankId debitor=$debitPaytoUri subject=\"$wireTransferSubject\"" - } -} - -/** ISO20022 outgoing payment */ -data class OutgoingPayment( - /** ISO20022 EndToEndId or MessageId (retrocompatibility) */ - val endToEndId: String, - /** ISO20022 MessageId */ - val msgId: String? = null, - val amount: TalerAmount, - val wireTransferSubject: String? = null, // Some implementation does not provide this for recovery - override val executionTime: Instant, - val creditPaytoUri: String? = null // Some implementation does not provide this for recovery -): TxNotification { - override fun toString(): String { - val msgIdFmt = if (msgId == null) "" else "$msgId." - return "OUT ${executionTime.fmtDate()} $amount $msgIdFmt$endToEndId creditor=$creditPaytoUri subject=\"$wireTransferSubject\"" - } -} - -/** ISO20022 outgoing batch */ -data class OutgoingBatch( - /** ISO20022 MessageId */ - val msgId: String, - override val executionTime: Instant, -): TxNotification { - override fun toString(): String { - return "BATCH ${executionTime.fmtDate()} $msgId" - } -} - -/** ISO20022 outgoing reversal */ -data class OutgoingReversal( - /** ISO20022 EndToEndId */ - val endToEndId: String, - /** ISO20022 MessageId */ - val msgId: String? = null, - val reason: String?, - override val executionTime: Instant -): TxNotification { - override fun toString(): String { - val msgIdFmt = if (msgId == null) "" else "$msgId." - return "BOUNCE ${executionTime.fmtDate()} $msgIdFmt$endToEndId: $reason" - } -} - -private fun XmlDestructor.payto(prefix: String): String? { - val iban = opt("${prefix}Acct")?.one("Id")?.one("IBAN")?.text() - return if (iban != null) { - val name = opt(prefix) { opt("Nm")?.text() ?: opt("Pty")?.one("Nm")?.text() } - // Parse bic ? - IbanPayto.build(iban, null, name) - } else { - null - } -} - -private class TxErr(val msg: String): Exception(msg) - -private enum class Kind { - CRDT, - DBIT -} - -/** Unique ID generated by libeufin-nexus */ -private data class OutgoingId( - // Unique msg ID generated by libeufin-nexus - val msgId: String?, - // Unique end-to-end ID generated by libeufin-nexus - val endToEndId: String? -) { - fun ref(): String? = endToEndId ?: msgId -} - -/** Parse camt.054 or camt.053 file */ -fun parseTx( - notifXml: InputStream, - acceptedCurrency: String, - dialect: Dialect -): List<TxNotification> { - /* - In ISO 20022 specifications, most fields are optional and the same information - can be written several times in different places. For libeufin, we're only - interested in a subset of the available values that can be found in both camt.052, - camt.053 and camt.054. This function should not fail on legitimate files and should - simply warn when available information are insufficient. - - EBICS and ISO20022 do not provide a perfect transaction identifier. The best is the - UETR (unique end-to-end transaction reference), which is a universally unique - identifier (UUID). However, it is not supplied by all banks. TxId (TransactionIdentification) - is a unique identification as assigned by the first instructing agent. As its format - is ambiguous, its uniqueness is not guaranteed by the standard, and it is only - supposed to be unique for a “pre-agreed period”, whatever that means. These two - identifiers are optional in the standard, but have the advantage of being unique - and can be used to track a transaction between banks so we use them when available. - - It is also possible to use AccountServicerReference, which is a unique reference - assigned by the account servicing institution. They can be present at several levels - (batch level, transaction level, etc.) and are often optional. They also have the - disadvantage of being known only by the account servicing institution. They should - therefore only be used as a last resort. - */ - - /** Check if an entry status is BOOK */ - fun XmlDestructor.isBooked(): Boolean { - // We check at the Sts or Sts/Cd level for retrocompatibility - return one("Sts") { - val status = opt("Cd")?.text() ?: text() - status == "BOOK" - } - } - - /** Parse the instruction execution date */ - fun XmlDestructor.executionDate(): Instant { - // Value date if present else booking date - val date = opt("ValDt") ?: one("BookgDt") - val parsed = date.opt("Dt") { - date().atStartOfDay() - } ?: date.one("DtTm") { - dateTime() - } - return parsed.toInstant(ZoneOffset.UTC) - } - - /** Parse batch message ID and transaction end-to-end ID as generated by libeufin-nexus */ - fun XmlDestructor.outgoingId(): OutgoingId = one("Refs") { - val endToEndId = opt("EndToEndId")?.text() - val msgId = opt("MsgId")?.text() - if (endToEndId == null) { - // This is a batch representation - OutgoingId(msgId, null) - } else if (endToEndId == "NOTPROVIDED") { - // If not set use MsgId as end-to-end ID for retrocompatibility - OutgoingId(msgId, msgId) - } else { - OutgoingId(msgId, endToEndId) - } - } - - /** Parse and format transaction return reasons */ - fun XmlDestructor.returnReason(): String = opt("RtrInf") { - val code = one("Rsn").one("Cd").enum<ExternalReturnReasonCode>() - val info = map("AddtlInf") { text() }.joinToString("") - buildString { - append("${code.isoCode} '${code.description}'") - if (info.isNotEmpty()) { - append(" - '$info'") - } - } - } ?: opt("RmtInf") { - map("Ustrd") { text() }.joinToString("") - } ?: "" - - /** Parse amount */ - fun XmlDestructor.amount(acceptedCurrency: String) = one("Amt") { - val currency = attr("Ccy") - /** FIXME: test by sending non-CHF to PoFi and see which currency gets here. */ - if (currency != acceptedCurrency) throw Exception("Currency $currency not supported") - TalerAmount("$currency:${text()}") - } - - /** Parse bank transaction code */ - fun XmlDestructor.bankTransactionCode(): BankTransactionCode { - return one("BkTxCd").one("Domn") { - val domain = one("Cd").enum<ExternalBankTransactionDomainCode>() - one("Fmly") { - val family = one("Cd").enum<ExternalBankTransactionFamilyCode>() - val subFamily = one("SubFmlyCd").enum<ExternalBankTransactionSubFamilyCode>() - - BankTransactionCode(domain, family, subFamily) - } - } - } - - /** Parse optional bank transaction code */ - fun XmlDestructor.optBankTransactionCode(): BankTransactionCode? { - return opt("BkTxCd")?.one("Domn") { - val domain = one("Cd").enum<ExternalBankTransactionDomainCode>() - one("Fmly") { - val family = one("Cd").enum<ExternalBankTransactionFamilyCode>() - val subFamily = one("SubFmlyCd").enum<ExternalBankTransactionSubFamilyCode>() - - BankTransactionCode(domain, family, subFamily) - } - } - } - - val txsInfo = mutableListOf<TxInfo>() - - XmlDestructor.fromStream(notifXml, "Document") { when (dialect) { - Dialect.gls -> { - /** Common parsing logic for camt.052 and camt.053 */ - fun XmlDestructor.parseGlsInner() { - opt("Acct") { - // Sanity check on currency and IBAN ? - } - each("Ntry") { - if (!isBooked()) return@each - val code = bankTransactionCode() - if (!code.isPayment()) return@each - val entryRef = opt("AcctSvcrRef")?.text() - val bookDate = executionDate() - val kind = one("CdtDbtInd").enum<Kind>() - val amount = amount(acceptedCurrency) - one("NtryDtls").one("TxDtls") { // TODO handle batches - val code = optBankTransactionCode() ?: code - val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() - if (code.isReversal()) { - val outgoingId = outgoingId() - if (kind == Kind.CRDT) { - val reason = returnReason() - txsInfo.add(TxInfo.CreditReversal( - ref = outgoingId.ref() ?: txRef ?: entryRef, - bookDate = bookDate, - id = outgoingId, - reason = reason, - code = code - )) - } - } else { - val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("") - when (kind) { - Kind.CRDT -> { - val bankId = one("Refs").opt("TxId")?.text() - val debtorPayto = opt("RltdPties") { payto("Dbtr") } - txsInfo.add(TxInfo.Credit( - ref = bankId ?: txRef ?: entryRef, - bookDate = bookDate, - bankId = bankId, - amount = amount, - subject = subject, - debtorPayto = debtorPayto, - code = code - )) - } - Kind.DBIT -> { - val outgoingId = outgoingId() - val creditorPayto = opt("RltdPties") { payto("Cdtr") } - txsInfo.add(TxInfo.Debit( - ref = outgoingId.ref() ?: txRef ?: entryRef, - bookDate = bookDate, - id = outgoingId, - amount = amount, - subject = subject, - creditorPayto = creditorPayto, - code = code - )) - } - } - } - } - } - } - opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 - // All transactions appear here the day after they are booked - parseGlsInner() - } - opt("BkToCstmrAcctRpt")?.each("Rpt") { // Camt.052 - // Transactions might appear here first before the end of the day - parseGlsInner() - } - opt("BkToCstmrDbtCdtNtfctn")?.each("Ntfctn") { // Camt.054 - // Instant transactions appear here a few seconds after being booked - opt("Acct") { - // Sanity check on currency and IBAN ? - } - each("Ntry") { - if (!isBooked()) return@each - val code = bankTransactionCode() - if (code.isReversal() || !code.isPayment()) return@each - val entryRef = opt("AcctSvcrRef")?.text() - val bookDate = executionDate() - val kind = one("CdtDbtInd").enum<Kind>() - val amount = amount(acceptedCurrency) - one("NtryDtls").one("TxDtls") { - val code = optBankTransactionCode() ?: code - val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() - val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("") - if (kind == Kind.CRDT) { - val bankId = one("Refs").opt("TxId")?.text() - val debtorPayto = opt("RltdPties") { payto("Dbtr") } - txsInfo.add(TxInfo.Credit( - ref = txRef ?: entryRef, - bookDate = bookDate, - // TODO use the bank ID again when Atruvia's implementation is fixed - bankId = null, - amount = amount, - subject = subject, - debtorPayto = debtorPayto, - code = code - )) - } - } - } - } - } - Dialect.postfinance -> { - opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 - /* - All transactions appear here on the day following their booking. Alas, some - necessary metadata is missing, which is only present in camt.054. However, - this file contains the structured return reasons that are missing from the - camt.054 files. That's why we only use this file for this purpose. - */ - opt("Acct") { - // Sanity check on currency and IBAN ? - } - each("Ntry") { - if (!isBooked()) return@each - val code = bankTransactionCode() - // Non reversal transaction are handled in camt.054 - if (!(code.isReversal() && code.isPayment())) return@each - - val entryRef = opt("AcctSvcrRef")?.text() - val bookDate = executionDate() - one("NtryDtls").one("TxDtls") { - val kind = one("CdtDbtInd").enum<Kind>() - val code = optBankTransactionCode() ?: code - if (kind == Kind.CRDT) { - val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() - val outgoingId = outgoingId() - val reason = returnReason() - txsInfo.add(TxInfo.CreditReversal( - ref = outgoingId.ref() ?: txRef ?: entryRef, - bookDate = bookDate, - id = outgoingId, - reason = reason, - code = code - )) - } - } - } - } - opt("BkToCstmrDbtCdtNtfctn")?.each("Ntfctn") { // Camt.054 - // Instant transactions appear here a moment after being booked - opt("Acct") { - // Sanity check on currency and IBAN ? - } - each("Ntry") { - if (!isBooked()) return@each - val code = bankTransactionCode() - // Reversal are handled from camt.053 - if (code.isReversal() || !code.isPayment()) return@each - - val entryRef = opt("AcctSvcrRef")?.text() - val bookDate = executionDate() - one("NtryDtls").each("TxDtls") { - val kind = one("CdtDbtInd").enum<Kind>() - val code = optBankTransactionCode() ?: code - val amount = amount(acceptedCurrency) - val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() - val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("") - when (kind) { - Kind.CRDT -> { - val bankId = opt("Refs")?.opt("UETR")?.text() - val debtorPayto = opt("RltdPties") { payto("Dbtr") } - txsInfo.add(TxInfo.Credit( - ref = bankId ?: txRef ?: entryRef, - bookDate = bookDate, - bankId = bankId, - amount = amount, - subject = subject, - debtorPayto = debtorPayto, - code = code - )) - } - Kind.DBIT -> { - val outgoingId = outgoingId() - val creditorPayto = opt("RltdPties") { payto("Cdtr") } - txsInfo.add(TxInfo.Debit( - ref = outgoingId.ref() ?: txRef ?: entryRef, - bookDate = bookDate, - id = outgoingId, - amount = amount, - subject = subject, - creditorPayto = creditorPayto, - code = code - )) - } - } - } - } - } - } - }} - - return txsInfo.mapNotNull { - try { - parseTxLogic(it) - } catch (e: TxErr) { - // TODO: add more info in doc or in log message? - logger.warn("skip incomplete tx: ${e.msg}") - null - } - } -} - -private sealed interface TxInfo { - // Bank provider ref for debugging - val ref: String? - // When was this transaction booked - val bookDate: Instant - // ISO20022 bank transaction code - val code: BankTransactionCode - data class CreditReversal( - override val ref: String?, - override val bookDate: Instant, - override val code: BankTransactionCode, - // Unique ID generated by libeufin-nexus - val id: OutgoingId, - val reason: String - ): TxInfo - data class Credit( - override val ref: String?, - override val bookDate: Instant, - override val code: BankTransactionCode, - // Unique ID generated by payment provider - val bankId: String?, - val amount: TalerAmount, - val subject: String?, - val debtorPayto: String? - ): TxInfo - data class Debit( - override val ref: String?, - override val bookDate: Instant, - override val code: BankTransactionCode, - // Unique ID generated by libeufin-nexus - val id: OutgoingId, - val amount: TalerAmount, - val subject: String?, - val creditorPayto: String? - ): TxInfo -} - -private fun parseTxLogic(info: TxInfo): TxNotification { - return when (info) { - is TxInfo.CreditReversal -> { - if (info.id.endToEndId == null) - throw TxErr("missing end-to-end ID for Credit reversal ${info.ref}") - OutgoingReversal( - endToEndId = info.id.endToEndId!!, - msgId = info.id.msgId, - reason = info.reason, - executionTime = info.bookDate - ) - } - is TxInfo.Credit -> { - /*if (info.bankId == null) TODO use the bank ID again when Atruvia's implementation is fixed - throw TxErr("missing bank ID for Credit ${info.ref}")*/ - if (info.subject == null) - throw TxErr("missing subject for Credit ${info.ref}") - if (info.debtorPayto == null) - throw TxErr("missing debtor info for Credit ${info.ref}") - IncomingPayment( - amount = info.amount, - bankId = info.bankId, - debitPaytoUri = info.debtorPayto, - executionTime = info.bookDate, - wireTransferSubject = info.subject - ) - } - is TxInfo.Debit -> { - if (info.id.endToEndId == null && info.id.msgId == null) { - throw TxErr("missing end-to-end ID for Debit ${info.ref}") - } else if (info.id.endToEndId != null) { - OutgoingPayment( - amount = info.amount, - endToEndId = info.id.endToEndId, - msgId = info.id.msgId, - executionTime = info.bookDate, - creditPaytoUri = info.creditorPayto, - wireTransferSubject = info.subject - ) - } else { - OutgoingBatch( - msgId = info.id.msgId!!, - executionTime = info.bookDate, - ) - } - } - } -} - -data class BankTransactionCode( - val domain: ExternalBankTransactionDomainCode, - val family: ExternalBankTransactionFamilyCode, - val subFamily: ExternalBankTransactionSubFamilyCode -) { - fun isReversal(): Boolean = REVERSAL_CODE.contains(subFamily) - fun isPayment(): Boolean = domain == ExternalBankTransactionDomainCode.PMNT || subFamily == ExternalBankTransactionSubFamilyCode.PSTE - - override fun toString(): String = - "${domain.name} ${family.name} ${subFamily.name} - '${domain.description}' '${family.description}' '${subFamily.description}'" - - companion object { - private val REVERSAL_CODE = setOf( - ExternalBankTransactionSubFamilyCode.RPCR, - ExternalBankTransactionSubFamilyCode.RRTN, - ExternalBankTransactionSubFamilyCode.PSTE, - ) - } -} -\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt @@ -1,386 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -// THIS FILE IS GENERATED, DO NOT EDIT - -package tech.libeufin.nexus - -enum class ExternalBankTransactionDomainCode(val description: String) { - ACMT("Account Management"), - CAMT("Cash Management"), - CMDT("Commodities"), - DERV("Derivatives"), - FORX("Foreign Exchange"), - LDAS("Loans, Deposits & Syndications"), - PMET("Precious Metal"), - PMNT("Payments"), - SECU("Securities"), - TRAD("Trade Services"), - XTND("Extended Domain"), -} - -enum class ExternalBankTransactionFamilyCode(val description: String) { - ACCB("Account Balancing"), - ACOP("Additional Miscellaneous Credit Operations"), - ADOP("Additional Miscellaneous Debit Operations"), - BLOC("Blocked Transactions"), - CAPL("Cash Pooling"), - CASH("Miscellaneous Securities Operations"), - CCRD("Customer Card Transactions"), - CLNC("Clean Collection"), - CNTR("Counter Transactions"), - COLC("Custody Collection"), - COLL("Collateral Management"), - CORP("Corporate Action"), - CSLN("Consumer Loans"), - CUST("Custody"), - DCCT("Documentary Credit"), - DLVR("Delivery"), - DOCC("Documentary Collection"), - DRFT("Drafts"), - FTDP("Fixed Term Deposits"), - FTLN("Fixed Term Loans"), - FTUR("Futures"), - FWRD("Forwards"), - GUAR("Guarantees"), - ICCN("Issued Cash Concentration Transactions"), - ICDT("Issued Credit Transfers"), - ICHQ("Issued Cheques"), - IDDT("Issued Direct Debits"), - IRCT("Issued Real-Time Credit Transfers"), - LACK("Lack"), - LBOX("Lockbox Transactions"), - LFUT("Listed Derivatives - Futures"), - LOCT("Stand-By Letter Of Credit"), - LOPT("Listed Derivatives - Options"), - MCOP("Miscellaneous Credit Operations"), - MCRD("Merchant Card Transactions"), - MDOP("Miscellaneous Debit Operations"), - MGLN("Mortgage Loans"), - NDFX("Non Deliverable"), - NSET("Non Settled"), - NTAV("Not Available"), - NTDP("Notice Deposits"), - NTLN("Notice Loans"), - OBND("OTC Derivatives - Bonds"), - OCRD("OTC Derivatives - Credit"), - OEQT("OTC Derivatives - Equity"), - OIRT("OTC Derivatives - Interest Rates"), - OPCL("Opening & Closing"), - OPTN("Options"), - OSED("OTC Derivatives - Structured Exotic Derivatives"), - OSWP("OTC Derivatives – Swaps"), - OTHB("CSD Blocked transactions"), - OTHR("Other"), - RCCN("Received Cash Concentration Transactions"), - RCDT("Received Credit Transfers"), - RCHQ("Received Cheques"), - RDDT("Received Direct Debits"), - RRCT("Received Real-Time Credit Transfers"), - SETT("Trade, Clearing and Settlement"), - SPOT("Spots"), - SWAP("Swaps"), - SYDN("Syndications"), -} - -enum class ExternalBankTransactionSubFamilyCode(val description: String) { - ACCC("Account Closing"), - ACCO("Account Opening"), - ACCT("Account Transfer"), - ACDT("ACH Credit"), - ACOR("ACH Corporate Trade"), - ADBT("ACH Debit"), - ADJT("Adjustments (Generic)"), - APAC("ACH Pre-Authorised"), - ARET("ACH Return"), - AREV("ACH Reversal"), - ARPD("ARP Debit"), - ASET("ACH Settlement"), - ATXN("ACH Transaction"), - AUTT("Automatic Transfer"), - BBDD("SEPA B2B Direct Debit"), - BCDP("Branch Deposit"), - BCHQ("Bank Cheque"), - BCKV("Back Value"), - BCWD("Branch Withdrawl"), - BFWD("Bond Forward"), - BIDS("Repurchase Offer/Issuer Bid/Reverse Rights."), - BKFE("Bank Fees"), - BONU("Bonus Issue/Capitalisation Issue"), - BOOK("Internal Book Transfer"), - BPUT("Put Redemption"), - BROK("Brokerage Fee"), - BSBC("Sell Buy Back"), - BSBO("Buy Sell Back"), - CAJT("Credit Adjustments (Generic)"), - CAPG("Capital Gains Distribution"), - CASH("Cash Letter"), - CCCH("Certified Customer Cheque"), - CCHQ("Cheque"), - CCIR("Cross Currency IRS"), - CCPC("CCP Cleared Initial Margin"), - CCPM("CCP Cleared Variation Margin"), - CCSM("CCP Cleared Segregated Initial Margin"), - CDIS("Controlled Disbursement"), - CDPT("Cash Deposit"), - CHAR("Charge/Fees"), - CHKD("Check Deposit"), - CHRG("Charges (Generic)"), - CLAI("Compensation/Claims"), - CLCQ("Circular Cheque"), - CMBO("Corporate Mark Broker Owned"), - CMCO("Corporate Mark Client Owned"), - COME("Commission Excluding Taxes (Generic)"), - COMI("Commission Including Taxes (Generic)"), - COMM("Commission (Generic)"), - COMT("Non Taxable Commissions (Generic)"), - CONV("Conversion"), - COVE("Cover Transaction"), - CPEN("Cash Penalties"), - CPRB("Corporate Rebate"), - CQRV("Cheque Reversal"), - CRCQ("Crossed Cheque"), - CRDS("Credit DefaultSwap"), - CROS("Cross Trade"), - CRPR("Cross Product"), - CRSP("Credit Support"), - CRTL("Credit Line"), - CSHA("Cash Letter Adjustment"), - CSLI("Cash In Lieu"), - CWDL("Cash Withdrawal"), - DAJT("Debit Adjustments (Generic)"), - DDFT("Discounted Draft"), - DDWN("Drawdown"), - DECR("Decrease in Value"), - DMCG("Draft Maturity Change"), - DMCT("Domestic Credit Transfer"), - DPST("Deposit"), - DRAW("Drawing"), - DRIP("Dividend Reinvestment"), - DSBR("Controlled Disbursement"), - DTCH("Dutch Auction"), - DVCA("Cash Dividend"), - DVOP("Dividend Option"), - ENCT("Nordic Payment Council Credit Transfer"), - EQBO("Equity Mark Broker Owned"), - EQCO("Equity Mark Client Owned"), - EQPT("Equity Option"), - EQUS("Equity Swap"), - ERTA("Exchange Rate Adjustment"), - ERWA("Lending Income"), - ERWI("Borrowing Fee"), - ESCT("SEPA Credit Transfer"), - ESDD("SEPA Core Direct Debit"), - EXOF("Exchange"), - EXPT("Exotic Option"), - EXRI("Call On Intermediate Securities"), - EXTD("Exchange Traded Derivatives"), - EXWA("Warrant Exercise/Warrant Conversion"), - FCDP("Foreign Currencies Deposit"), - FCTA("Factor Update"), - FCWD("Foreign Currencies Withdrawal"), - FEES("Fees (Generic)"), - FICT("Financial Institution Credit Transfer"), - FIDD("Financial Institution Direct Debit Payment"), - FIOA("Financial Institution Own Account Transfer"), - FIXI("Fixed Income"), - FLTA("Float Adjustment"), - FRZF("Freeze Of Funds"), - FUCO("Futures Commission"), - FUTU("Future Variation Margin"), - FWBC("Forwards Broker Owned Collateral"), - FWCC("Forwards Client Owned Collateral"), - FWSB("MFA Segregated Broker Cash Collateral"), - FWSC("MFA Segregated Client Cash Collateral"), - GEN1("Withdrawal/Distribution"), - GEN2("Deposit/Contribution"), - IADD("Invoice Accepted with Differed Due Date"), - INFD("Fixed Deposit Interest Amount"), - INSP("Inspeci/Share Exchange"), - INTR("Interest Payment"), - ISSU("Depositary Receipt Issue"), - LBCA("Credit Adjustment"), - LBDP("Deposit"), - LIQU("Liquidation Dividend / Liquidation Payment"), - MARG("Margin Payments"), - MBSB("Mortgage Back Segregated Broker Cash Collateral"), - MBSC("Mortgage Back Segregated Client Cash Collateral"), - MCAL("Full Call / Early Redemption"), - MGCC("Margin Client Owned Cash Collateral"), - MGSC("Initial Futures Margin Segregated Client Cash Collateral"), - MIXD("Mixed Deposit"), - MNFE("Management Fees"), - MRGR("Merger"), - MSCD("Miscellaneous Deposit"), - NETT("Netting"), - NPCC("Non Presented Circular Cheques"), - NSYN("Non Syndicated"), - NTAV("Not Available"), - NWID("New issue distribution"), - OCCC("Client owned OCC pledged collateral"), - ODFT("Overdraft"), - ODLT("Odd Lot Sale/Purchase"), - OODD("One-Off Direct Debit"), - OPBC("Option Broker Owned Collateral"), - OPCC("Option Client Owned Collateral"), - OPCQ("Open Cheque"), - OPSB("OTC Option Segregated Broker Cash Collateral"), - OPSC("OTC Option Segregated Client Cash Collateral"), - OPTN("FX Option"), - ORCQ("Order Cheque"), - OTCC("OTC CCP"), - OTCD("OTC Derivatives"), - OTCG("OTC"), - OTCN("OTC Non-CCP"), - OTHR("Other"), - OVCH("Overdraft Charge"), - OWNE("External Account Transfer"), - OWNI("Internal Account Transfer"), - PADD("Pre-Authorised Direct Debit"), - PAIR("Pair-Off"), - PCAL("Partial Redemption With Reduction Of Nominal Value"), - PLAC("Placement"), - PMDD("Direct Debit"), - PORT("Portfolio Move"), - POSC("Credit Card Payment"), - POSD("Point-of-Sale (POS) Payment - Debit Card"), - PPAY("Principal Payment"), - PRCT("Priority Credit Transfer"), - PRDD("Reversal Due To Payment Reversal"), - PRED("Partial Redemption Without Reduction Of Nominal Value"), - PRII("Interest Payment with Principles"), - PRIN("Interest Payment with Principles"), - PRIO("Priority Issue"), - PRUD("Principal Pay-Down/Pay-Up"), - PSTE("Posting Error"), - RCDD("Reversal Due To Payment Cancellation Request"), - RCOV("Reversal due to a Cover Transaction Return"), - REAA("Redemption Asset Allocation"), - REDM("Final Maturity"), - REPU("Repo"), - RESI("Futures Residual Amount"), - RHTS("Rights Issue/Subscription Rights/Rights Offer"), - RIMB("Reimbursement (Generic)"), - RNEW("Renewal"), - RPBC("Bi-lateral repo broker owned collateral"), - RPCC("Repo client owned collateral"), - RPCR("Reversal Due To Payment Cancellation Request"), - RPMT("Repayment"), - RPSB("Bi-lateral Repo Segregated Broker Cash Collateral"), - RPSC("Bi-lateral Repo Segregated Client Cash Collateral"), - RRTN("Reversal Due To Payment Return"), - RVPO("Reverse Repo"), - RWPL("Redemption Withdrawing Plan"), - SABG("Settlement Against Bank Guarantee"), - SALA("Payroll/Salary Payment"), - SBSC("Securities Buy Sell Sell Buy Back"), - SCIE("Single Currency IRS Exotic"), - SCIR("Single Currency IRS"), - SCRP("Securities Cross Products"), - SDVA("Same Day Value Credit Transfer"), - SECB("Securities Borrowing"), - SECL("Securities Lending"), - SHBC("Broker owned collateral Short Sale"), - SHCC("Client owned collateral Short Sale"), - SHPR("Equity Premium Reserve"), - SHSL("Short Sell"), - SLBC("Lending Broker Owned Cash Collateral"), - SLCC("Lending Client Owned Cash Collateral"), - SLEB("Securities Lending And Borrowing"), - SLOA("SecuredLoan"), - SOSE("Settlement Of Sight Export Document"), - SOSI("Settlement Of Sight Import Document"), - SSPL("Subscription Savings Plan"), - STAC("Settlement After Collection"), - STAM("Settlement At Maturity"), - STDO("Standing Order"), - STLM("Settlement"), - STLR("Settlement Under Reserve"), - STOD("Bill of Exchange Settlement on Demand"), - SUAA("Subscription Asset Allocation"), - SUBS("Subscription"), - SWAP("Swap Payment"), - SWBC("Swap Broker Owned Collateral"), - SWCC("Client Owned Collateral"), - SWEP("Sweep"), - SWFP("Final Payment"), - SWIC("Switch"), - SWPP("Partial Payment"), - SWPT("Swaption"), - SWRS("Reset Payment"), - SWSB("ISDA/CSA Segregated Broker Cash Collateral"), - SWSC("ISDA/CSA Segregated Client Cash Collateral"), - SWUF("Upfront Payment"), - SYND("Syndicated"), - TAXE("Taxes (Generic)"), - TBAC("TBA Closing"), - TBAS("To Be Announced"), - TBBC("TBA Broker owned cash collateral"), - TBCC("TBA Client owned cash collateral"), - TCDP("Travellers Cheques Deposit"), - TCWD("Travellers Cheques Withdrawal"), - TEND("Tender"), - TOPG("Topping"), - TOUT("Transfer Out"), - TRAD("Trade"), - TRCP("Treasury Cross Product"), - TREC("Tax Reclaim"), - TRFE("Transaction Fees"), - TRIN("Transfer In"), - TRPO("Triparty Repo"), - TRVO("Triparty Reverse Repo"), - TTLS("Treasury Tax And Loan Service"), - TURN("Turnaround"), - UDFT("Dishonoured/Unpaid Draft"), - UNCO("Underwriting Commission"), - UPCQ("Unpaid Cheque"), - UPCT("Unpaid Card Transaction"), - UPDD("Reversal Due To Return/Unpaid Direct Debit"), - URCQ("Cheque Under Reserve"), - URDD("Direct Debit Under Reserve"), - VALD("Value Date"), - VCOM("Credit Transfer With Agreed Commercial Information"), - WITH("Withholding Tax"), - XBCP("Cross-Border Credit Card Payment"), - XBCQ("Foreign Cheque"), - XBCT("Cross-Border Credit Transfer"), - XBCW("Cross-Border Cash Withdrawal"), - XBRD("Cross-Border"), - XBSA("Cross-Border Payroll/Salary Payment"), - XBST("Cross-Border Standing Order"), - XCHC("Exchange Traded CCP"), - XCHG("Exchange Traded"), - XCHN("Exchange Traded Non-CCP"), - XICT("Cross-Border Intra Company Transfer"), - XPCQ("Unpaid Foreign Cheque"), - XRCQ("Foreign Cheque Under Reserve"), - XRTN("Cross Border Reversal Due to Payment Return"), - YTDA("YTD Adjustment"), - ZABA("Zero Balancing"), - ACON("ACH Concentration"), - BACT("Branch Account Transfer"), - COAT("Corporate Own Account Transfer"), - ICCT("Intra Company Transfer"), - LBDB("Debit"), - POSP("Point-of-Sale (POS) Payment"), - SMCD("Smart-Card Payment"), - SMRT("Smart-Card Payment"), - XBDD("Cross-Border Direct Debit"), -} - diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022Constants.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022Constants.kt @@ -1,37 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -package tech.libeufin.nexus - -enum class HacAction(val description: String) { - FILE_UPLOAD("File submitted to the bank"), - FILE_DOWNLOAD("File downloaded from the bank"), - ES_UPLOAD("Electronic signature submitted to the bank"), - ES_DOWNLOAD("Electronic signature downloaded from the bank"), - ES_VERIFICATION("Signature verification"), - VEU_FORWARDING("Forwarding to EDS"), - VEU_VERIFICATION("EDS signature verification"), - VEU_VERIFICATION_END("VEU_VERIFICATION_END"), - VEU_CANCEL_ORDER("Cancellation of EDS order"), - ADDITIONAL("Additional information"), - ORDER_HAC_FINAL_POS("HAC end of order (positive)"), - ORDER_HAC_FINAL_NEG("HAC end of order (negative)"), - // Not in the spec but Credit Suisse test suite use it - ORDER_HAC_FINAL("HAC end of order") -} -\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022ExternalCodeSets.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022ExternalCodeSets.kt @@ -1,453 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -// THIS FILE IS GENERATED, DO NOT EDIT - -package tech.libeufin.nexus - -enum class ExternalStatusReasonCode(val isoCode: String, val description: String) { - AB01("AbortedClearingTimeout", "Clearing process aborted due to timeout."), - AB02("AbortedClearingFatalError", "Clearing process aborted due to a fatal error."), - AB03("AbortedSettlementTimeout", "Settlement aborted due to timeout."), - AB04("AbortedSettlementFatalError", "Settlement process aborted due to a fatal error."), - AB05("TimeoutCreditorAgent", "Transaction stopped due to timeout at the Creditor Agent."), - AB06("TimeoutInstructedAgent", "Transaction stopped due to timeout at the Instructed Agent."), - AB07("OfflineAgent", "Agent of message is not online."), - AB08("OfflineCreditorAgent", "Creditor Agent is not online."), - AB09("ErrorCreditorAgent", "Transaction stopped due to error at the Creditor Agent."), - AB10("ErrorInstructedAgent", "Transaction stopped due to error at the Instructed Agent."), - AB11("TimeoutDebtorAgent", "Transaction stopped due to timeout at the Debtor Agent."), - AB12("InvalidConcurrentBatch", "Duplicate Concurrent Batch Sequence number– for Settlement Instructions."), - AB13("InvalidRoutingCodeUtilised", "Wrong Message Routing Type for Return-of-Funds."), - AB15("InvalidAccountNumberForSettlementType", "Instruction may not be placed on the Continuous Processing Line settlement processor."), - AB21("InvalidSettlementAgreementNumberSpecified", "Agreement number not valid (beneficiary)."), - AB26("InvalidBatchSettlementInstruction", "Settlement Instruction does not exist."), - AC01("IncorrectAccountNumber", "Account number is invalid or missing."), - AC02("InvalidDebtorAccountNumber", "Debtor account number invalid or missing"), - AC03("InvalidCreditorAccountNumber", "Creditor account number invalid or missing"), - AC04("ClosedAccountNumber", "Account number specified has been closed on the bank of account's books."), - AC05("ClosedDebtorAccountNumber", "Debtor account number closed"), - AC06("BlockedAccount", "Account specified is blocked, prohibiting posting of transactions against it."), - AC07("ClosedCreditorAccountNumber", "Creditor account number closed"), - AC08("InvalidBranchCode", "Branch code is invalid or missing"), - AC09("InvalidAccountCurrency", "Account currency is invalid or missing"), - AC10("InvalidDebtorAccountCurrency", "Debtor account currency is invalid or missing"), - AC11("InvalidCreditorAccountCurrency", "Creditor account currency is invalid or missing"), - AC12("InvalidAccountType", "Account type missing or invalid."), - AC13("InvalidDebtorAccountType", "Debtor account type missing or invalid"), - AC14("InvalidCreditorAccountType", "Creditor account type missing or invalid"), - AC15("AccountDetailsChanged", "The account details for the counterparty have changed."), - AC16("CardNumberInvalid", "Credit or debit card number is invalid."), - AEXR("AlreadyExpiredRTP", "Request-to-pay Expiry Date and Time has already passed."), - AG01("TransactionForbidden", "Transaction forbidden on this type of account (formerly NoAgreement)"), - AG02("InvalidBankOperationCode", "Bank Operation code specified in the message is not valid for receiver"), - AG03("TransactionNotSupported", "Transaction type not supported/authorized on this account"), - AG04("InvalidAgentCountry", "Agent country code is missing or invalid."), - AG05("InvalidDebtorAgentCountry", "Debtor agent country code is missing or invalid"), - AG06("InvalidCreditorAgentCountry", "Creditor agent country code is missing or invalid"), - AG07("UnsuccesfulDirectDebit", "Debtor account cannot be debited for a generic reason."), - AG08("InvalidAccessRights", "Transaction failed due to invalid or missing user or access right"), - AG09("PaymentNotReceived", "Original payment never received."), - AG10("AgentSuspended", "Agent of message is suspended from the Real Time Payment system."), - AG11("CreditorAgentSuspended", "Creditor Agent of message is suspended from the Real Time Payment system."), - AG12("NotAllowedBookTransfer", "Payment orders made by transferring funds from one account to another at the same financial institution (bank or payment institution) are not allowed."), - AG13("ForbiddenReturnPayment", "Returned payments derived from previously returned transactions are not allowed."), - AGNT("IncorrectAgent", "Agent in the payment workflow is incorrect"), - ALAC("AlreadyAcceptedRTP", "Request-to-pay has already been accepted by the Debtor."), - AM01("ZeroAmount", "Specified message amount is equal to zero"), - AM02("NotAllowedAmount", "Specific transaction/message amount is greater than allowed maximum"), - AM03("NotAllowedCurrency", "Specified message amount is an non processable currency outside of existing agreement"), - AM04("InsufficientFunds", "Amount of funds available to cover specified message amount is insufficient."), - AM05("Duplication", "Duplication"), - AM06("TooLowAmount", "Specified transaction amount is less than agreed minimum."), - AM07("BlockedAmount", "Amount specified in message has been blocked by regulatory authorities."), - AM09("WrongAmount", "Amount received is not the amount agreed or expected"), - AM10("InvalidControlSum", "Sum of instructed amounts does not equal the control sum."), - AM11("InvalidTransactionCurrency", "Transaction currency is invalid or missing"), - AM12("InvalidAmount", "Amount is invalid or missing"), - AM13("AmountExceedsClearingSystemLimit", "Transaction amount exceeds limits set by clearing system"), - AM14("AmountExceedsAgreedLimit", "Transaction amount exceeds limits agreed between bank and client"), - AM15("AmountBelowClearingSystemMinimum", "Transaction amount below minimum set by clearing system"), - AM16("InvalidGroupControlSum", "Control Sum at the Group level is invalid"), - AM17("InvalidPaymentInfoControlSum", "Control Sum at the Payment Information level is invalid"), - AM18("InvalidNumberOfTransactions", "Number of transactions is invalid or missing."), - AM19("InvalidGroupNumberOfTransactions", "Number of transactions at the Group level is invalid or missing"), - AM20("InvalidPaymentInfoNumberOfTransactions", "Number of transactions at the Payment Information level is invalid"), - AM21("LimitExceeded", "Transaction amount exceeds limits agreed between bank and client."), - AM22("ZeroAmountNotApplied", "Unable to apply zero amount to designated account. For example, where the rules of a service allow the use of zero amount payments, however the back-office system is unable to apply the funds to the account. If the rules of a service prohibit the use of zero amount payments, then code AM01 is used to report the error condition."), - AM23("AmountExceedsSettlementLimit", "Transaction amount exceeds settlement limit."), - APAR("AlreadyPaidRTP", "Request To Pay has already been paid by the Debtor."), - ARFR("AlreadyRefusedRTP", "Request-to-pay has already been refused by the Debtor."), - ARJR("AlreadyRejectedRTP", "Request-to-pay has already been rejected."), - ATNS("AttachementsNotSupported", "Attachments to the request-to-pay are not supported."), - BDAY("NotBusinessDay", "Settlement Cycle Day and Calendar day should be the same."), - BE01("InconsistenWithEndCustomer", "Identification of end customer is not consistent with associated account number. (formerly CreditorConsistency)."), - BE04("MissingCreditorAddress", "Specification of creditor's address, which is required for payment, is missing/not correct (formerly IncorrectCreditorAddress)."), - BE05("UnrecognisedInitiatingParty", "Party who initiated the message is not recognised by the end customer"), - BE06("UnknownEndCustomer", "End customer specified is not known at associated Sort/National Bank Code or does no longer exist in the books"), - BE07("MissingDebtorAddress", "Specification of debtor's address, which is required for payment, is missing/not correct."), - BE08("MissingDebtorName", "Debtor name is missing"), - BE09("InvalidCountry", "Country code is missing or Invalid."), - BE10("InvalidDebtorCountry", "Debtor country code is missing or invalid"), - BE11("InvalidCreditorCountry", "Creditor country code is missing or invalid"), - BE12("InvalidCountryOfResidence", "Country code of residence is missing or Invalid."), - BE13("InvalidDebtorCountryOfResidence", "Country code of debtor's residence is missing or Invalid"), - BE14("InvalidCreditorCountryOfResidence", "Country code of creditor's residence is missing or Invalid"), - BE15("InvalidIdentificationCode", "Identification code missing or invalid."), - BE16("InvalidDebtorIdentificationCode", "Debtor or Ultimate Debtor identification code missing or invalid"), - BE17("InvalidCreditorIdentificationCode", "Creditor or Ultimate Creditor identification code missing or invalid"), - BE18("InvalidContactDetails", "Contact details missing or invalid"), - BE19("InvalidChargeBearerCode", "Charge bearer code for transaction type is invalid"), - BE20("InvalidNameLength", "Name length exceeds local rules for payment type."), - BE21("MissingName", "Name missing or invalid. Generic usage if cannot specifically identify debtor or creditor."), - BE22("MissingCreditorName", "Creditor name is missing"), - BE23("AccountProxyInvalid", "Phone number or email address, or any other proxy, used as the account proxy is unknown or invalid."), - CERI("CheckERI", "Credit transfer is not tagged as an Extended Remittance Information (ERI) transaction but contains ERI."), - CH03("RequestedExecutionDateOrRequestedCollectionDateTooFarInFuture", "Value in Requested Execution Date or Requested Collection Date is too far in the future"), - CH04("RequestedExecutionDateOrRequestedCollectionDateTooFarInPast", "Value in Requested Execution Date or Requested Collection Date is too far in the past"), - CH07("ElementIsNotToBeUsedAtB-andC-Level", "Element is not to be used at B- and C-Level"), - CH09("MandateChangesNotAllowed", "Mandate changes are not allowed"), - CH10("InformationOnMandateChangesMissing", "Information on mandate changes are missing"), - CH11("CreditorIdentifierIncorrect", "Value in Creditor Identifier is incorrect"), - CH12("CreditorIdentifierNotUnambiguouslyAtTransaction-Level", "Creditor Identifier is ambiguous at Transaction Level"), - CH13("OriginalDebtorAccountIsNotToBeUsed", "Original Debtor Account is not to be used"), - CH14("OriginalDebtorAgentIsNotToBeUsed", "Original Debtor Agent is not to be used"), - CH15("ElementContentIncludesMoreThan140Characters", "Content Remittance Information/Structured includes more than 140 characters"), - CH16("ElementContentFormallyIncorrect", "Content is incorrect"), - CH17("ElementNotAdmitted", "Element is not allowed"), - CH19("ValuesWillBeSetToNextTARGETday", "Values in Interbank Settlement Date or Requested Collection Date will be set to the next TARGET day"), - CH20("DecimalPointsNotCompatibleWithCurrency", "Number of decimal points not compatible with the currency"), - CH21("RequiredCompulsoryElementMissing", "Mandatory element is missing"), - CH22("COREandB2BwithinOnemessage", "SDD CORE and B2B not permitted within one message"), - CHQC("ChequeSettledOnCreditorAccount", "Cheque has been presented in cheque clearing and settled on the creditor’s account."), - CN01("AuthorisationCancelled", "Authorisation is cancelled."), - CNOR("CreditorBankIsNotRegistered", "Creditor bank is not registered under this BIC in the CSM"), - CURR("IncorrectCurrency", "Currency of the payment is incorrect"), - CUST("RequestedByCustomer", "Cancellation requested by the Debtor"), - DC02("SettlementNotReceived", "Rejection of a payment due to covering FI settlement not being received."), - DNOR("DebtorBankIsNotRegistered", "Debtor bank is not registered under this BIC in the CSM"), - DS01("ElectronicSignaturesCorrect", "The electronic signature(s) is/are correct"), - DS02("OrderCancelled", "An authorized user has cancelled the order"), - DS03("OrderNotCancelled", "The user’s attempt to cancel the order was not successful"), - DS04("OrderRejected", "The order was rejected by the bank side (for reasons concerning content)"), - DS05("OrderForwardedForPostprocessing", "The order was correct and could be forwarded for postprocessing"), - DS06("TransferOrder", "The order was transferred to VEU"), - DS07("ProcessingOK", "All actions concerning the order could be done by the EBICS bank server"), - DS08("DecompressionError", "The decompression of the file was not successful"), - DS09("DecryptionError", "The decryption of the file was not successful"), - DS0A("DataSignRequested", "Data signature is required."), - DS0B("UnknownDataSignFormat", "Data signature for the format is not available or invalid."), - DS0C("SignerCertificateRevoked", "The signer certificate is revoked."), - DS0D("SignerCertificateNotValid", "The signer certificate is not valid (revoked or not active)."), - DS0E("IncorrectSignerCertificate", "The signer certificate is not present."), - DS0F("SignerCertificationAuthoritySignerNotValid", "The authority of the signer certification sending the certificate is unknown."), - DS0G("NotAllowedPayment", "Signer is not allowed to sign this operation type."), - DS0H("NotAllowedAccount", "Signer is not allowed to sign for this account."), - DS0K("NotAllowedNumberOfTransaction", "The number of transaction is over the number allowed for this signer."), - DS10("Signer1CertificateRevoked", "The certificate is revoked for the first signer."), - DS11("Signer1CertificateNotValid", "The certificate is not valid (revoked or not active) for the first signer."), - DS12("IncorrectSigner1Certificate", "The certificate is not present for the first signer."), - DS13("SignerCertificationAuthoritySigner1NotValid", "The authority of signer certification sending the certificate is unknown for the first signer."), - DS14("UserDoesNotExist", "The user is unknown on the server"), - DS15("IdenticalSignatureFound", "The same signature has already been sent to the bank"), - DS16("PublicKeyVersionIncorrect", "The public key version is not correct. This code is returned when a customer sends signature files to the financial institution after conversion from an older program version (old ES format) to a new program version (new ES format) without having carried out re-initialisation with regard to a public key change."), - DS17("DifferentOrderDataInSignatures", "Order data and signatures don’t match"), - DS18("RepeatOrder", "File cannot be tested, the complete order has to be repeated. This code is returned in the event of a malfunction during the signature check, e.g. not enough storage space."), - DS19("ElectronicSignatureRightsInsufficient", "The user’s rights (concerning his signature) are insufficient to execute the order"), - DS20("Signer2CertificateRevoked", "The certificate is revoked for the second signer."), - DS21("Signer2CertificateNotValid", "The certificate is not valid (revoked or not active) for the second signer."), - DS22("IncorrectSigner2Certificate", "The certificate is not present for the second signer."), - DS23("SignerCertificationAuthoritySigner2NotValid", "The authority of signer certification sending the certificate is unknown for the second signer."), - DS24("WaitingTimeExpired", "Waiting time expired due to incomplete order"), - DS25("OrderFileDeleted", "The order file was deleted by the bank server"), - DS26("UserSignedMultipleTimes", "The same user has signed multiple times"), - DS27("UserNotYetActivated", "The user is not yet activated (technically)"), - DS28("ReturnForTechnicalReason", "Message routed to the wrong environment."), - DT01("InvalidDate", "Invalid date (eg, wrong or missing settlement date)"), - DT02("InvalidCreationDate", "Invalid creation date and time in Group Header (eg, historic date)"), - DT03("InvalidNonProcessingDate", "Invalid non bank processing date (eg, weekend or local public holiday)"), - DT04("FutureDateNotSupported", "Future date not supported"), - DT05("InvalidCutOffDate", "Associated message, payment information block or transaction was received after agreed processing cut-off date, i.e., date in the past."), - DT06("ExecutionDateChanged", "Execution Date has been modified in order for transaction to be processed"), - DU01("DuplicateMessageID", "Message Identification is not unique."), - DU02("DuplicatePaymentInformationID", "Payment Information Block is not unique."), - DU03("DuplicateTransaction", "Transaction is not unique."), - DU04("DuplicateEndToEndID", "End To End ID is not unique."), - DU05("DuplicateInstructionID", "Instruction ID is not unique."), - DUPL("DuplicatePayment", "Payment is a duplicate of another payment"), - ED01("CorrespondentBankNotPossible", "Correspondent bank not possible."), - ED03("BalanceInfoRequest", "Balance of payments complementary info is requested"), - ED05("SettlementFailed", "Settlement of the transaction has failed."), - ED06("SettlementSystemNotAvailable", "Interbank settlement system not available."), - EDTL("ExpiryDateTooLong", "Expiry date time of the request-to-pay is too far in the future."), - EDTR("ExpiryDateTimeReached", "Expiry date time of the request-to-pay is already reached."), - ERIN("ERIOptionNotSupported", "Extended Remittance Information (ERI) option is not supported."), - FF01("InvalidFileFormat", "File Format incomplete or invalid"), - FF02("SyntaxError", "Syntax error reason is provided as narrative information in the additional reason information."), - FF03("InvalidPaymentTypeInformation", "Payment Type Information is missing or invalid."), - FF04("InvalidServiceLevelCode", "Service Level code is missing or invalid"), - FF05("InvalidLocalInstrumentCode", "Local Instrument code is missing or invalid"), - FF06("InvalidCategoryPurposeCode", "Category Purpose code is missing or invalid"), - FF07("InvalidPurpose", "Purpose is missing or invalid"), - FF08("InvalidEndToEndId", "End to End Id missing or invalid"), - FF09("InvalidChequeNumber", "Cheque number missing or invalid"), - FF10("BankSystemProcessingError", "File or transaction cannot be processed due to technical issues at the bank side"), - FF11("ClearingRequestAborted", "Clearing request rejected due it being subject to an abort operation."), - FF12("OriginalTransactionNotEligibleForRequestedReturn", "Original payment is not eligible to be returned given its current status."), - FF13("RequestForCancellationNotFound", "No record of request for cancellation found."), - FOCR("FollowingCancellationRequest", "Return following a cancellation request."), - FR01("Fraud", "Returned as a result of fraud."), - FRAD("FraudulentOrigin", "Cancellation requested following a transaction that was originated fraudulently. The use of the FraudulentOrigin code should be governed by jurisdictions."), - G000("PaymentTransferredAndTracked", "In an FI To FI Customer Credit Transfer: The Status Originator transferred the payment to the next Agent or to a Market Infrastructure. The payment transfer is tracked. No further updates will follow from the Status Originator."), - G001("PaymentTransferredAndNotTracked", "In an FI To FI Customer Credit Transfer: The Status Originator transferred the payment to the next Agent or to a Market Infrastructure. The payment transfer is not tracked. No further updates will follow from the Status Originator."), - G002("CreditDebitNotConfirmed", "In a FIToFI Customer Credit Transfer: Credit to the creditor’s account may not be confirmed same day. Update will follow from the Status Originator."), - G003("CreditPendingDocuments", "In a FIToFI Customer Credit Transfer: Credit to creditor’s account is pending receipt of required documents. The Status Originator has requested creditor to provide additional documentation. Update will follow from the Status Originator."), - G004("CreditPendingFunds", "In a FIToFI Customer Credit Transfer: Credit to the creditor’s account is pending, status Originator is waiting for funds provided via a cover. Update will follow from the Status Originator."), - G005("DeliveredWithServiceLevel", "Payment has been delivered to creditor agent with service level."), - G006("DeliveredWIthoutServiceLevel", "Payment has been delivered to creditor agent without service level."), - ID01("CorrespondingOriginalFileStillNotSent", "Signature file was sent to the bank but the corresponding original file has not been sent yet."), - IEDT("IncorrectExpiryDateTime", "Expiry date time of the request-to-pay is incorrect."), - INDT("InvalidDetails", "Details not valid for this field."), - IRNR("InitialRTPNeverReceived", "No initial request-to-pay has been received."), - ISWS("InvalidSettlementWindow", "Cannot schedule instruction for Night Window."), - MD01("NoMandate", "No Mandate"), - MD02("MissingMandatoryInformationInMandate", "Mandate related information data required by the scheme is missing."), - MD05("CollectionNotDue", "Creditor or creditor's agent should not have collected the direct debit"), - MD06("RefundRequestByEndCustomer", "Return of funds requested by end customer"), - MD07("EndCustomerDeceased", "End customer is deceased."), - MINF("MissingInformation", "Information missing for the field or cannot be empty."), - MS02("NotSpecifiedReasonCustomerGenerated", "Reason has not been specified by end customer"), - MS03("NotSpecifiedReasonAgentGenerated", "Reason has not been specified by agent."), - NARR("Narrative", "Reason is provided as narrative information in the additional reason information."), - NERI("NoERI", "Credit transfer is tagged as an Extended Remittance Information (ERI) transaction but does not contain ERI."), - NOAR("NonAgreedRTP", "No existing agreement for receiving request-to-pay messages."), - NOAS("NoAnswerFromCustomer", "No response from Beneficiary."), - NOCM("NotCompliantGeneric", "Customer account is not compliant with regulatory requirements, for example FICA (in South Africa) or any other regulatory requirements which render an account inactive for certain processing."), - NOFR("OutstandingFundingForSettlement", "Continuous Processing Line on Hold Instruction."), - NOPG("NoPaymentGuarantee", "Requested payment guarantee (by Creditor) related to a request-to-pay cannot be provided."), - NRCH("PayerOrPayerRTPSPNotReachable", "Recipient side of the request-to-pay (payer or its request-to-pay service provider) is not reachable."), - PINS("TypeOfPaymentInstrumentNotSupported", "Type of payment requested in the request-to-pay is not supported by the payer."), - RC01("BankIdentifierIncorrect", "Bank identifier code specified in the message has an incorrect format (formerly IncorrectFormatForRoutingCode)."), - RC02("InvalidBankIdentifier", "Bank identifier is invalid or missing."), - RC03("InvalidDebtorBankIdentifier", "Debtor bank identifier is invalid or missing"), - RC04("InvalidCreditorBankIdentifier", "Creditor bank identifier is invalid or missing"), - RC05("InvalidBICIdentifier", "BIC identifier is invalid or missing."), - RC06("InvalidDebtorBICIdentifier", "Debtor BIC identifier is invalid or missing"), - RC07("InvalidCreditorBICIdentifier", "Creditor BIC identifier is invalid or missing"), - RC08("InvalidClearingSystemMemberIdentifier", "ClearingSystemMemberidentifier is invalid or missing."), - RC09("InvalidDebtorClearingSystemMemberIdentifier", "Debtor ClearingSystemMember identifier is invalid or missing"), - RC10("InvalidCreditorClearingSystemMemberIdentifier", "Creditor ClearingSystemMember identifier is invalid or missing"), - RC11("InvalidIntermediaryAgent", "Intermediary Agent is invalid or missing"), - RC12("MissingCreditorSchemeId", "Creditor Scheme Id is invalid or missing"), - RC13("ParticipantNotAnActiveMemberofRTGS", "Originator not active any more."), - RC15("ParticipantNotActiveMemberSettlementType", "Settlement agreement required."), - RC16("ParticipantNotActiveMemberofSADCRTGS", "Participant blocked from SADC-RTGS."), - RCON("RMessageConflict", "Conflict with R-Message"), - RECI("ReceiverCustomerInformation", "Further information regarding the intended recipient."), - REPR("RTPReceivedCanBeProcessed", "Request-to-pay has been received and can be processed further."), - RF01("NotUniqueTransactionReference", "Transaction reference is not unique within the message."), - RR01("MissingDebtorAccountOrIdentification", "Specification of the debtor’s account or unique identification needed for reasons of regulatory requirements is insufficient or missing"), - RR02("MissingDebtorNameOrAddress", "Specification of the debtor’s name and/or address needed for regulatory requirements is insufficient or missing."), - RR03("MissingCreditorNameOrAddress", "Specification of the creditor’s name and/or address needed for regulatory requirements is insufficient or missing."), - RR04("RegulatoryReason", "Regulatory Reason"), - RR05("RegulatoryInformationInvalid", "Regulatory or Central Bank Reporting information missing, incomplete or invalid."), - RR06("TaxInformationInvalid", "Tax information missing, incomplete or invalid."), - RR07("RemittanceInformationInvalid", "Remittance information structure does not comply with rules for payment type."), - RR08("RemittanceInformationTruncated", "Remittance information truncated to comply with rules for payment type."), - RR09("InvalidStructuredCreditorReference", "Structured creditor reference invalid or missing."), - RR10("InvalidCharacterSet", "Character set supplied not valid for the country and payment type."), - RR11("InvalidDebtorAgentServiceID", "Invalid or missing identification of a bank proprietary service."), - RR12("InvalidPartyID", "Invalid or missing identification required within a particular country or payment type."), - RTNS("RTPNotSupportedForDebtor", "Debtor does not support request-to-pay transactions."), - RUTA("ReturnUponUnableToApply", "Return following investigation request and no remediation possible."), - S000("ValidRequestForCancellationAcknowledged", "Request for Cancellation is acknowledged following validation."), - S001("UETRFlaggedForCancellation", "Unique End-to-end Transaction Reference (UETR) relating to a payment has been identified as being associated with a Request for Cancellation."), - S002("NetworkStopOfUETR", "Unique End-to-end Transaction Reference (UETR) relating to a payment has been prevent from traveling across a messaging network."), - S003("RequestForCancellationForwarded", "Request for Cancellation has been forwarded to the payment processing/last payment processing agent."), - S004("RequestForCancellationDeliveryAcknowledgement", "Request for Cancellation has been acknowledged as delivered to payment processing/last payment processing agent."), - SBRN("SettlementBatchRemovalNotification", "Remove Concurrent Batch Processing Line on hold instruction."), - SL01("SpecificServiceOfferedByDebtorAgent", "Due to specific service offered by the Debtor Agent."), - SL02("SpecificServiceOfferedByCreditorAgent", "Due to specific service offered by the Creditor Agent."), - SL03("ServiceofClearingSystem", "Due to a specific service offered by the clearing system."), - SL11("CreditorNotOnWhitelistOfDebtor", "Whitelisting service offered by the Debtor Agent; Debtor has not included the Creditor on its “Whitelist” (yet). In the Whitelist the Debtor may list all allowed Creditors to debit Debtor bank account."), - SL12("CreditorOnBlacklistOfDebtor", "Blacklisting service offered by the Debtor Agent; Debtor included the Creditor on his “Blacklist”. In the Blacklist the Debtor may list all Creditors not allowed to debit Debtor bank account."), - SL13("MaximumNumberOfDirectDebitTransactionsExceeded", "Due to Maximum allowed Direct Debit Transactions per period service offered by the Debtor Agent."), - SL14("MaximumDirectDebitTransactionAmountExceeded", "Due to Maximum allowed Direct Debit Transaction amount service offered by the Debtor Agent."), - SPII("RTPServiceProviderIdentifierIncorrect", "Identifier of the request-to-pay service provider is incorrect."), - TA01("TransmissonAborted", "The transmission of the file was not successful – it had to be aborted (for technical reasons)"), - TD01("NoDataAvailable", "There is no data available (for download)"), - TD02("FileNonReadable", "The file cannot be read (e.g. unknown format)"), - TD03("IncorrectFileStructure", "The file format is incomplete or invalid"), - TK01("TokenInvalid", "Token is invalid."), - TK02("SenderTokenNotFound", "Token used for the sender does not exist."), - TK03("ReceiverTokenNotFound", "Token used for the receiver does not exist."), - TK09("TokenMissing", "Token required for request is missing."), - TKCM("TokenCounterpartyMismatch", "Token found with counterparty mismatch."), - TKSG("TokenSingleUse", "Single Use Token already used."), - TKSP("TokenSuspended", "Token found with suspended status."), - TKVE("TokenValueLimitExceeded", "Token found with value limit rule violation."), - TKXP("TokenExpired", "Token expired."), - TM01("InvalidCutOffTime", "Associated message, payment information block, or transaction was received after agreed processing cut-off time."), - TS01("TransmissionSuccessful", "The (technical) transmission of the file was successful."), - TS04("TransferToSignByHand", "The order was transferred to pass by accompanying note signed by hand"), - UCRD("UnknownCreditor", "Unknown Creditor."), - UPAY("UnduePayment", "Payment is not justified."), -} - -enum class ExternalPaymentGroupStatusCode(val isoCode: String, val description: String) { - ACCC("AcceptedSettlementCompletedCreditorAccount", "Settlement on the creditor's account has been completed."), - ACCP("AcceptedCustomerProfile", "Preceding check of technical validation was successful. Customer profile check was also successful."), - ACSC("AcceptedSettlementCompletedDebitorAccount", "Settlement on the debtor's account has been completed."), - ACSP("AcceptedSettlementInProcess", "All preceding checks such as technical validation and customer profile were successful and therefore the payment initiation has been accepted for execution."), - ACTC("AcceptedTechnicalValidation", "Authentication and syntactical and semantical validation are successful"), - ACWC("AcceptedWithChange", "Instruction is accepted but a change will be made, such as date or remittance not sent."), - PART("PartiallyAccepted", "A number of transactions have been accepted, whereas another number of transactions have not yet achieved"), - PDNG("Pending", "Payment initiation or individual transaction included in the payment initiation is pending. Further checks and status update will be performed."), - RCVD("Received", "Payment initiation has been received by the receiving agent"), - RJCT("Rejected", "Payment initiation or individual transaction included in the payment initiation has been rejected."), -} - -enum class ExternalPaymentTransactionStatusCode(val isoCode: String, val description: String) { - ACCC("AcceptedSettlementCompletedCreditorAccount", "Settlement on the creditor's account has been completed."), - ACCP("AcceptedCustomerProfile", "Preceding check of technical validation was successful. Customer profile check was also successful."), - ACFC("AcceptedFundsChecked", "Preceding check of technical validation and customer profile was successful and an automatic funds check was positive."), - ACIS("AcceptedandChequeIssued", "Payment instruction to issue a cheque has been accepted, and the cheque has been issued but not yet been deposited or cleared."), - ACPD("AcceptedClearingProcessed", "Status of transaction released from the Debtor Agent and accepted by the clearing."), - ACSC("AcceptedSettlementCompletedDebitorAccount", "Settlement completed."), - ACSP("AcceptedSettlementInProcess", "All preceding checks such as technical validation and customer profile were successful and therefore the payment instruction has been accepted for execution."), - ACTC("AcceptedTechnicalValidation", "Authentication and syntactical and semantical validation are successful"), - ACWC("AcceptedWithChange", "Instruction is accepted but a change will be made, such as date or remittance not sent."), - ACWP("AcceptedWithoutPosting", "Payment instruction included in the credit transfer is accepted without being posted to the creditor customer’s account."), - BLCK("Blocked", "Payment transaction previously reported with status 'ACWP' is blocked, for example, funds will neither be posted to the Creditor's account, nor be returned to the Debtor."), - CANC("Cancelled", "Payment initiation has been successfully cancelled after having received a request for cancellation."), - CPUC("CashPickedUpByCreditor", "Cash has been picked up by the Creditor."), - PATC("PartiallyAcceptedTechnicalCorrect", "Payment initiation needs multiple authentications, where some but not yet all have been performed. Syntactical and semantical validations are successful."), - PDNG("Pending", "Payment instruction is pending. Further checks and status update will be performed."), - PRES("Presented", "Request for Payment has been presented to the Debtor."), - RCVD("Received", "Payment instruction has been received."), - RJCT("Rejected", "Payment instruction has been rejected."), -} - -enum class ExternalReturnReasonCode(val isoCode: String, val description: String) { - AC01("IncorrectAccountNumber", "Format of the account number specified is not correct"), - AC02("InvalidDebtorAccountNumber", "Debtor account number invalid or missing."), - AC03("InvalidCreditorAccountNumber", "Wrong IBAN in SCT"), - AC04("ClosedAccountNumber", "Account number specified has been closed on the bank of account's books"), - AC06("BlockedAccount", "Account specified is blocked, prohibiting posting of transactions against it."), - AC07("ClosedCreditorAccountNumber", "Creditor account number closed."), - AC13("InvalidDebtorAccountType", "Debtor account type is missing or invalid"), - AC14("InvalidAgent", "An agent in the payment chain is invalid."), - AC15("AccountDetailsChanged", "Account details have changed."), - AC16("AccountInSequestration", "Account is in sequestration."), - AC17("AccountInLiquidation", "Account is in liquidation."), - AG01("TransactionForbidden", "Transaction forbidden on this type of account (formerly NoAgreement)"), - AG02("InvalidBankOperationCode", "Bank Operation code specified in the message is not valid for receiver"), - AG07("UnsuccesfulDirectDebit", "Debtor account cannot be debited for a generic reason."), - AGNT("IncorrectAgent", "Agent in the payment workflow is incorrect."), - AM01("ZeroAmount", "Specified message amount is equal to zero"), - AM02("NotAllowedAmount", "Specific transaction/message amount is greater than allowed maximum"), - AM03("NotAllowedCurrency", "Specified message amount is an non processable currency outside of existing agreement"), - AM04("InsufficientFunds", "Amount of funds available to cover specified message amount is insufficient."), - AM05("Duplication", "Duplication"), - AM06("TooLowAmount", "Specified transaction amount is less than agreed minimum."), - AM07("BlockedAmount", "Amount specified in message has been blocked by regulatory authorities."), - AM09("WrongAmount", "Amount received is not the amount agreed or expected"), - AM10("InvalidControlSum", "Sum of instructed amounts does not equal the control sum."), - ARDT("AlreadyReturnedTransaction", "Already returned original SCT"), - BE01("InconsistenWithEndCustomer", "Identification of end customer is not consistent with associated account number, organisation ID or private ID."), - BE04("MissingCreditorAddress", "Specification of creditor's address, which is required for payment, is missing/not correct (formerly IncorrectCreditorAddress)."), - BE05("UnrecognisedInitiatingParty", "Party who initiated the message is not recognised by the end customer"), - BE06("UnknownEndCustomer", "End customer specified is not known at associated Sort/National Bank Code or does no longer exist in the books"), - BE07("MissingDebtorAddress", "Specification of debtor's address, which is required for payment, is missing/not correct."), - BE08("BankError", "Returned as a result of a bank error."), - BE10("InvalidDebtorCountry", "Debtor country code is missing or invalid."), - BE11("InvalidCreditorCountry", "Creditor country code is missing or invalid."), - BE16("InvalidDebtorIdentificationCode", "Debtor or Ultimate Debtor identification code missing or invalid."), - BE17("InvalidCreditorIdentificationCode", "Creditor or Ultimate Creditor identification code missing or invalid."), - CN01("AuthorisationCancelled", "Authorisation is cancelled."), - CNOR("CreditorBankIsNotRegistered", "Creditor bank is not registered under this BIC in the CSM"), - CNPC("CashNotPickedUp", "Cash not picked up by Creditor or cash could not be delivered to Creditor"), - CURR("IncorrectCurrency", "Currency of the payment is incorrect"), - CUST("RequestedByCustomer", "Cancellation requested by the Debtor"), - DC04("NoCustomerCreditTransferReceived", "Return of Covering Settlement due to the underlying Credit Transfer details not being received."), - DNOR("DebtorBankIsNotRegistered", "Debtor bank is not registered under this BIC in the CSM"), - DS28("ReturnForTechnicalReason", "Return following technical problems resulting in erroneous transaction."), - DT01("InvalidDate", "Invalid date (eg, wrong settlement date)"), - DT02("ChequeExpired", "Cheque has been issued but not deposited and is considered expired."), - DT04("FutureDateNotSupported", "Future date not supported."), - DUPL("DuplicatePayment", "Payment is a duplicate of another payment."), - ED01("CorrespondentBankNotPossible", "Correspondent bank not possible."), - ED03("BalanceInfoRequest", "Balance of payments complementary info is requested"), - ED05("SettlementFailed", "Settlement of the transaction has failed."), - EMVL("EMVLiabilityShift", "The card payment is fraudulent and was not processed with EMV technology for an EMV card."), - ERIN("ERIOptionNotSupported", "The Extended Remittance Information (ERI) option is not supported."), - FF03("InvalidPaymentTypeInformation", "Payment Type Information is missing or invalid."), - FF04("InvalidServiceLevelCode", "Service Level code is missing or invalid."), - FF05("InvalidLocalInstrumentCode", "Local Instrument code is missing or invalid"), - FF06("InvalidCategoryPurposeCode", "Category Purpose code is missing or invalid."), - FF07("InvalidPurpose", "Purpose is missing or invalid."), - FOCR("FollowingCancellationRequest", "Return following a cancellation request"), - FR01("Fraud", "Returned as a result of fraud."), - FRTR("FinalResponseMandateCancelled", "Final response/tracking is recalled as mandate is cancelled."), - G004("CreditPendingFunds", "In a FIToFI Customer Credit Transfer: Credit to the creditor’s account is pending, status Originator is waiting for funds provided via a cover. Update will follow from the Status Originator."), - MD01("NoMandate", "No Mandate"), - MD02("MissingMandatoryInformationInMandate", "Mandate related information data required by the scheme is missing."), - MD05("CollectionNotDue", "Creditor or creditor's agent should not have collected the direct debit."), - MD06("RefundRequestByEndCustomer", "Return of funds requested by end customer"), - MD07("EndCustomerDeceased", "End customer is deceased."), - MS02("NotSpecifiedReasonCustomerGenerated", "Reason has not been specified by end customer"), - MS03("NotSpecifiedReasonAgentGenerated", "Reason has not been specified by agent."), - NARR("Narrative", "Reason is provided as narrative information in the additional reason information."), - NOAS("NoAnswerFromCustomer", "No response from Beneficiary"), - NOCM("NotCompliant", "Customer account is not compliant with regulatory requirements, for example FICA (in South Africa) or any other regulatory requirements which render an account inactive for certain processing."), - NOOR("NoOriginalTransactionReceived", "Original SCT never received"), - PINL("PINLiabilityShift", "The card payment is fraudulent (lost and stolen fraud) and was processed as EMV transaction without PIN verification."), - RC01("BankIdentifierIncorrect", "Bank Identifier code specified in the message has an incorrect format (formerly IncorrectFormatForRoutingCode)."), - RC03("InvalidDebtorBankIdentifier", "Debtor bank identifier is invalid or missing."), - RC04("InvalidCreditorBankIdentifier", "Creditor bank identifier is invalid or missing."), - RC07("InvalidCreditorBICIdentifier", "Incorrrect BIC of the beneficiary Bank in the SCTR"), - RC08("InvalidClearingSystemMemberIdentifier", "ClearingSystemMemberidentifier is invalid or missing."), - RC11("InvalidIntermediaryAgent", "Intermediary Agent is invalid or missing."), - RF01("NotUniqueTransactionReference", "Transaction reference is not unique within the message."), - RR01("MissingDebtorAccountOrIdentification", "Specification of the debtor’s account or unique identification needed for reasons of regulatory requirements is insufficient or missing"), - RR02("MissingDebtorNameOrAddress", "Specification of the debtor’s name and/or address needed for regulatory requirements is insufficient or missing."), - RR03("MissingCreditorNameOrAddress", "Specification of the creditor’s name and/or address needed for regulatory requirements is insufficient or missing."), - RR04("RegulatoryReason", "Regulatory Reason"), - RR05("RegulatoryInformationInvalid", "Regulatory or Central Bank Reporting information missing, incomplete or invalid."), - RR06("TaxInformationInvalid", "Tax information missing, incomplete or invalid."), - RR07("RemittanceInformationInvalid", "Remittance information structure does not comply with rules for payment type."), - RR08("RemittanceInformationTruncated", "Remittance information truncated to comply with rules for payment type."), - RR09("InvalidStructuredCreditorReference", "Structured creditor reference invalid or missing."), - RR11("InvalidDebtorAgentServiceIdentification", "Invalid or missing identification of a bank proprietary service."), - RR12("InvalidPartyIdentification", "Invalid or missing identification required within a particular country or payment type."), - RUTA("ReturnUponUnableToApply", "Return following investigation request and no remediation possible."), - SL01("SpecificServiceOfferedByDebtorAgent", "Due to specific service offered by the Debtor Agent"), - SL02("SpecificServiceOfferedByCreditorAgent", "Due to specific service offered by the Creditor Agent"), - SL11("CreditorNotOnWhitelistOfDebtor", "Whitelisting service offered by the Debtor Agent; Debtor has not included the Creditor on its “Whitelist” (yet). In the Whitelist the Debtor may list all allowed Creditors to debit Debtor bank account."), - SL12("CreditorOnBlacklistOfDebtor", "Blacklisting service offered by the Debtor Agent; Debtor included the Creditor on his “Blacklist”. In the Blacklist the Debtor may list all Creditors not allowed to debit Debtor bank account."), - SL13("MaximumNumberOfDirectDebitTransactionsExceeded", "Due to Maximum allowed Direct Debit Transactions per period service offered by the Debtor Agent."), - SL14("MaximumDirectDebitTransactionAmountExceeded", "Due to Maximum allowed Direct Debit Transaction amount service offered by the Debtor Agent."), - SP01("PaymentStopped", "Payment is stopped by account holder."), - SP02("PreviouslyStopped", "Previously stopped by means of a stop payment advise."), - SVNR("ServiceNotRendered", "The card payment is returned since a cash amount rendered was not correct or goods or a service was not rendered to the customer, e.g. in an e-commerce situation."), - TM01("CutOffTime", "Associated message was received after agreed processing cut-off time."), - TRAC("RemovedFromTracking", "Return following direct debit being removed from tracking process."), - UPAY("UnduePayment", "Payment is not justified."), -} - diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt @@ -26,7 +26,7 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.util.pipeline.* import tech.libeufin.common.* -import tech.libeufin.nexus.IncomingPayment +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.NexusConfig import tech.libeufin.nexus.checkCurrency import tech.libeufin.nexus.db.Database diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt @@ -33,6 +33,7 @@ import tech.libeufin.nexus.* import tech.libeufin.nexus.db.* import tech.libeufin.nexus.db.PaymentDAO.IncomingRegistrationResult import tech.libeufin.nexus.ebics.* +import tech.libeufin.nexus.iso20022.* import java.io.IOException import java.io.InputStream import java.time.Duration diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt @@ -27,6 +27,7 @@ import io.ktor.client.* import tech.libeufin.common.* import tech.libeufin.common.crypto.CryptoUtil import tech.libeufin.nexus.* +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.ebics.* import tech.libeufin.nexus.ebics.EbicsKeyMng.Order.* import java.nio.file.FileAlreadyExistsException diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt @@ -27,6 +27,7 @@ import tech.libeufin.nexus.* import tech.libeufin.nexus.db.InitiatedPayment import tech.libeufin.nexus.db.PaymentBatch import tech.libeufin.nexus.ebics.* +import tech.libeufin.nexus.iso20022.* import java.time.Duration import java.time.Instant import kotlin.time.toKotlinDuration diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/InitiatePayment.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/InitiatePayment.kt @@ -29,6 +29,7 @@ import tech.libeufin.nexus.db.InitiatedPayment import tech.libeufin.nexus.logger import tech.libeufin.nexus.nexusConfig import tech.libeufin.nexus.withDb +import tech.libeufin.nexus.iso20022.* import java.time.Instant class InitiatePayment: CliktCommand("Initiate an outgoing payment") { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt @@ -32,6 +32,7 @@ import com.github.ajalt.clikt.parameters.types.enum import kotlinx.coroutines.delay import tech.libeufin.common.* import tech.libeufin.nexus.* +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.ebics.* import java.time.Instant diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt @@ -21,8 +21,8 @@ package tech.libeufin.nexus.db import tech.libeufin.common.* import tech.libeufin.common.db.* -import tech.libeufin.nexus.IncomingPayment -import tech.libeufin.nexus.OutgoingPayment +import tech.libeufin.nexus.iso20022.IncomingPayment +import tech.libeufin.nexus.iso20022.OutgoingPayment import java.time.Instant import java.sql.Types diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/BankTransactionCode.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/BankTransactionCode.kt @@ -0,0 +1,386 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +// THIS FILE IS GENERATED, DO NOT EDIT + +package tech.libeufin.nexus.iso20022 + +enum class ExternalBankTransactionDomainCode(val description: String) { + ACMT("Account Management"), + CAMT("Cash Management"), + CMDT("Commodities"), + DERV("Derivatives"), + FORX("Foreign Exchange"), + LDAS("Loans, Deposits & Syndications"), + PMET("Precious Metal"), + PMNT("Payments"), + SECU("Securities"), + TRAD("Trade Services"), + XTND("Extended Domain"), +} + +enum class ExternalBankTransactionFamilyCode(val description: String) { + ACCB("Account Balancing"), + ACOP("Additional Miscellaneous Credit Operations"), + ADOP("Additional Miscellaneous Debit Operations"), + BLOC("Blocked Transactions"), + CAPL("Cash Pooling"), + CASH("Miscellaneous Securities Operations"), + CCRD("Customer Card Transactions"), + CLNC("Clean Collection"), + CNTR("Counter Transactions"), + COLC("Custody Collection"), + COLL("Collateral Management"), + CORP("Corporate Action"), + CSLN("Consumer Loans"), + CUST("Custody"), + DCCT("Documentary Credit"), + DLVR("Delivery"), + DOCC("Documentary Collection"), + DRFT("Drafts"), + FTDP("Fixed Term Deposits"), + FTLN("Fixed Term Loans"), + FTUR("Futures"), + FWRD("Forwards"), + GUAR("Guarantees"), + ICCN("Issued Cash Concentration Transactions"), + ICDT("Issued Credit Transfers"), + ICHQ("Issued Cheques"), + IDDT("Issued Direct Debits"), + IRCT("Issued Real-Time Credit Transfers"), + LACK("Lack"), + LBOX("Lockbox Transactions"), + LFUT("Listed Derivatives - Futures"), + LOCT("Stand-By Letter Of Credit"), + LOPT("Listed Derivatives - Options"), + MCOP("Miscellaneous Credit Operations"), + MCRD("Merchant Card Transactions"), + MDOP("Miscellaneous Debit Operations"), + MGLN("Mortgage Loans"), + NDFX("Non Deliverable"), + NSET("Non Settled"), + NTAV("Not Available"), + NTDP("Notice Deposits"), + NTLN("Notice Loans"), + OBND("OTC Derivatives - Bonds"), + OCRD("OTC Derivatives - Credit"), + OEQT("OTC Derivatives - Equity"), + OIRT("OTC Derivatives - Interest Rates"), + OPCL("Opening & Closing"), + OPTN("Options"), + OSED("OTC Derivatives - Structured Exotic Derivatives"), + OSWP("OTC Derivatives – Swaps"), + OTHB("CSD Blocked transactions"), + OTHR("Other"), + RCCN("Received Cash Concentration Transactions"), + RCDT("Received Credit Transfers"), + RCHQ("Received Cheques"), + RDDT("Received Direct Debits"), + RRCT("Received Real-Time Credit Transfers"), + SETT("Trade, Clearing and Settlement"), + SPOT("Spots"), + SWAP("Swaps"), + SYDN("Syndications"), +} + +enum class ExternalBankTransactionSubFamilyCode(val description: String) { + ACCC("Account Closing"), + ACCO("Account Opening"), + ACCT("Account Transfer"), + ACDT("ACH Credit"), + ACOR("ACH Corporate Trade"), + ADBT("ACH Debit"), + ADJT("Adjustments (Generic)"), + APAC("ACH Pre-Authorised"), + ARET("ACH Return"), + AREV("ACH Reversal"), + ARPD("ARP Debit"), + ASET("ACH Settlement"), + ATXN("ACH Transaction"), + AUTT("Automatic Transfer"), + BBDD("SEPA B2B Direct Debit"), + BCDP("Branch Deposit"), + BCHQ("Bank Cheque"), + BCKV("Back Value"), + BCWD("Branch Withdrawl"), + BFWD("Bond Forward"), + BIDS("Repurchase Offer/Issuer Bid/Reverse Rights."), + BKFE("Bank Fees"), + BONU("Bonus Issue/Capitalisation Issue"), + BOOK("Internal Book Transfer"), + BPUT("Put Redemption"), + BROK("Brokerage Fee"), + BSBC("Sell Buy Back"), + BSBO("Buy Sell Back"), + CAJT("Credit Adjustments (Generic)"), + CAPG("Capital Gains Distribution"), + CASH("Cash Letter"), + CCCH("Certified Customer Cheque"), + CCHQ("Cheque"), + CCIR("Cross Currency IRS"), + CCPC("CCP Cleared Initial Margin"), + CCPM("CCP Cleared Variation Margin"), + CCSM("CCP Cleared Segregated Initial Margin"), + CDIS("Controlled Disbursement"), + CDPT("Cash Deposit"), + CHAR("Charge/Fees"), + CHKD("Check Deposit"), + CHRG("Charges (Generic)"), + CLAI("Compensation/Claims"), + CLCQ("Circular Cheque"), + CMBO("Corporate Mark Broker Owned"), + CMCO("Corporate Mark Client Owned"), + COME("Commission Excluding Taxes (Generic)"), + COMI("Commission Including Taxes (Generic)"), + COMM("Commission (Generic)"), + COMT("Non Taxable Commissions (Generic)"), + CONV("Conversion"), + COVE("Cover Transaction"), + CPEN("Cash Penalties"), + CPRB("Corporate Rebate"), + CQRV("Cheque Reversal"), + CRCQ("Crossed Cheque"), + CRDS("Credit DefaultSwap"), + CROS("Cross Trade"), + CRPR("Cross Product"), + CRSP("Credit Support"), + CRTL("Credit Line"), + CSHA("Cash Letter Adjustment"), + CSLI("Cash In Lieu"), + CWDL("Cash Withdrawal"), + DAJT("Debit Adjustments (Generic)"), + DDFT("Discounted Draft"), + DDWN("Drawdown"), + DECR("Decrease in Value"), + DMCG("Draft Maturity Change"), + DMCT("Domestic Credit Transfer"), + DPST("Deposit"), + DRAW("Drawing"), + DRIP("Dividend Reinvestment"), + DSBR("Controlled Disbursement"), + DTCH("Dutch Auction"), + DVCA("Cash Dividend"), + DVOP("Dividend Option"), + ENCT("Nordic Payment Council Credit Transfer"), + EQBO("Equity Mark Broker Owned"), + EQCO("Equity Mark Client Owned"), + EQPT("Equity Option"), + EQUS("Equity Swap"), + ERTA("Exchange Rate Adjustment"), + ERWA("Lending Income"), + ERWI("Borrowing Fee"), + ESCT("SEPA Credit Transfer"), + ESDD("SEPA Core Direct Debit"), + EXOF("Exchange"), + EXPT("Exotic Option"), + EXRI("Call On Intermediate Securities"), + EXTD("Exchange Traded Derivatives"), + EXWA("Warrant Exercise/Warrant Conversion"), + FCDP("Foreign Currencies Deposit"), + FCTA("Factor Update"), + FCWD("Foreign Currencies Withdrawal"), + FEES("Fees (Generic)"), + FICT("Financial Institution Credit Transfer"), + FIDD("Financial Institution Direct Debit Payment"), + FIOA("Financial Institution Own Account Transfer"), + FIXI("Fixed Income"), + FLTA("Float Adjustment"), + FRZF("Freeze Of Funds"), + FUCO("Futures Commission"), + FUTU("Future Variation Margin"), + FWBC("Forwards Broker Owned Collateral"), + FWCC("Forwards Client Owned Collateral"), + FWSB("MFA Segregated Broker Cash Collateral"), + FWSC("MFA Segregated Client Cash Collateral"), + GEN1("Withdrawal/Distribution"), + GEN2("Deposit/Contribution"), + IADD("Invoice Accepted with Differed Due Date"), + INFD("Fixed Deposit Interest Amount"), + INSP("Inspeci/Share Exchange"), + INTR("Interest Payment"), + ISSU("Depositary Receipt Issue"), + LBCA("Credit Adjustment"), + LBDP("Deposit"), + LIQU("Liquidation Dividend / Liquidation Payment"), + MARG("Margin Payments"), + MBSB("Mortgage Back Segregated Broker Cash Collateral"), + MBSC("Mortgage Back Segregated Client Cash Collateral"), + MCAL("Full Call / Early Redemption"), + MGCC("Margin Client Owned Cash Collateral"), + MGSC("Initial Futures Margin Segregated Client Cash Collateral"), + MIXD("Mixed Deposit"), + MNFE("Management Fees"), + MRGR("Merger"), + MSCD("Miscellaneous Deposit"), + NETT("Netting"), + NPCC("Non Presented Circular Cheques"), + NSYN("Non Syndicated"), + NTAV("Not Available"), + NWID("New issue distribution"), + OCCC("Client owned OCC pledged collateral"), + ODFT("Overdraft"), + ODLT("Odd Lot Sale/Purchase"), + OODD("One-Off Direct Debit"), + OPBC("Option Broker Owned Collateral"), + OPCC("Option Client Owned Collateral"), + OPCQ("Open Cheque"), + OPSB("OTC Option Segregated Broker Cash Collateral"), + OPSC("OTC Option Segregated Client Cash Collateral"), + OPTN("FX Option"), + ORCQ("Order Cheque"), + OTCC("OTC CCP"), + OTCD("OTC Derivatives"), + OTCG("OTC"), + OTCN("OTC Non-CCP"), + OTHR("Other"), + OVCH("Overdraft Charge"), + OWNE("External Account Transfer"), + OWNI("Internal Account Transfer"), + PADD("Pre-Authorised Direct Debit"), + PAIR("Pair-Off"), + PCAL("Partial Redemption With Reduction Of Nominal Value"), + PLAC("Placement"), + PMDD("Direct Debit"), + PORT("Portfolio Move"), + POSC("Credit Card Payment"), + POSD("Point-of-Sale (POS) Payment - Debit Card"), + PPAY("Principal Payment"), + PRCT("Priority Credit Transfer"), + PRDD("Reversal Due To Payment Reversal"), + PRED("Partial Redemption Without Reduction Of Nominal Value"), + PRII("Interest Payment with Principles"), + PRIN("Interest Payment with Principles"), + PRIO("Priority Issue"), + PRUD("Principal Pay-Down/Pay-Up"), + PSTE("Posting Error"), + RCDD("Reversal Due To Payment Cancellation Request"), + RCOV("Reversal due to a Cover Transaction Return"), + REAA("Redemption Asset Allocation"), + REDM("Final Maturity"), + REPU("Repo"), + RESI("Futures Residual Amount"), + RHTS("Rights Issue/Subscription Rights/Rights Offer"), + RIMB("Reimbursement (Generic)"), + RNEW("Renewal"), + RPBC("Bi-lateral repo broker owned collateral"), + RPCC("Repo client owned collateral"), + RPCR("Reversal Due To Payment Cancellation Request"), + RPMT("Repayment"), + RPSB("Bi-lateral Repo Segregated Broker Cash Collateral"), + RPSC("Bi-lateral Repo Segregated Client Cash Collateral"), + RRTN("Reversal Due To Payment Return"), + RVPO("Reverse Repo"), + RWPL("Redemption Withdrawing Plan"), + SABG("Settlement Against Bank Guarantee"), + SALA("Payroll/Salary Payment"), + SBSC("Securities Buy Sell Sell Buy Back"), + SCIE("Single Currency IRS Exotic"), + SCIR("Single Currency IRS"), + SCRP("Securities Cross Products"), + SDVA("Same Day Value Credit Transfer"), + SECB("Securities Borrowing"), + SECL("Securities Lending"), + SHBC("Broker owned collateral Short Sale"), + SHCC("Client owned collateral Short Sale"), + SHPR("Equity Premium Reserve"), + SHSL("Short Sell"), + SLBC("Lending Broker Owned Cash Collateral"), + SLCC("Lending Client Owned Cash Collateral"), + SLEB("Securities Lending And Borrowing"), + SLOA("SecuredLoan"), + SOSE("Settlement Of Sight Export Document"), + SOSI("Settlement Of Sight Import Document"), + SSPL("Subscription Savings Plan"), + STAC("Settlement After Collection"), + STAM("Settlement At Maturity"), + STDO("Standing Order"), + STLM("Settlement"), + STLR("Settlement Under Reserve"), + STOD("Bill of Exchange Settlement on Demand"), + SUAA("Subscription Asset Allocation"), + SUBS("Subscription"), + SWAP("Swap Payment"), + SWBC("Swap Broker Owned Collateral"), + SWCC("Client Owned Collateral"), + SWEP("Sweep"), + SWFP("Final Payment"), + SWIC("Switch"), + SWPP("Partial Payment"), + SWPT("Swaption"), + SWRS("Reset Payment"), + SWSB("ISDA/CSA Segregated Broker Cash Collateral"), + SWSC("ISDA/CSA Segregated Client Cash Collateral"), + SWUF("Upfront Payment"), + SYND("Syndicated"), + TAXE("Taxes (Generic)"), + TBAC("TBA Closing"), + TBAS("To Be Announced"), + TBBC("TBA Broker owned cash collateral"), + TBCC("TBA Client owned cash collateral"), + TCDP("Travellers Cheques Deposit"), + TCWD("Travellers Cheques Withdrawal"), + TEND("Tender"), + TOPG("Topping"), + TOUT("Transfer Out"), + TRAD("Trade"), + TRCP("Treasury Cross Product"), + TREC("Tax Reclaim"), + TRFE("Transaction Fees"), + TRIN("Transfer In"), + TRPO("Triparty Repo"), + TRVO("Triparty Reverse Repo"), + TTLS("Treasury Tax And Loan Service"), + TURN("Turnaround"), + UDFT("Dishonoured/Unpaid Draft"), + UNCO("Underwriting Commission"), + UPCQ("Unpaid Cheque"), + UPCT("Unpaid Card Transaction"), + UPDD("Reversal Due To Return/Unpaid Direct Debit"), + URCQ("Cheque Under Reserve"), + URDD("Direct Debit Under Reserve"), + VALD("Value Date"), + VCOM("Credit Transfer With Agreed Commercial Information"), + WITH("Withholding Tax"), + XBCP("Cross-Border Credit Card Payment"), + XBCQ("Foreign Cheque"), + XBCT("Cross-Border Credit Transfer"), + XBCW("Cross-Border Cash Withdrawal"), + XBRD("Cross-Border"), + XBSA("Cross-Border Payroll/Salary Payment"), + XBST("Cross-Border Standing Order"), + XCHC("Exchange Traded CCP"), + XCHG("Exchange Traded"), + XCHN("Exchange Traded Non-CCP"), + XICT("Cross-Border Intra Company Transfer"), + XPCQ("Unpaid Foreign Cheque"), + XRCQ("Foreign Cheque Under Reserve"), + XRTN("Cross Border Reversal Due to Payment Return"), + YTDA("YTD Adjustment"), + ZABA("Zero Balancing"), + ACON("ACH Concentration"), + BACT("Branch Account Transfer"), + COAT("Corporate Own Account Transfer"), + ICCT("Intra Company Transfer"), + LBDB("Debit"), + POSP("Point-of-Sale (POS) Payment"), + SMCD("Smart-Card Payment"), + SMRT("Smart-Card Payment"), + XBDD("Cross-Border Direct Debit"), +} + diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Constants.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Constants.kt @@ -0,0 +1,37 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +package tech.libeufin.nexus.iso20022 + +enum class HacAction(val description: String) { + FILE_UPLOAD("File submitted to the bank"), + FILE_DOWNLOAD("File downloaded from the bank"), + ES_UPLOAD("Electronic signature submitted to the bank"), + ES_DOWNLOAD("Electronic signature downloaded from the bank"), + ES_VERIFICATION("Signature verification"), + VEU_FORWARDING("Forwarding to EDS"), + VEU_VERIFICATION("EDS signature verification"), + VEU_VERIFICATION_END("VEU_VERIFICATION_END"), + VEU_CANCEL_ORDER("Cancellation of EDS order"), + ADDITIONAL("Additional information"), + ORDER_HAC_FINAL_POS("HAC end of order (positive)"), + ORDER_HAC_FINAL_NEG("HAC end of order (negative)"), + // Not in the spec but Credit Suisse test suite use it + ORDER_HAC_FINAL("HAC end of order") +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/ExternalCodeSets.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/ExternalCodeSets.kt @@ -0,0 +1,459 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +// THIS FILE IS GENERATED, DO NOT EDIT + +package tech.libeufin.nexus.iso20022 + +enum class ExternalStatusReasonCode(val isoCode: String, val description: String) { + AB01("AbortedClearingTimeout", "Clearing process aborted due to timeout."), + AB02("AbortedClearingFatalError", "Clearing process aborted due to a fatal error."), + AB03("AbortedSettlementTimeout", "Settlement aborted due to timeout."), + AB04("AbortedSettlementFatalError", "Settlement process aborted due to a fatal error."), + AB05("TimeoutCreditorAgent", "Transaction stopped due to timeout at the Creditor Agent."), + AB06("TimeoutInstructedAgent", "Transaction stopped due to timeout at the Instructed Agent."), + AB07("OfflineAgent", "Agent of message is not online."), + AB08("OfflineCreditorAgent", "Creditor Agent is not online."), + AB09("ErrorCreditorAgent", "Transaction stopped due to error at the Creditor Agent."), + AB10("ErrorInstructedAgent", "Transaction stopped due to error at the Instructed Agent."), + AB11("TimeoutDebtorAgent", "Transaction stopped due to timeout at the Debtor Agent."), + AB12("InvalidConcurrentBatch", "Duplicate Concurrent Batch Sequence number– for Settlement Instructions."), + AB13("InvalidRoutingCodeUtilised", "Wrong Message Routing Type for Return-of-Funds."), + AB15("InvalidAccountNumberForSettlementType", "Instruction may not be placed on the Continuous Processing Line settlement processor."), + AB21("InvalidSettlementAgreementNumberSpecified", "Agreement number not valid (beneficiary)."), + AB26("InvalidBatchSettlementInstruction", "Settlement Instruction does not exist."), + AC01("IncorrectAccountNumber", "Account number is invalid or missing."), + AC02("InvalidDebtorAccountNumber", "Debtor account number invalid or missing"), + AC03("InvalidCreditorAccountNumber", "Creditor account number invalid or missing"), + AC04("ClosedAccountNumber", "Account number specified has been closed on the bank of account's books."), + AC05("ClosedDebtorAccountNumber", "Debtor account number closed"), + AC06("BlockedAccount", "Account specified is blocked, prohibiting posting of transactions against it."), + AC07("ClosedCreditorAccountNumber", "Creditor account number closed"), + AC08("InvalidBranchCode", "Branch code is invalid or missing"), + AC09("InvalidAccountCurrency", "Account currency is invalid or missing"), + AC10("InvalidDebtorAccountCurrency", "Debtor account currency is invalid or missing"), + AC11("InvalidCreditorAccountCurrency", "Creditor account currency is invalid or missing"), + AC12("InvalidAccountType", "Account type missing or invalid."), + AC13("InvalidDebtorAccountType", "Debtor account type missing or invalid"), + AC14("InvalidCreditorAccountType", "Creditor account type missing or invalid"), + AC15("AccountDetailsChanged", "The account details for the counterparty have changed."), + AC16("CardNumberInvalid", "Credit or debit card number is invalid."), + AEXR("AlreadyExpiredRTP", "Request-to-pay Expiry Date and Time has already passed."), + AG01("TransactionForbidden", "Transaction forbidden on this type of account (formerly NoAgreement)"), + AG02("InvalidBankOperationCode", "Bank Operation code specified in the message is not valid for receiver"), + AG03("TransactionNotSupported", "Transaction type not supported/authorized on this account"), + AG04("InvalidAgentCountry", "Agent country code is missing or invalid."), + AG05("InvalidDebtorAgentCountry", "Debtor agent country code is missing or invalid"), + AG06("InvalidCreditorAgentCountry", "Creditor agent country code is missing or invalid"), + AG07("UnsuccesfulDirectDebit", "Debtor account cannot be debited for a generic reason."), + AG08("InvalidAccessRights", "Transaction failed due to invalid or missing user or access right"), + AG09("PaymentNotReceived", "Original payment never received."), + AG10("AgentSuspended", "Agent of message is suspended from the Real Time Payment system."), + AG11("CreditorAgentSuspended", "Creditor Agent of message is suspended from the Real Time Payment system."), + AG12("NotAllowedBookTransfer", "Payment orders made by transferring funds from one account to another at the same financial institution (bank or payment institution) are not allowed."), + AG13("ForbiddenReturnPayment", "Returned payments derived from previously returned transactions are not allowed."), + AGNT("IncorrectAgent", "Agent in the payment workflow is incorrect"), + ALAC("AlreadyAcceptedRTP", "Request-to-pay has already been accepted by the Debtor."), + AM01("ZeroAmount", "Specified message amount is equal to zero"), + AM02("NotAllowedAmount", "Specific transaction/message amount is greater than allowed maximum"), + AM03("NotAllowedCurrency", "Specified message amount is an non processable currency outside of existing agreement"), + AM04("InsufficientFunds", "Amount of funds available to cover specified message amount is insufficient."), + AM05("Duplication", "Duplication"), + AM06("TooLowAmount", "Specified transaction amount is less than agreed minimum."), + AM07("BlockedAmount", "Amount specified in message has been blocked by regulatory authorities."), + AM09("WrongAmount", "Amount received is not the amount agreed or expected"), + AM10("InvalidControlSum", "Sum of instructed amounts does not equal the control sum."), + AM11("InvalidTransactionCurrency", "Transaction currency is invalid or missing"), + AM12("InvalidAmount", "Amount is invalid or missing"), + AM13("AmountExceedsClearingSystemLimit", "Transaction amount exceeds limits set by clearing system"), + AM14("AmountExceedsAgreedLimit", "Transaction amount exceeds limits agreed between bank and client"), + AM15("AmountBelowClearingSystemMinimum", "Transaction amount below minimum set by clearing system"), + AM16("InvalidGroupControlSum", "Control Sum at the Group level is invalid"), + AM17("InvalidPaymentInfoControlSum", "Control Sum at the Payment Information level is invalid"), + AM18("InvalidNumberOfTransactions", "Number of transactions is invalid or missing."), + AM19("InvalidGroupNumberOfTransactions", "Number of transactions at the Group level is invalid or missing"), + AM20("InvalidPaymentInfoNumberOfTransactions", "Number of transactions at the Payment Information level is invalid"), + AM21("LimitExceeded", "Transaction amount exceeds limits agreed between bank and client."), + AM22("ZeroAmountNotApplied", "Unable to apply zero amount to designated account. For example, where the rules of a service allow the use of zero amount payments, however the back-office system is unable to apply the funds to the account. If the rules of a service prohibit the use of zero amount payments, then code AM01 is used to report the error condition."), + AM23("AmountExceedsSettlementLimit", "Transaction amount exceeds settlement limit."), + AMSE("AttachmentMaximumSize", "Size of the attachment exceeds the allowed maximum."), + APAR("AlreadyPaidRTP", "Request To Pay has already been paid by the Debtor."), + ARFR("AlreadyRefusedRTP", "Request-to-pay has already been refused by the Debtor."), + ARJR("AlreadyRejectedRTP", "Request-to-pay has already been rejected."), + ATNS("AttachementsNotSupported", "Attachments to the request-to-pay are not supported."), + BDAY("NotBusinessDay", "Settlement Cycle Day and Calendar day should be the same."), + BE01("InconsistenWithEndCustomer", "Identification of end customer is not consistent with associated account number. (formerly CreditorConsistency)."), + BE04("MissingCreditorAddress", "Specification of creditor's address, which is required for payment, is missing/not correct (formerly IncorrectCreditorAddress)."), + BE05("UnrecognisedInitiatingParty", "Party who initiated the message is not recognised by the end customer"), + BE06("UnknownEndCustomer", "End customer specified is not known at associated Sort/National Bank Code or does no longer exist in the books"), + BE07("MissingDebtorAddress", "Specification of debtor's address, which is required for payment, is missing/not correct."), + BE08("MissingDebtorName", "Debtor name is missing"), + BE09("InvalidCountry", "Country code is missing or Invalid."), + BE10("InvalidDebtorCountry", "Debtor country code is missing or invalid"), + BE11("InvalidCreditorCountry", "Creditor country code is missing or invalid"), + BE12("InvalidCountryOfResidence", "Country code of residence is missing or Invalid."), + BE13("InvalidDebtorCountryOfResidence", "Country code of debtor's residence is missing or Invalid"), + BE14("InvalidCreditorCountryOfResidence", "Country code of creditor's residence is missing or Invalid"), + BE15("InvalidIdentificationCode", "Identification code missing or invalid."), + BE16("InvalidDebtorIdentificationCode", "Debtor or Ultimate Debtor identification code missing or invalid"), + BE17("InvalidCreditorIdentificationCode", "Creditor or Ultimate Creditor identification code missing or invalid"), + BE18("InvalidContactDetails", "Contact details missing or invalid"), + BE19("InvalidChargeBearerCode", "Charge bearer code for transaction type is invalid"), + BE20("InvalidNameLength", "Name length exceeds local rules for payment type."), + BE21("MissingName", "Name missing or invalid. Generic usage if cannot specifically identify debtor or creditor."), + BE22("MissingCreditorName", "Creditor name is missing"), + BE23("AccountProxyInvalid", "Phone number or email address, or any other proxy, used as the account proxy is unknown or invalid."), + CERI("CheckERI", "Credit transfer is not tagged as an Extended Remittance Information (ERI) transaction but contains ERI."), + CH03("RequestedExecutionDateOrRequestedCollectionDateTooFarInFuture", "Value in Requested Execution Date or Requested Collection Date is too far in the future"), + CH04("RequestedExecutionDateOrRequestedCollectionDateTooFarInPast", "Value in Requested Execution Date or Requested Collection Date is too far in the past"), + CH07("ElementIsNotToBeUsedAtB-andC-Level", "Element is not to be used at B- and C-Level"), + CH09("MandateChangesNotAllowed", "Mandate changes are not allowed"), + CH10("InformationOnMandateChangesMissing", "Information on mandate changes are missing"), + CH11("CreditorIdentifierIncorrect", "Value in Creditor Identifier is incorrect"), + CH12("CreditorIdentifierNotUnambiguouslyAtTransaction-Level", "Creditor Identifier is ambiguous at Transaction Level"), + CH13("OriginalDebtorAccountIsNotToBeUsed", "Original Debtor Account is not to be used"), + CH14("OriginalDebtorAgentIsNotToBeUsed", "Original Debtor Agent is not to be used"), + CH15("ElementContentIncludesMoreThan140Characters", "Content Remittance Information/Structured includes more than 140 characters"), + CH16("ElementContentFormallyIncorrect", "Content is incorrect"), + CH17("ElementNotAdmitted", "Element is not allowed"), + CH19("ValuesWillBeSetToNextTARGETday", "Values in Interbank Settlement Date or Requested Collection Date will be set to the next TARGET day"), + CH20("DecimalPointsNotCompatibleWithCurrency", "Number of decimal points not compatible with the currency"), + CH21("RequiredCompulsoryElementMissing", "Mandatory element is missing"), + CH22("COREandB2BwithinOnemessage", "SDD CORE and B2B not permitted within one message"), + CHQC("ChequeSettledOnCreditorAccount", "Cheque has been presented in cheque clearing and settled on the creditor’s account."), + CN01("AuthorisationCancelled", "Authorisation is cancelled."), + CNOR("CreditorBankIsNotRegistered", "Creditor bank is not registered under this BIC in the CSM"), + CURR("IncorrectCurrency", "Currency of the payment is incorrect"), + CUST("RequestedByCustomer", "Cancellation requested by the Debtor"), + DC02("SettlementNotReceived", "Rejection of a payment due to covering FI settlement not being received."), + DNOR("DebtorBankIsNotRegistered", "Debtor bank is not registered under this BIC in the CSM"), + DS01("ElectronicSignaturesCorrect", "The electronic signature(s) is/are correct"), + DS02("OrderCancelled", "An authorized user has cancelled the order"), + DS03("OrderNotCancelled", "The user’s attempt to cancel the order was not successful"), + DS04("OrderRejected", "The order was rejected by the bank side (for reasons concerning content)"), + DS05("OrderForwardedForPostprocessing", "The order was correct and could be forwarded for postprocessing"), + DS06("TransferOrder", "The order was transferred to VEU"), + DS07("ProcessingOK", "All actions concerning the order could be done by the EBICS bank server"), + DS08("DecompressionError", "The decompression of the file was not successful"), + DS09("DecryptionError", "The decryption of the file was not successful"), + DS0A("DataSignRequested", "Data signature is required."), + DS0B("UnknownDataSignFormat", "Data signature for the format is not available or invalid."), + DS0C("SignerCertificateRevoked", "The signer certificate is revoked."), + DS0D("SignerCertificateNotValid", "The signer certificate is not valid (revoked or not active)."), + DS0E("IncorrectSignerCertificate", "The signer certificate is not present."), + DS0F("SignerCertificationAuthoritySignerNotValid", "The authority of the signer certification sending the certificate is unknown."), + DS0G("NotAllowedPayment", "Signer is not allowed to sign this operation type."), + DS0H("NotAllowedAccount", "Signer is not allowed to sign for this account."), + DS0K("NotAllowedNumberOfTransaction", "The number of transaction is over the number allowed for this signer."), + DS10("Signer1CertificateRevoked", "The certificate is revoked for the first signer."), + DS11("Signer1CertificateNotValid", "The certificate is not valid (revoked or not active) for the first signer."), + DS12("IncorrectSigner1Certificate", "The certificate is not present for the first signer."), + DS13("SignerCertificationAuthoritySigner1NotValid", "The authority of signer certification sending the certificate is unknown for the first signer."), + DS14("UserDoesNotExist", "The user is unknown on the server"), + DS15("IdenticalSignatureFound", "The same signature has already been sent to the bank"), + DS16("PublicKeyVersionIncorrect", "The public key version is not correct. This code is returned when a customer sends signature files to the financial institution after conversion from an older program version (old ES format) to a new program version (new ES format) without having carried out re-initialisation with regard to a public key change."), + DS17("DifferentOrderDataInSignatures", "Order data and signatures don’t match"), + DS18("RepeatOrder", "File cannot be tested, the complete order has to be repeated. This code is returned in the event of a malfunction during the signature check, e.g. not enough storage space."), + DS19("ElectronicSignatureRightsInsufficient", "The user’s rights (concerning his signature) are insufficient to execute the order"), + DS20("Signer2CertificateRevoked", "The certificate is revoked for the second signer."), + DS21("Signer2CertificateNotValid", "The certificate is not valid (revoked or not active) for the second signer."), + DS22("IncorrectSigner2Certificate", "The certificate is not present for the second signer."), + DS23("SignerCertificationAuthoritySigner2NotValid", "The authority of signer certification sending the certificate is unknown for the second signer."), + DS24("WaitingTimeExpired", "Waiting time expired due to incomplete order"), + DS25("OrderFileDeleted", "The order file was deleted by the bank server"), + DS26("UserSignedMultipleTimes", "The same user has signed multiple times"), + DS27("UserNotYetActivated", "The user is not yet activated (technically)"), + DS28("ReturnForTechnicalReason", "Message routed to the wrong environment."), + DT01("InvalidDate", "Invalid date (eg, wrong or missing settlement date)"), + DT02("InvalidCreationDate", "Invalid creation date and time in Group Header (eg, historic date)"), + DT03("InvalidNonProcessingDate", "Invalid non bank processing date (eg, weekend or local public holiday)"), + DT04("FutureDateNotSupported", "Future date not supported"), + DT05("InvalidCutOffDate", "Associated message, payment information block or transaction was received after agreed processing cut-off date, i.e., date in the past."), + DT06("ExecutionDateChanged", "Execution Date has been modified in order for transaction to be processed"), + DU01("DuplicateMessageID", "Message Identification is not unique."), + DU02("DuplicatePaymentInformationID", "Payment Information Block is not unique."), + DU03("DuplicateTransaction", "Transaction is not unique."), + DU04("DuplicateEndToEndID", "End To End ID is not unique."), + DU05("DuplicateInstructionID", "Instruction ID is not unique."), + DUPL("DuplicatePayment", "Payment is a duplicate of another payment"), + ED01("CorrespondentBankNotPossible", "Correspondent bank not possible."), + ED03("BalanceInfoRequest", "Balance of payments complementary info is requested"), + ED05("SettlementFailed", "Settlement of the transaction has failed."), + ED06("SettlementSystemNotAvailable", "Interbank settlement system not available."), + EDNA("ExecutionDateNotAccepted", "Requested execution date of the payment is not accepted."), + EDTL("ExpiryDateTooLong", "Expiry date time of the request-to-pay is too far in the future."), + EDTR("ExpiryDateTimeReached", "Expiry date time of the request-to-pay is already reached."), + ERIN("ERIOptionNotSupported", "Extended Remittance Information (ERI) option is not supported."), + FF01("InvalidFileFormat", "File Format incomplete or invalid"), + FF02("SyntaxError", "Syntax error reason is provided as narrative information in the additional reason information."), + FF03("InvalidPaymentTypeInformation", "Payment Type Information is missing or invalid."), + FF04("InvalidServiceLevelCode", "Service Level code is missing or invalid"), + FF05("InvalidLocalInstrumentCode", "Local Instrument code is missing or invalid"), + FF06("InvalidCategoryPurposeCode", "Category Purpose code is missing or invalid"), + FF07("InvalidPurpose", "Purpose is missing or invalid"), + FF08("InvalidEndToEndId", "End to End Id missing or invalid"), + FF09("InvalidChequeNumber", "Cheque number missing or invalid"), + FF10("BankSystemProcessingError", "File or transaction cannot be processed due to technical issues at the bank side"), + FF11("ClearingRequestAborted", "Clearing request rejected due it being subject to an abort operation."), + FF12("OriginalTransactionNotEligibleForRequestedReturn", "Original payment is not eligible to be returned given its current status."), + FF13("RequestForCancellationNotFound", "No record of request for cancellation found."), + FOCR("FollowingCancellationRequest", "Return following a cancellation request."), + FR01("Fraud", "Returned as a result of fraud."), + FRAD("FraudulentOrigin", "Cancellation requested following a transaction that was originated fraudulently. The use of the FraudulentOrigin code should be governed by jurisdictions."), + G000("PaymentTransferredAndTracked", "In an FI To FI Customer Credit Transfer: The Status Originator transferred the payment to the next Agent or to a Market Infrastructure. The payment transfer is tracked. No further updates will follow from the Status Originator."), + G001("PaymentTransferredAndNotTracked", "In an FI To FI Customer Credit Transfer: The Status Originator transferred the payment to the next Agent or to a Market Infrastructure. The payment transfer is not tracked. No further updates will follow from the Status Originator."), + G002("CreditDebitNotConfirmed", "In a FIToFI Customer Credit Transfer: Credit to the creditor’s account may not be confirmed same day. Update will follow from the Status Originator."), + G003("CreditPendingDocuments", "In a FIToFI Customer Credit Transfer: Credit to creditor’s account is pending receipt of required documents. The Status Originator has requested creditor to provide additional documentation. Update will follow from the Status Originator."), + G004("CreditPendingFunds", "In a FIToFI Customer Credit Transfer: Credit to the creditor’s account is pending, status Originator is waiting for funds provided via a cover. Update will follow from the Status Originator."), + G005("DeliveredWithServiceLevel", "Payment has been delivered to creditor agent with service level."), + G006("DeliveredWIthoutServiceLevel", "Payment has been delivered to creditor agent without service level."), + ID01("CorrespondingOriginalFileStillNotSent", "Signature file was sent to the bank but the corresponding original file has not been sent yet."), + IEDT("IncorrectExpiryDateTime", "Expiry date time of the request-to-pay is incorrect."), + INAR("InvalidActivationReference", "Payer’s activation reference is invalid."), + INDT("InvalidDetails", "Details not valid for this field."), + IRNR("InitialRTPNeverReceived", "No initial request-to-pay has been received."), + ISWS("InvalidSettlementWindow", "Cannot schedule instruction for Night Window."), + MD01("NoMandate", "No Mandate"), + MD02("MissingMandatoryInformationInMandate", "Mandate related information data required by the scheme is missing."), + MD05("CollectionNotDue", "Creditor or creditor's agent should not have collected the direct debit"), + MD06("RefundRequestByEndCustomer", "Return of funds requested by end customer"), + MD07("EndCustomerDeceased", "End customer is deceased."), + MINF("MissingInformation", "Information missing for the field or cannot be empty."), + MS02("NotSpecifiedReasonCustomerGenerated", "Reason has not been specified by end customer"), + MS03("NotSpecifiedReasonAgentGenerated", "Reason has not been specified by agent."), + NARR("Narrative", "Reason is provided as narrative information in the additional reason information."), + NERI("NoERI", "Credit transfer is tagged as an Extended Remittance Information (ERI) transaction but does not contain ERI."), + NOAR("NonAgreedRTP", "No existing agreement for receiving request-to-pay messages."), + NOAS("NoAnswerFromCustomer", "No response from Beneficiary."), + NOCM("NotCompliantGeneric", "Customer account is not compliant with regulatory requirements, for example FICA (in South Africa) or any other regulatory requirements which render an account inactive for certain processing."), + NOFR("OutstandingFundingForSettlement", "Continuous Processing Line on Hold Instruction."), + NOPG("NoPaymentGuarantee", "Requested payment guarantee (by Creditor) related to a request-to-pay cannot be provided."), + NRCH("PayerOrPayerRTPSPNotReachable", "Recipient side of the request-to-pay (payer or its request-to-pay service provider) is not reachable."), + OSNS("OptionalServiceNotSupported", "Requested optional service (for example instalment payments) is not supported."), + PINS("TypeOfPaymentInstrumentNotSupported", "Type of payment requested in the request-to-pay is not supported by the payer."), + RC01("BankIdentifierIncorrect", "Bank identifier code specified in the message has an incorrect format (formerly IncorrectFormatForRoutingCode)."), + RC02("InvalidBankIdentifier", "Bank identifier is invalid or missing."), + RC03("InvalidDebtorBankIdentifier", "Debtor bank identifier is invalid or missing"), + RC04("InvalidCreditorBankIdentifier", "Creditor bank identifier is invalid or missing"), + RC05("InvalidBICIdentifier", "BIC identifier is invalid or missing."), + RC06("InvalidDebtorBICIdentifier", "Debtor BIC identifier is invalid or missing"), + RC07("InvalidCreditorBICIdentifier", "Creditor BIC identifier is invalid or missing"), + RC08("InvalidClearingSystemMemberIdentifier", "ClearingSystemMemberidentifier is invalid or missing."), + RC09("InvalidDebtorClearingSystemMemberIdentifier", "Debtor ClearingSystemMember identifier is invalid or missing"), + RC10("InvalidCreditorClearingSystemMemberIdentifier", "Creditor ClearingSystemMember identifier is invalid or missing"), + RC11("InvalidIntermediaryAgent", "Intermediary Agent is invalid or missing"), + RC12("MissingCreditorSchemeId", "Creditor Scheme Id is invalid or missing"), + RC13("ParticipantNotAnActiveMemberofRTGS", "Originator not active any more."), + RC15("ParticipantNotActiveMemberSettlementType", "Settlement agreement required."), + RC16("ParticipantNotActiveMemberofSADCRTGS", "Participant blocked from SADC-RTGS."), + RCON("RMessageConflict", "Conflict with R-Message"), + RECI("ReceiverCustomerInformation", "Further information regarding the intended recipient."), + REPR("RTPReceivedCanBeProcessed", "Request-to-pay has been received and can be processed further."), + RF01("NotUniqueTransactionReference", "Transaction reference is not unique within the message."), + RQNR("RequestNotRecognized", "Payer did not recognize the request from Payee Participant,"), + RR01("MissingDebtorAccountOrIdentification", "Specification of the debtor’s account or unique identification needed for reasons of regulatory requirements is insufficient or missing"), + RR02("MissingDebtorNameOrAddress", "Specification of the debtor’s name and/or address needed for regulatory requirements is insufficient or missing."), + RR03("MissingCreditorNameOrAddress", "Specification of the creditor’s name and/or address needed for regulatory requirements is insufficient or missing."), + RR04("RegulatoryReason", "Regulatory Reason"), + RR05("RegulatoryInformationInvalid", "Regulatory or Central Bank Reporting information missing, incomplete or invalid."), + RR06("TaxInformationInvalid", "Tax information missing, incomplete or invalid."), + RR07("RemittanceInformationInvalid", "Remittance information structure does not comply with rules for payment type."), + RR08("RemittanceInformationTruncated", "Remittance information truncated to comply with rules for payment type."), + RR09("InvalidStructuredCreditorReference", "Structured creditor reference invalid or missing."), + RR10("InvalidCharacterSet", "Character set supplied not valid for the country and payment type."), + RR11("InvalidDebtorAgentServiceID", "Invalid or missing identification of a bank proprietary service."), + RR12("InvalidPartyID", "Invalid or missing identification required within a particular country or payment type."), + RTNS("RTPNotSupportedForDebtor", "Debtor does not support request-to-pay transactions."), + RUTA("ReturnUponUnableToApply", "Return following investigation request and no remediation possible."), + S000("ValidRequestForCancellationAcknowledged", "Request for Cancellation is acknowledged following validation."), + S001("UETRFlaggedForCancellation", "Unique End-to-end Transaction Reference (UETR) relating to a payment has been identified as being associated with a Request for Cancellation."), + S002("NetworkStopOfUETR", "Unique End-to-end Transaction Reference (UETR) relating to a payment has been prevent from traveling across a messaging network."), + S003("RequestForCancellationForwarded", "Request for Cancellation has been forwarded to the payment processing/last payment processing agent."), + S004("RequestForCancellationDeliveryAcknowledgement", "Request for Cancellation has been acknowledged as delivered to payment processing/last payment processing agent."), + SBRN("SettlementBatchRemovalNotification", "Remove Concurrent Batch Processing Line on hold instruction."), + SL01("SpecificServiceOfferedByDebtorAgent", "Due to specific service offered by the Debtor Agent."), + SL02("SpecificServiceOfferedByCreditorAgent", "Due to specific service offered by the Creditor Agent."), + SL03("ServiceofClearingSystem", "Due to a specific service offered by the clearing system."), + SL11("CreditorNotOnWhitelistOfDebtor", "Whitelisting service offered by the Debtor Agent; Debtor has not included the Creditor on its “Whitelist” (yet). In the Whitelist the Debtor may list all allowed Creditors to debit Debtor bank account."), + SL12("CreditorOnBlacklistOfDebtor", "Blacklisting service offered by the Debtor Agent; Debtor included the Creditor on his “Blacklist”. In the Blacklist the Debtor may list all Creditors not allowed to debit Debtor bank account."), + SL13("MaximumNumberOfDirectDebitTransactionsExceeded", "Due to Maximum allowed Direct Debit Transactions per period service offered by the Debtor Agent."), + SL14("MaximumDirectDebitTransactionAmountExceeded", "Due to Maximum allowed Direct Debit Transaction amount service offered by the Debtor Agent."), + SNRD("ServiceNotRendered", "Services are not yet rendered by the Payee Participant (Creditor)."), + SPII("RTPServiceProviderIdentifierIncorrect", "Identifier of the request-to-pay service provider is incorrect."), + TA01("TransmissonAborted", "The transmission of the file was not successful – it had to be aborted (for technical reasons)"), + TD01("NoDataAvailable", "There is no data available (for download)"), + TD02("FileNonReadable", "The file cannot be read (e.g. unknown format)"), + TD03("IncorrectFileStructure", "The file format is incomplete or invalid"), + TK01("TokenInvalid", "Token is invalid."), + TK02("SenderTokenNotFound", "Token used for the sender does not exist."), + TK03("ReceiverTokenNotFound", "Token used for the receiver does not exist."), + TK09("TokenMissing", "Token required for request is missing."), + TKCM("TokenCounterpartyMismatch", "Token found with counterparty mismatch."), + TKSG("TokenSingleUse", "Single Use Token already used."), + TKSP("TokenSuspended", "Token found with suspended status."), + TKVE("TokenValueLimitExceeded", "Token found with value limit rule violation."), + TKXP("TokenExpired", "Token expired."), + TM01("InvalidCutOffTime", "Associated message, payment information block, or transaction was received after agreed processing cut-off time."), + TS01("TransmissionSuccessful", "The (technical) transmission of the file was successful."), + TS04("TransferToSignByHand", "The order was transferred to pass by accompanying note signed by hand"), + UCRD("UnknownCreditor", "Unknown Creditor."), + UPAY("UnduePayment", "Payment is not justified."), +} + +enum class ExternalPaymentGroupStatusCode(val isoCode: String, val description: String) { + ACCC("AcceptedSettlementCompletedCreditorAccount", "Settlement on the creditor's account has been completed."), + ACCP("AcceptedCustomerProfile", "Preceding check of technical validation was successful. Customer profile check was also successful."), + ACSC("AcceptedSettlementCompletedDebitorAccount", "Settlement on the debtor's account has been completed."), + ACSP("AcceptedSettlementInProcess", "All preceding checks such as technical validation and customer profile were successful and therefore the payment initiation has been accepted for execution."), + ACTC("AcceptedTechnicalValidation", "Authentication and syntactical and semantical validation are successful"), + ACWC("AcceptedWithChange", "Instruction is accepted but a change will be made, such as date or remittance not sent."), + PART("PartiallyAccepted", "A number of transactions have been accepted, whereas another number of transactions have not yet achieved"), + PDNG("Pending", "Payment initiation or individual transaction included in the payment initiation is pending. Further checks and status update will be performed."), + RCVD("Received", "Payment initiation has been received by the receiving agent"), + RJCT("Rejected", "Payment initiation or individual transaction included in the payment initiation has been rejected."), +} + +enum class ExternalPaymentTransactionStatusCode(val isoCode: String, val description: String) { + ACCC("AcceptedSettlementCompletedCreditorAccount", "Settlement on the creditor's account has been completed."), + ACCP("AcceptedCustomerProfile", "Preceding check of technical validation was successful. Customer profile check was also successful."), + ACFC("AcceptedFundsChecked", "Preceding check of technical validation and customer profile was successful and an automatic funds check was positive."), + ACIS("AcceptedandChequeIssued", "Payment instruction to issue a cheque has been accepted, and the cheque has been issued but not yet been deposited or cleared."), + ACPD("AcceptedClearingProcessed", "Status of transaction released from the Debtor Agent and accepted by the clearing."), + ACSC("AcceptedSettlementCompletedDebitorAccount", "Settlement completed."), + ACSP("AcceptedSettlementInProcess", "All preceding checks such as technical validation and customer profile were successful and therefore the payment instruction has been accepted for execution."), + ACTC("AcceptedTechnicalValidation", "Authentication and syntactical and semantical validation are successful"), + ACWC("AcceptedWithChange", "Instruction is accepted but a change will be made, such as date or remittance not sent."), + ACWP("AcceptedWithoutPosting", "Payment instruction included in the credit transfer is accepted without being posted to the creditor customer’s account."), + BLCK("Blocked", "Payment transaction previously reported with status 'ACWP' is blocked, for example, funds will neither be posted to the Creditor's account, nor be returned to the Debtor."), + CANC("Cancelled", "Payment initiation has been successfully cancelled after having received a request for cancellation."), + CPUC("CashPickedUpByCreditor", "Cash has been picked up by the Creditor."), + PATC("PartiallyAcceptedTechnicalCorrect", "Payment initiation needs multiple authentications, where some but not yet all have been performed. Syntactical and semantical validations are successful."), + PDNG("Pending", "Payment instruction is pending. Further checks and status update will be performed."), + PRES("Presented", "Request for Payment has been presented to the Debtor."), + RCVD("Received", "Payment instruction has been received."), + RJCT("Rejected", "Payment instruction has been rejected."), +} + +enum class ExternalReturnReasonCode(val isoCode: String, val description: String) { + AC01("IncorrectAccountNumber", "Format of the account number specified is not correct"), + AC02("InvalidDebtorAccountNumber", "Debtor account number invalid or missing."), + AC03("InvalidCreditorAccountNumber", "Wrong IBAN in SCT"), + AC04("ClosedAccountNumber", "Account number specified has been closed on the bank of account's books"), + AC06("BlockedAccount", "Account specified is blocked, prohibiting posting of transactions against it."), + AC07("ClosedCreditorAccountNumber", "Creditor account number closed."), + AC13("InvalidDebtorAccountType", "Debtor account type is missing or invalid"), + AC14("InvalidAgent", "An agent in the payment chain is invalid."), + AC15("AccountDetailsChanged", "Account details have changed."), + AC16("AccountInSequestration", "Account is in sequestration."), + AC17("AccountInLiquidation", "Account is in liquidation."), + AG01("TransactionForbidden", "Transaction forbidden on this type of account (formerly NoAgreement)"), + AG02("InvalidBankOperationCode", "Bank Operation code specified in the message is not valid for receiver"), + AG07("UnsuccesfulDirectDebit", "Debtor account cannot be debited for a generic reason."), + AGNT("IncorrectAgent", "Agent in the payment workflow is incorrect."), + AM01("ZeroAmount", "Specified message amount is equal to zero"), + AM02("NotAllowedAmount", "Specific transaction/message amount is greater than allowed maximum"), + AM03("NotAllowedCurrency", "Specified message amount is an non processable currency outside of existing agreement"), + AM04("InsufficientFunds", "Amount of funds available to cover specified message amount is insufficient."), + AM05("Duplication", "Duplication"), + AM06("TooLowAmount", "Specified transaction amount is less than agreed minimum."), + AM07("BlockedAmount", "Amount specified in message has been blocked by regulatory authorities."), + AM09("WrongAmount", "Amount received is not the amount agreed or expected"), + AM10("InvalidControlSum", "Sum of instructed amounts does not equal the control sum."), + ARDT("AlreadyReturnedTransaction", "Already returned original SCT"), + BE01("InconsistenWithEndCustomer", "Identification of end customer is not consistent with associated account number, organisation ID or private ID."), + BE04("MissingCreditorAddress", "Specification of creditor's address, which is required for payment, is missing/not correct (formerly IncorrectCreditorAddress)."), + BE05("UnrecognisedInitiatingParty", "Party who initiated the message is not recognised by the end customer"), + BE06("UnknownEndCustomer", "End customer specified is not known at associated Sort/National Bank Code or does no longer exist in the books"), + BE07("MissingDebtorAddress", "Specification of debtor's address, which is required for payment, is missing/not correct."), + BE08("BankError", "Returned as a result of a bank error."), + BE10("InvalidDebtorCountry", "Debtor country code is missing or invalid."), + BE11("InvalidCreditorCountry", "Creditor country code is missing or invalid."), + BE16("InvalidDebtorIdentificationCode", "Debtor or Ultimate Debtor identification code missing or invalid."), + BE17("InvalidCreditorIdentificationCode", "Creditor or Ultimate Creditor identification code missing or invalid."), + CN01("AuthorisationCancelled", "Authorisation is cancelled."), + CNOR("CreditorBankIsNotRegistered", "Creditor bank is not registered under this BIC in the CSM"), + CNPC("CashNotPickedUp", "Cash not picked up by Creditor or cash could not be delivered to Creditor"), + CURR("IncorrectCurrency", "Currency of the payment is incorrect"), + CUST("RequestedByCustomer", "Cancellation requested by the Debtor"), + DC04("NoCustomerCreditTransferReceived", "Return of Covering Settlement due to the underlying Credit Transfer details not being received."), + DNOR("DebtorBankIsNotRegistered", "Debtor bank is not registered under this BIC in the CSM"), + DS28("ReturnForTechnicalReason", "Return following technical problems resulting in erroneous transaction."), + DT01("InvalidDate", "Invalid date (eg, wrong settlement date)"), + DT02("ChequeExpired", "Cheque has been issued but not deposited and is considered expired."), + DT04("FutureDateNotSupported", "Future date not supported."), + DUPL("DuplicatePayment", "Payment is a duplicate of another payment."), + ED01("CorrespondentBankNotPossible", "Correspondent bank not possible."), + ED03("BalanceInfoRequest", "Balance of payments complementary info is requested"), + ED05("SettlementFailed", "Settlement of the transaction has failed."), + EMVL("EMVLiabilityShift", "The card payment is fraudulent and was not processed with EMV technology for an EMV card."), + ERIN("ERIOptionNotSupported", "The Extended Remittance Information (ERI) option is not supported."), + FF03("InvalidPaymentTypeInformation", "Payment Type Information is missing or invalid."), + FF04("InvalidServiceLevelCode", "Service Level code is missing or invalid."), + FF05("InvalidLocalInstrumentCode", "Local Instrument code is missing or invalid"), + FF06("InvalidCategoryPurposeCode", "Category Purpose code is missing or invalid."), + FF07("InvalidPurpose", "Purpose is missing or invalid."), + FOCR("FollowingCancellationRequest", "Return following a cancellation request"), + FR01("Fraud", "Returned as a result of fraud."), + FRTR("FinalResponseMandateCancelled", "Final response/tracking is recalled as mandate is cancelled."), + G004("CreditPendingFunds", "In a FIToFI Customer Credit Transfer: Credit to the creditor’s account is pending, status Originator is waiting for funds provided via a cover. Update will follow from the Status Originator."), + MD01("NoMandate", "No Mandate"), + MD02("MissingMandatoryInformationInMandate", "Mandate related information data required by the scheme is missing."), + MD05("CollectionNotDue", "Creditor or creditor's agent should not have collected the direct debit."), + MD06("RefundRequestByEndCustomer", "Return of funds requested by end customer"), + MD07("EndCustomerDeceased", "End customer is deceased."), + MS02("NotSpecifiedReasonCustomerGenerated", "Reason has not been specified by end customer"), + MS03("NotSpecifiedReasonAgentGenerated", "Reason has not been specified by agent."), + NARR("Narrative", "Reason is provided as narrative information in the additional reason information."), + NOAS("NoAnswerFromCustomer", "No response from Beneficiary"), + NOCM("NotCompliant", "Customer account is not compliant with regulatory requirements, for example FICA (in South Africa) or any other regulatory requirements which render an account inactive for certain processing."), + NOOR("NoOriginalTransactionReceived", "Original SCT never received"), + PINL("PINLiabilityShift", "The card payment is fraudulent (lost and stolen fraud) and was processed as EMV transaction without PIN verification."), + RC01("BankIdentifierIncorrect", "Bank Identifier code specified in the message has an incorrect format (formerly IncorrectFormatForRoutingCode)."), + RC03("InvalidDebtorBankIdentifier", "Debtor bank identifier is invalid or missing."), + RC04("InvalidCreditorBankIdentifier", "Creditor bank identifier is invalid or missing."), + RC07("InvalidCreditorBICIdentifier", "Incorrrect BIC of the beneficiary Bank in the SCTR"), + RC08("InvalidClearingSystemMemberIdentifier", "ClearingSystemMemberidentifier is invalid or missing."), + RC11("InvalidIntermediaryAgent", "Intermediary Agent is invalid or missing."), + RF01("NotUniqueTransactionReference", "Transaction reference is not unique within the message."), + RR01("MissingDebtorAccountOrIdentification", "Specification of the debtor’s account or unique identification needed for reasons of regulatory requirements is insufficient or missing"), + RR02("MissingDebtorNameOrAddress", "Specification of the debtor’s name and/or address needed for regulatory requirements is insufficient or missing."), + RR03("MissingCreditorNameOrAddress", "Specification of the creditor’s name and/or address needed for regulatory requirements is insufficient or missing."), + RR04("RegulatoryReason", "Regulatory Reason"), + RR05("RegulatoryInformationInvalid", "Regulatory or Central Bank Reporting information missing, incomplete or invalid."), + RR06("TaxInformationInvalid", "Tax information missing, incomplete or invalid."), + RR07("RemittanceInformationInvalid", "Remittance information structure does not comply with rules for payment type."), + RR08("RemittanceInformationTruncated", "Remittance information truncated to comply with rules for payment type."), + RR09("InvalidStructuredCreditorReference", "Structured creditor reference invalid or missing."), + RR11("InvalidDebtorAgentServiceIdentification", "Invalid or missing identification of a bank proprietary service."), + RR12("InvalidPartyIdentification", "Invalid or missing identification required within a particular country or payment type."), + RUTA("ReturnUponUnableToApply", "Return following investigation request and no remediation possible."), + SL01("SpecificServiceOfferedByDebtorAgent", "Due to specific service offered by the Debtor Agent"), + SL02("SpecificServiceOfferedByCreditorAgent", "Due to specific service offered by the Creditor Agent"), + SL11("CreditorNotOnWhitelistOfDebtor", "Whitelisting service offered by the Debtor Agent; Debtor has not included the Creditor on its “Whitelist” (yet). In the Whitelist the Debtor may list all allowed Creditors to debit Debtor bank account."), + SL12("CreditorOnBlacklistOfDebtor", "Blacklisting service offered by the Debtor Agent; Debtor included the Creditor on his “Blacklist”. In the Blacklist the Debtor may list all Creditors not allowed to debit Debtor bank account."), + SL13("MaximumNumberOfDirectDebitTransactionsExceeded", "Due to Maximum allowed Direct Debit Transactions per period service offered by the Debtor Agent."), + SL14("MaximumDirectDebitTransactionAmountExceeded", "Due to Maximum allowed Direct Debit Transaction amount service offered by the Debtor Agent."), + SP01("PaymentStopped", "Payment is stopped by account holder."), + SP02("PreviouslyStopped", "Previously stopped by means of a stop payment advise."), + SVNR("ServiceNotRendered", "The card payment is returned since a cash amount rendered was not correct or goods or a service was not rendered to the customer, e.g. in an e-commerce situation."), + TM01("CutOffTime", "Associated message was received after agreed processing cut-off time."), + TRAC("RemovedFromTracking", "Return following direct debit being removed from tracking process."), + UPAY("UnduePayment", "Payment is not justified."), +} + diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt @@ -0,0 +1,547 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ +package tech.libeufin.nexus.iso20022 + +import tech.libeufin.common.* +import tech.libeufin.nexus.* +import tech.libeufin.nexus.ebics.Dialect +import java.io.InputStream +import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +sealed interface TxNotification { + val executionTime: Instant +} + +/** ISO20022 incoming payment */ +data class IncomingPayment( + /** ISO20022 UETR or TxID */ + val bankId: String? = null, // Null when TxID is wrong with Atruvia's implementation of instant transactions + val amount: TalerAmount, + val wireTransferSubject: String, + override val executionTime: Instant, + val debitPaytoUri: String +): TxNotification { + override fun toString(): String { + return "IN ${executionTime.fmtDate()} $amount $bankId debitor=$debitPaytoUri subject=\"$wireTransferSubject\"" + } +} + +/** ISO20022 outgoing payment */ +data class OutgoingPayment( + /** ISO20022 EndToEndId or MessageId (retrocompatibility) */ + val endToEndId: String, + /** ISO20022 MessageId */ + val msgId: String? = null, + val amount: TalerAmount, + val wireTransferSubject: String? = null, // Some implementation does not provide this for recovery + override val executionTime: Instant, + val creditPaytoUri: String? = null // Some implementation does not provide this for recovery +): TxNotification { + override fun toString(): String { + val msgIdFmt = if (msgId == null) "" else "$msgId." + return "OUT ${executionTime.fmtDate()} $amount $msgIdFmt$endToEndId creditor=$creditPaytoUri subject=\"$wireTransferSubject\"" + } +} + +/** ISO20022 outgoing batch */ +data class OutgoingBatch( + /** ISO20022 MessageId */ + val msgId: String, + override val executionTime: Instant, +): TxNotification { + override fun toString(): String { + return "BATCH ${executionTime.fmtDate()} $msgId" + } +} + +/** ISO20022 outgoing reversal */ +data class OutgoingReversal( + /** ISO20022 EndToEndId */ + val endToEndId: String, + /** ISO20022 MessageId */ + val msgId: String? = null, + val reason: String?, + override val executionTime: Instant +): TxNotification { + override fun toString(): String { + val msgIdFmt = if (msgId == null) "" else "$msgId." + return "BOUNCE ${executionTime.fmtDate()} $msgIdFmt$endToEndId: $reason" + } +} + +private fun XmlDestructor.payto(prefix: String): String? { + val iban = opt("${prefix}Acct")?.one("Id")?.one("IBAN")?.text() + return if (iban != null) { + val name = opt(prefix) { opt("Nm")?.text() ?: opt("Pty")?.one("Nm")?.text() } + // Parse bic ? + IbanPayto.build(iban, null, name) + } else { + null + } +} + +private class TxErr(val msg: String): Exception(msg) + +private enum class Kind { + CRDT, + DBIT +} + +/** Unique ID generated by libeufin-nexus */ +private data class OutgoingId( + // Unique msg ID generated by libeufin-nexus + val msgId: String?, + // Unique end-to-end ID generated by libeufin-nexus + val endToEndId: String? +) { + fun ref(): String? = endToEndId ?: msgId +} + +/** Parse camt.054 or camt.053 file */ +fun parseTx( + notifXml: InputStream, + acceptedCurrency: String, + dialect: Dialect +): List<TxNotification> { + /* + In ISO 20022 specifications, most fields are optional and the same information + can be written several times in different places. For libeufin, we're only + interested in a subset of the available values that can be found in both camt.052, + camt.053 and camt.054. This function should not fail on legitimate files and should + simply warn when available information are insufficient. + + EBICS and ISO20022 do not provide a perfect transaction identifier. The best is the + UETR (unique end-to-end transaction reference), which is a universally unique + identifier (UUID). However, it is not supplied by all banks. TxId (TransactionIdentification) + is a unique identification as assigned by the first instructing agent. As its format + is ambiguous, its uniqueness is not guaranteed by the standard, and it is only + supposed to be unique for a “pre-agreed period”, whatever that means. These two + identifiers are optional in the standard, but have the advantage of being unique + and can be used to track a transaction between banks so we use them when available. + + It is also possible to use AccountServicerReference, which is a unique reference + assigned by the account servicing institution. They can be present at several levels + (batch level, transaction level, etc.) and are often optional. They also have the + disadvantage of being known only by the account servicing institution. They should + therefore only be used as a last resort. + */ + + /** Check if an entry status is BOOK */ + fun XmlDestructor.isBooked(): Boolean { + // We check at the Sts or Sts/Cd level for retrocompatibility + return one("Sts") { + val status = opt("Cd")?.text() ?: text() + status == "BOOK" + } + } + + /** Parse the instruction execution date */ + fun XmlDestructor.executionDate(): Instant { + // Value date if present else booking date + val date = opt("ValDt") ?: one("BookgDt") + val parsed = date.opt("Dt") { + date().atStartOfDay() + } ?: date.one("DtTm") { + dateTime() + } + return parsed.toInstant(ZoneOffset.UTC) + } + + /** Parse batch message ID and transaction end-to-end ID as generated by libeufin-nexus */ + fun XmlDestructor.outgoingId(): OutgoingId = one("Refs") { + val endToEndId = opt("EndToEndId")?.text() + val msgId = opt("MsgId")?.text() + if (endToEndId == null) { + // This is a batch representation + OutgoingId(msgId, null) + } else if (endToEndId == "NOTPROVIDED") { + // If not set use MsgId as end-to-end ID for retrocompatibility + OutgoingId(msgId, msgId) + } else { + OutgoingId(msgId, endToEndId) + } + } + + /** Parse and format transaction return reasons */ + fun XmlDestructor.returnReason(): String = opt("RtrInf") { + val code = one("Rsn").one("Cd").enum<ExternalReturnReasonCode>() + val info = map("AddtlInf") { text() }.joinToString("") + buildString { + append("${code.isoCode} '${code.description}'") + if (info.isNotEmpty()) { + append(" - '$info'") + } + } + } ?: opt("RmtInf") { + map("Ustrd") { text() }.joinToString("") + } ?: "" + + /** Parse amount */ + fun XmlDestructor.amount(acceptedCurrency: String) = one("Amt") { + val currency = attr("Ccy") + /** FIXME: test by sending non-CHF to PoFi and see which currency gets here. */ + if (currency != acceptedCurrency) throw Exception("Currency $currency not supported") + TalerAmount("$currency:${text()}") + } + + /** Parse bank transaction code */ + fun XmlDestructor.bankTransactionCode(): BankTransactionCode { + return one("BkTxCd").one("Domn") { + val domain = one("Cd").enum<ExternalBankTransactionDomainCode>() + one("Fmly") { + val family = one("Cd").enum<ExternalBankTransactionFamilyCode>() + val subFamily = one("SubFmlyCd").enum<ExternalBankTransactionSubFamilyCode>() + + BankTransactionCode(domain, family, subFamily) + } + } + } + + /** Parse optional bank transaction code */ + fun XmlDestructor.optBankTransactionCode(): BankTransactionCode? { + return opt("BkTxCd")?.one("Domn") { + val domain = one("Cd").enum<ExternalBankTransactionDomainCode>() + one("Fmly") { + val family = one("Cd").enum<ExternalBankTransactionFamilyCode>() + val subFamily = one("SubFmlyCd").enum<ExternalBankTransactionSubFamilyCode>() + + BankTransactionCode(domain, family, subFamily) + } + } + } + + val txsInfo = mutableListOf<TxInfo>() + + XmlDestructor.fromStream(notifXml, "Document") { when (dialect) { + Dialect.gls -> { + /** Common parsing logic for camt.052 and camt.053 */ + fun XmlDestructor.parseGlsInner() { + opt("Acct") { + // Sanity check on currency and IBAN ? + } + each("Ntry") { + if (!isBooked()) return@each + val code = bankTransactionCode() + if (!code.isPayment()) return@each + val entryRef = opt("AcctSvcrRef")?.text() + val bookDate = executionDate() + val kind = one("CdtDbtInd").enum<Kind>() + val amount = amount(acceptedCurrency) + one("NtryDtls").one("TxDtls") { // TODO handle batches + val code = optBankTransactionCode() ?: code + val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() + if (code.isReversal()) { + val outgoingId = outgoingId() + if (kind == Kind.CRDT) { + val reason = returnReason() + txsInfo.add(TxInfo.CreditReversal( + ref = outgoingId.ref() ?: txRef ?: entryRef, + bookDate = bookDate, + id = outgoingId, + reason = reason, + code = code + )) + } + } else { + val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("") + when (kind) { + Kind.CRDT -> { + val bankId = one("Refs").opt("TxId")?.text() + val debtorPayto = opt("RltdPties") { payto("Dbtr") } + txsInfo.add(TxInfo.Credit( + ref = bankId ?: txRef ?: entryRef, + bookDate = bookDate, + bankId = bankId, + amount = amount, + subject = subject, + debtorPayto = debtorPayto, + code = code + )) + } + Kind.DBIT -> { + val outgoingId = outgoingId() + val creditorPayto = opt("RltdPties") { payto("Cdtr") } + txsInfo.add(TxInfo.Debit( + ref = outgoingId.ref() ?: txRef ?: entryRef, + bookDate = bookDate, + id = outgoingId, + amount = amount, + subject = subject, + creditorPayto = creditorPayto, + code = code + )) + } + } + } + } + } + } + opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 + // All transactions appear here the day after they are booked + parseGlsInner() + } + opt("BkToCstmrAcctRpt")?.each("Rpt") { // Camt.052 + // Transactions might appear here first before the end of the day + parseGlsInner() + } + opt("BkToCstmrDbtCdtNtfctn")?.each("Ntfctn") { // Camt.054 + // Instant transactions appear here a few seconds after being booked + opt("Acct") { + // Sanity check on currency and IBAN ? + } + each("Ntry") { + if (!isBooked()) return@each + val code = bankTransactionCode() + if (code.isReversal() || !code.isPayment()) return@each + val entryRef = opt("AcctSvcrRef")?.text() + val bookDate = executionDate() + val kind = one("CdtDbtInd").enum<Kind>() + val amount = amount(acceptedCurrency) + one("NtryDtls").one("TxDtls") { + val code = optBankTransactionCode() ?: code + val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() + val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("") + if (kind == Kind.CRDT) { + val bankId = one("Refs").opt("TxId")?.text() + val debtorPayto = opt("RltdPties") { payto("Dbtr") } + txsInfo.add(TxInfo.Credit( + ref = txRef ?: entryRef, + bookDate = bookDate, + // TODO use the bank ID again when Atruvia's implementation is fixed + bankId = null, + amount = amount, + subject = subject, + debtorPayto = debtorPayto, + code = code + )) + } + } + } + } + } + Dialect.postfinance -> { + opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 + /* + All transactions appear here on the day following their booking. Alas, some + necessary metadata is missing, which is only present in camt.054. However, + this file contains the structured return reasons that are missing from the + camt.054 files. That's why we only use this file for this purpose. + */ + opt("Acct") { + // Sanity check on currency and IBAN ? + } + each("Ntry") { + if (!isBooked()) return@each + val code = bankTransactionCode() + // Non reversal transaction are handled in camt.054 + if (!(code.isReversal() && code.isPayment())) return@each + + val entryRef = opt("AcctSvcrRef")?.text() + val bookDate = executionDate() + one("NtryDtls").one("TxDtls") { + val kind = one("CdtDbtInd").enum<Kind>() + val code = optBankTransactionCode() ?: code + if (kind == Kind.CRDT) { + val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() + val outgoingId = outgoingId() + val reason = returnReason() + txsInfo.add(TxInfo.CreditReversal( + ref = outgoingId.ref() ?: txRef ?: entryRef, + bookDate = bookDate, + id = outgoingId, + reason = reason, + code = code + )) + } + } + } + } + opt("BkToCstmrDbtCdtNtfctn")?.each("Ntfctn") { // Camt.054 + // Instant transactions appear here a moment after being booked + opt("Acct") { + // Sanity check on currency and IBAN ? + } + each("Ntry") { + if (!isBooked()) return@each + val code = bankTransactionCode() + // Reversal are handled from camt.053 + if (code.isReversal() || !code.isPayment()) return@each + + val entryRef = opt("AcctSvcrRef")?.text() + val bookDate = executionDate() + one("NtryDtls").each("TxDtls") { + val kind = one("CdtDbtInd").enum<Kind>() + val code = optBankTransactionCode() ?: code + val amount = amount(acceptedCurrency) + val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() + val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("") + when (kind) { + Kind.CRDT -> { + val bankId = opt("Refs")?.opt("UETR")?.text() + val debtorPayto = opt("RltdPties") { payto("Dbtr") } + txsInfo.add(TxInfo.Credit( + ref = bankId ?: txRef ?: entryRef, + bookDate = bookDate, + bankId = bankId, + amount = amount, + subject = subject, + debtorPayto = debtorPayto, + code = code + )) + } + Kind.DBIT -> { + val outgoingId = outgoingId() + val creditorPayto = opt("RltdPties") { payto("Cdtr") } + txsInfo.add(TxInfo.Debit( + ref = outgoingId.ref() ?: txRef ?: entryRef, + bookDate = bookDate, + id = outgoingId, + amount = amount, + subject = subject, + creditorPayto = creditorPayto, + code = code + )) + } + } + } + } + } + } + }} + + return txsInfo.mapNotNull { + try { + parseTxLogic(it) + } catch (e: TxErr) { + // TODO: add more info in doc or in log message? + logger.warn("skip incomplete tx: ${e.msg}") + null + } + } +} + +private sealed interface TxInfo { + // Bank provider ref for debugging + val ref: String? + // When was this transaction booked + val bookDate: Instant + // ISO20022 bank transaction code + val code: BankTransactionCode + data class CreditReversal( + override val ref: String?, + override val bookDate: Instant, + override val code: BankTransactionCode, + // Unique ID generated by libeufin-nexus + val id: OutgoingId, + val reason: String + ): TxInfo + data class Credit( + override val ref: String?, + override val bookDate: Instant, + override val code: BankTransactionCode, + // Unique ID generated by payment provider + val bankId: String?, + val amount: TalerAmount, + val subject: String?, + val debtorPayto: String? + ): TxInfo + data class Debit( + override val ref: String?, + override val bookDate: Instant, + override val code: BankTransactionCode, + // Unique ID generated by libeufin-nexus + val id: OutgoingId, + val amount: TalerAmount, + val subject: String?, + val creditorPayto: String? + ): TxInfo +} + +private fun parseTxLogic(info: TxInfo): TxNotification { + return when (info) { + is TxInfo.CreditReversal -> { + if (info.id.endToEndId == null) + throw TxErr("missing end-to-end ID for Credit reversal ${info.ref}") + OutgoingReversal( + endToEndId = info.id.endToEndId!!, + msgId = info.id.msgId, + reason = info.reason, + executionTime = info.bookDate + ) + } + is TxInfo.Credit -> { + /*if (info.bankId == null) TODO use the bank ID again when Atruvia's implementation is fixed + throw TxErr("missing bank ID for Credit ${info.ref}")*/ + if (info.subject == null) + throw TxErr("missing subject for Credit ${info.ref}") + if (info.debtorPayto == null) + throw TxErr("missing debtor info for Credit ${info.ref}") + IncomingPayment( + amount = info.amount, + bankId = info.bankId, + debitPaytoUri = info.debtorPayto, + executionTime = info.bookDate, + wireTransferSubject = info.subject + ) + } + is TxInfo.Debit -> { + if (info.id.endToEndId == null && info.id.msgId == null) { + throw TxErr("missing end-to-end ID for Debit ${info.ref}") + } else if (info.id.endToEndId != null) { + OutgoingPayment( + amount = info.amount, + endToEndId = info.id.endToEndId, + msgId = info.id.msgId, + executionTime = info.bookDate, + creditPaytoUri = info.creditorPayto, + wireTransferSubject = info.subject + ) + } else { + OutgoingBatch( + msgId = info.id.msgId!!, + executionTime = info.bookDate, + ) + } + } + } +} + +data class BankTransactionCode( + val domain: ExternalBankTransactionDomainCode, + val family: ExternalBankTransactionFamilyCode, + val subFamily: ExternalBankTransactionSubFamilyCode +) { + fun isReversal(): Boolean = REVERSAL_CODE.contains(subFamily) + fun isPayment(): Boolean = domain == ExternalBankTransactionDomainCode.PMNT || subFamily == ExternalBankTransactionSubFamilyCode.PSTE + + override fun toString(): String = + "${domain.name} ${family.name} ${subFamily.name} - '${domain.description}' '${family.description}' '${subFamily.description}'" + + companion object { + private val REVERSAL_CODE = setOf( + ExternalBankTransactionSubFamilyCode.RPCR, + ExternalBankTransactionSubFamilyCode.RRTN, + ExternalBankTransactionSubFamilyCode.PSTE, + ) + } +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/hac.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/hac.kt @@ -0,0 +1,77 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ +package tech.libeufin.nexus.iso20022 + +import tech.libeufin.common.* +import tech.libeufin.nexus.* +import tech.libeufin.nexus.ebics.Dialect +import java.io.InputStream +import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +data class CustomerAck( + val actionType: HacAction, + val orderId: String?, + val code: ExternalStatusReasonCode?, + val info: String, + val timestamp: Instant +) { + fun msg(): String = buildString { + append("$actionType") + if (code != null) append(" ${code.isoCode}") + append(" - '${actionType.description}'") + if (code != null) append(" '${code.description}'") + if (info != "") append(" - '$info'") + } + + override fun toString(): String = buildString { + append(timestamp.fmtDateTime()) + if (orderId != null) append(" $orderId") + append(" ${msg()}") + } +} + +/** Parse HAC pain.002 XML file */ +fun parseCustomerAck(xml: InputStream): List<CustomerAck> { + return XmlDestructor.fromStream(xml, "Document") { + one("CstmrPmtStsRpt").map("OrgnlPmtInfAndSts") { + val actionType = one("OrgnlPmtInfId").enum<HacAction>() + one("StsRsnInf") { + var timestamp: Instant? = null + var orderId: String? = null + one("Orgtr").one("Id").one("OrgId").each("Othr") { + val value = one("Id") + val key = one("SchmeNm").one("Prtry").text() + when (key) { + "TimeStamp" -> { + timestamp = value.dateTime().toInstant(ZoneOffset.UTC) + } + "OrderID" -> orderId = value.text() + } + } + val code = opt("Rsn")?.one("Cd")?.enum<ExternalStatusReasonCode>() + val info = map("AddtlInf") { text() }.joinToString("") + CustomerAck(actionType, orderId, code, info, requireNotNull(timestamp)) + } + } + } +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain001.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain001.kt @@ -0,0 +1,128 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ +package tech.libeufin.nexus.iso20022 + +import tech.libeufin.common.* +import tech.libeufin.nexus.* +import tech.libeufin.nexus.ebics.Dialect +import java.io.InputStream +import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +/** String representation of a Taler [amount] compatible with EBICS */ +fun getAmountNoCurrency(amount: TalerAmount): String { + if (amount.isSubCent()) { + throw Exception("Sub-cent amounts not supported") + } + return amount.number().toString() +} + +/** pain.001 transaction metadata */ +data class Pain001Tx( + val creditor: IbanAccountMetadata, + val amount: TalerAmount, + val subject: String, + val endToEndId: String +) + +/** pain.001 message metadata */ +data class Pain001Msg( + val messageId: String, + val timestamp: Instant, + val debtor: IbanAccountMetadata, + val sum: TalerAmount, + val txs: List<Pain001Tx> +) + +/** Create a pain.001 XML document [msg] valid for [dialect] */ +fun createPain001( + msg: Pain001Msg, + dialect: Dialect +): ByteArray { + val version = "09" + val zonedTimestamp = ZonedDateTime.ofInstant(msg.timestamp, ZoneId.of("UTC")) + val totalAmount = getAmountNoCurrency(msg.sum) + return XmlBuilder.toBytes("Document") { + attr("xmlns", "urn:iso:std:iso:20022:tech:xsd:pain.001.001.$version") + attr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + attr("xsi:schemaLocation", "urn:iso:std:iso:20022:tech:xsd:pain.001.001.$version pain.001.001.$version.xsd") + el("CstmrCdtTrfInitn") { + el("GrpHdr") { + // Used for idempotency as banks will refuse to process EBICS request with the same MsgId for a pre-agreed period + // Used to uniquely identify batches of transactions in other files + el("MsgId", msg.messageId) + el("CreDtTm", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedTimestamp)) + el("NbOfTxs", msg.txs.size.toString()) + el("CtrlSum", totalAmount) + el("InitgPty/Nm", msg.debtor.name) + } + el("PmtInf") { + el("PmtInfId", "NOTPROVIDED") + el("PmtMtd", "TRF") + el("BtchBookg", "false") + el("NbOfTxs", msg.txs.size.toString()) + el("CtrlSum", totalAmount) + el("PmtTpInf/SvcLvl/Cd", + when (dialect) { + Dialect.postfinance -> "SDVA" + Dialect.gls -> "SEPA" + } + ) + el("ReqdExctnDt/Dt", DateTimeFormatter.ISO_DATE.format(zonedTimestamp)) + el("Dbtr/Nm", msg.debtor.name) + el("DbtrAcct/Id/IBAN", msg.debtor.iban) + el("DbtrAgt/FinInstnId") { + if (msg.debtor.bic != null) { + el("BICFI", msg.debtor.bic) + } else { + el("Othr/Id", "NOTPROVIDED") + } + } + el("ChrgBr", "SLEV") + for (tx in msg.txs) { + el("CdtTrfTxInf") { + el("PmtId") { + el("InstrId", tx.endToEndId) + // Used to uniquely identify transactions in other files + el("EndToEndId", tx.endToEndId) + } + el("Amt/InstdAmt") { + attr("Ccy", tx.amount.currency) + text(getAmountNoCurrency(tx.amount)) + } + if (tx.creditor.bic != null) el("CdtrAgt/FinInstnId/BICFI", tx.creditor.bic) + el("Cdtr") { + el("Nm", tx.creditor.name) + // Addr might become a requirement in the future + /*el("PstlAdr") { + el("TwnNm", "Bochum") + el("Ctry", "DE") + }*/ + } + el("CdtrAcct/Id/IBAN", tx.creditor.iban) + el("RmtInf/Ustrd", tx.subject) + } + } + } + } + } +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain002.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain002.kt @@ -0,0 +1,155 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ +package tech.libeufin.nexus.iso20022 + +import tech.libeufin.common.* +import tech.libeufin.nexus.* +import tech.libeufin.nexus.ebics.Dialect +import java.io.InputStream +import java.time.Instant +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +private fun fmtMsg(code: String?, description: String?, reasons: List<Reason>) = buildString { + if (code != null) { + append(code) + append(" ") + if (description != null) { + append("'") + append(description) + append("'") + } + if (reasons.isNotEmpty()) { + append(":") + } + } + for (reason in reasons) { + append(" ") + append(reason.code.isoCode) + append(" '") + append(reason.code.description) + append("'") + if (reason.information.isNotEmpty()) { + append(" '") + append(reason.information) + append("'") + } + } +} + +data class MsgStatus( + val id: String, + val code: ExternalPaymentGroupStatusCode?, + val reasons: List<Reason>, + val payments: List<PmtStatus> +) { + fun msg() = fmtMsg(code?.isoCode, code?.description, reasons) + override fun toString() = buildString { + append(id) + val msg = msg() + if (msg.isNotEmpty()) { + append(" ") + append(msg) + } + for (pmt in payments) { + append("\n>") + append(pmt.id) + val msg = pmt.msg() + if (msg.isNotEmpty()) { + append(" ") + append(msg) + } + + for (tx in pmt.transactions) { + append("\n>>") + if (tx.id != tx.endToEndId) { + append(tx.id) + append(" ") + } + append(tx.endToEndId) + val msg = tx.msg() + if (msg.isNotEmpty()) { + append(" ") + append(msg) + } + } + } + } +} + +data class PmtStatus( + val id: String, + val code: ExternalPaymentGroupStatusCode?, + val reasons: List<Reason>, + val transactions: List<TxStatus> +) { + fun msg() = fmtMsg(code?.isoCode, code?.description, reasons) +} + +data class TxStatus( + val id: String, + val endToEndId: String, + val code: ExternalPaymentTransactionStatusCode, + val reasons: List<Reason> +) { + fun msg() = fmtMsg(code?.isoCode, code?.description, reasons) +} + +data class Reason ( + val code: ExternalStatusReasonCode, + val information: String +) + +/** Parse pain.002 XML file */ +fun parseCustomerPaymentStatusReport(xml: InputStream): MsgStatus { + fun XmlDestructor.reasons(): List<Reason> { + return map("StsRsnInf") { + val code = one("Rsn").one("Cd").enum<ExternalStatusReasonCode>() + val info = map("AddtlInf") { text() }.joinToString("") + Reason(code, info) + } + } + + return XmlDestructor.fromStream(xml, "Document") { + one("CstmrPmtStsRpt") { + val (id, code, reasons) = one("OrgnlGrpInfAndSts") { + val id = one("OrgnlMsgId").text() + val code = opt("GrpSts")?.enum<ExternalPaymentGroupStatusCode>() + val reasons = reasons() + Triple(id, code, reasons) + } + val payments = map("OrgnlPmtInfAndSts") { + val id = one("OrgnlPmtInfId").text() + val code = opt("PmtInfSts")?.enum<ExternalPaymentGroupStatusCode>() + val reasons = reasons() + val transactions = map("TxInfAndSts") { + val id = one("OrgnlInstrId").text() + val endToEndId = one("OrgnlEndToEndId").text() + val code = one("TxSts").enum<ExternalPaymentTransactionStatusCode>() + val reasons = reasons() + TxStatus(id, endToEndId, code, reasons) + } + PmtStatus(id, code, reasons, transactions) + } + MsgStatus(id, code, reasons, payments) + } + } +} +\ No newline at end of file diff --git a/nexus/src/test/kotlin/IngestionTest.kt b/nexus/src/test/kotlin/IngestionTest.kt @@ -23,6 +23,7 @@ import tech.libeufin.common.db.* import tech.libeufin.nexus.db.* import tech.libeufin.nexus.ebics.* import tech.libeufin.nexus.cli.* +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.* import java.nio.file.Files import java.time.Instant diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -20,6 +20,7 @@ import org.junit.Test import tech.libeufin.common.* import tech.libeufin.nexus.ebics.Dialect +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.* import kotlin.io.path.* import kotlin.test.* diff --git a/nexus/src/test/kotlin/helpers.kt b/nexus/src/test/kotlin/helpers.kt @@ -28,6 +28,7 @@ import tech.libeufin.common.* import tech.libeufin.common.db.dbInit import tech.libeufin.common.db.pgDataSource import tech.libeufin.nexus.* +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.cli.ingestIncomingPayment import tech.libeufin.nexus.cli.ingestOutgoingPayment import tech.libeufin.nexus.db.Database diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt @@ -36,11 +36,11 @@ import tech.libeufin.common.* import tech.libeufin.common.api.engine import tech.libeufin.common.db.one import tech.libeufin.nexus.AccountType -import tech.libeufin.nexus.IncomingPayment import tech.libeufin.nexus.cli.LibeufinNexus import tech.libeufin.nexus.cli.ingestIncomingPayment import tech.libeufin.nexus.nexusConfig import tech.libeufin.nexus.withDb +import tech.libeufin.nexus.iso20022.* import java.time.Instant import kotlin.io.path.Path import kotlin.io.path.readText diff --git a/testbench/src/test/kotlin/Iso20022Test.kt b/testbench/src/test/kotlin/Iso20022Test.kt @@ -19,6 +19,7 @@ import org.junit.Test import tech.libeufin.nexus.ebics.* +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.* import java.nio.file.Files import java.nio.file.Path