commit 236b6d7e0948121efe52fdbed4c108c9e5903fb5
parent d9d3db1b094f412c85ddbe52480df6289a144f33
Author: MS <ms@taler.net>
Date: Fri, 11 Dec 2020 23:20:58 +0100
Taler facade.
Getting to the point where one payment issued via
the Taler Wire Gateway shows up in a Camt53 report,
and is subsequently returned along the /outgoing
endpoint offered by the Taler facade.
Diffstat:
11 files changed, 72 insertions(+), 53 deletions(-)
diff --git a/integration-tests/tests.py b/integration-tests/tests.py
@@ -282,11 +282,7 @@ def test_taler_facade_config(make_taler_facade):
)
-# This test makes one payment via the Taler facade,
-# and expects too see it in the outgoing history.
-@pytest.mark.skip("Needs more attention")
-def test_taler_facade(make_taler_facade):
-
+def test_taler_facade_history(make_taler_facade):
assertResponse(
post(
f"{N}/facades/{TALER_FACADE}/taler/transfer",
@@ -295,13 +291,28 @@ def test_taler_facade(make_taler_facade):
amount="EUR:1",
exchange_base_url="http//url",
wtid="nice",
- credit_account="payto://iban/THEBIC/THEIBAN?receiver-name=theName"
+ credit_account="payto://iban/AGRIFRPP/FR7630006000011234567890189?receiver-name=theName"
),
auth=NEXUS_AUTH
)
)
- sleep(5) # Let automatic tasks ingest the history.
+ # normally done by background tasks:
+ assertResponse(
+ post(
+ f"{N}/bank-accounts/{NEXUS_BANK_LABEL}/payment-initiations/1/submit",
+ json=dict(),
+ auth=NEXUS_AUTH
+ )
+ )
+ # normally done by background tasks:
+ assertResponse(
+ post(
+ f"{N}/bank-accounts/{NEXUS_BANK_LABEL}/fetch-transactions", # _with_ ingestion
+ auth=NEXUS_AUTH
+ )
+ )
+
resp = assertResponse(
get(
f"{N}/facades/{TALER_FACADE}/taler/history/outgoing?delta=5",
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
@@ -419,15 +419,17 @@ fun ingestTalerTransactions() {
(NexusBankTransactionsTable.id.greater(lastId))
}.orderBy(Pair(NexusBankTransactionsTable.id, SortOrder.ASC)).forEach {
// Incoming payment.
- val tx = jacksonObjectMapper().readValue(it.transactionJson, CamtBankAccountEntry::class.java)
- val txDetails = tx.details
- if (txDetails == null) {
- // We don't support batch transactions at the moment!
- logger.warn("batch transactions not supported")
- } else {
- when (tx.creditDebitIndicator) {
- CreditDebitIndicator.CRDT -> ingestIncoming(it, txDtls = txDetails)
- }
+ logger.debug("Taler checks payment: ${it.transactionJson}")
+ val tx = jacksonObjectMapper().readValue(
+ it.transactionJson, CamtBankAccountEntry::class.java
+ )
+ val details = tx.batches?.get(0)?.batchTransactions?.get(0)?.details
+ if (details == null) {
+ logger.warn("Met a void money movement: VERY strange")
+ return@forEach
+ }
+ when (tx.creditDebitIndicator) {
+ CreditDebitIndicator.CRDT -> ingestIncoming(it, txDtls = details)
}
lastId = it.id.value
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -148,9 +148,14 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String):
}
}
val entries = res.reports.map { it.entries }.flatten()
- logger.info("found ${entries.size} transactions")
- txloop@ for (tx in entries) {
- val acctSvcrRef = tx.accountServicerRef
+ logger.info("found ${entries.size} money movements")
+ txloop@ for (entry in entries) {
+ val singletonBatchedTransaction = entry.batches?.get(0)?.batchTransactions?.get(0)
+ ?: throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "Singleton money movements policy wasn't respected"
+ )
+ val acctSvcrRef = entry.accountServicerRef
if (acctSvcrRef == null) {
// FIXME(dold): Report this!
logger.error("missing account servicer reference in transaction")
@@ -165,18 +170,18 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String):
val rawEntity = NexusBankTransactionEntity.new {
bankAccount = acct
accountTransactionId = "AcctSvcrRef:$acctSvcrRef"
- amount = tx.amount.value.toPlainString()
- currency = tx.amount.currency
- transactionJson = jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx)
- creditDebitIndicator = tx.creditDebitIndicator.name
- status = tx.status
+ amount = singletonBatchedTransaction.amount.value.toPlainString()
+ currency = singletonBatchedTransaction.amount.currency
+ transactionJson = jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(entry)
+ creditDebitIndicator = singletonBatchedTransaction.creditDebitIndicator.name
+ status = entry.status
}
rawEntity.flush()
- if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) {
- val t0 = tx.details
- val msgId = t0?.messageId
- val pmtInfId = t0?.paymentInformationId
- if (t0 != null && msgId != null && pmtInfId != null) {
+ if (singletonBatchedTransaction.creditDebitIndicator == CreditDebitIndicator.DBIT) {
+ val t0 = singletonBatchedTransaction.details
+ val msgId = t0.messageId
+ val pmtInfId = t0.paymentInformationId
+ if (msgId != null && pmtInfId != null) {
val paymentInitiation = PaymentInitiationEntity.find {
(PaymentInitiationsTable.messageId eq msgId) and
(PaymentInitiationsTable.bankAccount eq acct.id) and
@@ -184,6 +189,7 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String):
}.firstOrNull()
if (paymentInitiation != null) {
+ logger.info("Could confirm one initiated payment: $msgId")
paymentInitiation.confirmationTransaction = rawEntity
}
}
@@ -314,7 +320,6 @@ suspend fun fetchBankAccountTransactions(
fun importBankAccount(call: ApplicationCall, offeredBankAccountId: String, nexusBankAccountId: String) {
transaction {
- addLogger(StdOutSqlLogger)
val conn = requireBankConnection(call, "connid")
// first get handle of the offered bank account
val offeredAccount = OfferedBankAccountsTable.select {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -195,6 +195,7 @@ private suspend fun fetchEbicsC5x(
orderParams: EbicsOrderParams,
subscriberDetails: EbicsClientSubscriberDetails
) {
+ logger.debug("Requesting $historyType")
val response = doEbicsDownloadTransaction(
client,
subscriberDetails,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
@@ -264,9 +264,9 @@ data class ReturnInfo(
)
data class BatchTransaction(
- val amount: CurrencyAmount?,
- val creditDebitIndicator: CreditDebitIndicator?,
- val details: TransactionDetails?
+ val amount: CurrencyAmount,
+ val creditDebitIndicator: CreditDebitIndicator,
+ val details: TransactionDetails
)
@JsonInclude(JsonInclude.Include.NON_NULL)
@@ -323,9 +323,6 @@ data class CamtBankAccountEntry(
*/
val instructedAmount: CurrencyAmount?,
- // This field got recently obsoleted.
- val details: TransactionDetails? = null,
-
// list of sub-transactions participating in this money movement.
val batches: List<Batch>?
)
@@ -656,7 +653,7 @@ private fun XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang
}
private fun XmlElementDestructor.extractBatches(
- inheritableAmount: CurrencyAmount?,
+ inheritableAmount: CurrencyAmount,
outerCreditDebitIndicator: CreditDebitIndicator
): List<Batch> {
if (mapEachChildNamed("NtryDtls") {}.size != 1) throw CamtParsingError("This money movement is not a singleton #0")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -669,7 +669,7 @@ fun serverMain(dbName: String, host: String) {
call.receive<FetchSpecJson>()
} else {
FetchSpecLatestJson(
- FetchLevel.ALL,
+ FetchLevel.STATEMENT,
null
)
}
@@ -678,7 +678,7 @@ fun serverMain(dbName: String, host: String) {
fetchSpec,
accountid
)
- call.respondText("Collection performed")
+ call.respond(object {})
return@post
}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -323,7 +323,6 @@ fun dbCreateTables(dbConnectionString: String) {
Database.connect("${dbConnectionString}")
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
transaction {
- addLogger(StdOutSqlLogger)
SchemaUtils.create(
EbicsSubscribersTable,
EbicsHostsTable,
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -200,11 +200,7 @@ private fun getRelatedParty(branch: XmlElementBuilder, payment: RawPayment) {
}
}
-fun buildCamtString(
- type: Int,
- subscriberIban: String,
- history: List<RawPayment>
-): String {
+fun buildCamtString(type: Int, subscriberIban: String, history: List<RawPayment>): String {
/**
* ID types required:
*
@@ -387,10 +383,10 @@ fun buildCamtString(
element("NtryDtls/TxDtls") {
element("Refs") {
element("MsgId") {
- text("0")
+ text(it.msgId ?: "NOTPROVIDED")
}
element("PmtInfId") {
- text("0")
+ text(it.pmtInfId ?: "NOTPROVIDED")
}
element("EndToEndId") {
text("NOTPROVIDED")
@@ -556,6 +552,8 @@ private fun parsePain001(paymentRequest: String, initiatorName: String): PainPar
* Process a payment request in the pain.001 format.
*/
private fun handleCct(paymentRequest: String, initiatorName: String, ctx: RequestContext) {
+ LOGGER.debug("Handling CCT")
+ LOGGER.debug("Pain.001: $paymentRequest")
val parseResult = parsePain001(paymentRequest, initiatorName)
transaction {
try {
@@ -586,7 +584,7 @@ private fun handleCct(paymentRequest: String, initiatorName: String, ctx: Reques
}
private fun handleEbicsC53(requestContext: RequestContext): ByteArray {
- logger.debug("Handling C53 request")
+ LOGGER.debug("Handling C53 request")
val camt = constructCamtResponse(
53,
requestContext.requestObject.header,
@@ -757,7 +755,6 @@ private suspend fun ApplicationCall.handleEbicsHpb(
*/
private fun ApplicationCall.ensureEbicsHost(requestHostID: String): EbicsHostPublicInfo {
return transaction {
- addLogger(StdOutSqlLogger)
val ebicsHost =
EbicsHostEntity.find { EbicsHostsTable.hostID.upperCase() eq requestHostID.toUpperCase() }.firstOrNull()
if (ebicsHost == null) {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -392,7 +392,6 @@ fun serverMain(dbName: String) {
val pairB = CryptoUtil.generateRsaKeyPair(2048)
val pairC = CryptoUtil.generateRsaKeyPair(2048)
transaction {
- addLogger(StdOutSqlLogger)
EbicsHostEntity.new {
this.ebicsVersion = req.ebicsVersion
this.hostId = req.hostID
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -41,7 +41,9 @@ fun historyForAccount(iban: String): List<RawPayment> {
// and it makes the document invalid!
// uid = "${it[pmtInfId]}-${it[msgId]}"
uid = "${it[BankAccountTransactionsTable.pmtInfId]}",
- direction = it[BankAccountTransactionsTable.direction]
+ direction = it[BankAccountTransactionsTable.direction],
+ pmtInfId = it[BankAccountTransactionsTable.pmtInfId],
+ msgId = it[BankAccountTransactionsTable.msgId]
)
)
}
diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt
@@ -35,6 +35,12 @@ data class RawPayment(
val currency: String,
val subject: String,
val date: String? = null,
- val uid: String? = null,
- val direction: String
+ val uid: String? = null, // FIXME: explain this value.
+ val direction: String,
+ // 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
+
)
\ No newline at end of file