commit ce91844da940b21187b13b5db08b1a19b4e536a5
parent 8487b5351280a80d721a46ee8ce30cfd29de7bec
Author: MS <ms@taler.net>
Date: Wed, 25 Oct 2023 15:26:50 +0200
nexus: drafting pain.001 generation
Diffstat:
4 files changed, 243 insertions(+), 91 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Ebics3.kt
@@ -1,90 +0,0 @@
-package tech.libeufin.nexus
-
-import tech.libeufin.util.PreparedUploadData
-import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h005.Ebics3Request
-import tech.libeufin.util.getNonce
-import tech.libeufin.util.toHexString
-import java.math.BigInteger
-import java.util.*
-import javax.xml.datatype.DatatypeFactory
-
-/**
- * Creates the EBICS 3 document for the init phase of an upload
- * transaction.
- *
- * @param cfg configuration handle.
- * @param preparedUploadData business payload to send.
- * @param bankkeys bank public keys.
- * @param clientKeys client private keys.
- * @param orderService EBICS 3 document defining the request type
- * @return raw XML of the EBICS 3 init phase.
- */
-fun createEbics3RequestForUploadInitialization(
- cfg: EbicsSetupConfig,
- preparedUploadData: PreparedUploadData,
- bankkeys: BankPublicKeysFile,
- clientKeys: ClientPrivateKeysFile,
- orderService: Ebics3Request.OrderDetails.Service
-): String {
- val nonce = getNonce(128)
- val req = Ebics3Request.createForUploadInitializationPhase(
- preparedUploadData.transactionKey,
- preparedUploadData.userSignatureDataEncrypted,
- preparedUploadData.dataDigest,
- cfg.ebicsHostId,
- nonce,
- cfg.ebicsPartnerId,
- cfg.ebicsUserId,
- DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
- bankkeys.bank_authentication_public_key,
- bankkeys.bank_encryption_public_key,
- BigInteger.ONE,
- orderService
- )
- val doc = XMLUtil.convertJaxbToDocument(
- req,
- withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd"
- )
- tech.libeufin.util.logger.debug("Created EBICS 3 document for upload initialization," +
- " nonce: ${nonce.toHexString()}")
- XMLUtil.signEbicsDocument(
- doc,
- clientKeys.authentication_private_key,
- withEbics3 = true
- )
- return XMLUtil.convertDomToString(doc)
-}
-
-/**
- * Crafts one EBICS 3 request for the upload transfer phase. Currently
- * only 1-chunk payloads are supported.
- *
- * @param cfg configuration handle.
- * @param clientKeys client private keys.
- * @param transactionId EBICS transaction ID obtained from an init phase.
- * @param uploadData business content to upload.
- *
- * @return raw XML document.
- */
-fun createEbics3RequestForUploadTransferPhase(
- cfg: EbicsSetupConfig,
- clientKeys: ClientPrivateKeysFile,
- transactionId: String,
- uploadData: PreparedUploadData
-): String {
- val chunkIndex = 1 // only 1-chunk communication currently supported.
- val req = Ebics3Request.createForUploadTransferPhase(
- cfg.ebicsHostId,
- transactionId,
- BigInteger.valueOf(chunkIndex.toLong()),
- uploadData.encryptedPayloadChunks[chunkIndex]
- )
- val doc = XMLUtil.convertJaxbToDocument(req)
- XMLUtil.signEbicsDocument(
- doc,
- clientKeys.authentication_private_key,
- withEbics3 = true
- )
- return XMLUtil.convertDomToString(doc)
-}
-\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -0,0 +1,148 @@
+package tech.libeufin.nexus
+
+import tech.libeufin.util.IbanPayto
+import tech.libeufin.util.constructXml
+import tech.libeufin.util.parsePayto
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+
+
+data class Pain001Namespaces(
+ val fullNamespace: String,
+ val xsdFilename: String
+)
+
+/**
+ * Create a pain.001 document. It requires the debtor BIC.
+ *
+ * @param requestUid UID of this request, helps to make this request idempotent.
+ * @param debtorMetadataFile bank account information of the EBICS subscriber that
+ * sends this request.
+ * @param amount amount to pay.
+ * @param wireTransferSubject wire transfer subject.
+ * @param creditAccount payment receiver.
+ * @return raw pain.001 XML, or throws if the debtor BIC is not found.
+ */
+fun createPain001(
+ requestUid: String,
+ debtorMetadataFile: BankAccountMetadataFile,
+ amount: TalerAmount,
+ wireTransferSubject: String,
+ creditAccount: IbanPayto
+): String {
+ val namespace = Pain001Namespaces(
+ fullNamespace = "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09",
+ xsdFilename = "pain.001.001.09.ch.03.xsd"
+ )
+ val creationTimestamp = Instant.now()
+ val amountWithoutCurrency: String = amount.toString().split(":").run {
+ if (this.size != 2) throw Exception("Invalid stringified amount: $amount")
+ return@run this[1]
+ }
+ if (debtorMetadataFile.bank_code == null)
+ throw Exception("Need debtor BIC, but not found in the debtor account metadata file.")
+
+ return constructXml(indent = true) {
+ root("Document") {
+ attribute("xmlns", namespace.fullNamespace)
+ attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
+ attribute(
+ "xsi:schemaLocation",
+ "${namespace.fullNamespace} ${namespace.xsdFilename}"
+ )
+ element("CstmrCdtTrfInitn") {
+ element("GrpHdr") {
+ element("MsgId") {
+ text(requestUid)
+ }
+ element("CreDtTm") {
+ val dateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
+ val zoned = ZonedDateTime.ofInstant(
+ creationTimestamp,
+ ZoneId.systemDefault() // FIXME: should this be UTC?
+ )
+ text(dateFormatter.format(zoned))
+ }
+ element("NbOfTxs") {
+ text("1")
+ }
+ element("CtrlSum") {
+ text(amount.toString())
+ }
+ element("InitgPty/Nm") { // optional
+ text(debtorMetadataFile.account_holder_name)
+ }
+ }
+ element("PmtInf") {
+ element("PmtInfId") {
+ text("NOT GIVEN")
+ }
+ element("PmtMtd") {
+ text("TRF")
+ }
+ element("BtchBookg") {
+ text("true")
+ }
+ element("NbOfTxs") {
+ text("1")
+ }
+ element("CtrlSum") {
+ text(amount.toString())
+ }
+ element("PmtTpInf/SvcLvl/Cd") {
+ text("SDVA")
+ }
+ element("ReqdExctnDt") {
+ text(DateTimeFormatter.ISO_DATE.format(creationTimestamp))
+ }
+ element("Dbtr/Nm") { // optional
+ text(debtorMetadataFile.account_holder_name)
+ }
+ element("DbtrAcct/Id/IBAN") {
+ text(debtorMetadataFile.account_holder_iban)
+ }
+ element("DbtrAgt/FinInstnId") {
+ element("BICFI") {
+ text(debtorMetadataFile.bank_code)
+ }
+ }
+ element("ChrgBr") {
+ text("SLEV")
+ }
+ element("CdtTrfTxInf") {
+ element("PmtId") {
+ element("InstrId") { text("NOT PROVIDED") }
+ element("EndToEndId") { text("NOT PROVIDED") }
+ }
+ element("Amt/InstdAmt") {
+ attribute("Ccy", amount.currency)
+ text(amountWithoutCurrency)
+ }
+ creditAccount.bic.apply {
+ if (this != null)
+ element("CdtrAgt/FinInstnId") {
+ element("BICFI") {
+ text(this@apply)
+ }
+ }
+ }
+ creditAccount.receiverName.apply {
+ if (this != null)
+ element("Cdtr/Nm") {
+ text(this@apply)
+ }
+ }
+ element("CdtrAcct/Id/IBAN") {
+ text(creditAccount.iban)
+ }
+ element("RmtInf/Ustrd") {
+ text(wireTransferSubject)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+\ 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
@@ -0,0 +1,93 @@
+package tech.libeufin.nexus.ebics
+
+import tech.libeufin.nexus.BankPublicKeysFile
+import tech.libeufin.nexus.ClientPrivateKeysFile
+import tech.libeufin.nexus.EbicsSetupConfig
+import tech.libeufin.util.PreparedUploadData
+import tech.libeufin.util.XMLUtil
+import tech.libeufin.util.ebics_h005.Ebics3Request
+import tech.libeufin.util.getNonce
+import tech.libeufin.util.toHexString
+import java.math.BigInteger
+import java.util.*
+import javax.xml.datatype.DatatypeFactory
+
+/**
+ * Creates the EBICS 3 document for the init phase of an upload
+ * transaction.
+ *
+ * @param cfg configuration handle.
+ * @param preparedUploadData business payload to send.
+ * @param bankkeys bank public keys.
+ * @param clientKeys client private keys.
+ * @param orderService EBICS 3 document defining the request type
+ * @return raw XML of the EBICS 3 init phase.
+ */
+fun createEbics3RequestForUploadInitialization(
+ cfg: EbicsSetupConfig,
+ preparedUploadData: PreparedUploadData,
+ bankkeys: BankPublicKeysFile,
+ clientKeys: ClientPrivateKeysFile,
+ orderService: Ebics3Request.OrderDetails.Service
+): String {
+ val nonce = getNonce(128)
+ val req = Ebics3Request.createForUploadInitializationPhase(
+ preparedUploadData.transactionKey,
+ preparedUploadData.userSignatureDataEncrypted,
+ preparedUploadData.dataDigest,
+ cfg.ebicsHostId,
+ nonce,
+ cfg.ebicsPartnerId,
+ cfg.ebicsUserId,
+ DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+ bankkeys.bank_authentication_public_key,
+ bankkeys.bank_encryption_public_key,
+ BigInteger.ONE,
+ orderService
+ )
+ val doc = XMLUtil.convertJaxbToDocument(
+ req,
+ withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd"
+ )
+ tech.libeufin.util.logger.debug("Created EBICS 3 document for upload initialization," +
+ " nonce: ${nonce.toHexString()}")
+ XMLUtil.signEbicsDocument(
+ doc,
+ clientKeys.authentication_private_key,
+ withEbics3 = true
+ )
+ return XMLUtil.convertDomToString(doc)
+}
+
+/**
+ * Crafts one EBICS 3 request for the upload transfer phase. Currently
+ * only 1-chunk payloads are supported.
+ *
+ * @param cfg configuration handle.
+ * @param clientKeys client private keys.
+ * @param transactionId EBICS transaction ID obtained from an init phase.
+ * @param uploadData business content to upload.
+ *
+ * @return raw XML document.
+ */
+fun createEbics3RequestForUploadTransferPhase(
+ cfg: EbicsSetupConfig,
+ clientKeys: ClientPrivateKeysFile,
+ transactionId: String,
+ uploadData: PreparedUploadData
+): String {
+ val chunkIndex = 1 // only 1-chunk communication currently supported.
+ val req = Ebics3Request.createForUploadTransferPhase(
+ cfg.ebicsHostId,
+ transactionId,
+ BigInteger.valueOf(chunkIndex.toLong()),
+ uploadData.encryptedPayloadChunks[chunkIndex]
+ )
+ val doc = XMLUtil.convertJaxbToDocument(req)
+ XMLUtil.signEbicsDocument(
+ doc,
+ clientKeys.authentication_private_key,
+ withEbics3 = true
+ )
+ return XMLUtil.convertDomToString(doc)
+}
+\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt