commit 08850ccd91ca448c7fa9d63e2e0088578c6b070d
parent 640cc009d787ee69aaef8e32b35dcbd74d932c73
Author: Antoine A <>
Date: Sat, 14 Oct 2023 00:24:40 +0000
Fix add_incoming and start checking exchange URL and payto URI
Diffstat:
12 files changed, 195 insertions(+), 110 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -176,11 +176,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
if (this == null) return@run null
db.bankAccountGetFromOwnerId(this.expectRowId())
}
- val internalPayto: String = if (req.internal_payto_uri != null) {
- stripIbanPayto(req.internal_payto_uri) ?: throw badRequest("internal_payto_uri is invalid")
- } else {
- stripIbanPayto(genIbanPaytoUri()) ?: throw internalServerError("Bank generated an invalid internal payto URI")
- }
+ val internalPayto = req.internal_payto_uri ?: IbanPayTo(genIbanPaytoUri())
if (maybeCustomerExists != null && maybeHasBankAccount != null) {
logger.debug("Registering username was found: ${maybeCustomerExists.login}") // Checking _all_ the details are the same.
val isIdentic =
@@ -191,7 +187,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
CryptoUtil.checkpw(req.password, maybeCustomerExists.passwordHash) &&
maybeHasBankAccount.isPublic == req.is_public &&
maybeHasBankAccount.isTalerExchange == req.is_taler_exchange &&
- maybeHasBankAccount.internalPaytoUri == internalPayto
+ maybeHasBankAccount.internalPaytoUri.stripped == internalPayto.stripped
if (isIdentic) {
call.respond(HttpStatusCode.Created)
return@post
@@ -516,24 +512,23 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
val c: Customer = call.authenticateBankRequest(db, TokenScope.readwrite) ?: throw unauthorized()
val resourceName = call.expectUriComponent("USERNAME") // admin has no rights here.
if ((c.login != resourceName) && (call.getAuthToken() == null)) throw forbidden()
- val txData = call.receive<BankAccountTransactionCreate>()
- val payto = parsePayto(txData.payto_uri) ?: throw badRequest("Invalid creditor Payto")
- val subject = payto.message ?: throw badRequest("Wire transfer lacks subject")
+ val tx = call.receive<BankAccountTransactionCreate>()
+ val subject = tx.payto_uri.message ?: throw badRequest("Wire transfer lacks subject")
val debtorBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
?: throw internalServerError("Debtor bank account not found")
- if (txData.amount.currency != ctx.currency) throw badRequest(
- "Wrong currency: ${txData.amount.currency}",
+ if (tx.amount.currency != ctx.currency) throw badRequest(
+ "Wrong currency: ${tx.amount.currency}",
talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
)
if (!isBalanceEnough(
balance = debtorBankAccount.expectBalance(),
- due = txData.amount,
+ due = tx.amount,
hasBalanceDebt = debtorBankAccount.hasDebt,
maxDebt = debtorBankAccount.maxDebt
))
throw conflict(hint = "Insufficient balance.", talerEc = TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT)
- logger.info("creditor payto: ${txData.payto_uri}")
- val creditorBankAccount = db.bankAccountGetFromInternalPayto("payto://iban/${payto.iban.lowercase()}")
+ logger.info("creditor payto: ${tx.payto_uri}")
+ val creditorBankAccount = db.bankAccountGetFromInternalPayto(tx.payto_uri)
?: throw notFound(
"Creditor account not found",
TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT
@@ -542,7 +537,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
debtorAccountId = debtorBankAccount.expectRowId(),
creditorAccountId = creditorBankAccount.expectRowId(),
subject = subject,
- amount = txData.amount,
+ amount = tx.amount,
transactionDate = Instant.now()
)
val res = db.bankTransactionCreate(dbInstructions)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -575,7 +575,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
(?, ?, ?, ?, (?, ?)::taler_amount)
RETURNING bank_account_id;
""")
- stmt.setString(1, bankAccount.internalPaytoUri)
+ stmt.setString(1, bankAccount.internalPaytoUri.stripped)
stmt.setLong(2, bankAccount.owningCustomerId)
stmt.setBoolean(3, bankAccount.isPublic)
stmt.setBoolean(4, bankAccount.isTalerExchange)
@@ -637,7 +637,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
stmt.oneOrNull {
BankAccount(
- internalPaytoUri = it.getString("internal_payto_uri"),
+ internalPaytoUri = IbanPayTo(it.getString("internal_payto_uri")),
balance = TalerAmount(
it.getLong("balance_val"),
it.getInt("balance_frac"),
@@ -685,7 +685,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
}
}
- suspend fun bankAccountGetFromInternalPayto(internalPayto: String): BankAccount? = conn { conn ->
+ suspend fun bankAccountGetFromInternalPayto(internalPayto: IbanPayTo): BankAccount? = conn { conn ->
val stmt = conn.prepareStatement("""
SELECT
bank_account_id
@@ -701,7 +701,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
FROM bank_accounts
WHERE internal_payto_uri=?
""")
- stmt.setString(1, internalPayto)
+ stmt.setString(1, internalPayto.stripped)
stmt.oneOrNull {
BankAccount(
@@ -797,7 +797,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
VALUES (?, ?, ?)
""")
stmt.setBytes(1, metadata.wtid.raw)
- stmt.setString(2, metadata.exchangeBaseUrl)
+ stmt.setString(2, metadata.exchangeBaseUrl.url)
stmt.setLong(3, rowId)
stmt.executeUpdate()
conn.execSQLUpdate("NOTIFY outgoing_tx, '${"${tx.debtorAccountId} $rowId"}'")
@@ -1059,9 +1059,9 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
it.getInt("amount_frac"),
getCurrency()
),
- credit_account = it.getString("creditor_payto_uri"),
+ credit_account = IbanPayTo(it.getString("creditor_payto_uri")),
wtid = ShortHashCode(it.getBytes("wtid")),
- exchange_base_url = it.getString("exchange_base_url")
+ exchange_base_url = ExchangeUrl(it.getString("exchange_base_url"))
)
}
}
@@ -1173,7 +1173,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
getCurrency()
),
selectionDone = it.getBoolean("selection_done"),
- selectedExchangePayto = it.getString("selected_exchange_payto"),
+ selectedExchangePayto = it.getString("selected_exchange_payto")?.run(::IbanPayTo),
walletBankAccount = it.getLong("wallet_bank_account"),
confirmationDone = it.getBoolean("confirmation_done"),
aborted = it.getBoolean("aborted"),
@@ -1208,7 +1208,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
*/
suspend fun talerWithdrawalSetDetails(
opUuid: UUID,
- exchangePayto: String,
+ exchangePayto: IbanPayTo,
reservePub: String
): Boolean = conn { conn ->
val stmt = conn.prepareStatement("""
@@ -1217,7 +1217,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
WHERE withdrawal_uuid=?
"""
)
- stmt.setString(1, exchangePayto)
+ stmt.setString(1, exchangePayto.stripped)
stmt.setString(2, reservePub)
stmt.setObject(3, opUuid)
stmt.executeUpdateViolation()
@@ -1490,7 +1490,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
pmtInfId: String = "not used",
endToEndId: String = "not used",
): TalerTransferCreationResult = conn { conn ->
- val subject = OutgoingTxMetadata(req.wtid, req.exchange_base_url).toString()
+ val subject = OutgoingTxMetadata(req.wtid, req.exchange_base_url).encode()
val stmt = conn.prepareStatement("""
SELECT
out_debtor_not_found
@@ -1515,8 +1515,8 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
stmt.setString(3, subject)
stmt.setLong(4, req.amount.value)
stmt.setInt(5, req.amount.frac)
- stmt.setString(6, req.exchange_base_url)
- stmt.setString(7, stripIbanPayto(req.credit_account) ?: throw badRequest("credit_account payto URI is invalid"))
+ stmt.setString(6, req.exchange_base_url.url)
+ stmt.setString(7, req.credit_account.stripped)
stmt.setString(8, username)
stmt.setLong(9, timestamp.toDbMicros() ?: throw faultyTimestampByBank())
stmt.setString(10, acctSvcrRef)
@@ -1578,7 +1578,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
pmtInfId: String = "not used",
endToEndId: String = "not used",
): TalerAddIncomingCreationResult = conn { conn ->
- val subject = IncomingTxMetadata(req.reserve_pub).toString()
+ val subject = IncomingTxMetadata(req.reserve_pub).encode()
val stmt = conn.prepareStatement("""
SELECT
out_creditor_not_found
@@ -1589,11 +1589,11 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
,out_reserve_pub_reuse
,out_debitor_balance_insufficient
,out_tx_row_id
- FROM
- taler_add_incoming (
- ?, ?,
- (?,?)::taler_amount,
- ?, ?, ?, ?, ?, ?
+ FROM
+ taler_add_incoming (
+ ?, ?,
+ (?,?)::taler_amount,
+ ?, ?, ?, ?, ?, ?
);
""")
@@ -1601,7 +1601,7 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos
stmt.setString(2, subject)
stmt.setLong(3, req.amount.value)
stmt.setInt(4, req.amount.frac)
- stmt.setString(5, stripIbanPayto(req.debit_account) ?: throw badRequest("debit_account payto URI is invalid"))
+ stmt.setString(5, req.debit_account.stripped)
stmt.setString(6, username)
stmt.setLong(7, timestamp.toDbMicros() ?: throw faultyTimestampByBank())
stmt.setString(8, acctSvcrRef)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
@@ -26,7 +26,6 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import net.taler.common.errorcodes.TalerErrorCode
-import tech.libeufin.util.stripIbanPayto
fun Routing.talerIntegrationHandlers(db: Database, ctx: BankApplicationContext) {
get("/taler-integration/config") {
@@ -56,7 +55,7 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx: BankApplicationContext)
selection_done = op.selectionDone,
transfer_done = op.confirmationDone,
amount = op.amount,
- sender_wire = relatedBankAccount.internalPaytoUri,
+ sender_wire = relatedBankAccount.internalPaytoUri.stripped,
suggested_exchange = suggestedExchange,
confirm_transfer_url = confirmUrl
)
@@ -77,9 +76,8 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx: BankApplicationContext)
if (db.bankTransactionCheckExists(req.reserve_pub) != null) throw conflict(
"Reserve pub. already used", TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT
)
- val exchangePayto = stripIbanPayto(req.selected_exchange) ?: throw badRequest("selected_exchange payto is invalid")
db.talerWithdrawalSetDetails(
- op.withdrawalUuid, exchangePayto, req.reserve_pub
+ op.withdrawalUuid, req.selected_exchange, req.reserve_pub
)
} else { // Nothing to do in the database, i.e. we were successful
true
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt b/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt
@@ -30,7 +30,7 @@ sealed interface TxMetadata {
// OutgoingTxMetadata
try {
val (wtid, exchangeBaseUrl) = subject.split(" ", limit=2) ;
- return OutgoingTxMetadata(ShortHashCode(wtid), exchangeBaseUrl)
+ return OutgoingTxMetadata(ShortHashCode(wtid), ExchangeUrl(exchangeBaseUrl))
} catch (e: Exception) { }
// No well formed metadata
@@ -40,15 +40,13 @@ sealed interface TxMetadata {
fun encode(metadata: TxMetadata): String {
return when (metadata) {
is IncomingTxMetadata -> "${metadata.reservePub}"
- is OutgoingTxMetadata -> "${metadata.wtid} ${metadata.exchangeBaseUrl}"
+ is OutgoingTxMetadata -> "${metadata.wtid} ${metadata.exchangeBaseUrl.url}"
}
}
}
+
+ fun encode(): String = TxMetadata.encode(this)
}
-data class IncomingTxMetadata(val reservePub: EddsaPublicKey): TxMetadata {
- override fun toString(): String = TxMetadata.encode(this)
-}
-data class OutgoingTxMetadata(val wtid: ShortHashCode, val exchangeBaseUrl: String): TxMetadata {
- override fun toString(): String = TxMetadata.encode(this)
-}
-\ No newline at end of file
+data class IncomingTxMetadata(val reservePub: EddsaPublicKey): TxMetadata
+data class OutgoingTxMetadata(val wtid: ShortHashCode, val exchangeBaseUrl: ExchangeUrl): TxMetadata
+\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt
@@ -29,6 +29,7 @@ import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.concurrent.TimeUnit
import java.util.*
+import java.net.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
@@ -352,4 +353,93 @@ data class RelativeTime(
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
}
-}
-\ No newline at end of file
+}
+
+
+@Serializable(with = ExchangeUrl.Serializer::class)
+class ExchangeUrl {
+ val url: String
+
+ constructor(raw: String) {
+ url = URL(raw).toString()
+ }
+
+ override fun toString(): String = url
+
+ internal object Serializer : KSerializer<ExchangeUrl> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("ExchangeUrl", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: ExchangeUrl) {
+ encoder.encodeString(value.toString())
+ }
+
+ override fun deserialize(decoder: Decoder): ExchangeUrl {
+ return ExchangeUrl(decoder.decodeString())
+ }
+ }
+}
+
+
+@Serializable(with = IbanPayTo.Serializer::class)
+class IbanPayTo {
+ val parsed: URI
+ val stripped: String
+ // represent query param "sender-name" or "receiver-name".
+ val receiverName: String?
+ val iban: String
+ val bic: String?
+ // Typically, a wire transfer's subject.
+ val message: String?
+ val amount: String?
+
+ constructor(raw: String) {
+ parsed = URI(raw)
+ require(parsed.scheme == "payto") { "expect a payto URI" }
+ require(parsed.host == "iban") { "expect a IBAN payto URI" }
+ val splitPath = parsed.path.split("/").filter { it.isNotEmpty() }
+ require(splitPath.size < 3 && splitPath.isNotEmpty()) { "too many path segments" }
+ val parts = if (splitPath.size == 1) {
+ Pair(splitPath[0], null)
+ } else Pair(splitPath[1], splitPath[0])
+ // TODO normalize IBAN & BIC ?
+ iban = parts.first.uppercase()
+ bic = parts.second?.uppercase()
+ stripped = "payto://iban/$iban"
+
+ val params: List<Pair<String, String>>? = if (parsed.query != null) {
+ val queryString: List<String> = parsed.query.split("&")
+ queryString.map {
+ val split = it.split("=");
+ require(split.size == 2) { "parameter '$it' was malformed" }
+ Pair(split[0], split[1])
+ }
+ } else null
+
+ // Return the value of query string parameter 'name', or null if not found.
+ // 'params' is the list of key-value elements of all the query parameters found in the URI.
+ fun getQueryParamOrNull(name: String): String? {
+ if (params == null) return null
+ return params.firstNotNullOfOrNull { pair ->
+ URLDecoder.decode(pair.second, Charsets.UTF_8).takeIf { pair.first == name }
+ }
+ }
+
+ amount = getQueryParamOrNull("amount")
+ message = getQueryParamOrNull("message")
+ receiverName = getQueryParamOrNull("receiver-name")
+ }
+
+ internal object Serializer : KSerializer<IbanPayTo> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("IbanPayTo", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: IbanPayTo) {
+ encoder.encodeString(value.parsed.toString())
+ }
+
+ override fun deserialize(decoder: Decoder): IbanPayTo {
+ return IbanPayTo(decoder.decodeString())
+ }
+ }
+}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -91,7 +91,7 @@ data class RegisterAccountRequest(
// External bank account where to send cashout amounts.
val cashout_payto_uri: String? = null,
// Bank account internal to Libeufin-Bank.
- val internal_payto_uri: String? = null
+ val internal_payto_uri: IbanPayTo? = null
)
@@ -151,7 +151,7 @@ data class Customer(
* from/to the database.
*/
data class BankAccount(
- val internalPaytoUri: String,
+ val internalPaytoUri: IbanPayTo,
// Database row ID of the customer that owns this bank account.
val owningCustomerId: Long,
val bankAccountId: Long? = null, // null at INSERT.
@@ -270,7 +270,7 @@ data class TalerWithdrawalOperation(
val aborted: Boolean = false,
val confirmationDone: Boolean = false,
val reservePub: String?,
- val selectedExchangePayto: String?,
+ val selectedExchangePayto: IbanPayTo?,
val walletBankAccount: Long
)
@@ -345,7 +345,7 @@ data class ListBankAccountsResponse(
data class AccountData(
val name: String,
val balance: Balance,
- val payto_uri: String,
+ val payto_uri: IbanPayTo,
val debit_threshold: TalerAmount,
val contact_data: ChallengeContactData? = null,
val cashout_payto_uri: String? = null,
@@ -356,7 +356,7 @@ data class AccountData(
*/
@Serializable
data class BankAccountTransactionCreate(
- val payto_uri: String,
+ val payto_uri: IbanPayTo,
val amount: TalerAmount
)
@@ -400,7 +400,7 @@ data class BankAccountGetWithdrawalResponse(
val confirmation_done: Boolean,
val selection_done: Boolean,
val selected_reserve_pub: String? = null,
- val selected_exchange_account: String? = null
+ val selected_exchange_account: IbanPayTo? = null
)
typealias ResourceName = String
@@ -492,7 +492,7 @@ data class BankWithdrawalOperationStatus(
@Serializable
data class BankWithdrawalOperationPostRequest(
val reserve_pub: String,
- val selected_exchange: String,
+ val selected_exchange: IbanPayTo,
)
/**
@@ -513,7 +513,7 @@ data class BankWithdrawalOperationPostResponse(
data class AddIncomingRequest(
val amount: TalerAmount,
val reserve_pub: EddsaPublicKey,
- val debit_account: String
+ val debit_account: IbanPayTo
)
/**
@@ -571,9 +571,9 @@ data class OutgoingTransaction(
val row_id: Long, // DB row ID of the payment.
val date: TalerProtocolTimestamp,
val amount: TalerAmount,
- val credit_account: String, // Payto of the receiver.
+ val credit_account: IbanPayTo, // Payto of the receiver.
val wtid: ShortHashCode,
- val exchange_base_url: String,
+ val exchange_base_url: ExchangeUrl,
)
/**
@@ -583,9 +583,9 @@ data class OutgoingTransaction(
data class TransferRequest(
val request_uid: HashCode,
val amount: TalerAmount,
- val exchange_base_url: String,
+ val exchange_base_url: ExchangeUrl,
val wtid: ShortHashCode,
- val credit_account: String
+ val credit_account: IbanPayTo
)
/**
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -359,7 +359,7 @@ suspend fun maybeCreateAdminAccount(db: Database, ctx: BankApplicationContext):
}
val adminBankAccount = BankAccount(
hasDebt = false,
- internalPaytoUri = adminInternalPayto,
+ internalPaytoUri = IbanPayTo(adminInternalPayto),
owningCustomerId = adminCustomerId,
isPublic = false,
isTalerExchange = false,
diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt
@@ -68,7 +68,7 @@ class DatabaseTest {
cashoutCurrency = "KUDOS"
)
private val bankAccountFoo = BankAccount(
- internalPaytoUri = "payto://iban/FOO-IBAN-XYZ".lowercase(),
+ internalPaytoUri = IbanPayTo("payto://iban/FOO-IBAN-XYZ"),
lastNexusFetchRowId = 1L,
owningCustomerId = 1L,
hasDebt = false,
@@ -76,7 +76,7 @@ class DatabaseTest {
isTalerExchange = true
)
private val bankAccountBar = BankAccount(
- internalPaytoUri = "payto://iban/BAR-IBAN-ABC".lowercase(),
+ internalPaytoUri = IbanPayTo("payto://iban/BAR-IBAN-ABC"),
lastNexusFetchRowId = 1L,
owningCustomerId = 2L,
hasDebt = false,
@@ -114,8 +114,8 @@ class DatabaseTest {
fun talerTransferTest() = setupDb { db ->
val exchangeReq = TransferRequest(
amount = TalerAmount(9, 0, "KUDOS"),
- credit_account = "payto://iban/BAR-IBAN-ABC".lowercase(), // foo pays bar
- exchange_base_url = "example.com/exchange",
+ credit_account = IbanPayTo("payto://iban/BAR-IBAN-ABC"),
+ exchange_base_url = ExchangeUrl("https://example.com/exchange"),
request_uid = randHashCode(),
wtid = randShortHashCode()
)
@@ -319,7 +319,7 @@ class DatabaseTest {
// Setting the details.
assert(db.talerWithdrawalSetDetails(
opUuid = uuid,
- exchangePayto = "payto://iban/BAR-IBAN-ABC".lowercase(),
+ exchangePayto = IbanPayTo("payto://iban/BAR-IBAN-ABC"),
reservePub = "UNCHECKED-RESERVE-PUB"
))
val opSelected = db.talerWithdrawalGet(uuid)
@@ -443,7 +443,7 @@ class DatabaseTest {
assert(db.bankAccountCreate(
BankAccount(
isPublic = true,
- internalPaytoUri = "payto://iban/non-used",
+ internalPaytoUri = IbanPayTo("payto://iban/non-used"),
lastNexusFetchRowId = 1L,
owningCustomerId = this!!,
hasDebt = false,
@@ -500,10 +500,10 @@ class DatabaseTest {
// Getting the updated account from the database and checking values.
db.customerGetFromLogin("foo").apply {
assertNotNull(this)
- assert((this.login == "foo") &&
- (this.name == "Bar") &&
- (this.cashoutPayto) == "payto://cashout" &&
- (this.email) == "foo@example.com" &&
+ assert(this.login == "foo" &&
+ this.name == "Bar" &&
+ this.cashoutPayto == "payto://cashout" &&
+ this.email == "foo@example.com" &&
this.phone == "+99"
)
db.bankAccountGetFromOwnerId(this.expectRowId()).apply {
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -43,7 +43,7 @@ class LibeuFinApiTest {
private fun genBankAccount(rowId: Long) = BankAccount(
hasDebt = false,
- internalPaytoUri = "payto://iban/ac${rowId}",
+ internalPaytoUri = IbanPayTo("payto://iban/ac${rowId}"),
maxDebt = TalerAmount(100, 0, "KUDOS"),
owningCustomerId = rowId
)
@@ -248,7 +248,7 @@ class LibeuFinApiTest {
assert(db.bankAccountCreate(
BankAccount(
hasDebt = false,
- internalPaytoUri = "payto://iban/DE1234",
+ internalPaytoUri = IbanPayTo("payto://iban/DE1234"),
maxDebt = TalerAmount(100, 0, "KUDOS"),
owningCustomerId = 1
)
@@ -276,7 +276,7 @@ class LibeuFinApiTest {
db.bankAccountCreate(
BankAccount(
isPublic = true,
- internalPaytoUri = "payto://iban/non-used",
+ internalPaytoUri = IbanPayTo("payto://iban/non-used"),
lastNexusFetchRowId = 1L,
owningCustomerId = this!!,
hasDebt = false,
@@ -439,7 +439,7 @@ class LibeuFinApiTest {
db.bankAccountCreate(
BankAccount(
hasDebt = false,
- internalPaytoUri = "payto://iban/DE1234",
+ internalPaytoUri = IbanPayTo("payto://iban/DE1234"),
maxDebt = TalerAmount(100, 0, "KUDOS"),
owningCustomerId = customerRowId!!
)
@@ -468,7 +468,7 @@ class LibeuFinApiTest {
db.bankAccountCreate(
BankAccount(
hasDebt = false,
- internalPaytoUri = "payto://iban/SANDBOXX/ADMIN-IBAN",
+ internalPaytoUri = IbanPayTo("payto://iban/SANDBOXX/ADMIN-IBAN"),
maxDebt = TalerAmount(100, 0, "KUDOS"),
owningCustomerId = adminRowId!!
)
diff --git a/bank/src/test/kotlin/TalerApiTest.kt b/bank/src/test/kotlin/TalerApiTest.kt
@@ -28,7 +28,7 @@ class TalerApiTest {
cashoutCurrency = "KUDOS"
)
private val bankAccountFoo = BankAccount(
- internalPaytoUri = "payto://iban/FOO-IBAN-XYZ".lowercase(),
+ internalPaytoUri = IbanPayTo("payto://iban/FOO-IBAN-XYZ"),
lastNexusFetchRowId = 1L,
owningCustomerId = 1L,
hasDebt = false,
@@ -45,7 +45,7 @@ class TalerApiTest {
cashoutCurrency = "KUDOS"
)
val bankAccountBar = BankAccount(
- internalPaytoUri = stripIbanPayto("payto://iban/BAR-IBAN-ABC")!!,
+ internalPaytoUri = IbanPayTo("payto://iban/BAR-IBAN-ABC"),
lastNexusFetchRowId = 1L,
owningCustomerId = 2L,
hasDebt = false,
@@ -59,28 +59,29 @@ class TalerApiTest {
req = TransferRequest(
request_uid = randHashCode(),
amount = TalerAmount(amount),
- exchange_base_url = "http://exchange.example.com/",
+ exchange_base_url = ExchangeUrl("http://exchange.example.com/"),
wtid = randShortHashCode(),
- credit_account ="${stripIbanPayto(to.internalPaytoUri)}"
+ credit_account = to.internalPaytoUri
),
username = from,
timestamp = Instant.now()
- )
+ ).run {
+ assertEquals(TalerTransferResult.SUCCESS, txResult)
+ }
}
- suspend fun Database.genIncoming(from: Long, to: Long) {
- bankTransactionCreate(
- BankInternalTransaction(
- creditorAccountId = from,
- debtorAccountId = to,
- subject = IncomingTxMetadata(randShortHashCode()).toString(),
+ suspend fun Database.genIncoming(to: String, from: BankAccount) {
+ talerAddIncomingCreate(
+ req = AddIncomingRequest(
+ reserve_pub = randShortHashCode(),
amount = TalerAmount( 10, 0, "KUDOS"),
- accountServicerReference = "acct-svcr-ref",
- endToEndId = "end-to-end-id",
- paymentInformationId = "pmtinfid",
- transactionDate = Instant.now()
- )
- ).assertSuccess()
+ debit_account = from.internalPaytoUri,
+ ),
+ username = to,
+ timestamp = Instant.now()
+ ).run {
+ assertEquals(TalerAddIncomingResult.SUCCESS, txResult)
+ }
}
fun commonSetup(lambda: suspend (Database, BankApplicationContext) -> Unit) {
@@ -138,7 +139,7 @@ class TalerApiTest {
"amount" to "KUDOS:55"
"exchange_base_url" to "http://exchange.example.com/"
"wtid" to randShortHashCode()
- "credit_account" to stripIbanPayto(bankAccountFoo.internalPaytoUri)
+ "credit_account" to bankAccountFoo.internalPaytoUri
};
authRoutine(client, "/accounts/foo/taler-wire-gateway/transfer", valid_req)
@@ -289,17 +290,19 @@ class TalerApiTest {
basicAuth("bar", "secret")
}.assertStatus(HttpStatusCode.NoContent)
- // Foo pays Bar (the exchange) three time
+ // Gen three transactions using clean add incoming logic
repeat(3) {
- db.genIncoming(2, 1)
+ db.genIncoming("bar", bankAccountFoo)
}
// Should not show up in the taler wire gateway API history
db.bankTransactionCreate(genTx("bogus foobar")).assertSuccess()
// Bar pays Foo once, but that should not appear in the result.
db.bankTransactionCreate(genTx("payout", creditorId = 1, debtorId = 2)).assertSuccess()
- // Foo pays Bar (the exchange) twice, we should see five valid transactions
+ // Gen two transactions using row bank transaction logic
repeat(2) {
- db.genIncoming(2, 1)
+ db.bankTransactionCreate(
+ genTx(IncomingTxMetadata(randShortHashCode()).encode(), 2, 1)
+ ).assertSuccess()
}
// Check ignore bogus subject
@@ -356,14 +359,14 @@ class TalerApiTest {
},
launch {
delay(200)
- db.genIncoming(2, 1)
+ db.genIncoming("bar", bankAccountFoo)
}
)
}
// Testing ranges.
repeat(300) {
- db.genIncoming(2, 1)
+ db.genIncoming("bar", bankAccountFoo)
}
// forward range:
@@ -428,7 +431,7 @@ class TalerApiTest {
basicAuth("bar", "secret")
}.assertStatus(HttpStatusCode.NoContent)
- // Bar pays Foo three time
+ // Gen three transactions using clean transfer logic
repeat(3) {
db.genTransfer("bar", bankAccountFoo)
}
@@ -436,9 +439,11 @@ class TalerApiTest {
db.bankTransactionCreate(genTx("bogus foobar", 1, 2)).assertSuccess()
// Foo pays Bar once, but that should not appear in the result.
db.bankTransactionCreate(genTx("payout")).assertSuccess()
- // Bar pays Foo twice, we should see five valid transactions
+ // Gen two transactions using row bank transaction logic
repeat(2) {
- db.genTransfer("bar", bankAccountFoo)
+ db.bankTransactionCreate(
+ genTx(OutgoingTxMetadata(randShortHashCode(), ExchangeUrl("http://exchange.example.com/")).encode(), 1, 2)
+ ).assertSuccess()
}
// Check ignore bogus subject
@@ -613,7 +618,7 @@ class TalerApiTest {
val r = client.post("/taler-integration/withdrawal-operation/${uuid}") {
jsonBody(BankWithdrawalOperationPostRequest(
reserve_pub = "RESERVE-FOO",
- selected_exchange = "payto://iban/ABC123"
+ selected_exchange = IbanPayTo("payto://iban/ABC123")
))
}.assertOk()
println(r.bodyAsText())
@@ -653,7 +658,7 @@ class TalerApiTest {
))
val op = db.talerWithdrawalGet(uuid)
assert(op?.aborted == false)
- assert(db.talerWithdrawalSetDetails(uuid, "exchange-payto", "reserve_pub"))
+ assert(db.talerWithdrawalSetDetails(uuid, IbanPayTo("payto://iban/exchange-payto"), "reserve_pub"))
testApplication {
application {
corebankWebApp(db, ctx)
@@ -700,7 +705,7 @@ class TalerApiTest {
// Specifying Bar as the exchange, via its Payto URI.
assert(db.talerWithdrawalSetDetails(
opUuid = uuid,
- exchangePayto = "payto://iban/BAR-IBAN-ABC".lowercase(),
+ exchangePayto = IbanPayTo("payto://iban/BAR-IBAN-ABC"),
reservePub = "UNCHECKED-RESERVE-PUB"
))
diff --git a/database-versioning/procedures.sql b/database-versioning/procedures.sql
@@ -387,7 +387,7 @@ END IF;
-- Perform bank transfer
SELECT
out_balance_insufficient,
- out_debit_row_id,
+ out_credit_row_id,
transfer.out_same_account
INTO
out_debitor_balance_insufficient,
diff --git a/util/src/main/kotlin/IbanPayto.kt b/util/src/main/kotlin/IbanPayto.kt
@@ -104,6 +104,6 @@ fun buildIbanPaytoUri(
*/
fun stripIbanPayto(paytoUri: String): String? {
val parsedPayto = parsePayto(paytoUri) ?: return null
- val canonIban = parsedPayto.iban.lowercase()
+ val canonIban = parsedPayto.iban.uppercase()
return "payto://iban/${canonIban}"
}
\ No newline at end of file