[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: nexus: get pain.001 to validate at PostFinance sandbox.,
gnunet <=