gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] 02/02: x-libeufin-bank connection.


From: gnunet
Subject: [libeufin] 02/02: x-libeufin-bank connection.
Date: Sat, 01 Apr 2023 22:07:09 +0200

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

ms pushed a commit to branch master
in repository libeufin.

commit 04c5aa01c3b57e3c0c30795c0d9fdf0aeab10844
Author: MS <ms@taler.net>
AuthorDate: Sat Apr 1 22:06:11 2023 +0200

    x-libeufin-bank connection.
    
    Adding payment submission, the "connect()"
    method, and the CLI command to create a new
    x-libeufin-bank connection.
---
 cli/bin/libeufin-cli                               | 39 ++++++++++
 .../tech/libeufin/nexus/BankConnectionProtocol.kt  |  5 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  5 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt |  1 -
 .../tech/libeufin/nexus/bankaccount/BankAccount.kt |  8 +-
 .../kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 11 +--
 .../nexus/xlibeufinbank/XLibeufinBankNexus.kt      | 88 ++++++++++++++++++----
 nexus/src/test/kotlin/EbicsTest.kt                 | 29 +++----
 nexus/src/test/kotlin/TalerTest.kt                 | 54 +++++++------
 nexus/src/test/kotlin/XLibeufinBankTest.kt         | 41 ++++++++--
 .../src/main/kotlin/tech/libeufin/sandbox/JSON.kt  | 11 ---
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  |  5 +-
 util/src/main/kotlin/JSON.kt                       | 24 +++++-
 util/src/main/kotlin/Payto.kt                      | 15 ++--
 14 files changed, 241 insertions(+), 95 deletions(-)

diff --git a/cli/bin/libeufin-cli b/cli/bin/libeufin-cli
index ebd16ebf..9bbef229 100755
--- a/cli/bin/libeufin-cli
+++ b/cli/bin/libeufin-cli
@@ -498,6 +498,45 @@ def restore_backup(obj, backup_file, passphrase, 
connection_name):
     check_response_status(resp)
 
 
+@connections.command(help="make a new x-libeufin-bank connection")
+@click.option(
+    "--bank-url",
+    help="Bank base URL, typically ending with 
'../demobanks/default/access-api'",
+    required=True
+)
+@click.option(
+    "--username",
+    help="Username at the bank, that this connection impersonates.",
+    required=True
+)
+@click.password_option()
+@click.argument("connection-name")
+@click.pass_obj
+def new_xlibeufinbank_connection(obj, bank_url, username, password, 
connection_name):
+    url = urljoin_nodrop(obj.nexus_base_url, "/bank-connections")
+    body = dict(
+        name=connection_name,
+        source="new",
+        type="x-libeufin-bank",
+        data=dict(
+            baseUrl=bank_url,
+            username=username,
+            password=password
+        ),
+    )
+    try:
+        resp = post(
+            url,
+            json=body,
+            auth=auth.HTTPBasicAuth(obj.username, obj.password)
+        )
+    except Exception as e:
+        print(e)
+        print(f"Could not reach nexus at {url}")
+        exit(1)
+
+    check_response_status(resp)
+
 @connections.command(help="make new EBICS bank connection")
 @click.option("--ebics-url", help="EBICS URL", required=True)
 @click.option("--host-id", help="Host ID", required=True)
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt
index ac52095c..e87e9444 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt
@@ -25,10 +25,13 @@ import io.ktor.http.HttpStatusCode
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.nexus.server.BankConnectionType
 import tech.libeufin.nexus.server.FetchSpecJson
+import tech.libeufin.nexus.server.XLibeufinBankTransport
+import tech.libeufin.nexus.xlibeufinbank.XlibeufinBankConnectionProtocol
 
 // 'const' allows only primitive types.
 val bankConnectionRegistry: Map<BankConnectionType, BankConnectionProtocol> = 
mapOf(
-    BankConnectionType.EBICS to EbicsBankConnectionProtocol()
+    BankConnectionType.EBICS to EbicsBankConnectionProtocol(),
+    BankConnectionType.X_LIBEUFIN_BANK to XlibeufinBankConnectionProtocol()
 )
 
 interface BankConnectionProtocol {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index 38fb7bd3..5679b8ca 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -237,9 +237,7 @@ class NexusBankTransactionEntity(id: EntityID<Long>) : 
LongEntity(id) {
     }
 }
 
-/**
- * Represents a prepared payment.
- */
+// Represents a prepared payment.
 object PaymentInitiationsTable : LongIdTable() {
     /**
      * Bank account that wants to initiate the payment.
@@ -259,7 +257,6 @@ object PaymentInitiationsTable : LongIdTable() {
     val submitted = bool("submitted").default(false)
     var invalid = bool("invalid").nullable()
     val messageId = text("messageId")
-
     /**
      * Points at the raw transaction witnessing that this
      * initiated payment was successfully performed.
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
index 365a4ea5..d630b06b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
@@ -247,7 +247,6 @@ fun talerFilter(
     payment: NexusBankTransactionEntity,
     txDtls: TransactionDetails
 ) {
-    val channelsToNotify = mutableListOf<String>()
     var isInvalid = false // True when pub is invalid or duplicate.
     val subject = txDtls.unstructuredRemittanceInformation
     val debtorName = txDtls.debtor?.name
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
index 7843345f..145556ed 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -125,17 +125,15 @@ suspend fun submitAllPaymentInitiations(
                         "(pointed by bank account 
'${it.bankAccount.bankAccountName}')" +
                         " not found in the database."
             )
-            // Filter out non EBICS.
-            if (bankConnection.type != "ebics") {
+            try { 
BankConnectionType.parseBankConnectionType(bankConnection.type) }
+            catch (e: Exception) {
                 logger.info("Skipping non-implemented bank connection 
'${bankConnection.type}'")
                 return@forEach
             }
             workQueue.add(Submission(it.id.value))
         }
     }
-    workQueue.forEach {
-        submitPaymentInitiation(httpClient, it.id)
-    }
+    workQueue.forEach { submitPaymentInitiation(httpClient, it.id) }
 }
 
 /**
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
index f41a3729..ce644ddf 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -125,10 +125,8 @@ private suspend fun fetchEbicsC5x(
     }
 
     when (historyType) {
-        "C52" -> {
-        }
-        "C53" -> {
-        }
+        "C52" -> {}
+        "C53" -> {}
         else -> {
             throw NexusError(HttpStatusCode.BadRequest, "history type 
'$historyType' not supported")
         }
@@ -526,7 +524,10 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol {
     override suspend fun submitPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long) {
         val dbData = transaction {
             val preparedPayment = getPaymentInitiation(paymentInitiationId)
-            val conn = preparedPayment.bankAccount.defaultBankConnection ?: 
throw NexusError(HttpStatusCode.NotFound, "no default bank connection available 
for submission")
+            val conn = preparedPayment.bankAccount.defaultBankConnection ?: 
throw NexusError(
+                HttpStatusCode.NotFound,
+                "no default bank connection available for submission"
+            )
             val subscriberDetails = 
getEbicsSubscriberDetails(conn.connectionId)
             val painMessage = createPain001document(
                 NexusPaymentInitiationData(
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/xlibeufinbank/XLibeufinBankNexus.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/xlibeufinbank/XLibeufinBankNexus.kt
index b819163a..f83046c3 100644
--- 
a/nexus/src/main/kotlin/tech/libeufin/nexus/xlibeufinbank/XLibeufinBankNexus.kt
+++ 
b/nexus/src/main/kotlin/tech/libeufin/nexus/xlibeufinbank/XLibeufinBankNexus.kt
@@ -15,10 +15,7 @@ import tech.libeufin.nexus.*
 import tech.libeufin.nexus.bankaccount.*
 import tech.libeufin.nexus.iso20022.*
 import tech.libeufin.nexus.server.*
-import tech.libeufin.util.XLibeufinBankDirection
-import tech.libeufin.util.XLibeufinBankTransaction
-import tech.libeufin.util.badRequest
-import tech.libeufin.util.internalServerError
+import tech.libeufin.util.*
 import java.net.MalformedURLException
 import java.net.URL
 
@@ -46,7 +43,16 @@ fun getXLibeufinBankCredentials(connId: String): 
XLibeufinBankTransport {
 
 class XlibeufinBankConnectionProtocol : BankConnectionProtocol {
     override suspend fun connect(client: HttpClient, connId: String) {
-        TODO("Not yet implemented")
+        // Only checking that the credentials + bank URL are correct.
+        val conn = getBankConnection(connId)
+        val credentials = getXLibeufinBankCredentials(conn)
+        // Defining the URL to request the bank account balance.
+        val url = credentials.baseUrl + "/accounts/${credentials.username}"
+        // Error handling expected by the caller.
+        client.get(url) {
+            expectSuccess = true
+            basicAuth(credentials.username, credentials.password)
+        }
     }
 
     override suspend fun fetchAccounts(client: HttpClient, connId: String) {
@@ -66,7 +72,6 @@ class XlibeufinBankConnectionProtocol : 
BankConnectionProtocol {
         connId: String,
         user: NexusUserEntity,
         data: JsonNode) {
-
         val bankConn = transaction {
             NexusBankConnectionEntity.new {
                 this.connectionId = connId
@@ -105,8 +110,67 @@ class XlibeufinBankConnectionProtocol : 
BankConnectionProtocol {
         throw NotImplementedError("x-libeufin-bank does not need analog 
details")
     }
 
-    override suspend fun submitPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long) {
-        TODO("Not yet implemented")
+    override suspend fun submitPaymentInitiation(
+        httpClient: HttpClient,
+        paymentInitiationId: Long
+    ) {
+        /**
+         * Main steps.
+         *
+         * 1) Get prep from the DB.
+         * 2) Collect credentials.
+         * 3) Create the format to POST.
+         * 4) POST the transaction.
+         * 5) Mark the prep as submitted.
+         * */
+        // 1
+        val preparedPayment = getPaymentInitiation(paymentInitiationId)
+        // 2
+        val conn = transaction { 
preparedPayment.bankAccount.defaultBankConnection } ?: throw
+                internalServerError("Default connection not found for bank 
account: ${preparedPayment.bankAccount.bankAccountName}")
+        val credentials: XLibeufinBankTransport = 
getXLibeufinBankCredentials(conn)
+        // 3
+        val paytoUri = buildIbanPaytoUri(
+            iban = preparedPayment.creditorIban,
+            bic = preparedPayment.creditorBic ?: "SANDBOXX",
+            receiverName = preparedPayment.creditorName,
+            message = preparedPayment.subject
+        )
+        val req = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(
+            XLibeufinBankPaytoReq(
+                paytoUri = paytoUri,
+                amount = "${preparedPayment.currency}:${preparedPayment.sum}",
+                pmtInfId = preparedPayment.paymentInformationId
+            )
+        )
+        // 4
+        val url = credentials.baseUrl + 
"/accounts/${credentials.username}/transactions"
+        logger.debug("POSTing transactions to x-libeufin-bank at: $url")
+        val r = httpClient.post(url) {
+            expectSuccess = false
+            contentType(ContentType.Application.Json)
+            basicAuth(credentials.username, credentials.password)
+            setBody(req)
+        }
+        if (r.status.value.toString().startsWith("5")) {
+            throw NexusError(
+                HttpStatusCode.BadGateway,
+                "The bank failed: ${r.bodyAsText()}"
+            )
+        }
+        if (!r.status.value.toString().startsWith("2")) {
+            throw NexusError(
+                /**
+                 * Echoing whichever status code the bank gave.  That
+                 * however masks client errors where - for example - a
+                 * request detail causes 404 where Nexus has no power.
+                 */
+                HttpStatusCode(r.status.value, r.status.description),
+                r.bodyAsText()
+            )
+        }
+        // 5
+        transaction { preparedPayment.submitted = true }
     }
 
     override suspend fun fetchTransactions(
@@ -132,7 +196,7 @@ class XlibeufinBankConnectionProtocol : 
BankConnectionProtocol {
         /**
          * Now builds the URL to ask the transactions, according to the
          * FetchSpec gotten in the args.  Level 'statement' and time range
-         * 'previous-dayes' are NOT implemented.
+         * 'previous-days' are NOT implemented.
          */
         val baseUrl = URL(credentials.baseUrl)
         val fetchUrl = url {
@@ -237,9 +301,7 @@ fun processXLibeufinBankMessage(
         }
         // Searching for duplicates.
         if (findDuplicate(bankAccountId, it.uid) != null) {
-            logger.debug(
-                "x-libeufin-bank ingestion: transaction ${it.uid} is a 
duplicate, skipping."
-            )
+            logger.debug("x-libeufin-bank ingestion: transaction ${it.uid} is 
a duplicate, skipping.")
             return@forEach
         }
         val direction = if (it.debtorIban == bankAccount.iban)
@@ -268,7 +330,7 @@ fun processXLibeufinBankMessage(
              * (outgoing) payment with the one being iterated over.
              */
             if (direction == XLibeufinBankDirection.DEBIT) {
-                val maybePrepared = getPaymentInitiation(pmtInfId = it.uid)
+                val maybePrepared = it.pmtInfId?.let { it1 -> 
getPaymentInitiation(pmtInfId = it1) }
                 if (maybePrepared != null) 
maybePrepared.confirmationTransaction = localTx
             }
             // x-libeufin-bank transactions are ALWAYS modeled as reports
diff --git a/nexus/src/test/kotlin/EbicsTest.kt 
b/nexus/src/test/kotlin/EbicsTest.kt
index 622ff928..8de0d5a5 100644
--- a/nexus/src/test/kotlin/EbicsTest.kt
+++ b/nexus/src/test/kotlin/EbicsTest.kt
@@ -1,7 +1,4 @@
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
 import io.ktor.server.application.*
-import io.ktor.client.*
-import io.ktor.client.request.*
 import io.ktor.http.*
 import io.ktor.server.plugins.contentnegotiation.*
 import io.ktor.server.request.*
@@ -23,14 +20,17 @@ import 
tech.libeufin.nexus.iso20022.NexusPaymentInitiationData
 import tech.libeufin.nexus.iso20022.createPain001document
 import tech.libeufin.nexus.server.FetchLevel
 import tech.libeufin.nexus.server.FetchSpecAllJson
-import tech.libeufin.nexus.server.FetchSpecJson
 import tech.libeufin.nexus.server.Pain001Data
 import tech.libeufin.sandbox.*
 import tech.libeufin.util.*
 import tech.libeufin.util.ebics_h004.EbicsRequest
 import tech.libeufin.util.ebics_h004.EbicsResponse
 import tech.libeufin.util.ebics_h004.EbicsTypes
-import kotlin.reflect.full.isSubclassOf
+
+/**
+ * These test cases run EBICS CCT and C52, mixing ordinary operations
+ * and some error cases.
+ */
 
 /**
  * Data to make the test server return for EBICS
@@ -87,9 +87,7 @@ fun getCustomEbicsServer(r: EbicsResponses, endpoint: String 
= "/ebicsweb"): App
 }
 
 class DownloadAndSubmit {
-    /**
-     * Download a C52 report from the bank.
-     */
+    // Downloads a C52 report from the bank.
     @Test
     fun download() {
         withNexusAndSandboxUser {
@@ -129,9 +127,8 @@ class DownloadAndSubmit {
             }
         }
     }
-    /**
-     * Upload one payment instruction to the bank.
-     */
+
+    // Uploads one payment instruction to the bank.
     @Test
     fun upload() {
         withNexusAndSandboxUser {
@@ -223,8 +220,8 @@ class DownloadAndSubmit {
     }
 
     /**
-     * Submit one payment instruction with an invalid Pain.001
-     * document, and check that it was marked as invalid.  Hence,
+     * Submits one payment instruction with an invalid Pain.001
+     * document, and checks that it was marked as invalid.  Hence,
      * the error is expected only by the first submission, since
      * the second won't pick the invalid payment.
      */
@@ -264,6 +261,10 @@ class DownloadAndSubmit {
         }
     }
 
+    /**
+     * Submits one pain.001 document with the wrong currency and checks
+     * that the bank responded with EBICS_PROCESSING_ERROR.
+     */
     @Test
     fun unsupportedCurrency() {
         withNexusAndSandboxUser {
@@ -278,7 +279,7 @@ class DownloadAndSubmit {
                             creditorName = "Tester",
                             subject = "test payment",
                             sum = "1",
-                            currency = "EUR"
+                            currency = "EUR" // EUR not supported.
                         ),
                         transaction {
                             NexusBankAccountEntity.findByName("foo") ?: throw 
Exception("Test failed")
diff --git a/nexus/src/test/kotlin/TalerTest.kt 
b/nexus/src/test/kotlin/TalerTest.kt
index f877203b..6565c197 100644
--- a/nexus/src/test/kotlin/TalerTest.kt
+++ b/nexus/src/test/kotlin/TalerTest.kt
@@ -18,53 +18,61 @@ import tech.libeufin.nexus.talerFilter
 import tech.libeufin.sandbox.sandboxApp
 import tech.libeufin.sandbox.wireTransfer
 import tech.libeufin.util.NotificationsChannelDomains
+import tech.libeufin.util.getIban
 
 // This class tests the features related to the Taler facade.
 class TalerTest {
-    val mapper = ObjectMapper()
+    private val mapper = ObjectMapper()
+
+    @Test
+    fun historyOutgoingTestEbics() {
+        historyOutgoingTest("foo")
+    }
+    @Test
+    fun historyOutgoingTestXLibeufinBank() {
+        historyOutgoingTest("bar")
+    }
 
     // Checking that a call to POST /transfer results in
     // an outgoing payment in GET /history/outgoing.
-    @Test
-    fun historyOutgoingTest() {
+    fun historyOutgoingTest(testedAccount: String) {
         withNexusAndSandboxUser {
             testApplication {
                 application(nexusApp)
-                client.post("/facades/foo-facade/taler-wire-gateway/transfer") 
{
+                
client.post("/facades/$testedAccount-facade/taler-wire-gateway/transfer") {
                     contentType(ContentType.Application.Json)
-                    basicAuth("foo", "foo") // exchange's credentials
+                    basicAuth(testedAccount, testedAccount) // exchange's 
credentials
                     expectSuccess = true
                     setBody("""
                         { "request_uid": "twg_transfer_0",
                           "amount": "TESTKUDOS:3",
                           "exchange_base_url": "http://exchange.example.com/";,
                           "wtid": "T0",
-                          "credit_account": 
"payto://iban/${BAR_USER_IBAN}?receiver-name=Bar"
-                        
+                          "credit_account": 
"payto://iban/${BANK_IBAN}?receiver-name=Not-Used"
                         }
                     """.trimIndent())
                 }
             }
-            /* The EBICS layer sends the payment instruction to the bank here.
-             *  and the reconciliation mechanism in Nexus should detect that 
one
-             *  outgoing payment was indeed the one instructed via the TWG.  
The
-             *  reconciliation will make the outgoing payment visible via 
/history/outgoing.
-             *  The following block achieve this by starting Sandbox and 
sending all
-             *  the prepared payments to it.
+            /* The bank connection sends the payment instruction to the bank 
here.
+             * and the reconciliation mechanism in Nexus should detect that one
+             * outgoing payment was indeed the one instructed via the TWG.  The
+             * reconciliation will make the outgoing payment visible via 
/history/outgoing.
+             * The following block achieve this by starting Sandbox and 
sending all
+             * the prepared payments to it.
              */
             testApplication {
                 application(sandboxApp)
-                submitAllPaymentInitiations(client, "foo")
+                submitAllPaymentInitiations(client, testedAccount)
                 /* Now downloads transactions from the bank, where the payment
                    submitted in the previous block is expected to appear as 
outgoing.
                  */
                 fetchBankAccountTransactions(
                     client,
                     fetchSpec = FetchSpecAllJson(
-                        level = FetchLevel.REPORT,
-                        "foo"
+                        level = if (testedAccount == "bar") 
FetchLevel.STATEMENT else FetchLevel.REPORT,
+                        bankConnection = testedAccount
                     ),
-                    "foo"
+                    accountId = testedAccount
                 )
             }
             /**
@@ -73,10 +81,10 @@ class TalerTest {
              */
             testApplication {
                 application(nexusApp)
-                val r = 
client.get("/facades/foo-facade/taler-wire-gateway/history/outgoing?delta=5") {
+                val r = 
client.get("/facades/$testedAccount-facade/taler-wire-gateway/history/outgoing?delta=5")
 {
                     expectSuccess = true
                     contentType(ContentType.Application.Json)
-                    basicAuth("foo", "foo")
+                    basicAuth(testedAccount, testedAccount)
                 }
                 val j = mapper.readTree(r.readBytes())
                 val wtidFromTwg = 
j.get("outgoing_transactions").get(0).get("wtid").asText()
@@ -103,7 +111,7 @@ class TalerTest {
         )
     }
 
-    // Tests that even if one call is long-polling, other calls
+    // Tests that even if one call is long-polling, other calls respond.
     @Test
     fun servingTest() {
         withTestDatabase {
@@ -135,7 +143,7 @@ class TalerTest {
 
     // Downloads Taler txs via the default connection of 'testedAccount'.
     // This allows to test the Taler logic on different connection types.
-    fun historyIncomingTest(testedAccount: String, connType: 
BankConnectionType) {
+    private fun historyIncomingTest(testedAccount: String, connType: 
BankConnectionType) {
         val reservePub = "GX5H5RME193FDRCM1HZKERXXQ2K21KH7788CKQM8X6MYKYRBP8F0"
         withNexusAndSandboxUser {
             testApplication {
@@ -163,10 +171,6 @@ class TalerTest {
                     }
                     launch {
                         delay(500)
-                        /**
-                         * FIXME: this test never gets the server to wait 
notifications from the DBMS.
-                         * Somehow, the wire transfer arrives always before 
the blocking await on the DBMS.
-                         */
                         newNexusBankTransaction(
                             currency = "KUDOS",
                             value = "10",
diff --git a/nexus/src/test/kotlin/XLibeufinBankTest.kt 
b/nexus/src/test/kotlin/XLibeufinBankTest.kt
index 9af9133c..852caff4 100644
--- a/nexus/src/test/kotlin/XLibeufinBankTest.kt
+++ b/nexus/src/test/kotlin/XLibeufinBankTest.kt
@@ -3,17 +3,18 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
 import io.ktor.server.testing.*
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.junit.Test
-import tech.libeufin.nexus.BankConnectionProtocol
-import tech.libeufin.nexus.NexusBankTransactionEntity
-import tech.libeufin.nexus.NexusBankTransactionsTable
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.bankaccount.addPaymentInitiation
 import tech.libeufin.nexus.bankaccount.ingestBankMessagesIntoAccount
-import tech.libeufin.nexus.getNexusUser
 import tech.libeufin.nexus.iso20022.CamtBankAccountEntry
 import tech.libeufin.nexus.server.*
 import tech.libeufin.nexus.xlibeufinbank.XlibeufinBankConnectionProtocol
+import tech.libeufin.sandbox.BankAccountTransactionEntity
+import tech.libeufin.sandbox.BankAccountTransactionsTable
 import tech.libeufin.sandbox.sandboxApp
 import tech.libeufin.sandbox.wireTransfer
 import tech.libeufin.util.XLibeufinBankTransaction
+import tech.libeufin.util.getIban
 import java.net.URL
 
 // Testing the x-libeufin-bank communication
@@ -34,9 +35,37 @@ class XLibeufinBankTest {
      */
     @Test
     fun submitTransaction() {
-
+        withTestDatabase {
+            prepSandboxDb()
+            prepNexusDb()
+            testApplication {
+                application(sandboxApp)
+                val pId = addPaymentInitiation(
+                    Pain001Data(
+                        creditorIban = FOO_USER_IBAN,
+                        creditorBic = "SANDBOXX",
+                        creditorName = "Tester",
+                        subject = "test payment",
+                        sum = "1",
+                        currency = "TESTKUDOS"
+                    ),
+                    transaction {
+                        NexusBankAccountEntity.findByName("bar") ?:
+                        throw Exception("Test failed, env didn't provide Nexus 
bank account 'bar'")
+                    }
+                )
+                val conn = XlibeufinBankConnectionProtocol()
+                conn.submitPaymentInitiation(this.client, pId.id.value)
+                val maybeArrivedPayment = transaction {
+                    BankAccountTransactionEntity.find {
+                        BankAccountTransactionsTable.pmtInfId eq 
pId.paymentInformationId
+                    }.firstOrNull()
+                }
+                // Now look for the payment in the database.
+                assert(maybeArrivedPayment != null)
+            }
+        }
     }
-
     /**
      * Testing that Nexus downloads one transaction from
      * Sandbox via the x-libeufin-bank protocol supplier
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index 87021146..dac660da 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -19,7 +19,6 @@
 
 package tech.libeufin.sandbox
 
-import com.fasterxml.jackson.annotation.JsonProperty
 import tech.libeufin.util.PaymentInfo
 
 data class WithdrawalRequest(
@@ -148,16 +147,6 @@ data class TalerWithdrawalSelection(
     val selected_exchange: String?
 )
 
-data class NewTransactionReq(
-    /**
-     * This Payto address must contain the wire transfer
-     * subject among its query parameters -- 'message' parameter.
-     */
-    val paytoUri: String,
-    // $currency:X.Y format
-    val amount: String?
-)
-
 data class SandboxConfig(
     val currency: String,
     val version: String,
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index a1a4d70b..9cc331d2 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -1259,7 +1259,7 @@ val sandboxApp: Application.() -> Unit = {
                     val authGranted: Boolean = !WITH_AUTH
                     if (!authGranted && username != bankAccount.label)
                         throw unauthorized("Username '$username' has no rights 
over bank account ${bankAccount.label}")
-                    val req = call.receive<NewTransactionReq>()
+                    val req = call.receive<XLibeufinBankPaytoReq>()
                     val payto = parsePayto(req.paytoUri)
                     val amount: String? = payto.amount ?: req.amount
                     if (amount == null) throw badRequest("Amount is missing")
@@ -1274,7 +1274,8 @@ val sandboxApp: Application.() -> Unit = {
                             subject = payto.message ?: throw badRequest(
                                 "'message' query parameter missing in Payto 
address"
                             ),
-                            amount = amount
+                            amount = amount,
+                            pmtInfId = req.pmtInfId
                         )
                     }
                     call.respond(object {})
diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt
index 96ee1e05..71274ccd 100644
--- a/util/src/main/kotlin/JSON.kt
+++ b/util/src/main/kotlin/JSON.kt
@@ -52,6 +52,22 @@ enum class XLibeufinBankDirection(val direction: String) {
         }
     }
 }
+
+data class XLibeufinBankPaytoReq(
+    /**
+     * This Payto address MUST contain the wire transfer
+     * subject among its query parameters -- 'message' parameter.
+     */
+    val paytoUri: String,
+    // $currency:X.Y format
+    val amount: String?,
+    /**
+     * This value MAY be specified by the payment submitter to
+     * help reconcile the payment when they later download new
+     * transactions.  The name is only borrowed from CaMt terminology.
+     */
+    val pmtInfId: String?
+)
 data class XLibeufinBankTransaction(
     val creditorIban: String,
     val creditorBic: String?,
@@ -66,9 +82,11 @@ data class XLibeufinBankTransaction(
     val date: String,
     val uid: String,
     val direction: XLibeufinBankDirection,
-    // The following two values are rather CAMT/PAIN
-    // specific, therefore do not need to be returned
-    // along every API call using this object.
+    /**
+     * The following two values are rather CAMT/PAIN
+     * specific, therefore do not need to be returned
+     * along every API call using this object.
+     */
     val pmtInfId: String? = null,
     val msgId: String? = null
 )
diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/Payto.kt
index f7466787..9d1f401d 100644
--- a/util/src/main/kotlin/Payto.kt
+++ b/util/src/main/kotlin/Payto.kt
@@ -5,10 +5,9 @@ import io.ktor.http.*
 import java.net.URI
 import java.net.URLDecoder
 import java.net.URLEncoder
+import javax.security.auth.Subject
 
-/**
- * Payto information.
- */
+// Payto information.
 data class Payto(
     // represent query param "sender-name" or "receiver-name".
     val receiverName: String?,
@@ -78,9 +77,15 @@ fun parsePayto(payto: String): Payto {
 
 fun buildIbanPaytoUri(
     iban: String,
-    bic: String?,
+    bic: String,
     receiverName: String,
+    message: String? = null
 ): String {
     val nameUrlEnc = URLEncoder.encode(receiverName, "utf-8")
-    return "payto://iban/${if (bic != null) "$bic/" else 
""}$iban?receiver-name=$nameUrlEnc"
+    val ret = "payto://iban/$bic/$iban?receiver-name=$nameUrlEnc"
+    if (message != null) {
+        val messageUrlEnc = URLEncoder.encode(receiverName, "utf-8")
+        return "$ret&message=$messageUrlEnc"
+    }
+    return ret
 }
\ 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]