libeufin

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

commit 4ea1cd3ac13a815c8a92064b7bbc7bdef5bd2e2c
parent b252736bab5d7bee04178b15c659ed0aaf222200
Author: MS <ms@taler.net>
Date:   Thu,  4 Feb 2021 00:00:46 +0100

TWG: add refund-routine

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 25+------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 57 insertions(+), 28 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -134,36 +134,13 @@ object NexusBankTransactionsTable : LongIdTable() { * transaction can show up multiple times with a different status. */ val accountTransactionId = text("accountTransactionId") - - /** - * Bank account that this transaction happened on. - */ val bankAccount = reference("bankAccount", NexusBankAccountsTable) - - /** - * Direction of the amount. - */ val creditDebitIndicator = text("creditDebitIndicator") - - /** - * Currency of the amount. - */ val currency = text("currency") val amount = text("amount") - - /** - * Booked / pending / informational. - */ val status = enumerationByName("status", 16, EntryStatus::class) - - /** - * Another, later transaction that updates the status of the current transaction. - */ + // Another, later transaction that updates the status of the current transaction. val updatedBy = optReference("updatedBy", NexusBankTransactionsTable) - - /** - * Full details of the transaction in JSON format. - */ val transactionJson = text("transactionJson") } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt @@ -40,10 +40,7 @@ import org.jetbrains.exposed.dao.id.IdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import tech.libeufin.nexus.bankaccount.addPaymentInitiation -import tech.libeufin.nexus.iso20022.CamtBankAccountEntry -import tech.libeufin.nexus.iso20022.CreditDebitIndicator -import tech.libeufin.nexus.iso20022.EntryStatus -import tech.libeufin.nexus.iso20022.TransactionDetails +import tech.libeufin.nexus.iso20022.* import tech.libeufin.nexus.server.* import tech.libeufin.util.CryptoUtil import tech.libeufin.util.EbicsProtocolError @@ -422,9 +419,64 @@ private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: Transact debtorIban, debtorAgent.bic, debtorName, "DBIT" ) } + refundInvalidPayments() return } +fun refundInvalidPayments() { + logger.debug("Finding new invalid payments to refund") + transaction { + TalerInvalidIncomingPaymentEntity.find { + TalerInvalidIncomingPaymentsTable.refunded eq false + }.forEach { + val paymentData = jacksonObjectMapper().readValue( + it.payment.transactionJson, + CamtBankAccountEntry::class.java + ) + if (paymentData.batches == null) { + logger.error("A singleton batched payment was expected to be refunded," + + " but none was found (in transaction (AcctSvcrRef): ${paymentData.accountServicerRef})") + throw NexusError(HttpStatusCode.InternalServerError, "Unexpected void payment, cannot refund") + } + val debtorAccount = paymentData.batches[0].batchTransactions[0].details.debtorAccount + if (debtorAccount == null || debtorAccount.iban == null) { + logger.error("Could not find a IBAN to refund in transaction (AcctSvcrRef): ${paymentData.accountServicerRef}, aborting refund") + throw NexusError(HttpStatusCode.InternalServerError, "IBAN to refund not found") + } + val debtorAgent = paymentData.batches[0].batchTransactions[0].details.debtorAgent + if (debtorAgent?.bic == null) { + logger.error("Could not find the BIC of refundable IBAN at transaction (AcctSvcrRef): ${paymentData.accountServicerRef}, aborting refund") + throw NexusError(HttpStatusCode.InternalServerError, "BIC to refund not found") + } + val debtorPerson = paymentData.batches[0].batchTransactions[0].details.debtor + if (debtorPerson?.name == null) { + logger.error("Could not find the owner's name of refundable IBAN at transaction (AcctSvcrRef): ${paymentData.accountServicerRef}, aborting refund") + throw NexusError(HttpStatusCode.InternalServerError, "Name to refund not found") + } + // FIXME: investigate this amount! + val amount = paymentData.batches[0].batchTransactions[0].details.instructedAmount + if (amount == null) { + logger.error("Could not find the amount to refund for transaction (AcctSvcrRef): ${paymentData.accountServicerRef}, aborting refund") + throw NexusError(HttpStatusCode.InternalServerError, "Amount to refund not found") + } + // FIXME: the amount to refund should be reduced, according to the refund fees. + + addPaymentInitiation( + Pain001Data( + creditorIban = debtorAccount.iban, + creditorBic = debtorAgent.bic, + creditorName = debtorPerson.name, + subject = "Taler refund of: ${paymentData.batches[0].batchTransactions[0].details.unstructuredRemittanceInformation}", + sum = amount.value, + currency = amount.currency + ), + it.payment.bankAccount // the Exchange bank account. + ) + it.refunded = true + } + } +} + /** * Crawls the database to find ALL the users that have a Taler * facade and process their histories respecting the TWG policy.