[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.