libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 22e474fa22b5dba46f01d141d1da46a427469b72
parent c688de41036ab5e6a7fcf4fcdad278136e2a66d1
Author: MS <ms@taler.net>
Date:   Wed, 17 Jun 2020 18:01:17 +0200

Linking a raw payment to a initiated one,

after the raw shows up in one report from the bank.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 4++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 43++++++++++++++++++++++++++++++++++++++++++-
Mnexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 2++
3 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -190,6 +190,9 @@ object InitiatedPaymentsTable : LongIdTable() { val debitorBic = text("debitorBic") val debitorName = text("debitorName").nullable() val submitted = bool("submitted").default(false) + // points at the raw transaction witnessing that this + // initiated payment was successfully performed. + val rawConfirmation = reference("rawConfirmation", RawBankTransactionsTable).nullable() } class InitiatedPaymentEntity(id: EntityID<Long>) : LongEntity(id) { @@ -207,6 +210,7 @@ class InitiatedPaymentEntity(id: EntityID<Long>) : LongEntity(id) { var creditorBic by InitiatedPaymentsTable.creditorBic var creditorName by InitiatedPaymentsTable.creditorName var submitted by InitiatedPaymentsTable.submitted + var rawConfirmation by RawBankTransactionEntity optionalReferencedOn InitiatedPaymentsTable.rawConfirmation } /** diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt @@ -110,6 +110,26 @@ private fun findDuplicate(bankAccountId: String, acctSvcrRef: String): RawBankTr } } +// retrieves the initiated payment and marks it as "performed +// by the bank". This avoids to submit it again. 'subject' is +// the the A.K.A. unstructured remittance information. +fun markInitiatedAsConfirmed(subject: String, debtorIban: String, rawUuid: Long) { + // not introducing a 'transaction {}' block since + // this function should be always be invoked from one. + val initiatedPayment = InitiatedPaymentEntity.find { + InitiatedPaymentsTable.subject eq subject and + (InitiatedPaymentsTable.debitorIban eq debtorIban) + }.firstOrNull() + if (initiatedPayment == null) { + logger.info("Payment '$subject' was never programmatically prepared") + return + } + val rawEntity = RawBankTransactionEntity.findById(rawUuid) ?: throw NexusError( + HttpStatusCode.InternalServerError, "Raw payment '$rawUuid' disappeared from database" + ) + initiatedPayment.rawConfirmation = rawEntity +} + fun processCamtMessage( bankAccountId: String, camtDoc: Document @@ -135,7 +155,8 @@ fun processCamtMessage( // https://bugs.gnunet.org/view.php?id=6381 break } - RawBankTransactionEntity.new { + + val rawEntity = RawBankTransactionEntity.new { bankAccount = acct accountTransactionId = "AcctSvcrRef:$acctSvcrRef" amount = tx.amount @@ -144,6 +165,26 @@ fun processCamtMessage( creditDebitIndicator = tx.creditDebitIndicator.name status = tx.status } + if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) { + // assuming batches contain always one element, as aren't fully + // implemented now. + val uniqueBatchElement = tx.details.get(0) + markInitiatedAsConfirmed( + // if the user has two initiated payments under the same + // IBAN with the same subject, then this logic will cause + // problems. But a programmatic user should take care of this. + uniqueBatchElement.unstructuredRemittanceInformation, + if (uniqueBatchElement.relatedParties.debtorAccount !is AccountIdentificationIban) { + throw NexusError( + HttpStatusCode.InternalServerError, + "Parsed CAMT didn't have IBAN in debtor!" + ) + } else { + uniqueBatchElement.relatedParties.debtorAccount.iban + }, + rawEntity.id.value + ) + } } } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt @@ -379,6 +379,7 @@ private suspend fun talerAddIncoming(call: ApplicationCall, httpClient: HttpClie // submits ALL the prepared payments from ALL the Taler facades. // FIXME(dold): This should not be done here. +// -> why? It crawls the *taler* facade to find payment to submit. suspend fun submitPreparedPaymentsViaEbics(httpClient: HttpClient) { data class EbicsSubmission( val subscriberDetails: EbicsClientSubscriberDetails, @@ -417,6 +418,7 @@ suspend fun submitPreparedPaymentsViaEbics(httpClient: HttpClient) { val subscriberDetails = getEbicsSubscriberDetailsInternal(subscriberEntity) workQueue.add(EbicsSubmission(subscriberDetails, pain001document)) // FIXME: the payment must be flagged AFTER the submission happens. + // -> this is an open question: see #6367. it.submitted = true } }