summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMS <ms@taler.net>2023-03-31 13:56:54 +0200
committerMS <ms@taler.net>2023-03-31 13:56:54 +0200
commit3be48ded81149a3379eaa9ddff1baa40f1d75c56 (patch)
tree15fd46bd4a9a9c8045ff0acf854c58365887707f
parent71aa5ab6c70bc3c5d0882d1d3a9fdf2b50048272 (diff)
downloadlibeufin-3be48ded81149a3379eaa9ddff1baa40f1d75c56.tar.gz
libeufin-3be48ded81149a3379eaa9ddff1baa40f1d75c56.tar.bz2
libeufin-3be48ded81149a3379eaa9ddff1baa40f1d75c56.zip
matching /cashout/estimates to the API
-rw-r--r--.idea/modules.xml8
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt101
2 files changed, 84 insertions, 25 deletions
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..dbca1434
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/libeufin.iml" filepath="$PROJECT_DIR$/.idea/libeufin.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
index 8cd2750e..773d6450 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
@@ -145,17 +145,33 @@ 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 BigDecimal.roundToTwoDigits(): BigDecimal {
+ val twoDigitsRounding = MathContext(2)
+ return this.round(twoDigitsRounding)
+}
+
+/**
+ * By default, it takes the amount in the regional currency
+ * and applies ratio and fees to convert it to fiat. If the
+ * 'fromCredit' parameter is true, then it does the inverse
+ * operation: returns the regional amount that would lead to
+ * such fiat amount given in the 'amount' parameter.
+ */
fun applyCashoutRatioAndFee(
- regioAmount: BigDecimal,
- ratiosAndFees: RatioAndFees
-): BigDecimal =
- (regioAmount * ratiosAndFees.sell_at_ratio.toBigDecimal()) -
- ratiosAndFees.sell_out_fee.toBigDecimal()
+ amount: BigDecimal,
+ ratiosAndFees: RatioAndFees,
+ fromCredit: Boolean = false
+): BigDecimal {
+ // Normal case, when the calculation starts from the regional amount.
+ if (!fromCredit) {
+ return ((amount * ratiosAndFees.sell_at_ratio.toBigDecimal()) -
+ ratiosAndFees.sell_out_fee.toBigDecimal()).roundToTwoDigits()
+ }
+ // UI convenient case, when the calculation start from the
+ // desired fiat amount that the user wants eventually be paid.
+ return ((amount + ratiosAndFees.sell_out_fee.toBigDecimal()) /
+ ratiosAndFees.sell_at_ratio.toBigDecimal()).roundToTwoDigits()
+}
/**
* NOTE: future versions take the supported TAN method from
@@ -379,20 +395,56 @@ fun circuitApi(circuitRoute: Route) {
}
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)
- val twoDigitsRounding = MathContext(2)
- val estimateRounded = estimate.round(twoDigitsRounding)
- call.respond(object { val amount_credit = "$FIAT_CURRENCY:$estimateRounded" })
+ // Optionally parsing param 'amount_debit' into number and checking its currency
+ val maybeAmountDebit: String? = call.request.queryParameters["amount_debit"]
+ val amountDebit: BigDecimal? = if (maybeAmountDebit != null) {
+ val amount = parseAmount(maybeAmountDebit)
+ if (amount.currency != demobank.config.currency) throw badRequest(
+ "parameter 'amount_debit' has the wrong currency: ${amount.currency}"
+ )
+ try { amount.amount.toBigDecimal() } catch (e: Exception) {
+ throw badRequest("Cannot extract a number from 'amount_debit'")
+ }
+ } else null
+ // Optionally parsing param 'amount_credit' into number and checking its currency
+ val maybeAmountCredit: String? = call.request.queryParameters["amount_credit"]
+ val amountCredit: BigDecimal? = if (maybeAmountCredit != null) {
+ val amount = parseAmount(maybeAmountCredit)
+ if (amount.currency != FIAT_CURRENCY) throw badRequest(
+ "parameter 'amount_credit' has the wrong currency: ${amount.currency}"
+ )
+ try { amount.amount.toBigDecimal() } catch (e: Exception) {
+ throw badRequest("Cannot extract a number from 'amount_credit'")
+ }
+ } else null
+ val respAmountCredit = if (amountDebit != null) {
+ val estimate = applyCashoutRatioAndFee(amountDebit, ratiosAndFees)
+ if (amountCredit != null && estimate != amountCredit) throw badRequest(
+ "Wrong calculation found in 'amount_credit', bank estimates: $estimate"
+ )
+ estimate
+ } else null
+ if (amountDebit == null && amountCredit == null) throw badRequest(
+ "Both 'amount_credit' and 'amount_debit' are missing"
+ )
+ val respAmountDebit = if (amountCredit != null) {
+ val estimate = applyCashoutRatioAndFee(
+ amountCredit,
+ ratiosAndFees,
+ fromCredit = true
+ )
+ if (amountDebit != null && estimate != amountDebit) throw badRequest(
+ "Wrong calculation found in 'amount_credit', bank estimates: $estimate"
+ )
+ estimate
+ } else null
+ call.respond(object {
+ val amount_credit = "$FIAT_CURRENCY:$respAmountCredit"
+ val amount_debit = "${demobank.config.currency}:$respAmountDebit"
+ })
}
+
// Create a cash-out operation.
circuitRoute.post("/cashouts") {
val user = call.request.basicAuth()
@@ -441,9 +493,8 @@ fun circuitApi(circuitRoute: Route) {
// check rates correctness
val amountDebitAsNumber = BigDecimal(amountDebit.amount)
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)) {
+ val amountCreditAsNumber = BigDecimal(amountCredit.amount).roundToTwoDigits()
+ if (expectedAmountCredit != amountCreditAsNumber) {
throw badRequest("Rates application are incorrect." +
" The expected amount to credit is: ${expectedAmountCredit}," +
" but ${amountCredit.amount} was specified.")