gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: nexus: get pain.001 to validate at Pos


From: gnunet
Subject: [libeufin] branch master updated: nexus: get pain.001 to validate at PostFinance sandbox.
Date: Thu, 26 Oct 2023 16:10:22 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 0920cb07 nexus: get pain.001 to validate at PostFinance sandbox.
0920cb07 is described below

commit 0920cb07545501e54659f6a7ae94afbab86633f5
Author: MS <ms@taler.net>
AuthorDate: Thu Oct 26 16:09:22 2023 +0200

    nexus: get pain.001 to validate at PostFinance sandbox.
---
 .../main/kotlin/tech/libeufin/nexus/Database.kt    | 17 +++++
 .../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt  | 87 +++++++++++++---------
 .../main/kotlin/tech/libeufin/nexus/Iso20022.kt    | 64 ++++++++--------
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 17 +++++
 nexus/src/test/kotlin/Common.kt                    |  8 +-
 nexus/src/test/kotlin/Ebics.kt                     | 52 -------------
 nexus/src/test/kotlin/PostFinance.kt               | 85 +++++++++++++++++++++
 nexus/src/test/kotlin/pain001.xml                  | 63 ++++++++++++++++
 8 files changed, 272 insertions(+), 121 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
index 0b053603..9b18d2b7 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -18,6 +18,23 @@ data class TalerAmount(
     val currency: String
 )
 
+/**
+ * Stringifies TalerAmount's.  NOTE: the caller must enforce
+ * length-checks on the output fractional part, to ensure compatibility
+ * with the bank.
+ *
+ * @return the amount in the $currency:x.y format.
+ */
+fun TalerAmount.stringify(): String {
+    if (fraction == 0) {
+        return "$currency:$value"
+    } else {
+        val fractionFormat = this.fraction.toString().padStart(8, 
'0').dropLastWhile { it == '0' }
+        if (fractionFormat.length > 2) throw Exception("Sub-cent amounts not 
supported")
+        return "$currency:$value.$fractionFormat"
+    }
+}
+
 // INCOMING PAYMENTS STRUCTS
 
 /**
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index b842b978..95a5bf25 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -32,6 +32,7 @@ import TalerConfigError
 import kotlinx.serialization.encodeToString
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.util.*
+import tech.libeufin.util.ebics_h004.HTDResponseOrderData
 import java.time.Instant
 import kotlin.reflect.typeOf
 
@@ -346,6 +347,57 @@ private fun makePdf(privs: ClientPrivateKeysFile, cfg: 
EbicsSetupConfig) {
     println("PDF file with keys hex encoding created at: $pdfFile")
 }
 
+/**
+ * Extracts bank account information and stores it to the bank account
+ * metadata file that's found in the configuration. It returns void in
+ * case of success, or fails the whole process upon errors.
+ *
+ * @param cfg configuration handle.
+ * @param bankAccounts bank response to EBICS HTD request.
+ * @param showAssociatedAccounts if true, the function only shows the
+ *        users account, without persisting anything to disk.
+ */
+fun extractBankAccountMetadata(
+    cfg: EbicsSetupConfig,
+    bankAccounts: HTDResponseOrderData,
+    showAssociatedAccounts: Boolean
+) {
+    // Now trying to extract whatever IBAN & BIC pair the bank gave in the 
response.
+    val foundIban: String? = findIban(bankAccounts.partnerInfo.accountInfoList)
+    val foundBic: String? = findBic(bankAccounts.partnerInfo.accountInfoList)
+    // _some_ IBAN & BIC _might_ have been found, compare it with the config.
+    if (foundIban == null)
+        logger.warn("Bank seems NOT to show any IBAN for our account.")
+    if (foundBic == null)
+        logger.warn("Bank seems NOT to show any BIC for our account.")
+    // Warn the user if instead one IBAN was found but that differs from the 
config.
+    if (foundIban != null && foundIban != cfg.accountNumber) {
+        logger.error("Bank has another IBAN for us: $foundIban, while config 
has: ${cfg.accountNumber}")
+        exitProcess(1)
+    }
+    // Users wants to only _see_ the accounts, NOT checking values and 
returning here.
+    if (showAssociatedAccounts) {
+        println("Bank associates this account to the EBICS user 
${cfg.ebicsUserId}: IBAN: $foundIban, BIC: $foundBic, Name: 
${bankAccounts.userInfo.name}")
+        return
+    }
+    // No divergences were found, either because the config was right
+    // _or_ the bank didn't give any information.  Setting the account
+    // metadata accordingly.
+    val accountMetaData = BankAccountMetadataFile(
+        account_holder_name = bankAccounts.userInfo.name ?: "Account holder 
name not given",
+        account_holder_iban = foundIban ?: run iban@ {
+            logger.warn("Bank did not show any IBAN for us, defaulting to the 
one we configured.")
+            return@iban cfg.accountNumber },
+        bank_code = foundBic ?: run bic@ {
+            logger.warn("Bank did not show any BIC for us, setting it as 
null.")
+            return@bic null }
+    )
+    if (!syncJsonToDisk(accountMetaData, cfg.bankAccountMetadataFilename)) {
+        logger.error("Failed to persist bank account meta-data at: 
${cfg.bankAccountMetadataFilename}")
+        exitProcess(1)
+    }
+}
+
 /**
  * CLI class implementing the "ebics-setup" subcommand.
  */
@@ -469,40 +521,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS 
subscriber") {
             exitProcess(1)
         }
         logger.info("Subscriber's bank accounts fetched.")
-        // Now trying to extract whatever IBAN & BIC pair the bank gave in the 
response.
-        val foundIban: String? = 
findIban(bankAccounts.partnerInfo.accountInfoList)
-        val foundBic: String? = 
findBic(bankAccounts.partnerInfo.accountInfoList)
-        // _some_ IBAN & BIC _might_ have been found, compare it with the 
config.
-        if (foundIban == null)
-            logger.warn("Bank seems NOT to show any IBAN for our account.")
-        if (foundBic == null)
-            logger.warn("Bank seems NOT to show any BIC for our account.")
-        // Warn the user if instead one IBAN was found but that differs from 
the config.
-        if (foundIban != null && foundIban != cfg.accountNumber) {
-            logger.error("Bank has another IBAN for us: $foundIban, while 
config has: ${cfg.accountNumber}")
-            exitProcess(1)
-        }
-        // Users wants only _see_ the accounts, NOT checking values and 
returning here.
-        if (showAssociatedAccounts) {
-            println("Bank associates this account to the EBICS user 
${cfg.ebicsUserId}: IBAN: $foundIban, BIC: $foundBic, Name: 
${bankAccounts.userInfo.name}")
-            return
-        }
-        // No divergences were found, either because the config was right
-        // _or_ the bank didn't give any information.  Setting the account
-        // metadata accordingly.
-        val accountMetaData = BankAccountMetadataFile(
-            account_holder_name = bankAccounts.userInfo.name ?: "Account 
holder name not given",
-            account_holder_iban = foundIban ?: run iban@ {
-                logger.warn("Bank did not show any IBAN for us, defaulting to 
the one we configured.")
-                return@iban cfg.accountNumber },
-            bank_code = foundBic ?: run bic@ {
-                logger.warn("Bank did not show any BIC for us, setting it as 
null.")
-                return@bic null }
-        )
-        if (!syncJsonToDisk(accountMetaData, cfg.bankAccountMetadataFilename)) 
{
-            logger.error("Failed to persist bank account meta-data at: 
${cfg.bankAccountMetadataFilename}")
-            exitProcess(1)
-        }
+        extractBankAccountMetadata(cfg, bankAccounts, showAssociatedAccounts)
         println("setup ready")
     }
 }
\ 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
index 368b205b..cf5416c5 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -2,7 +2,6 @@ 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
@@ -18,16 +17,23 @@ data class Pain001Namespaces(
  * 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 initiationTimestamp timestamp when the payment was initiated in the 
database.
+ *                            Although this is NOT the pain.001 creation 
timestamp, it
+ *                            will help making idempotent requests where one 
MsgId is
+ *                            always associated with one, and only one 
creation timestamp.
+ * @param debtorAccount [IbanPayto] bank account information of the EBICS 
subscriber that
+ *                           sends this request.  It's expected to contain 
IBAN, BIC, and NAME.
+ * @param amount amount to pay.  The caller is responsible for sanity-checking 
this
+ *               value to match the bank expectation.  For example, that the 
decimal
+ *               part formats always to at most two digits.
  * @param wireTransferSubject wire transfer subject.
- * @param creditAccount payment receiver.
+ * @param creditAccount payment receiver in [IbanPayto].  It should contain 
IBAN and NAME.
  * @return raw pain.001 XML, or throws if the debtor BIC is not found.
  */
 fun createPain001(
     requestUid: String,
-    debtorMetadataFile: BankAccountMetadataFile,
+    initiationTimestamp: Instant,
+    debitAccount: IbanPayto,
     amount: TalerAmount,
     wireTransferSubject: String,
     creditAccount: IbanPayto
@@ -36,17 +42,14 @@ fun createPain001(
         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 {
+    val zonedTimestamp = ZonedDateTime.ofInstant(initiationTimestamp, 
ZoneId.of("UTC"))
+    val amountWithoutCurrency: String = amount.stringify().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.")
-    // Current version expects the receiver BIC, TODO: try also without.
-    if (creditAccount.bic == null)
-        throw Exception("Expecting the receiver BIC.")
-
+    val debtorBic: String = debitAccount.bic ?: throw Exception("Cannot 
operate without the debtor BIC")
+    val debtorName: String = debitAccount.receiverName ?: throw 
Exception("Cannot operate without the debtor name")
+    val creditorName: String = creditAccount.receiverName ?: throw 
Exception("Cannot operate without the creditor name")
     return constructXml(indent = true) {
         root("Document") {
             attribute("xmlns", namespace.fullNamespace)
@@ -62,20 +65,16 @@ fun createPain001(
                     }
                     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))
+                        text(dateFormatter.format(zonedTimestamp))
                     }
                     element("NbOfTxs") {
                         text("1")
                     }
                     element("CtrlSum") {
-                        text(amount.toString())
+                        text(amountWithoutCurrency)
                     }
-                    element("InitgPty/Nm") { // optional
-                        text(debtorMetadataFile.account_holder_name)
+                    element("InitgPty/Nm") {
+                        text(debtorName)
                     }
                 }
                 element("PmtInf") {
@@ -92,23 +91,25 @@ fun createPain001(
                         text("1")
                     }
                     element("CtrlSum") {
-                        text(amount.toString())
+                        text(amountWithoutCurrency)
                     }
                     element("PmtTpInf/SvcLvl/Cd") {
                         text("SDVA")
                     }
                     element("ReqdExctnDt") {
-                        
text(DateTimeFormatter.ISO_DATE.format(creationTimestamp))
+                        element("Dt") {
+                            
text(DateTimeFormatter.ISO_DATE.format(zonedTimestamp))
+                        }
                     }
-                    element("Dbtr/Nm") { // optional
-                        text(debtorMetadataFile.account_holder_name)
+                    element("Dbtr/Nm") {
+                        text(debtorName)
                     }
                     element("DbtrAcct/Id/IBAN") {
-                        text(debtorMetadataFile.account_holder_iban)
+                        text(debitAccount.iban)
                     }
                     element("DbtrAgt/FinInstnId") {
                         element("BICFI") {
-                            text(debtorMetadataFile.bank_code)
+                            text(debtorBic)
                         }
                     }
                     element("ChrgBr") {
@@ -131,11 +132,8 @@ fun createPain001(
                                     }
                                 }
                         }
-                        creditAccount.receiverName.apply {
-                            if (this != null)
-                                element("Cdtr/Nm") {
-                                    text(this@apply)
-                                }
+                        element("Cdtr/Nm") {
+                            text(creditorName)
                         }
                         element("CdtrAcct/Id/IBAN") {
                             text(creditAccount.iban)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 8668912c..3c2b9e0a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -200,6 +200,23 @@ data class BankPublicKeysFile(
     var accepted: Boolean
 )
 
+/**
+ * Gets the bank account metadata file, according to the
+ * location found in the configuration.  The caller may still
+ * have to handle the exception, in case the found file doesn't
+ * parse to the wanted JSON type.
+ *
+ * @param cfg configuration handle.
+ * @return [BankAccountMetadataFile] or null, if the file wasn't found.
+ */
+fun loadBankAccountFile(cfg: EbicsSetupConfig): BankAccountMetadataFile? {
+    val f = File(cfg.bankAccountMetadataFilename)
+    if (!f.exists()) {
+        logger.error("Bank account metadata file not found in 
${cfg.bankAccountMetadataFilename}")
+        return null
+    }
+    return myJson.decodeFromString(f.readText())
+}
 /**
  * Load the bank keys file from disk.
  *
diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt
index 2c3d84e2..b6a6aa02 100644
--- a/nexus/src/test/kotlin/Common.kt
+++ b/nexus/src/test/kotlin/Common.kt
@@ -56,7 +56,11 @@ fun getMockedClient(
 }
 
 // Partial config to talk to PostFinance.
-fun getPofiConfig(userId: String, partnerId: String) = """
+fun getPofiConfig(
+    userId: String,
+    partnerId: String,
+    accountOwner: String? = "NotGiven"
+    ) = """
     [nexus-ebics]
     CURRENCY = KUDOS
     HOST_BASE_URL = https://isotest.postfinance.ch/ebicsweb/ebicsweb
@@ -64,7 +68,7 @@ fun getPofiConfig(userId: String, partnerId: String) = """
     USER_ID = $userId
     PARTNER_ID = $partnerId
     SYSTEM_ID = not-used
-    ACCOUNT_NUMBER = not-used-yet
+    ACCOUNT_NUMBER = 
payto://iban/POFICHBE/CH9789144829733648596?receiver-name=$accountOwner
     BANK_PUBLIC_KEYS_FILE = /tmp/enc-auth-keys.json
     CLIENT_PRIVATE_KEYS_FILE = /tmp/my-private-keys.json
     ACCOUNT_META_DATA_FILE = /tmp/ebics-meta.json
diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt
index 5fd7e32a..1399ee76 100644
--- a/nexus/src/test/kotlin/Ebics.kt
+++ b/nexus/src/test/kotlin/Ebics.kt
@@ -67,56 +67,4 @@ class Ebics {
         val pdf = generateKeysPdf(clientKeys, config)
         File("/tmp/libeufin-nexus-test-keys.pdf").writeBytes(pdf)
     }
-}
-
-@Ignore // manual tests
-class PostFinance {
-    private fun prep(): EbicsSetupConfig {
-        val handle = TalerConfig(NEXUS_CONFIG_SOURCE)
-        val ebicsUserId = File("/tmp/pofi-ebics-user-id.txt").readText()
-        val ebicsPartnerId = File("/tmp/pofi-ebics-partner-id.txt").readText()
-        handle.loadFromString(getPofiConfig(ebicsUserId, ebicsPartnerId))
-        return EbicsSetupConfig(handle)
-    }
-    // Tests sending client keys to the PostFinance test platform.
-    @Test
-    fun postClientKeys() {
-        val cfg = prep()
-        runBlocking {
-            val httpClient = HttpClient()
-            assertTrue(doKeysRequestAndUpdateState(cfg, clientKeys, 
httpClient, KeysOrderType.INI))
-            assertTrue(doKeysRequestAndUpdateState(cfg, clientKeys, 
httpClient, KeysOrderType.HIA))
-        }
-    }
-
-    // Tests getting the PostFinance keys from their test platform.
-    @Test
-    fun getBankKeys() {
-        val cfg = prep()
-        val keys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
-        assertNotNull(keys)
-        assertTrue(keys.submitted_ini)
-        assertTrue(keys.submitted_hia)
-        runBlocking {
-            assertTrue(doKeysRequestAndUpdateState(
-                cfg,
-                keys,
-                HttpClient(),
-                KeysOrderType.HPB
-            ))
-        }
-    }
-
-    // Tests the HTD message type.
-    @Test
-    fun fetchAccounts() {
-        val cfg = prep()
-        val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
-        assertNotNull(clientKeys)
-        val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
-        assertNotNull(bankKeys)
-        val htd = runBlocking { fetchBankAccounts(cfg, clientKeys, bankKeys, 
HttpClient()) }
-        assertNotNull(htd)
-        println(htd.partnerInfo.accountInfoList?.size)
-    }
 }
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/PostFinance.kt 
b/nexus/src/test/kotlin/PostFinance.kt
new file mode 100644
index 00000000..cf604ec6
--- /dev/null
+++ b/nexus/src/test/kotlin/PostFinance.kt
@@ -0,0 +1,85 @@
+import io.ktor.client.*
+import kotlinx.coroutines.runBlocking
+import org.junit.Ignore
+import org.junit.Test
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.ebics.fetchBankAccounts
+import tech.libeufin.util.IbanPayto
+import tech.libeufin.util.parsePayto
+import java.io.File
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class Iso20022 {
+    @Test
+    fun sendPayment() {
+        val xml = createPain001(
+            "random",
+            Instant.now(),
+            
parsePayto("payto://iban/POFICHBE/CH9789144829733648596?receiver-name=NotGiven")!!,
+            TalerAmount(4, 0, "CHF"),
+            "Test reimbursement",
+            
parsePayto("payto://iban/CH9300762011623852957?receiver-name=NotGiven")!!
+        )
+        println(xml)
+        File("/tmp/pain.001-test.xml").writeText(xml)
+    }
+}
+
+@Ignore
+class PostFinance {
+    private fun prep(): EbicsSetupConfig {
+        val handle = TalerConfig(NEXUS_CONFIG_SOURCE)
+        val ebicsUserId = File("/tmp/pofi-ebics-user-id.txt").readText()
+        val ebicsPartnerId = File("/tmp/pofi-ebics-partner-id.txt").readText()
+        handle.loadFromString(getPofiConfig(ebicsUserId, ebicsPartnerId))
+        return EbicsSetupConfig(handle)
+    }
+
+    // Tests sending client keys to the PostFinance test platform.
+    @Test
+    fun postClientKeys() {
+        val cfg = prep()
+        runBlocking {
+            val httpClient = HttpClient()
+            assertTrue(doKeysRequestAndUpdateState(cfg, clientKeys, 
httpClient, KeysOrderType.INI))
+            assertTrue(doKeysRequestAndUpdateState(cfg, clientKeys, 
httpClient, KeysOrderType.HIA))
+        }
+    }
+
+    // Tests getting the PostFinance keys from their test platform.
+    @Test
+    fun getBankKeys() {
+        val cfg = prep()
+        val keys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
+        assertNotNull(keys)
+        assertTrue(keys.submitted_ini)
+        assertTrue(keys.submitted_hia)
+        runBlocking {
+            assertTrue(
+                doKeysRequestAndUpdateState(
+                cfg,
+                keys,
+                HttpClient(),
+                KeysOrderType.HPB
+            )
+            )
+        }
+    }
+
+    // Tests the HTD message type.
+    @Test
+    fun fetchAccounts() {
+        val cfg = prep()
+        val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
+        assertNotNull(clientKeys)
+        val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
+        assertNotNull(bankKeys)
+        val htd = runBlocking { fetchBankAccounts(cfg, clientKeys, bankKeys, 
HttpClient()) }
+        extractBankAccountMetadata(cfg, htd!!, false)
+    }
+}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/pain001.xml 
b/nexus/src/test/kotlin/pain001.xml
new file mode 100644
index 00000000..4fd0eba8
--- /dev/null
+++ b/nexus/src/test/kotlin/pain001.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 
pain.001.001.09.ch.03.xsd">
+    <CstmrCdtTrfInitn>
+        <GrpHdr>
+            <MsgId>random</MsgId>
+            <CreDtTm>2023-10-26T12:54:13.423443377Z</CreDtTm>
+            <NbOfTxs>1</NbOfTxs>
+            <CtrlSum>TalerAmount(value=4, fraction=0, currency=CHF)</CtrlSum>
+            <InitgPty>
+                <Nm>Marcello Stanisci</Nm>
+            </InitgPty>
+        </GrpHdr>
+        <PmtInf>
+            <PmtInfId>NOT GIVEN</PmtInfId>
+            <PmtMtd>TRF</PmtMtd>
+            <BtchBookg>true</BtchBookg>
+            <NbOfTxs>1</NbOfTxs>
+            <CtrlSum>TalerAmount(value=4, fraction=0, currency=CHF)</CtrlSum>
+            <PmtTpInf>
+                <SvcLvl>
+                    <Cd>SDVA</Cd>
+                </SvcLvl>
+            </PmtTpInf>
+            <ReqdExctnDt>2023-10-26Z</ReqdExctnDt>
+            <Dbtr>
+                <Nm>Marcello Stanisci</Nm>
+            </Dbtr>
+            <DbtrAcct>
+                <Id>
+                    <IBAN>not-used-yet</IBAN>
+                </Id>
+            </DbtrAcct>
+            <DbtrAgt>
+                <FinInstnId>
+                    <BICFI>POFICHBE</BICFI>
+                </FinInstnId>
+            </DbtrAgt>
+            <ChrgBr>SLEV</ChrgBr>
+            <CdtTrfTxInf>
+                <PmtId>
+                    <InstrId>NOT PROVIDED</InstrId>
+                    <EndToEndId>NOT PROVIDED</EndToEndId>
+                </PmtId>
+                <Amt>
+                    <InstdAmt Ccy="CHF">4</InstdAmt>
+                </Amt>
+                <CdtrAgt>
+                    <FinInstnId>
+                        <BICFI>BIC</BICFI>
+                    </FinInstnId>
+                </CdtrAgt>
+                <CdtrAcct>
+                    <Id>
+                        <IBAN>INVALID</IBAN>
+                    </Id>
+                </CdtrAcct>
+                <RmtInf>
+                    <Ustrd>Test reimbursement</Ustrd>
+                </RmtInf>
+            </CdtTrfTxInf>
+        </PmtInf>
+    </CstmrCdtTrfInitn>
+</Document>
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]