gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: re- enabling the Taler module


From: gnunet
Subject: [libeufin] branch master updated: re- enabling the Taler module
Date: Wed, 27 May 2020 16:57:44 +0200

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

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 10ced95  re- enabling the Taler module
10ced95 is described below

commit 10ced95a5d4c70dfb06fad8e852972e00e19bfb4
Author: MS <address@hidden>
AuthorDate: Wed May 27 16:57:27 2020 +0200

    re- enabling the Taler module
---
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |    1 -
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  |    1 -
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |    1 -
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 1075 ++++++++++----------
 4 files changed, 519 insertions(+), 559 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 0db30ba..4999851 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -224,7 +224,6 @@ suspend fun fetchEbicsC5x(
     }
 }
 
-
 /**
  * Create a PAIN.001 XML document according to the input data.
  * Needs to be called within a transaction block.
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index 8c042a1..e9c16ff 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -261,7 +261,6 @@ data class Pain001Data(
     val creditorIban: String,
     val creditorBic: String,
     val creditorName: String,
-    val debitorAccount: String,
     val sum: Amount,
     val currency: String,
     val subject: String
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 017a27e..cb3c638 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -480,7 +480,6 @@ fun serverMain(dbName: String) {
                             creditorIban = body.iban,
                             creditorBic = body.bic,
                             creditorName = body.name,
-                            debitorAccount = bankAccount.id.value,
                             sum = amount.amount,
                             currency = amount.currency,
                             subject = body.subject
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index c15b370..c9f9e48 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -20,559 +20,522 @@ import tech.libeufin.util.*
 import kotlin.math.abs
 import kotlin.math.min
 
-//class Taler(app: Route) {
-//
-//    /** Payment initiating data structures: one endpoint 
"$BASE_URL/transfer". */
-//    private data class TalerTransferRequest(
-//        val request_uid: String,
-//        val amount: String,
-//        val exchange_base_url: String,
-//        val wtid: String,
-//        val credit_account: String
-//    )
-//    private data class TalerTransferResponse(
-//        // point in time when the nexus put the payment instruction into the 
database.
-//        val timestamp: GnunetTimestamp,
-//        val row_id: Long
-//    )
-//
-//    /** History accounting data structures */
-//    private data class TalerIncomingBankTransaction(
-//        val row_id: Long,
-//        val date: GnunetTimestamp, // timestamp
-//        val amount: String,
-//        val credit_account: String, // payto form,
-//        val debit_account: String,
-//        val reserve_pub: String
-//    )
-//    private data class TalerIncomingHistory(
-//        var incoming_transactions: MutableList<TalerIncomingBankTransaction> 
= mutableListOf()
-//    )
-//    private data class TalerOutgoingBankTransaction(
-//        val row_id: Long,
-//        val date: GnunetTimestamp, // timestamp
-//        val amount: String,
-//        val credit_account: String, // payto form,
-//        val debit_account: String,
-//        val wtid: String,
-//        val exchange_base_url: String
-//    )
-//    private data class TalerOutgoingHistory(
-//        var outgoing_transactions: MutableList<TalerOutgoingBankTransaction> 
= mutableListOf()
-//    )
-//
-//    /** Test APIs' data structures. */
-//    private data class TalerAdminAddIncoming(
-//        val amount: String,
-//        val reserve_pub: String,
-//        /**
-//         * This account is the one giving money to the exchange.  It doesn't
-//         * have to be 'created' as it might (and normally is) simply be a 
payto://
-//         * address pointing to a bank account hosted in a different financial
-//         * institution.
-//         */
-//        val debit_account: String
-//    )
-//
-//    private data class GnunetTimestamp(
-//        val t_ms: Long
-//    )
-//    private data class TalerAddIncomingResponse(
-//        val timestamp: GnunetTimestamp,
-//        val row_id: Long
-//    )
-//
-//    /**
-//     * Helper data structures.
-//     */
-//    data class Payto(
-//        val name: String = "NOTGIVEN",
-//        val iban: String,
-//        val bic: String = "NOTGIVEN"
-//    )
-//    /**
-//     * Helper functions
-//     */
-//    fun parsePayto(paytoUri: String): Payto {
-//        /**
-//         * First try to parse a "iban"-type payto URI.  If that fails,
-//         * then assume a test is being run under the "x-taler-bank" type.
-//         * If that one fails too, throw exception.
-//         *
-//         * Note: since the Nexus doesn't have the notion of "x-taler-bank",
-//         * such URIs must yield a iban-compatible tuple of values.  
Therefore,
-//         * the plain bank account number maps to a "iban", and the <bank 
hostname>
-//         * maps to a "bic".
-//         */
-//
-//
-//        /**
-//         * payto://iban/BIC?/IBAN?name=<name>
-//         * payto://x-taler-bank/<bank hostname>/<plain account number>
-//         */
-//
-//        val ibanMatch = 
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
-//        if (ibanMatch != null) {
-//            val (bic, iban, name) = ibanMatch.destructured
-//            return Payto(name, iban, bic.replace("/", ""))
-//        }
-//        val xTalerBankMatch = 
Regex("payto://x-taler-bank/localhost/([0-9]+)").find(paytoUri)
-//        if (xTalerBankMatch != null) {
-//            val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
-//            return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
-//        }
-//
-//        throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
-//    }
-//
-//    /** Sort query results in descending order for negative deltas, and 
ascending otherwise.  */
-//    private fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): 
List<T> {
-//        return if (delta < 0) {
-//            this.sortedByDescending { it.id }
-//        } else {
-//            this.sortedBy { it.id }
-//        }
-//    }
-//
-//    /**
-//     * NOTE: those payto-builders default all to the x-taler-bank transport.
-//     * A mechanism to easily switch transport is needed, as production needs
-//     * 'iban'.
-//     */
-//    private fun buildPaytoUri(name: String, iban: String, bic: String): 
String {
-//        return "payto://x-taler-bank/localhost/$iban"
-//    }
-//    private fun buildPaytoUri(iban: String, bic: String): String {
-//        return "payto://x-taler-bank/localhost/$iban"
-//    }
-//
-//    /** Builds the comparison operator for history entries based on the sign 
of 'delta'  */
-//    private fun getComparisonOperator(delta: Int, start: Long, table: 
IdTable<Long>): Op<Boolean> {
-//        return if (delta < 0) {
-//            Expression.build {
-//                table.id less start
-//            }
-//        } else {
-//            Expression.build {
-//                table.id greater start
-//            }
-//        }
-//    }
-//    /** Helper handling 'start' being optional and its dependence on 
'delta'.  */
-//    private fun handleStartArgument(start: String?, delta: Int): Long {
-//        return expectLong(start) ?: if (delta >= 0) {
-//            /**
-//             * Using -1 as the smallest value, as some DBMS might use 0 and 
some
-//             * others might use 1 as the smallest row id.
-//             */
-//            -1
-//        } else {
-//            /**
-//             * NOTE: the database currently enforces there MAX_VALUE is 
always
-//             * strictly greater than any row's id in the database.  In fact, 
the
-//             * database throws exception whenever a new row is going to 
occupy
-//             * the MAX_VALUE with its id.
-//             */
-//            Long.MAX_VALUE
-//        }
-//    }
-//
-//    /**
-//     * The Taler layer cannot rely on the ktor-internal 
JSON-converter/responder,
-//     * because this one adds a "charset" extra information in the 
Content-Type header
-//     * that makes the GNUnet JSON parser unhappy.
-//     *
-//     * The workaround is to explicitly convert the 'data class'-object into 
a JSON
-//     * string (what this function does), and use the simpler respondText 
method.
-//     */
-//    private fun customConverter(body: Any): String {
-//        return jacksonObjectMapper().writeValueAsString(body)
-//    }
-//
-//    /**
-//     * This function indicates whether a payment in the raw table was 
already reported
-//     * by some other EBICS message.  It works for both incoming and outgoing 
payments.
-//     * Basically, it tries to match all the relevant details with those from 
the records
-//     * that are already stored in the local "taler" database.
-//     *
-//     * @param entry a new raw payment to be checked.
-//     * @return true if the payment was already "seen" by the Taler layer, 
false otherwise.
-//     */
-//    private fun duplicatePayment(entry: RawBankTransactionEntity): Boolean {
-//        return false
-//    }
-//
-//    /**
-//     * This function checks whether the bank didn't accept one exchange's 
payment initiation.
-//     *
-//     * @param entry the raw entry to check
-//     * @return true if the payment failed, false if it was successful.
-//     */
-//    private fun paymentFailed(entry: RawBankTransactionEntity): Boolean {
-//        return false
-//    }
-//
-//    /** Attach Taler endpoints to the main Web server */
-//
-//    init {
-//        app.get("/taler") {
-//            call.respondText("Taler Gateway Hello\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
-//            return@get
-//        }
-//        app.post("/taler/transfer") {
-//            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-//            val transferRequest = call.receive<TalerTransferRequest>()
-//            val amountObj = parseAmount(transferRequest.amount)
-//            val creditorObj = parsePayto(transferRequest.credit_account)
-//            val opaque_row_id = transaction {
-//                val creditorData = parsePayto(transferRequest.credit_account)
-//                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
-//                val nexusUser = extractNexusUser(exchangeId)
-//                /** Checking the UID has the desired characteristics */
-//                TalerRequestedPaymentEntity.find {
-//                    TalerRequestedPayments.requestUId eq 
transferRequest.request_uid
-//                }.forEach {
-//                    if (
-//                        (it.amount != transferRequest.amount) or
-//                        (it.creditAccount != 
transferRequest.exchange_base_url) or
-//                        (it.wtid != transferRequest.wtid)
-//                    ) {
-//                        throw NexusError(
-//                            HttpStatusCode.Conflict,
-//                            "This uid (${transferRequest.request_uid}) 
belongs to a different payment already"
-//                        )
-//                    }
-//                }
-//                val pain001 = addPreparedPayment(
-//                    Pain001Data(
-//                        creditorIban = creditorData.iban,
-//                        creditorBic = creditorData.bic,
-//                        creditorName = creditorData.name,
-//                        subject = transferRequest.wtid,
-//                        sum = amountObj.amount,
-//                        currency = amountObj.currency,
-//                        debitorAccount = exchangeBankAccount.id.value
-//                    ),
-//                    nexusUser
-//                )
-//                val rawEbics = if (!isProduction()) {
-//                    RawBankTransactionEntity.new {
-//                        sourceFileName = "test"
-//                        unstructuredRemittanceInformation = 
transferRequest.wtid
-//                        transactionType = "DBIT"
-//                        currency = amountObj.currency
-//                        this.amount = amountObj.amount.toPlainString()
-//                        counterpartBic = creditorObj.bic
-//                        counterpartIban = creditorObj.iban
-//                        counterpartName = creditorObj.name
-//                        bankAccount = exchangeBankAccount
-//                        bookingDate = DateTime.now().millis
-//                        this.nexusUser = nexusUser
-//                        status = "BOOK"
-//                    }
-//                } else null
-//
-//                val row = TalerRequestedPaymentEntity.new {
-//                    preparedPayment = pain001 // not really used/needed, 
just here to silence warnings
-//                    exchangeBaseUrl = transferRequest.exchange_base_url
-//                    requestUId = transferRequest.request_uid
-//                    amount = transferRequest.amount
-//                    wtid = transferRequest.wtid
-//                    creditAccount = transferRequest.credit_account
-//                    rawConfirmed = rawEbics
-//                }
-//
-//                row.id.value
-//            }
-//            call.respond(
-//                HttpStatusCode.OK,
-//                TextContent(
-//                    customConverter(
-//                        TalerTransferResponse(
-//                            /**
-//                             * Normally should point to the next round where 
the background
-//                             * routine will send new PAIN.001 data to the 
bank; work in progress..
-//                             */
-//                            timestamp = 
GnunetTimestamp(DateTime.now().millis),
-//                            row_id = opaque_row_id
-//                        )
-//                    ),
-//                    ContentType.Application.Json
-//                )
-//            )
-//            return@post
-//        }
-//        /** Test-API that creates one new payment addressed to the exchange. 
 */
-//        app.post("/taler/admin/add-incoming") {
-//            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-//            val addIncomingData = call.receive<TalerAdminAddIncoming>()
-//            val debtor = parsePayto(addIncomingData.debit_account)
-//            val amount = parseAmount(addIncomingData.amount)
-//            val (bookingDate, opaque_row_id) = transaction {
-//                val exchangeBankAccount = 
getBankAccountsFromNexusUserId(exchangeId).first()
-//                val rawPayment = RawBankTransactionEntity.new {
-//                    sourceFileName = "test"
-//                    unstructuredRemittanceInformation = 
addIncomingData.reserve_pub
-//                    transactionType = "CRDT"
-//                    currency = amount.currency
-//                    this.amount = amount.amount.toPlainString()
-//                    counterpartBic = debtor.bic
-//                    counterpartName = debtor.name
-//                    counterpartIban = debtor.iban
-//                    bookingDate = DateTime.now().millis
-//                    status = "BOOK"
-//                    nexusUser = extractNexusUser(exchangeId)
-//                    bankAccount = exchangeBankAccount
-//                }
-//                /** This payment is "valid by default" and will be returned
-//                 * as soon as the exchange will ask for new payments.  */
-//                val row = TalerIncomingPaymentEntity.new {
-//                    payment = rawPayment
-//                    valid = true
-//                }
-//                Pair(rawPayment.bookingDate, row.id.value)
-//            }
-//            call.respond(
-//                TextContent(
-//                    customConverter(
-//                        TalerAddIncomingResponse(
-//                            timestamp = GnunetTimestamp(bookingDate/ 1000),
-//                            row_id = opaque_row_id
-//                        )
-//                    ),
-//                ContentType.Application.Json
-//                )
-//            )
-//            return@post
-//        }
-//
-//        /** This endpoint triggers the refunding of invalid payments.  
'Refunding'
-//         * in this context means that nexus _prepares_ the payment 
instruction and
-//         * places it into a further table.  Eventually, another routine will 
perform
-//         * all the prepared payments.  */
-//        
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
-//            val userId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-//            val nexusUser = getNexusUser(userId)
-//            val callerBankAccount = expectNonNull(call.parameters["acctid"])
-//            transaction {
-//                val bankAccount = getBankAccount(
-//                    userId,
-//                    callerBankAccount
-//                )
-//                TalerIncomingPaymentEntity.find {
-//                    TalerIncomingPayments.refunded eq false and 
(TalerIncomingPayments.valid eq false)
-//                }.forEach {
-//                    addPreparedPayment(
-//                        Pain001Data(
-//                            creditorName = it.payment.counterpartName,
-//                            creditorIban = it.payment.counterpartIban,
-//                            creditorBic = it.payment.counterpartBic,
-//                            sum = calculateRefund(it.payment.amount),
-//                            subject = "Taler refund",
-//                            currency = it.payment.currency,
-//                            debitorAccount = callerBankAccount
-//                        ),
-//                        nexusUser
-//                    )
-//                    it.refunded = true
-//                }
-//            }
-//            return@post
-//        }
-//
-//        /** This endpoint triggers the examination of raw incoming payments 
aimed
-//         * at separating the good payments (those that will lead to a new 
reserve
-//         * being created), from the invalid payments (those with a invalid 
subject
-//         * that will soon be refunded.)  Recently, the examination of raw 
OUTGOING
-//         * payment was added as well.
-//         */
-//        app.post("/ebics/taler/{id}/crunch-raw-transactions") {
-//            val id = ensureNonNull(call.parameters["id"])
-//            // first find highest ID value of already processed rows.
-//            transaction {
-//                val subscriberAccount = 
getBankAccountsFromNexusUserId(id).first()
-//                /**
-//                 * Search for fresh incoming payments in the raw table, and 
making pointers
-//                 * from the Taler incoming payments table to the found fresh 
(and valid!) payments.
-//                 */
-//                val latestIncomingPaymentId: Long = 
TalerIncomingPaymentEntity.getLast()
-//                RawBankTransactionEntity.find {
-//                    /** Those with exchange bank account involved */
-//                    RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value and
-//                            /** Those that are incoming */
-//                            (RawBankTransactionsTable.transactionType eq 
"CRDT") and
-//                            /** Those that are booked */
-//                            (RawBankTransactionsTable.status eq "BOOK") and
-//                            /** Those that came later than the latest 
processed payment */
-//                            
(RawBankTransactionsTable.id.greater(latestIncomingPaymentId))
-//
-//                }.forEach {
-//                    if (duplicatePayment(it)) {
-//                        logger.warn("Incomint payment already seen")
-//                        throw NexusError(
-//                            HttpStatusCode.InternalServerError,
-//                            "Incoming payment already seen"
-//                        )
-//                    }
-//                    if 
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
-//                        TalerIncomingPaymentEntity.new {
-//                            payment = it
-//                            valid = true
-//                        }
-//                    } else {
-//                        TalerIncomingPaymentEntity.new {
-//                            payment = it
-//                            valid = false
-//                        }
-//                    }
-//                }
-//                /**
-//                 * Search for fresh OUTGOING transactions acknowledged by 
the bank.  As well
-//                 * searching only for BOOKed transactions, even though 
status changes should
-//                 * be really unexpected here.
-//                 */
-//                val latestOutgoingPaymentId = 
TalerRequestedPaymentEntity.getLast()
-//                RawBankTransactionEntity.find {
-//                    /** Those that came after the last processed payment */
-//                    RawBankTransactionsTable.id greater 
latestOutgoingPaymentId and
-//                            /** Those involving the exchange bank account */
-//                            (RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value) and
-//                            /** Those that are outgoing */
-//                            (RawBankTransactionsTable.transactionType eq 
"DBIT")
-//                }.forEach {
-//                    if (paymentFailed(it)) {
-//                        logger.error("Bank didn't accept one payment from 
the exchange")
-//                        throw NexusError(
-//                            HttpStatusCode.InternalServerError,
-//                            "Bank didn't accept one payment from the 
exchange"
-//                        )
-//                    }
-//                    if (duplicatePayment(it)) {
-//                        logger.warn("Incomint payment already seen")
-//                        throw NexusError(
-//                            HttpStatusCode.InternalServerError,
-//                            "Outgoing payment already seen"
-//                        )
-//                    }
-//                    var talerRequested = TalerRequestedPaymentEntity.find {
-//                        TalerRequestedPayments.wtid eq 
it.unstructuredRemittanceInformation
-//                    }.firstOrNull() ?: throw NexusError(
-//                        HttpStatusCode.InternalServerError,
-//                        "Unrecognized fresh outgoing payment met (subject: 
${it.unstructuredRemittanceInformation})."
-//                    )
-//                    talerRequested.rawConfirmed = it
-//                }
-//            }
-//
-//            call.respondText (
-//                "New raw payments Taler-processed",
-//                ContentType.Text.Plain,
-//                HttpStatusCode.OK
-//            )
-//            return@post
-//        }
-//        /** Responds only with the payments that the EXCHANGE made.  
Typically to
-//         * merchants but possibly to refund invalid incoming payments.  A 
payment is
-//         * counted only if was once confirmed by the bank.
-//         */
-//        app.get("/taler/history/outgoing") {
-//            /* sanitize URL arguments */
-//            val subscriberId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-//            val delta: Int = expectInt(call.expectUrlParameter("delta"))
-//            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
-//            val startCmpOp = getComparisonOperator(delta, start, 
TalerRequestedPayments)
-//            /* retrieve database elements */
-//            val history = TalerOutgoingHistory()
-//            transaction {
-//                /** Retrieve all the outgoing payments from the _clean Taler 
outgoing table_ */
-//                val subscriberBankAccount = 
getBankAccountsFromNexusUserId(subscriberId).first()
-//                val reqPayments = TalerRequestedPaymentEntity.find {
-//                    TalerRequestedPayments.rawConfirmed.isNotNull() and 
startCmpOp
-//                }.orderTaler(delta)
-//                if (reqPayments.isNotEmpty()) {
-//                    reqPayments.subList(0, min(abs(delta), 
reqPayments.size)).forEach {
-//                        history.outgoing_transactions.add(
-//                            TalerOutgoingBankTransaction(
-//                                row_id = it.id.value,
-//                                amount = it.amount,
-//                                wtid = it.wtid,
-//                                date = 
GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError(
-//                                    HttpStatusCode.InternalServerError, 
"Null value met after check, VERY strange.")),
-//                                credit_account = it.creditAccount,
-//                                debit_account = 
buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
-//                                exchange_base_url = 
"FIXME-to-request-along-subscriber-registration"
-//                            )
-//                        )
-//                    }
-//                }
-//            }
-//            call.respond(
-//                HttpStatusCode.OK,
-//                TextContent(customConverter(history), 
ContentType.Application.Json)
-//            )
-//            return@get
-//        }
-//        /** Responds only with the valid incoming payments */
-//        app.get("/taler/history/incoming") {
-//            val exchangeId = transaction { 
authenticateRequest(call.request.headers["Authorization"]).id.value }
-//            val delta: Int = expectInt(call.expectUrlParameter("delta"))
-//            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
-//            val history = TalerIncomingHistory()
-//            val startCmpOp = getComparisonOperator(delta, start, 
TalerIncomingPayments)
-//            transaction {
-//                /**
-//                 * Below, the test harness creates the exchange's bank 
account
-//                 * object based on the payto:// given as the funds receiver.
-//                 *
-//                 * This is needed because nexus takes this information from 
the
-//                 * bank - normally - but tests are currently avoiding any 
interaction
-//                 * with banks or sandboxes.
-//                 */
-//                if (!isProduction()) {
-//                    throw Error("currently not implemented")
-//                    val EXCHANGE_BANKACCOUNT_ID = "exchange-bankaccount-id"
-//                    if (BankAccountEntity.findById(EXCHANGE_BANKACCOUNT_ID) 
== null) {
-//                        val newBankAccount = BankAccountEntity.new(id = 
EXCHANGE_BANKACCOUNT_ID) {
-//                            accountHolder = "Test Exchange"
-//                            iban = "42"
-//                            bankCode = "localhost"
-//                        }
-//                        val nexusUser = extractNexusUser(exchangeId)
-//                        BankAccountMapEntity.new {
-//                            bankAccount = newBankAccount
-//                            ebicsSubscriber = getEbicsTransport(exchangeId)
-//                            this.nexusUser = nexusUser
-//                        }
-//                    }
-//                }
-//                val orderedPayments = TalerIncomingPaymentEntity.find {
-//                    TalerIncomingPayments.valid eq true and startCmpOp
-//                }.orderTaler(delta)
-//                if (orderedPayments.isNotEmpty()) {
-//                    orderedPayments.subList(0, min(abs(delta), 
orderedPayments.size)).forEach {
-//                        history.incoming_transactions.add(
-//                            TalerIncomingBankTransaction(
-//                                date = 
GnunetTimestamp(it.payment.bookingDate / 1000),
-//                                row_id = it.id.value,
-//                                amount = 
"${it.payment.currency}:${it.payment.amount}",
-//                                reserve_pub = 
it.payment.unstructuredRemittanceInformation,
-//                                credit_account = buildPaytoUri(
-//                                    it.payment.bankAccount.accountHolder,
-//                                    it.payment.bankAccount.iban,
-//                                    it.payment.bankAccount.bankCode
-//                                ),
-//                                debit_account = buildPaytoUri(
-//                                    it.payment.counterpartName,
-//                                    it.payment.counterpartIban,
-//                                    it.payment.counterpartBic
-//                                )
-//                            )
-//                        )
-//                    }
-//                }
-//            }
-//            call.respond(TextContent(customConverter(history), 
ContentType.Application.Json))
-//            return@get
-//        }
-//    }
-//}
\ No newline at end of file
+/** Payment initiating data structures: one endpoint "$BASE_URL/transfer". */
+data class TalerTransferRequest(
+    val request_uid: String,
+    val amount: String,
+    val exchange_base_url: String,
+    val wtid: String,
+    val credit_account: String
+)
+
+data class TalerTransferResponse(
+    // point in time when the nexus put the payment instruction into the 
database.
+    val timestamp: GnunetTimestamp,
+    val row_id: Long
+)
+
+/** History accounting data structures */
+data class TalerIncomingBankTransaction(
+    val row_id: Long,
+    val date: GnunetTimestamp, // timestamp
+    val amount: String,
+    val credit_account: String, // payto form,
+    val debit_account: String,
+    val reserve_pub: String
+)
+
+data class TalerIncomingHistory(
+    var incoming_transactions: MutableList<TalerIncomingBankTransaction> = 
mutableListOf()
+)
+data class TalerOutgoingBankTransaction(
+    val row_id: Long,
+    val date: GnunetTimestamp, // timestamp
+    val amount: String,
+    val credit_account: String, // payto form,
+    val debit_account: String,
+    val wtid: String,
+    val exchange_base_url: String
+)
+
+data class TalerOutgoingHistory(
+    var outgoing_transactions: MutableList<TalerOutgoingBankTransaction> = 
mutableListOf()
+)
+
+/** Test APIs' data structures. */
+data class TalerAdminAddIncoming(
+    val amount: String,
+    val reserve_pub: String,
+    /**
+     * This account is the one giving money to the exchange.  It doesn't
+     * have to be 'created' as it might (and normally is) simply be a payto://
+     * address pointing to a bank account hosted in a different financial
+     * institution.
+     */
+    val debit_account: String
+)
+
+data class GnunetTimestamp(
+    val t_ms: Long
+)
+data class TalerAddIncomingResponse(
+    val timestamp: GnunetTimestamp,
+    val row_id: Long
+)
+
+/**
+ * Helper data structures.
+ */
+data class Payto(
+    val name: String = "NOTGIVEN",
+    val iban: String,
+    val bic: String = "NOTGIVEN"
+)
+
+fun parsePayto(paytoUri: String): Payto {
+    /**
+     * First try to parse a "iban"-type payto URI.  If that fails,
+     * then assume a test is being run under the "x-taler-bank" type.
+     * If that one fails too, throw exception.
+     *
+     * Note: since the Nexus doesn't have the notion of "x-taler-bank",
+     * such URIs must yield a iban-compatible tuple of values.  Therefore,
+     * the plain bank account number maps to a "iban", and the <bank hostname>
+     * maps to a "bic".
+     */
+
+
+    /**
+     * payto://iban/BIC?/IBAN?name=<name>
+     * payto://x-taler-bank/<bank hostname>/<plain account number>
+     */
+
+    val ibanMatch = 
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
+    if (ibanMatch != null) {
+        val (bic, iban, name) = ibanMatch.destructured
+        return Payto(name, iban, bic.replace("/", ""))
+    }
+    val xTalerBankMatch = 
Regex("payto://x-taler-bank/localhost/([0-9]+)").find(paytoUri)
+    if (xTalerBankMatch != null) {
+        val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
+        return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
+    }
+
+    throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
+}
+
+/** Sort query results in descending order for negative deltas, and ascending 
otherwise.  */
+fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): List<T> {
+    return if (delta < 0) {
+        this.sortedByDescending { it.id }
+    } else {
+        this.sortedBy { it.id }
+    }
+}
+
+/**
+ * NOTE: those payto-builders default all to the x-taler-bank transport.
+ * A mechanism to easily switch transport is needed, as production needs
+ * 'iban'.
+ */
+fun buildPaytoUri(name: String, iban: String, bic: String): String {
+    return "payto://x-taler-bank/localhost/$iban"
+}
+fun buildPaytoUri(iban: String, bic: String): String {
+    return "payto://x-taler-bank/localhost/$iban"
+}
+
+/** Builds the comparison operator for history entries based on the sign of 
'delta'  */
+fun getComparisonOperator(delta: Int, start: Long, table: IdTable<Long>): 
Op<Boolean> {
+    return if (delta < 0) {
+        Expression.build {
+            table.id less start
+        }
+    } else {
+        Expression.build {
+            table.id greater start
+        }
+    }
+}
+/** Helper handling 'start' being optional and its dependence on 'delta'.  */
+fun handleStartArgument(start: String?, delta: Int): Long {
+    return expectLong(start) ?: if (delta >= 0) {
+        /**
+         * Using -1 as the smallest value, as some DBMS might use 0 and some
+         * others might use 1 as the smallest row id.
+         */
+        -1
+    } else {
+        /**
+         * NOTE: the database currently enforces there MAX_VALUE is always
+         * strictly greater than any row's id in the database.  In fact, the
+         * database throws exception whenever a new row is going to occupy
+         * the MAX_VALUE with its id.
+         */
+        Long.MAX_VALUE
+    }
+}
+
+/**
+ * The Taler layer cannot rely on the ktor-internal JSON-converter/responder,
+ * because this one adds a "charset" extra information in the Content-Type 
header
+ * that makes the GNUnet JSON parser unhappy.
+ *
+ * The workaround is to explicitly convert the 'data class'-object into a JSON
+ * string (what this function does), and use the simpler respondText method.
+ */
+fun customConverter(body: Any): String {
+    return jacksonObjectMapper().writeValueAsString(body)
+}
+
+/**
+ * This function indicates whether a payment in the raw table was already 
reported
+ * by some other EBICS message.  It works for both incoming and outgoing 
payments.
+ * Basically, it tries to match all the relevant details with those from the 
records
+ * that are already stored in the local "taler" database.
+ *
+ * @param entry a new raw payment to be checked.
+ * @return true if the payment was already "seen" by the Taler layer, false 
otherwise.
+ */
+fun duplicatePayment(entry: RawBankTransactionEntity): Boolean {
+    return false
+}
+
+/**
+ * This function checks whether the bank didn't accept one exchange's payment 
initiation.
+ *
+ * @param entry the raw entry to check
+ * @return true if the payment failed, false if it was successful.
+ */
+fun paymentFailed(entry: RawBankTransactionEntity): Boolean {
+    return false
+}
+
+class Taler(app: Route) {
+
+    /** Attach Taler endpoints to the main Web server */
+
+    init {
+        app.get("/taler") {
+            call.respondText("Taler Gateway Hello\n", ContentType.Text.Plain, 
HttpStatusCode.OK)
+            return@get
+        }
+        app.post("/taler/transfer") {
+            val exchangeUser = authenticateRequest(call.request)
+            val transferRequest = call.receive<TalerTransferRequest>()
+            val amountObj = parseAmount(transferRequest.amount)
+            val creditorObj = parsePayto(transferRequest.credit_account)
+            val opaque_row_id = transaction {
+                val creditorData = parsePayto(transferRequest.credit_account)
+                /** Checking the UID has the desired characteristics */
+                TalerRequestedPaymentEntity.find {
+                    TalerRequestedPayments.requestUId eq 
transferRequest.request_uid
+                }.forEach {
+                    if (
+                        (it.amount != transferRequest.amount) or
+                        (it.creditAccount != 
transferRequest.exchange_base_url) or
+                        (it.wtid != transferRequest.wtid)
+                    ) {
+                        throw NexusError(
+                            HttpStatusCode.Conflict,
+                            "This uid (${transferRequest.request_uid}) belongs 
to a different payment already"
+                        )
+                    }
+                }
+                val pain001 = addPreparedPayment(
+                    Pain001Data(
+                        creditorIban = creditorData.iban,
+                        creditorBic = creditorData.bic,
+                        creditorName = creditorData.name,
+                        subject = transferRequest.wtid,
+                        sum = amountObj.amount,
+                        currency = amountObj.currency
+                    ),
+                    NexusBankAccountEntity.new { /* FIXME; exchange should 
communicate this value */ }
+                )
+                val rawEbics = if (!isProduction()) {
+                    RawBankTransactionEntity.new {
+                        unstructuredRemittanceInformation = 
transferRequest.wtid
+                        transactionType = "DBIT"
+                        currency = amountObj.currency
+                        this.amount = amountObj.amount.toPlainString()
+                        counterpartBic = creditorObj.bic
+                        counterpartIban = creditorObj.iban
+                        counterpartName = creditorObj.name
+                        bankAccount = NexusBankAccountEntity.new { /* FIXME; 
exchange should communicate this value */ }
+                        bookingDate = DateTime.now().millis
+                        status = "BOOK"
+                    }
+                } else null
+
+                val row = TalerRequestedPaymentEntity.new {
+                    preparedPayment = pain001 // not really used/needed, just 
here to silence warnings
+                    exchangeBaseUrl = transferRequest.exchange_base_url
+                    requestUId = transferRequest.request_uid
+                    amount = transferRequest.amount
+                    wtid = transferRequest.wtid
+                    creditAccount = transferRequest.credit_account
+                    rawConfirmed = rawEbics
+                }
+
+                row.id.value
+            }
+            call.respond(
+                HttpStatusCode.OK,
+                TextContent(
+                    customConverter(
+                        TalerTransferResponse(
+                            /**
+                             * Normally should point to the next round where 
the background
+                             * routine will send new PAIN.001 data to the 
bank; work in progress..
+                             */
+                            timestamp = GnunetTimestamp(DateTime.now().millis),
+                            row_id = opaque_row_id
+                        )
+                    ),
+                    ContentType.Application.Json
+                )
+            )
+            return@post
+        }
+        /** Test-API that creates one new payment addressed to the exchange.  
*/
+        app.post("/taler/admin/add-incoming") {
+            val exchangeUser = authenticateRequest(call.request)
+            val addIncomingData = call.receive<TalerAdminAddIncoming>()
+            val debtor = parsePayto(addIncomingData.debit_account)
+            val amount = parseAmount(addIncomingData.amount)
+            val (bookingDate, opaque_row_id) = transaction {
+                val rawPayment = RawBankTransactionEntity.new {
+                    unstructuredRemittanceInformation = 
addIncomingData.reserve_pub
+                    transactionType = "CRDT"
+                    currency = amount.currency
+                    this.amount = amount.amount.toPlainString()
+                    counterpartBic = debtor.bic
+                    counterpartName = debtor.name
+                    counterpartIban = debtor.iban
+                    bookingDate = DateTime.now().millis
+                    status = "BOOK"
+                    bankAccount = NexusBankAccountEntity.new { /* FIXME; 
exchange should communicate this value */ }
+                }
+                /** This payment is "valid by default" and will be returned
+                 * as soon as the exchange will ask for new payments.  */
+                val row = TalerIncomingPaymentEntity.new {
+                    payment = rawPayment
+                    valid = true
+                }
+                Pair(rawPayment.bookingDate, row.id.value)
+            }
+            call.respond(
+                TextContent(
+                    customConverter(
+                        TalerAddIncomingResponse(
+                            timestamp = GnunetTimestamp(bookingDate/ 1000),
+                            row_id = opaque_row_id
+                        )
+                    ),
+                ContentType.Application.Json
+                )
+            )
+            return@post
+        }
+
+        /** This endpoint triggers the refunding of invalid payments.  
'Refunding'
+         * in this context means that nexus _prepares_ the payment instruction 
and
+         * places it into a further table.  Eventually, another routine will 
perform
+         * all the prepared payments.  */
+        
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
+            val exchangeUser = authenticateRequest(call.request)
+            val callerBankAccount = ensureNonNull(call.parameters["acctid"])
+            transaction {
+                val bankAccount = 
NexusBankAccountEntity.findById(callerBankAccount)
+                TalerIncomingPaymentEntity.find {
+                    TalerIncomingPayments.refunded eq false and 
(TalerIncomingPayments.valid eq false)
+                }.forEach {
+                    addPreparedPayment(
+                        Pain001Data(
+                            creditorName = it.payment.counterpartName,
+                            creditorIban = it.payment.counterpartIban,
+                            creditorBic = it.payment.counterpartBic,
+                            sum = calculateRefund(it.payment.amount),
+                            subject = "Taler refund",
+                            currency = it.payment.currency
+                        ),
+                        NexusBankAccountEntity.new { /* FIXME; exchange should 
communicate this value */ }
+                    )
+                    it.refunded = true
+                }
+            }
+            return@post
+        }
+
+        /** This endpoint triggers the examination of raw incoming payments 
aimed
+         * at separating the good payments (those that will lead to a new 
reserve
+         * being created), from the invalid payments (those with a invalid 
subject
+         * that will soon be refunded.)  Recently, the examination of raw 
OUTGOING
+         * payment was added as well.
+         */
+        app.post("/ebics/taler/{id}/crunch-raw-transactions") {
+            val id = ensureNonNull(call.parameters["id"])
+            // first find highest ID value of already processed rows.
+            transaction {
+                val subscriberAccount = NexusBankAccountEntity.new { /* FIXME; 
exchange should communicate this value */ }
+                /**
+                 * Search for fresh incoming payments in the raw table, and 
making pointers
+                 * from the Taler incoming payments table to the found fresh 
(and valid!) payments.
+                 */
+                val latestIncomingPaymentId: Long = 
TalerIncomingPaymentEntity.getLast()
+                RawBankTransactionEntity.find {
+                    /** Those with exchange bank account involved */
+                    RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value and
+                            /** Those that are incoming */
+                            (RawBankTransactionsTable.transactionType eq 
"CRDT") and
+                            /** Those that are booked */
+                            (RawBankTransactionsTable.status eq "BOOK") and
+                            /** Those that came later than the latest 
processed payment */
+                            
(RawBankTransactionsTable.id.greater(latestIncomingPaymentId))
+
+                }.forEach {
+                    if (duplicatePayment(it)) {
+                        logger.warn("Incoming payment already seen")
+                        throw NexusError(
+                            HttpStatusCode.InternalServerError,
+                            "Incoming payment already seen"
+                        )
+                    }
+                    if 
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
+                        TalerIncomingPaymentEntity.new {
+                            payment = it
+                            valid = true
+                        }
+                    } else {
+                        TalerIncomingPaymentEntity.new {
+                            payment = it
+                            valid = false
+                        }
+                    }
+                }
+                /**
+                 * Search for fresh OUTGOING transactions acknowledged by the 
bank.  As well
+                 * searching only for BOOKed transactions, even though status 
changes should
+                 * be really unexpected here.
+                 */
+                val latestOutgoingPaymentId = 
TalerRequestedPaymentEntity.getLast()
+                RawBankTransactionEntity.find {
+                    /** Those that came after the last processed payment */
+                    RawBankTransactionsTable.id greater 
latestOutgoingPaymentId and
+                            /** Those involving the exchange bank account */
+                            (RawBankTransactionsTable.bankAccount eq 
subscriberAccount.id.value) and
+                            /** Those that are outgoing */
+                            (RawBankTransactionsTable.transactionType eq 
"DBIT")
+                }.forEach {
+                    if (paymentFailed(it)) {
+                        logger.error("Bank didn't accept one payment from the 
exchange")
+                        throw NexusError(
+                            HttpStatusCode.InternalServerError,
+                            "Bank didn't accept one payment from the exchange"
+                        )
+                    }
+                    if (duplicatePayment(it)) {
+                        logger.warn("Incomint payment already seen")
+                        throw NexusError(
+                            HttpStatusCode.InternalServerError,
+                            "Outgoing payment already seen"
+                        )
+                    }
+                    var talerRequested = TalerRequestedPaymentEntity.find {
+                        TalerRequestedPayments.wtid eq 
it.unstructuredRemittanceInformation
+                    }.firstOrNull() ?: throw NexusError(
+                        HttpStatusCode.InternalServerError,
+                        "Unrecognized fresh outgoing payment met (subject: 
${it.unstructuredRemittanceInformation})."
+                    )
+                    talerRequested.rawConfirmed = it
+                }
+            }
+
+            call.respondText (
+                "New raw payments Taler-processed",
+                ContentType.Text.Plain,
+                HttpStatusCode.OK
+            )
+            return@post
+        }
+        /** Responds only with the payments that the EXCHANGE made.  Typically 
to
+         * merchants but possibly to refund invalid incoming payments.  A 
payment is
+         * counted only if was once confirmed by the bank.
+         */
+        app.get("/taler/history/outgoing") {
+            /* sanitize URL arguments */
+            val subscriberId = authenticateRequest(call.request)
+            val delta: Int = expectInt(call.expectUrlParameter("delta"))
+            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
+            val startCmpOp = getComparisonOperator(delta, start, 
TalerRequestedPayments)
+            /* retrieve database elements */
+            val history = TalerOutgoingHistory()
+            transaction {
+                /** Retrieve all the outgoing payments from the _clean Taler 
outgoing table_ */
+                val subscriberBankAccount = NexusBankAccountEntity.new { /* 
FIXME; exchange should communicate this value */ }
+                val reqPayments = TalerRequestedPaymentEntity.find {
+                    TalerRequestedPayments.rawConfirmed.isNotNull() and 
startCmpOp
+                }.orderTaler(delta)
+                if (reqPayments.isNotEmpty()) {
+                    reqPayments.subList(0, min(abs(delta), 
reqPayments.size)).forEach {
+                        history.outgoing_transactions.add(
+                            TalerOutgoingBankTransaction(
+                                row_id = it.id.value,
+                                amount = it.amount,
+                                wtid = it.wtid,
+                                date = 
GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError(
+                                    HttpStatusCode.InternalServerError, "Null 
value met after check, VERY strange.")),
+                                credit_account = it.creditAccount,
+                                debit_account = 
buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
+                                exchange_base_url = 
"FIXME-to-request-along-subscriber-registration"
+                            )
+                        )
+                    }
+                }
+            }
+            call.respond(
+                HttpStatusCode.OK,
+                TextContent(customConverter(history), 
ContentType.Application.Json)
+            )
+            return@get
+        }
+        /** Responds only with the valid incoming payments */
+        app.get("/taler/history/incoming") {
+            val exchangeUser = authenticateRequest(call.request)
+            val delta: Int = expectInt(call.expectUrlParameter("delta"))
+            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
+            val history = TalerIncomingHistory()
+            val startCmpOp = getComparisonOperator(delta, start, 
TalerIncomingPayments)
+            transaction {
+                val orderedPayments = TalerIncomingPaymentEntity.find {
+                    TalerIncomingPayments.valid eq true and startCmpOp
+                }.orderTaler(delta)
+                if (orderedPayments.isNotEmpty()) {
+                    orderedPayments.subList(0, min(abs(delta), 
orderedPayments.size)).forEach {
+                        history.incoming_transactions.add(
+                            TalerIncomingBankTransaction(
+                                date = GnunetTimestamp(it.payment.bookingDate 
/ 1000),
+                                row_id = it.id.value,
+                                amount = 
"${it.payment.currency}:${it.payment.amount}",
+                                reserve_pub = 
it.payment.unstructuredRemittanceInformation,
+                                credit_account = buildPaytoUri(
+                                    it.payment.bankAccount.accountHolder,
+                                    it.payment.bankAccount.iban,
+                                    it.payment.bankAccount.bankCode
+                                ),
+                                debit_account = buildPaytoUri(
+                                    it.payment.counterpartName,
+                                    it.payment.counterpartIban,
+                                    it.payment.counterpartBic
+                                )
+                            )
+                        )
+                    }
+                }
+            }
+            call.respond(TextContent(customConverter(history), 
ContentType.Application.Json))
+            return@get
+        }
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]