commit b041de6e185e334e37572b33e31966028ae50bb6
parent 8a2da270d8ded0c99626c96bc60c06a33b902954
Author: ms <ms@taler.net>
Date: Thu, 21 Oct 2021 16:39:21 +0200
extend Payto parser
Diffstat:
3 files changed, 65 insertions(+), 25 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
@@ -181,6 +181,7 @@ private suspend fun talerTransfer(call: ApplicationCall) {
val transferRequest = call.receive<TalerTransferRequest>()
val amountObj = parseAmount(transferRequest.amount)
// FIXME: Right now we only parse the credit_account, should we also validate that it matches our account info?
+ // FIXME, another parse happens below; is this really useful here?
parsePayto(transferRequest.credit_account)
val facadeId = expectNonNull(call.parameters["fcid"])
val opaqueRowId = transaction {
diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/Payto.kt
@@ -1,21 +1,40 @@
package tech.libeufin.util
import java.net.URI
+import java.net.URLDecoder
/**
- * Helper data structures.
+ * Payto information.
*/
data class Payto(
- // Can represent a the sender or a receiver.
+ // represent query param "sender-name" or "receiver-name".
val name: String?,
val iban: String,
- val bic: String?
+ val bic: String?,
+ // Typically, a wire transfer's subject.
+ val message: String?,
+ val amount: String?
)
class InvalidPaytoError(msg: String) : Exception(msg)
+// Return the value of query string parameter 'name', or null if not found.
+// 'params' is the a list of key-value elements of all the query parameters found in the URI.
+private fun getQueryParamOrNull(name: String, params: List<Pair<String, String>>?): String? {
+ if (params == null) return null
+ return params.firstNotNullOfOrNull { pair ->
+ URLDecoder.decode(pair.second, Charsets.UTF_8).takeIf { pair.first == name }
+ }
+}
+
fun parsePayto(paytoLine: String): Payto {
+ /**
+ * This check is due because URIs having a "payto:" prefix without
+ * slashes are correctly parsed by the Java 'URI' class. 'mailto'
+ * for example lacks the double-slash part.
+ */
if (!paytoLine.startsWith("payto://"))
throw InvalidPaytoError("Invalid payto URI: $paytoLine")
+
val javaParsedUri = try {
URI(paytoLine)
} catch (e: java.lang.Exception) {
@@ -25,22 +44,9 @@ fun parsePayto(paytoLine: String): Payto {
throw InvalidPaytoError("'${paytoLine}' is not payto")
}
val wireMethod = javaParsedUri.host
- if (wireMethod != "sepa") {
- throw InvalidPaytoError("Only SEPA is supported, not '$wireMethod'")
+ if (wireMethod != "iban") {
+ throw InvalidPaytoError("Only 'iban' is supported, not '$wireMethod'")
}
- val accountOwner = if (javaParsedUri.query != null) {
- val queryStringAsList = javaParsedUri.query.split("&")
- // admit only ONE parameter: receiver-name.
- if (queryStringAsList.size != 1) {
- throw InvalidPaytoError("'${paytoLine}' has unsupported query string")
- }
- val splitParameter = queryStringAsList.first().split("=")
- if (splitParameter.first() != "receiver-name" && splitParameter.first() != "sender-name") {
- throw InvalidPaytoError("'${paytoLine}' has unsupported query string")
- }
- splitParameter.last()
- } else null
-
val splitPath = javaParsedUri.path.split("/").filter { it.isNotEmpty() }
if (splitPath.size > 2) {
throw InvalidPaytoError("too many path segments in iban payto URI")
@@ -48,5 +54,23 @@ fun parsePayto(paytoLine: String): Payto {
val (iban, bic) = if (splitPath.size == 1) {
Pair(splitPath[0], null)
} else Pair(splitPath[1], splitPath[0])
- return Payto(iban = iban, bic = bic, name = accountOwner)
+
+ val params: List<Pair<String, String>>? = if (javaParsedUri.query != null) {
+ val queryString: List<String> = javaParsedUri.query.split("&")
+ queryString.map {
+ val split = it.split("="); Pair(split[0], split[1])
+ }
+ } else null
+
+ val receiverName = getQueryParamOrNull("receiver-name", params)
+ val senderName = getQueryParamOrNull("sender-name", params)
+ if (receiverName != null && senderName != null) throw InvalidPaytoError("URI had both sender and receiver")
+
+ return Payto(
+ iban = iban,
+ bic = bic,
+ amount = getQueryParamOrNull("amount", params),
+ message = getQueryParamOrNull("message", params),
+ name = listOf(receiverName, senderName).firstNotNullOfOrNull { it }
+ )
}
\ No newline at end of file
diff --git a/util/src/test/kotlin/PaytoTest.kt b/util/src/test/kotlin/PaytoTest.kt
@@ -21,19 +21,21 @@ class PaytoTest {
}
try {
parsePayto(
- "payto://iban/BIC123/IBAN123?receiver-name=The%20Name&address=house"
+ "payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house"
)
} catch (e: InvalidPaytoError) {
println(e)
- println("more than one parameter isn't allowed")
+ println("'://' missing, invalid Payto")
}
try {
- parsePayto(
- "payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house"
- )
+ parsePayto("payto://iban/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo")
+ } catch (e: InvalidPaytoError) {
+ println(e)
+ }
+ try {
+ parsePayto("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo")
} catch (e: InvalidPaytoError) {
println(e)
- println("'://' missing, invalid Payto")
}
}
@@ -43,5 +45,18 @@ class PaytoTest {
assert(withBic.iban == "IBAN123")
assert(withBic.bic == "BIC123")
assert(withBic.name == "The Name")
+ val complete = parsePayto("payto://iban/BIC123/IBAN123?sender-name=The%20Name&amount=EUR:1&message=donation")
+ assert(withBic.iban == "IBAN123")
+ assert(withBic.bic == "BIC123")
+ assert(withBic.name == "The Name")
+ assert(complete.message == "donation")
+ assert(complete.amount == "EUR:1")
+ val withoutOptionals = parsePayto(
+ "payto://iban/IBAN123"
+ )
+ assert(withoutOptionals.bic == null)
+ assert(withoutOptionals.message == null)
+ assert(withoutOptionals.name == null)
+ assert(withoutOptionals.amount == null)
}
}
\ No newline at end of file