gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (9ca3c129 -> 5276f88d)


From: gnunet
Subject: [libeufin] branch master updated (9ca3c129 -> 5276f88d)
Date: Mon, 20 Mar 2023 15:44:26 +0100

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

ms pushed a change to branch master
in repository libeufin.

    from 9ca3c129 Testing facade deletion.
     new f0c83f82 indentation
     new 265a8239 cash-out estimation test
     new 5276f88d cash-out estimation endpoint

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../main/kotlin/tech/libeufin/nexus/Anastasis.kt   | 12 ++++--
 .../tech/libeufin/nexus/server/NexusServer.kt      |  2 +-
 nexus/src/test/kotlin/SandboxCircuitApiTest.kt     | 27 ++++++++++++-
 .../kotlin/tech/libeufin/sandbox/CircuitApi.kt     | 44 +++++++++++++++++++---
 4 files changed, 74 insertions(+), 11 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt
index f1c5114d..a7b51fd1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Anastasis.kt
@@ -66,11 +66,15 @@ fun anastasisFilter(payment: NexusBankTransactionEntity, 
txDtls: TransactionDeta
 // Handle a /taler-wire-gateway/history/incoming request.
 private suspend fun historyIncoming(call: ApplicationCall) {
     val facadeId = expectNonNull(call.parameters["fcid"])
-    call.request.requirePermission(PermissionQuery("facade", facadeId, 
"facade.anastasis.history"))
+    call.request.requirePermission(
+        PermissionQuery(
+            "facade",
+            facadeId,
+            "facade.anastasis.history"
+        )
+    )
     val param = call.expectUrlParameter("delta")
-    val delta: Int = try {
-        param.toInt()
-    } catch (e: Exception) {
+    val delta: Int = try { param.toInt() } catch (e: Exception) {
         throw EbicsProtocolError(HttpStatusCode.BadRequest, "'${param}' is not 
Int")
     }
     val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
index dc69829a..058f944f 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -409,7 +409,7 @@ val nexusApp: Application.() -> Unit = {
             requireSuperuser(call.request)
             call.respond(
                 HttpStatusCode.OK,
-                BankProtocolsResponse(listOf("ebics", "loopback"))
+                BankProtocolsResponse(listOf("ebics"))
             )
             return@get
         }
diff --git a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt 
b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
index 83a63104..4664cceb 100644
--- a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
+++ b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
@@ -11,6 +11,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import org.junit.Ignore
 import org.junit.Test
 import tech.libeufin.sandbox.*
+import tech.libeufin.util.parseAmount
 import java.io.File
 import java.math.BigDecimal
 import java.util.*
@@ -30,6 +31,30 @@ class SandboxCircuitApiTest {
         }
     }
 
+    // Tests the application of cash-out ratio and fee.
+    @Test
+    fun estimationTest() {
+        withTestDatabase {
+            prepSandboxDb()
+            testApplication {
+                application(sandboxApp)
+                val R = client.get(
+                    
"/demobanks/default/circuit-api/cashouts/estimates?amount_debit=TESTKUDOS:2"
+                ) {
+                    expectSuccess = true
+                    basicAuth("foo", "foo")
+                }
+                println(R.bodyAsText())
+                val mapper = ObjectMapper()
+                val respJson = mapper.readTree(R.bodyAsText())
+                val creditAmount = respJson.get("amount_credit").asText()
+                // sell ratio and fee are the following constants: 0.95 and 0.
+                // expected credit amount = 2 * 0.95 - 0 = 1.90
+                assert("CHF:1.90" == creditAmount)
+            }
+        }
+    }
+
     /**
      * Checking that the ordinary user foo doesn't get to access bar's
      * data, but admin does.
@@ -45,7 +70,7 @@ class SandboxCircuitApiTest {
                     expectSuccess = false
                 }
                 assert(R.status.value == HttpStatusCode.Forbidden.value)
-                R = client.get("/demobanks/default/circuit-api/accounts/bar") {
+                client.get("/demobanks/default/circuit-api/accounts/bar") {
                     basicAuth("admin", "foo")
                     expectSuccess = true
                 }
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
index 8ddbab47..4fd041ee 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
@@ -6,8 +6,6 @@ import io.ktor.http.*
 import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
-import org.jetbrains.exposed.sql.deleteAll
-import org.jetbrains.exposed.sql.lowerCase
 import org.jetbrains.exposed.sql.transactions.transaction
 import tech.libeufin.sandbox.CashoutOperationsTable.uuid
 import tech.libeufin.util.*
@@ -19,6 +17,18 @@ import java.util.concurrent.TimeUnit
 import kotlin.text.toByteArray
 
 // CIRCUIT API TYPES
+/**
+ * This type is used by clients to ask the bank a cash-out
+ * estimate to show to the customer before they confirm the
+ * cash-out creation.
+ */
+data class CircuitCashoutEstimateRequest(
+    /**
+     * This is the amount that the customer will get deducted
+     * from their regio bank account to fuel the cash-out operation.
+     */
+    val amount_debit: String
+)
 data class CircuitCashoutRequest(
     val subject: String?,
     val amount_debit: String, // As specified by the user via the SPA.
@@ -135,6 +145,18 @@ fun generateCashoutSubject(
             " to ${amountCredit.currency}:${amountCredit.amount}"
 }
 
+/* Takes one amount value as input, applies cash-out rates
+* and fees to it, and returns the result.  Typically, the input
+* comes from a regional currency amount and the output will be
+* the fiat currency amount that the customer will get in their
+* fiat bank account. */
+fun applyCashoutRatioAndFee(
+    regioAmount: BigDecimal,
+    ratiosAndFees: RatioAndFees
+): BigDecimal =
+    (regioAmount * ratiosAndFees.sell_at_ratio.toBigDecimal()) -
+            ratiosAndFees.sell_out_fee.toBigDecimal()
+
 /**
  * NOTE: future versions take the supported TAN method from
  * the configuration, or options passed when starting the bank.
@@ -355,6 +377,20 @@ fun circuitApi(circuitRoute: Route) {
         call.respond(node)
         return@get
     }
+    circuitRoute.get("/cashouts/estimates") {
+        call.request.basicAuth()
+        val maybeAmountDebit: String? = 
call.request.queryParameters["amount_debit"]
+        if (maybeAmountDebit == null) throw badRequest("Missing 'amount_debit' 
URI parameter.")
+        val amountDebit = parseAmount(maybeAmountDebit)
+        val demobank = ensureDemobank(call)
+        if (amountDebit.currency != demobank.config.currency)
+            throw badRequest("POSTed debit amount has wrong currency 
(${amountDebit.currency}).  Give '${demobank.config.currency}' instead.")
+        val amountDebitValue = try {
+            amountDebit.amount.toBigDecimal()
+        } catch (e: Exception) { throw badRequest("POSTed debit amount has 
invalid number.") }
+        val estimate = applyCashoutRatioAndFee(amountDebitValue, ratiosAndFees)
+        call.respond(object { val amount_credit = "$FIAT_CURRENCY:$estimate" })
+    }
     // Create a cash-out operation.
     circuitRoute.post("/cashouts") {
         val user = call.request.basicAuth()
@@ -401,10 +437,8 @@ fun circuitApi(circuitRoute: Route) {
         if ((tanChannel == SupportedTanChannels.SMS.name) && (customer.phone 
== null))
             throw conflict("Phone number not found for '$user'.  Can't send 
the TAN")
         // check rates correctness
-        val sellRatio = BigDecimal(ratiosAndFees.sell_at_ratio.toString())
-        val sellFee = BigDecimal(ratiosAndFees.sell_out_fee.toString())
         val amountDebitAsNumber = BigDecimal(amountDebit.amount)
-        val expectedAmountCredit = (amountDebitAsNumber * sellRatio) - sellFee
+        val expectedAmountCredit = 
applyCashoutRatioAndFee(amountDebitAsNumber, ratiosAndFees)
         val commonRounding = MathContext(2) // ensures both amounts end with 
".XY"
         val amountCreditAsNumber = BigDecimal(amountCredit.amount)
         if (expectedAmountCredit.round(commonRounding) != 
amountCreditAsNumber.round(commonRounding)) {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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