summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-07 23:26:50 +0100
committerAntoine A <>2024-03-07 23:26:50 +0100
commit854803e6510eed234844c1d58930ff74ee4f054e (patch)
tree59d18980c07dfe7decae72f97ae5a467dd341ed7
parent6f079d6469f6d5ab9e603e27b8e965215d38587c (diff)
downloadlibeufin-854803e6510eed234844c1d58930ff74ee4f054e.tar.gz
libeufin-854803e6510eed234844c1d58930ff74ee4f054e.tar.bz2
libeufin-854803e6510eed234844c1d58930ff74ee4f054e.zip
Clean some code
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt288
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt47
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt121
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt179
4 files changed, 295 insertions, 340 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt
deleted file mode 100644
index 6cd031ac..00000000
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt
+++ /dev/null
@@ -1,288 +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 is the main "EBICS library interface". Functions here are stateless helpers
- * used to implement both an EBICS server and EBICS client.
- */
-
-package tech.libeufin.nexus.ebics
-
-import io.ktor.http.*
-import org.w3c.dom.Document
-import tech.libeufin.common.crypto.CryptoUtil
-import tech.libeufin.common.*
-import tech.libeufin.nexus.*
-import java.io.InputStream
-import java.security.SecureRandom
-import java.security.interfaces.RSAPrivateCrtKey
-import java.security.interfaces.RSAPublicKey
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import javax.xml.datatype.DatatypeFactory
-import javax.xml.datatype.XMLGregorianCalendar
-
-data class EbicsProtocolError(
- val httpStatusCode: HttpStatusCode,
- val reason: String,
- /**
- * This class is also used when Nexus finds itself
- * in an inconsistent state, without interacting with the
- * bank. In this case, the EBICS code below can be left
- * null.
- */
- val ebicsTechnicalCode: EbicsReturnCode? = null
-) : Exception(reason)
-
-/**
- * @param size in bits
- */
-fun getNonce(size: Int): ByteArray {
- val sr = SecureRandom()
- val ret = ByteArray(size / 8)
- sr.nextBytes(ret)
- return ret
-}
-
-data class PreparedUploadData(
- val transactionKey: ByteArray,
- val userSignatureDataEncrypted: ByteArray,
- val dataDigest: ByteArray,
- val encryptedPayloadChunks: List<String>
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as PreparedUploadData
-
- if (!transactionKey.contentEquals(other.transactionKey)) return false
- if (!userSignatureDataEncrypted.contentEquals(other.userSignatureDataEncrypted)) return false
- if (encryptedPayloadChunks != other.encryptedPayloadChunks) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = transactionKey.contentHashCode()
- result = 31 * result + userSignatureDataEncrypted.contentHashCode()
- result = 31 * result + encryptedPayloadChunks.hashCode()
- return result
- }
-}
-
-data class DataEncryptionInfo(
- val transactionKey: ByteArray,
- val bankPubDigest: ByteArray
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as DataEncryptionInfo
-
- if (!transactionKey.contentEquals(other.transactionKey)) return false
- if (!bankPubDigest.contentEquals(other.bankPubDigest)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = transactionKey.contentHashCode()
- result = 31 * result + bankPubDigest.contentHashCode()
- return result
- }
-}
-
-
-// TODO import missing using a script
-@Suppress("SpellCheckingInspection")
-enum class EbicsReturnCode(val errorCode: String) {
- EBICS_OK("000000"),
- EBICS_DOWNLOAD_POSTPROCESS_DONE("011000"),
- EBICS_DOWNLOAD_POSTPROCESS_SKIPPED("011001"),
- EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"),
- EBICS_AUTHENTICATION_FAILED("061001"),
- EBICS_INVALID_REQUEST("061002"),
- EBICS_INTERNAL_ERROR("061099"),
- EBICS_TX_RECOVERY_SYNC("061101"),
- EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
- EBICS_INVALID_ORDER_DATA_FORMAT("090004"),
- EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"),
- EBICS_INVALID_USER_OR_USER_STATE("091002"),
- EBICS_USER_UNKNOWN("091003"),
- EBICS_EBICS_INVALID_USER_STATE("091004"),
- EBICS_INVALID_ORDER_IDENTIFIER("091005"),
- EBICS_UNSUPPORTED_ORDER_TYPE("091006"),
- EBICS_INVALID_XML("091010"),
- EBICS_TX_MESSAGE_REPLAY("091103"),
- EBICS_PROCESSING_ERROR("091116"),
- EBICS_ACCOUNT_AUTHORISATION_FAILED("091302"),
- EBICS_AMOUNT_CHECK_FAILED("091303");
-
- companion object {
- fun lookup(errorCode: String): EbicsReturnCode {
- for (x in entries) {
- if (x.errorCode == errorCode) {
- return x
- }
- }
- throw Exception(
- "Unknown EBICS status code: $errorCode"
- )
- }
- }
-}
-
-
-fun signOrderEbics3(
- orderBlob: ByteArray,
- signKey: RSAPrivateCrtKey,
- partnerId: String,
- userId: String
-): ByteArray {
- return XmlBuilder.toString("UserSignatureData") {
- attr("xmlns", "http://www.ebics.org/S002")
- el("OrderSignatureData") {
- el("SignatureVersion", "A006")
- el("SignatureValue", CryptoUtil.signEbicsA006(
- CryptoUtil.digestEbicsOrderA006(orderBlob),
- signKey
- ).encodeBase64())
- el("PartnerID", partnerId)
- el("UserID", userId)
- }
- }.toByteArray()
-}
-
-data class EbicsResponseContent(
- val transactionID: String?,
- val orderID: String?,
- val dataEncryptionInfo: DataEncryptionInfo?,
- val orderDataEncChunk: String?,
- val technicalReturnCode: EbicsReturnCode,
- val bankReturnCode: EbicsReturnCode,
- val reportText: String,
- val segmentNumber: Int?,
- // Only present in init phase
- val numSegments: Int?
-)
-
-data class EbicsKeyManagementResponseContent(
- val technicalReturnCode: EbicsReturnCode,
- val bankReturnCode: EbicsReturnCode?,
- val orderData: ByteArray?
-)
-
-
-class HpbResponseData(
- val hostID: String,
- val encryptionPubKey: RSAPublicKey,
- val encryptionVersion: String,
- val authenticationPubKey: RSAPublicKey,
- val authenticationVersion: String
-)
-
-
-fun ebics3toInternalRepr(response: Document): EbicsResponseContent {
- // TODO better ebics response type
- return XmlDestructor.fromDoc(response, "ebicsResponse") {
- var transactionID: String? = null
- var numSegments: Int? = null
- lateinit var technicalReturnCode: EbicsReturnCode
- lateinit var bankReturnCode: EbicsReturnCode
- lateinit var reportText: String
- var orderID: String? = null
- var segmentNumber: Int? = null
- var orderDataEncChunk: String? = null
- var dataEncryptionInfo: DataEncryptionInfo? = null
- one("header") {
- one("static") {
- transactionID = opt("TransactionID")?.text()
- numSegments = opt("NumSegments")?.text()?.toInt()
- }
- one("mutable") {
- segmentNumber = opt("SegmentNumber")?.text()?.toInt()
- orderID = opt("OrderID")?.text()
- technicalReturnCode = EbicsReturnCode.lookup(one("ReturnCode").text())
- reportText = one("ReportText").text()
- }
- }
- one("body") {
- opt("DataTransfer") {
- orderDataEncChunk = one("OrderData").text()
- dataEncryptionInfo = opt("DataEncryptionInfo") {
- DataEncryptionInfo(
- one("TransactionKey").text().decodeBase64(),
- one("EncryptionPubKeyDigest").text().decodeBase64()
- )
- }
- }
- bankReturnCode = EbicsReturnCode.lookup(one("ReturnCode").text())
- }
- EbicsResponseContent(
- transactionID = transactionID,
- orderID = orderID,
- bankReturnCode = bankReturnCode,
- technicalReturnCode = technicalReturnCode,
- reportText = reportText,
- orderDataEncChunk = orderDataEncChunk,
- dataEncryptionInfo = dataEncryptionInfo,
- numSegments = numSegments,
- segmentNumber = segmentNumber
- )
- }
-}
-
-fun parseEbicsHpbOrder(orderDataRaw: InputStream): HpbResponseData {
- return XmlDestructor.fromStream(orderDataRaw, "HPBResponseOrderData") {
- val (authenticationPubKey, authenticationVersion) = one("AuthenticationPubKeyInfo") {
- Pair(
- one("PubKeyValue").one("RSAKeyValue") {
- CryptoUtil.loadRsaPublicKeyFromComponents(
- one("Modulus").text().decodeBase64(),
- one("Exponent").text().decodeBase64(),
- )
- },
- one("AuthenticationVersion").text()
- )
- }
- val (encryptionPubKey, encryptionVersion) = one("EncryptionPubKeyInfo") {
- Pair(
- one("PubKeyValue").one("RSAKeyValue") {
- CryptoUtil.loadRsaPublicKeyFromComponents(
- one("Modulus").text().decodeBase64(),
- one("Exponent").text().decodeBase64(),
- )
- },
- one("EncryptionVersion").text()
- )
-
- }
- val hostID: String = one("HostID").text()
- HpbResponseData(
- hostID = hostID,
- encryptionPubKey = encryptionPubKey,
- encryptionVersion = encryptionVersion,
- authenticationPubKey = authenticationPubKey,
- authenticationVersion = authenticationVersion
- )
- }
-} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
index 0879d3c3..70ec30ab 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
@@ -25,17 +25,18 @@ package tech.libeufin.nexus.ebics
import org.slf4j.Logger
import org.slf4j.LoggerFactory
+import tech.libeufin.common.crypto.CryptoUtil
import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.BankPublicKeysFile
import tech.libeufin.nexus.ClientPrivateKeysFile
import tech.libeufin.nexus.EbicsSetupConfig
import java.io.InputStream
-import java.security.interfaces.RSAPrivateCrtKey
import java.time.Instant
import java.time.ZoneId
import java.util.*
import javax.xml.datatype.DatatypeFactory
+import java.security.interfaces.*
private val logger: Logger = LoggerFactory.getLogger("libeufin-nexus-ebics2")
@@ -220,4 +221,48 @@ fun generateHpbMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile)
}
XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key)
return XMLUtil.convertDomToBytes(doc)
+}
+
+class HpbResponseData(
+ val hostID: String,
+ val encryptionPubKey: RSAPublicKey,
+ val encryptionVersion: String,
+ val authenticationPubKey: RSAPublicKey,
+ val authenticationVersion: String
+)
+
+fun parseEbicsHpbOrder(orderDataRaw: InputStream): HpbResponseData {
+ return XmlDestructor.fromStream(orderDataRaw, "HPBResponseOrderData") {
+ val (authenticationPubKey, authenticationVersion) = one("AuthenticationPubKeyInfo") {
+ Pair(
+ one("PubKeyValue").one("RSAKeyValue") {
+ CryptoUtil.loadRsaPublicKeyFromComponents(
+ one("Modulus").text().decodeBase64(),
+ one("Exponent").text().decodeBase64(),
+ )
+ },
+ one("AuthenticationVersion").text()
+ )
+ }
+ val (encryptionPubKey, encryptionVersion) = one("EncryptionPubKeyInfo") {
+ Pair(
+ one("PubKeyValue").one("RSAKeyValue") {
+ CryptoUtil.loadRsaPublicKeyFromComponents(
+ one("Modulus").text().decodeBase64(),
+ one("Exponent").text().decodeBase64(),
+ )
+ },
+ one("EncryptionVersion").text()
+ )
+
+ }
+ val hostID: String = one("HostID").text()
+ HpbResponseData(
+ hostID = hostID,
+ encryptionPubKey = encryptionPubKey,
+ encryptionVersion = encryptionVersion,
+ authenticationPubKey = authenticationPubKey,
+ authenticationVersion = authenticationVersion
+ )
+ }
} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
index 993c209c..7cdbc2d4 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
@@ -30,13 +30,9 @@ import java.io.File
import org.w3c.dom.*
import javax.xml.datatype.XMLGregorianCalendar
import javax.xml.datatype.DatatypeFactory
+import java.security.interfaces.*
-//fun String.toDate(): LocalDate = LocalDate.parse(this, DateTimeFormatter.ISO_DATE)
-//fun String.toDateTime(): LocalDateTime = LocalDateTime.parse(this, DateTimeFormatter.ISO_DATE_TIME)
-//fun String.toYearMonth(): YearMonth = YearMonth.parse(this, DateTimeFormatter.ISO_DATE)
-//fun String.toYear(): Year = Year.parse(this, DateTimeFormatter.ISO_DATE)
-
fun Instant.xmlDate(): String = DateTimeFormatter.ISO_DATE.withZone(ZoneId.of("UTC")).format(this)
fun Instant.xmlDateTime(): String = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("UTC")).format(this)
@@ -48,8 +44,7 @@ data class Ebics3Service(
val container: String?
)
-
-
+// TODO WIP
fun iniRequest(
cfg: EbicsSetupConfig,
clientKeys: ClientPrivateKeysFile
@@ -337,47 +332,73 @@ class Ebics3Impl(
}
}
-// TODO this function should not be here
-/**
- * Collects all the steps to prepare the submission of a pain.001
- * document to the bank, and finally send it. Indirectly throws
- * [EbicsSideException] or [EbicsUploadException]. The first means
- * that the bank sent an invalid response or signature, the second
- * that a proper EBICS or business error took place. The caller must
- * catch those exceptions and decide the retry policy.
- *
- * @param pain001xml pain.001 document in XML. The caller should
- * ensure its validity.
- * @param cfg configuration handle.
- * @param clientKeys client private keys.
- * @param bankkeys bank public keys.
- * @param httpClient HTTP client to connect to the bank.
- */
-suspend fun submitPain001(
- pain001xml: String,
- cfg: EbicsSetupConfig,
- clientKeys: ClientPrivateKeysFile,
- bankkeys: BankPublicKeysFile,
- httpClient: HttpClient
-): String {
- val service = Ebics3Service(
- name = "MCT",
- scope = "CH",
- messageName = "pain.001",
- messageVersion = "09",
- container = null
- )
- val maybeUploaded = doEbicsUpload(
- httpClient,
- cfg,
- clientKeys,
- bankkeys,
- service,
- pain001xml.toByteArray(Charsets.UTF_8),
- )
- logger.debug("Payment submitted, report text is: ${maybeUploaded.reportText}," +
- " EBICS technical code is: ${maybeUploaded.technicalReturnCode}," +
- " bank technical return code is: ${maybeUploaded.bankReturnCode}"
- )
- return maybeUploaded.orderID!!
+fun signOrderEbics3(
+ orderBlob: ByteArray,
+ signKey: RSAPrivateCrtKey,
+ partnerId: String,
+ userId: String
+): ByteArray {
+ return XmlBuilder.toString("UserSignatureData") {
+ attr("xmlns", "http://www.ebics.org/S002")
+ el("OrderSignatureData") {
+ el("SignatureVersion", "A006")
+ el("SignatureValue", CryptoUtil.signEbicsA006(
+ CryptoUtil.digestEbicsOrderA006(orderBlob),
+ signKey
+ ).encodeBase64())
+ el("PartnerID", partnerId)
+ el("UserID", userId)
+ }
+ }.toByteArray()
+}
+
+
+fun parseEbics3Response(response: Document): EbicsResponseContent {
+ // TODO better ebics response type
+ return XmlDestructor.fromDoc(response, "ebicsResponse") {
+ var transactionID: String? = null
+ var numSegments: Int? = null
+ lateinit var technicalReturnCode: EbicsReturnCode
+ lateinit var bankReturnCode: EbicsReturnCode
+ lateinit var reportText: String
+ var orderID: String? = null
+ var segmentNumber: Int? = null
+ var orderDataEncChunk: String? = null
+ var dataEncryptionInfo: DataEncryptionInfo? = null
+ one("header") {
+ one("static") {
+ transactionID = opt("TransactionID")?.text()
+ numSegments = opt("NumSegments")?.text()?.toInt()
+ }
+ one("mutable") {
+ segmentNumber = opt("SegmentNumber")?.text()?.toInt()
+ orderID = opt("OrderID")?.text()
+ technicalReturnCode = EbicsReturnCode.lookup(one("ReturnCode").text())
+ reportText = one("ReportText").text()
+ }
+ }
+ one("body") {
+ opt("DataTransfer") {
+ orderDataEncChunk = one("OrderData").text()
+ dataEncryptionInfo = opt("DataEncryptionInfo") {
+ DataEncryptionInfo(
+ one("TransactionKey").text().decodeBase64(),
+ one("EncryptionPubKeyDigest").text().decodeBase64()
+ )
+ }
+ }
+ bankReturnCode = EbicsReturnCode.lookup(one("ReturnCode").text())
+ }
+ EbicsResponseContent(
+ transactionID = transactionID,
+ orderID = orderID,
+ bankReturnCode = bankReturnCode,
+ technicalReturnCode = technicalReturnCode,
+ reportText = reportText,
+ orderDataEncChunk = orderDataEncChunk,
+ dataEncryptionInfo = dataEncryptionInfo,
+ numSegments = numSegments,
+ segmentNumber = segmentNumber
+ )
+ }
} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
index 228580d3..9144997e 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -54,6 +54,7 @@ import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import kotlinx.coroutines.*
+import java.security.SecureRandom
/**
* Available EBICS versions.
@@ -247,7 +248,7 @@ suspend fun postEbics(
)
}
- return ebics3toInternalRepr(doc)
+ return parseEbics3Response(doc)
}
/**
@@ -543,4 +544,180 @@ suspend fun doEbicsUpload(
)
// EBICS- and bank-technical codes were both EBICS_OK, success!
transferResp
+}
+
+
+data class EbicsProtocolError(
+ val httpStatusCode: HttpStatusCode,
+ val reason: String,
+ /**
+ * This class is also used when Nexus finds itself
+ * in an inconsistent state, without interacting with the
+ * bank. In this case, the EBICS code below can be left
+ * null.
+ */
+ val ebicsTechnicalCode: EbicsReturnCode? = null
+) : Exception(reason)
+
+/**
+ * @param size in bits
+ */
+fun getNonce(size: Int): ByteArray {
+ val sr = SecureRandom()
+ val ret = ByteArray(size / 8)
+ sr.nextBytes(ret)
+ return ret
+}
+
+data class PreparedUploadData(
+ val transactionKey: ByteArray,
+ val userSignatureDataEncrypted: ByteArray,
+ val dataDigest: ByteArray,
+ val encryptedPayloadChunks: List<String>
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as PreparedUploadData
+
+ if (!transactionKey.contentEquals(other.transactionKey)) return false
+ if (!userSignatureDataEncrypted.contentEquals(other.userSignatureDataEncrypted)) return false
+ if (encryptedPayloadChunks != other.encryptedPayloadChunks) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = transactionKey.contentHashCode()
+ result = 31 * result + userSignatureDataEncrypted.contentHashCode()
+ result = 31 * result + encryptedPayloadChunks.hashCode()
+ return result
+ }
+}
+
+data class DataEncryptionInfo(
+ val transactionKey: ByteArray,
+ val bankPubDigest: ByteArray
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as DataEncryptionInfo
+
+ if (!transactionKey.contentEquals(other.transactionKey)) return false
+ if (!bankPubDigest.contentEquals(other.bankPubDigest)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = transactionKey.contentHashCode()
+ result = 31 * result + bankPubDigest.contentHashCode()
+ return result
+ }
+}
+
+
+// TODO import missing using a script
+@Suppress("SpellCheckingInspection")
+enum class EbicsReturnCode(val errorCode: String) {
+ EBICS_OK("000000"),
+ EBICS_DOWNLOAD_POSTPROCESS_DONE("011000"),
+ EBICS_DOWNLOAD_POSTPROCESS_SKIPPED("011001"),
+ EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"),
+ EBICS_AUTHENTICATION_FAILED("061001"),
+ EBICS_INVALID_REQUEST("061002"),
+ EBICS_INTERNAL_ERROR("061099"),
+ EBICS_TX_RECOVERY_SYNC("061101"),
+ EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
+ EBICS_INVALID_ORDER_DATA_FORMAT("090004"),
+ EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"),
+ EBICS_INVALID_USER_OR_USER_STATE("091002"),
+ EBICS_USER_UNKNOWN("091003"),
+ EBICS_EBICS_INVALID_USER_STATE("091004"),
+ EBICS_INVALID_ORDER_IDENTIFIER("091005"),
+ EBICS_UNSUPPORTED_ORDER_TYPE("091006"),
+ EBICS_INVALID_XML("091010"),
+ EBICS_TX_MESSAGE_REPLAY("091103"),
+ EBICS_PROCESSING_ERROR("091116"),
+ EBICS_ACCOUNT_AUTHORISATION_FAILED("091302"),
+ EBICS_AMOUNT_CHECK_FAILED("091303");
+
+ companion object {
+ fun lookup(errorCode: String): EbicsReturnCode {
+ for (x in entries) {
+ if (x.errorCode == errorCode) {
+ return x
+ }
+ }
+ throw Exception(
+ "Unknown EBICS status code: $errorCode"
+ )
+ }
+ }
+}
+
+data class EbicsResponseContent(
+ val transactionID: String?,
+ val orderID: String?,
+ val dataEncryptionInfo: DataEncryptionInfo?,
+ val orderDataEncChunk: String?,
+ val technicalReturnCode: EbicsReturnCode,
+ val bankReturnCode: EbicsReturnCode,
+ val reportText: String,
+ val segmentNumber: Int?,
+ // Only present in init phase
+ val numSegments: Int?
+)
+
+data class EbicsKeyManagementResponseContent(
+ val technicalReturnCode: EbicsReturnCode,
+ val bankReturnCode: EbicsReturnCode?,
+ val orderData: ByteArray?
+)
+
+/**
+ * Collects all the steps to prepare the submission of a pain.001
+ * document to the bank, and finally send it. Indirectly throws
+ * [EbicsSideException] or [EbicsUploadException]. The first means
+ * that the bank sent an invalid response or signature, the second
+ * that a proper EBICS or business error took place. The caller must
+ * catch those exceptions and decide the retry policy.
+ *
+ * @param pain001xml pain.001 document in XML. The caller should
+ * ensure its validity.
+ * @param cfg configuration handle.
+ * @param clientKeys client private keys.
+ * @param bankkeys bank public keys.
+ * @param httpClient HTTP client to connect to the bank.
+ */
+suspend fun submitPain001(
+ pain001xml: String,
+ cfg: EbicsSetupConfig,
+ clientKeys: ClientPrivateKeysFile,
+ bankkeys: BankPublicKeysFile,
+ httpClient: HttpClient
+): String {
+ val service = Ebics3Service(
+ name = "MCT",
+ scope = "CH",
+ messageName = "pain.001",
+ messageVersion = "09",
+ container = null
+ )
+ val maybeUploaded = doEbicsUpload(
+ httpClient,
+ cfg,
+ clientKeys,
+ bankkeys,
+ service,
+ pain001xml.toByteArray(Charsets.UTF_8),
+ )
+ logger.debug("Payment submitted, report text is: ${maybeUploaded.reportText}," +
+ " EBICS technical code is: ${maybeUploaded.technicalReturnCode}," +
+ " bank technical return code is: ${maybeUploaded.bankReturnCode}"
+ )
+ return maybeUploaded.orderID!!
} \ No newline at end of file