gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (cd53b2f7 -> 836956ed)


From: gnunet
Subject: [libeufin] branch master updated (cd53b2f7 -> 836956ed)
Date: Fri, 02 Feb 2024 21:11:04 +0100

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

antoine pushed a change to branch master
in repository libeufin.

    from cd53b2f7 simply message
     new e8c44ca6 Prepare support for more payto URI kind
     new 836956ed Add support for x-taler-bank payto URI

The 2 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:
 Makefile                                           |   4 +
 bank/conf/{test.conf => test_x_taler_bank.conf}    |   6 +-
 .../tech/libeufin/bank/BankIntegrationApi.kt       |   6 +-
 bank/src/main/kotlin/tech/libeufin/bank/Config.kt  |  17 +-
 .../main/kotlin/tech/libeufin/bank/CoreBankApi.kt  | 107 +++++++----
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |  15 +-
 .../main/kotlin/tech/libeufin/bank/RevenueApi.kt   |   4 +-
 .../main/kotlin/tech/libeufin/bank/TalerMessage.kt |  43 +++--
 .../kotlin/tech/libeufin/bank/WireGatewayApi.kt    |   8 +-
 .../kotlin/tech/libeufin/bank/db/AccountDAO.kt     |  57 +++---
 .../kotlin/tech/libeufin/bank/db/ExchangeDAO.kt    |  10 +-
 .../main/kotlin/tech/libeufin/bank/db/TanDAO.kt    |  12 +-
 .../kotlin/tech/libeufin/bank/db/TransactionDAO.kt |  20 +-
 .../kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt  |  10 +-
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt |   9 +-
 bank/src/test/kotlin/AmountTest.kt                 |   4 +-
 bank/src/test/kotlin/BankIntegrationApiTest.kt     |   5 +-
 bank/src/test/kotlin/CoreBankApiTest.kt            |  36 ++--
 bank/src/test/kotlin/DatabaseTest.kt               |   5 +-
 bank/src/test/kotlin/PaytoTest.kt                  |  75 ++++++++
 bank/src/test/kotlin/WireGatewayApiTest.kt         |   4 +-
 bank/src/test/kotlin/helpers.kt                    |  18 +-
 common/src/main/kotlin/DB.kt                       |   7 +-
 common/src/main/kotlin/TalerCommon.kt              | 205 ++++++++++++++-------
 common/src/main/kotlin/iban.kt                     |  12 --
 common/src/test/kotlin/PaytoTest.kt                |  36 ++--
 contrib/bank.conf                                  |   9 +
 ebics/src/main/kotlin/Ebics.kt                     |   6 +-
 .../main/kotlin/tech/libeufin/nexus/Database.kt    |  39 ++--
 .../main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt |  30 ++-
 .../main/kotlin/tech/libeufin/nexus/Iso20022.kt    |  10 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |   2 +-
 nexus/src/test/kotlin/Common.kt                    |   3 +-
 nexus/src/test/kotlin/DatabaseTest.kt              |  14 +-
 testbench/src/main/kotlin/Main.kt                  |   9 +-
 testbench/src/test/kotlin/IntegrationTest.kt       |  14 +-
 36 files changed, 541 insertions(+), 330 deletions(-)
 copy bank/conf/{test.conf => test_x_taler_bank.conf} (72%)
 create mode 100644 bank/src/test/kotlin/PaytoTest.kt

diff --git a/Makefile b/Makefile
index d0665d94..1e0efa78 100644
--- a/Makefile
+++ b/Makefile
@@ -114,6 +114,10 @@ nexus-test: install-nobuild-nexus-files
 ebics-test:
        ./gradlew :ebics:test --tests $(test) -i
 
+.PHONY: common-test
+common-test:
+       ./gradlew :common:test --tests $(test) -i
+
 .PHONY: testbench-test
 testbench-test: install-nobuild-bank-files install-nobuild-nexus-files
        ./gradlew :testbench:test --tests $(test) -i
diff --git a/bank/conf/test.conf b/bank/conf/test_x_taler_bank.conf
similarity index 72%
copy from bank/conf/test.conf
copy to bank/conf/test_x_taler_bank.conf
index 4a7a4476..294aa371 100644
--- a/bank/conf/test.conf
+++ b/bank/conf/test_x_taler_bank.conf
@@ -5,10 +5,8 @@ ALLOW_REGISTRATION = yes
 ALLOW_ACCOUNT_DELETION = yes
 ALLOW_EDIT_NAME = yes
 ALLOW_EDIT_CASHOUT_PAYTO_URI = yes
-allow_conversion = YES
-FIAT_CURRENCY = EUR
-tan_sms = libeufin-tan-file.sh
-tan_email = libeufin-tan-file.sh
+PAYMENT_METHOD = x-taler-bank
+X_TALER_BANK_PAYTO_HOSTNAME = bank.hostname.test
 
 [libeufin-bankdb-postgres]
 CONFIG = postgresql:///libeufincheck
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
index be38286f..3440bd54 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
@@ -43,7 +43,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) 
{
     get("/taler-integration/withdrawal-operation/{wopid}") {
         val uuid = call.uuidParameter("wopid")
         val params = StatusParams.extract(call.request.queryParameters)
-        val op = db.withdrawal.pollStatus(uuid, params) ?: throw notFound(
+        val op = db.withdrawal.pollStatus(uuid, params, ctx.wireMethod) ?: 
throw notFound(
             "Withdrawal operation '$uuid' not found", 
             TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
         )
@@ -78,11 +78,11 @@ fun Routing.bankIntegrationApi(db: Database, ctx: 
BankConfig) {
                 TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT
             )
             is WithdrawalSelectionResult.UnknownAccount -> throw conflict(
-                "Account ${req.selected_exchange.canonical} not found",
+                "Account ${req.selected_exchange} not found",
                 TalerErrorCode.BANK_UNKNOWN_ACCOUNT
             )
             is WithdrawalSelectionResult.AccountIsNotExchange -> throw 
conflict(
-                "Account ${req.selected_exchange.canonical} is not an 
exchange",
+                "Account ${req.selected_exchange} is not an exchange",
                 TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE
             )
             is WithdrawalSelectionResult.Success -> {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
index 0548758c..7539da69 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -49,7 +49,9 @@ data class BankConfig(
     val fiatCurrency: String?,
     val fiatCurrencySpec: CurrencySpecification?,
     val spaPath: Path?,
-    val tanChannels: Map<TanChannel, Path> 
+    val tanChannels: Map<TanChannel, Path>,
+    val payto: BankPaytoCtx,
+    val wireMethod: WireMethod
 )
 
 @Serializable
@@ -104,6 +106,15 @@ fun TalerConfig.loadBankConfig(): BankConfig  {
             }
         }
     }
+    val method = when (val raw = lookupString("libeufin-bank", 
"payment_method")) {
+        "iban" -> WireMethod.IBAN
+        "x-taler-bank" -> WireMethod.X_TALER_BANK
+        else -> throw TalerConfigError("expected wire method for section 
libeufin-bank, option payment_method, but $raw is unknown")
+    }
+    val payto = when (method) {
+        WireMethod.IBAN -> BankPaytoCtx(bic = lookupString("libeufin-bank", 
"iban_payto_bic"))
+        WireMethod.X_TALER_BANK -> BankPaytoCtx(hostname = 
lookupString("libeufin-bank", "x_taler_bank_payto_hostname"))
+    }
     return BankConfig(
         regionalCurrency = regionalCurrency,
         regionalCurrencySpec =  currencySpecificationFor(regionalCurrency),
@@ -119,7 +130,9 @@ fun TalerConfig.loadBankConfig(): BankConfig  {
         allowConversion = allowConversion,
         fiatCurrency = fiatCurrency,
         fiatCurrencySpec = fiatCurrencySpec,
-        tanChannels = tanChannels
+        tanChannels = tanChannels,
+        payto = payto,
+        wireMethod = method
     )
 }
 
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
index 6724eeef..e6b8ae20 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -138,7 +138,12 @@ private fun Routing.coreBankTokenApi(db: Database) {
     }
 }
 
-suspend fun createAccount(db: Database, ctx: BankConfig, req: 
RegisterAccountRequest, isAdmin: Boolean): Pair<AccountCreationResult, 
FullIbanPayto>  {
+suspend fun createAccount(
+    db: Database, 
+    cfg: BankConfig, 
+    req: RegisterAccountRequest, 
+    isAdmin: Boolean
+): Pair<AccountCreationResult, String>  {
     // Prohibit reserved usernames:
     if (RESERVED_ACCOUNTS.contains(req.username))
         throw conflict(
@@ -160,7 +165,7 @@ suspend fun createAccount(db: Database, ctx: BankConfig, 
req: RegisterAccountReq
             )
 
     } else if (req.tan_channel != null) {
-        if (ctx.tanChannels.get(req.tan_channel) == null) {
+        if (cfg.tanChannels.get(req.tan_channel) == null) {
             throw unsupportedTanChannel(req.tan_channel)
         } 
         val missing = when (req.tan_channel) {
@@ -180,38 +185,64 @@ suspend fun createAccount(db: Database, ctx: BankConfig, 
req: RegisterAccountReq
             TalerErrorCode.END
         )
 
-    var retry = if (req.payto_uri == null) IBAN_ALLOCATION_RETRY_COUNTER else 0
+    when (cfg.wireMethod) {
+        WireMethod.IBAN -> {
+            var retry = if (req.payto_uri == null) 
IBAN_ALLOCATION_RETRY_COUNTER else 0
 
-    while (true) {
-        val internalPayto = req.payto_uri ?: IbanPayto(genIbanPaytoUri())
-        val res = db.account.create(
-            login = req.username,
-            name = req.name,
-            email = req.contact_data?.email?.get(),
-            phone = req.contact_data?.phone?.get(),
-            cashoutPayto = req.cashout_payto_uri,
-            password = req.password,
-            internalPaytoUri = internalPayto,
-            isPublic = req.is_public,
-            isTalerExchange = req.is_taler_exchange,
-            maxDebt = req.debit_threshold ?: ctx.defaultDebtLimit,
-            bonus = if (!req.is_taler_exchange) ctx.registrationBonus 
-                    else TalerAmount(0, 0, ctx.regionalCurrency),
-            tanChannel = req.tan_channel,
-            checkPaytoIdempotent = req.payto_uri != null
-        )
-        // Retry with new IBAN
-        if (res == AccountCreationResult.PayToReuse && retry > 0) {
-            retry--
-            continue
+            while (true) {
+                val internalPayto = req.payto_uri ?: IbanPayto.rand() as Payto
+                val res = db.account.create(
+                    login = req.username,
+                    name = req.name,
+                    email = req.contact_data?.email?.get(),
+                    phone = req.contact_data?.phone?.get(),
+                    cashoutPayto = req.cashout_payto_uri,
+                    password = req.password,
+                    internalPayto = internalPayto,
+                    isPublic = req.is_public,
+                    isTalerExchange = req.is_taler_exchange,
+                    maxDebt = req.debit_threshold ?: cfg.defaultDebtLimit,
+                    bonus = if (!req.is_taler_exchange) cfg.registrationBonus 
+                            else TalerAmount(0, 0, cfg.regionalCurrency),
+                    tanChannel = req.tan_channel,
+                    checkPaytoIdempotent = req.payto_uri != null
+                )
+                // Retry with new IBAN
+                if (res == AccountCreationResult.PayToReuse && retry > 0) {
+                    retry--
+                    continue
+                }
+                return Pair(res, internalPayto.bank(req.name, cfg.payto))
+            }
+        }
+        WireMethod.X_TALER_BANK -> {
+            val internalPayto = XTalerBankPayto.forUsername(req.username)
+            val res = db.account.create(
+                login = req.username,
+                name = req.name,
+                email = req.contact_data?.email?.get(),
+                phone = req.contact_data?.phone?.get(),
+                cashoutPayto = req.cashout_payto_uri,
+                password = req.password,
+                internalPayto = internalPayto,
+                isPublic = req.is_public,
+                isTalerExchange = req.is_taler_exchange,
+                maxDebt = req.debit_threshold ?: cfg.defaultDebtLimit,
+                bonus = if (!req.is_taler_exchange) cfg.registrationBonus 
+                        else TalerAmount(0, 0, cfg.regionalCurrency),
+                tanChannel = req.tan_channel,
+                checkPaytoIdempotent = req.payto_uri != null
+            )
+            return Pair(res, internalPayto.bank(req.name, cfg.payto))
         }
-        return Pair(res, internalPayto.withName(req.name))
     }
+
+    
 }
 
 suspend fun patchAccount(
     db: Database, 
-    ctx: BankConfig, 
+    cfg: BankConfig, 
     req: AccountReconfiguration, 
     username: String, 
     isAdmin: Boolean, 
@@ -219,7 +250,7 @@ suspend fun patchAccount(
     channel: TanChannel? = null, 
     info: String? = null
 ): AccountPatchResult {
-    req.debit_threshold?.run { ctx.checkRegionalCurrency(this) }
+    req.debit_threshold?.run { cfg.checkRegionalCurrency(this) }
 
     if (username == "admin" && req.is_public == true)
         throw conflict(
@@ -227,7 +258,7 @@ suspend fun patchAccount(
             TalerErrorCode.END
         )
     
-    if (req.tan_channel is Option.Some && req.tan_channel.value != null && 
ctx.tanChannels.get(req.tan_channel.value ) == null) {
+    if (req.tan_channel is Option.Some && req.tan_channel.value != null && 
!cfg.tanChannels.contains(req.tan_channel.value)) {
         throw unsupportedTanChannel(req.tan_channel.value)
     }
 
@@ -244,8 +275,8 @@ suspend fun patchAccount(
         is2fa = is2fa,
         faChannel = channel,
         faInfo = info,
-        allowEditName = ctx.allowEditName,
-        allowEditCashout = ctx.allowEditCashout
+        allowEditName = cfg.allowEditName,
+        allowEditCashout = cfg.allowEditCashout
     )
 }
 
@@ -264,7 +295,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: 
BankConfig) {
                     TalerErrorCode.BANK_REGISTER_USERNAME_REUSE
                 )
                 AccountCreationResult.PayToReuse -> throw conflict(
-                    "Bank internalPayToUri reuse '${internalPayto.payto}'",
+                    "Bank internalPayToUri reuse '$internalPayto'",
                     TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE
                 )
                 AccountCreationResult.Success -> 
call.respond(RegisterAccountResponse(internalPayto))
@@ -353,7 +384,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: 
BankConfig) {
     }
     get("/public-accounts") {
         val params = AccountParams.extract(call.request.queryParameters)
-        val publicAccounts = db.account.pagePublic(params)
+        val publicAccounts = db.account.pagePublic(params, ctx.payto)
         if (publicAccounts.isEmpty()) {
             call.respond(HttpStatusCode.NoContent)
         } else {
@@ -363,7 +394,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: 
BankConfig) {
     authAdmin(db, TokenScope.readonly) {
         get("/accounts") {
             val params = AccountParams.extract(call.request.queryParameters)
-            val accounts = db.account.pageAdmin(params)
+            val accounts = db.account.pageAdmin(params, ctx.payto)
             if (accounts.isEmpty()) {
                 call.respond(HttpStatusCode.NoContent)
             } else {
@@ -373,7 +404,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: 
BankConfig) {
     }
     auth(db, TokenScope.readonly, allowAdmin = true) {
         get("/accounts/{USERNAME}") {
-            val account = db.account.get(username) ?: throw 
unknownAccount(username)
+            val account = db.account.get(username, ctx.payto) ?: throw 
unknownAccount(username)
             call.respond(account)
         }
     }
@@ -383,10 +414,10 @@ private fun Routing.coreBankTransactionsApi(db: Database, 
ctx: BankConfig) {
     auth(db, TokenScope.readonly) {
         get("/accounts/{USERNAME}/transactions") {
             val params = HistoryParams.extract(call.request.queryParameters)
-            val bankAccount = call.bankInfo(db)
+            val bankAccount = call.bankInfo(db, ctx.payto)
     
             val history: List<BankAccountTransactionInfo> =
-                    db.transaction.pollHistory(params, 
bankAccount.bankAccountId)
+                    db.transaction.pollHistory(params, 
bankAccount.bankAccountId, ctx.payto)
             if (history.isEmpty()) {
                 call.respond(HttpStatusCode.NoContent)
             } else {
@@ -395,7 +426,7 @@ private fun Routing.coreBankTransactionsApi(db: Database, 
ctx: BankConfig) {
         }
         get("/accounts/{USERNAME}/transactions/{T_ID}") {
             val tId = call.longParameter("T_ID")
-            val tx = db.transaction.get(tId, username) ?: throw notFound(
+            val tx = db.transaction.get(tId, username, ctx.payto) ?: throw 
notFound(
                     "Bank transaction '$tId' not found",
                     TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
                 )
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index eb6d7ad7..a85cff41 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -179,7 +179,7 @@ fun Application.corebankWebApp(db: Database, ctx: 
BankConfig) {
                         rootCause is CommonError -> when (rootCause) {
                             is CommonError.AmountFormat -> 
TalerErrorCode.BANK_BAD_FORMAT_AMOUNT
                             is CommonError.AmountNumberTooBig -> 
TalerErrorCode.BANK_NUMBER_TOO_BIG
-                            is CommonError.IbanPayto -> 
TalerErrorCode.GENERIC_JSON_INVALID
+                            is CommonError.Payto -> 
TalerErrorCode.GENERIC_JSON_INVALID
                         }
                         else -> TalerErrorCode.GENERIC_JSON_INVALID
                     }
@@ -264,7 +264,7 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP 
server", name = "serve")
             runBlocking {
                 if (ctx.allowConversion) {
                     logger.info("Ensure exchange account exists")
-                    val info = db.account.bankInfo("exchange")
+                    val info = db.account.bankInfo("exchange", ctx.payto)
                     if (info == null) {
                         throw Exception("Exchange account missing: an exchange 
account named 'exchange' is required for conversion to be enabled")
                     } else if (!info.isTalerExchange) {
@@ -364,7 +364,7 @@ class EditAccount : CliktCommand(
     private val email: String? by option(help = "E-Mail address used for TAN 
transmission")
     private val phone: String? by option(help = "Phone number used for TAN 
transmission")
     private val tan_channel: String? by option(help = "which channel TAN 
challenges should be sent to")
-    private val cashout_payto_uri: IbanPayto? by option(help = "Payto URI of a 
fiant account who receive cashout amount").convert { IbanPayto(it) }
+    private val cashout_payto_uri: IbanPayto? by option(help = "Payto URI of a 
fiant account who receive cashout amount").convert { 
Payto.parse(it).expectIban() }
     private val debit_threshold: TalerAmount? by option(help = "Max debit 
allowed for this account").convert { TalerAmount(it) }
  
     override fun run() = cliCmd(logger, common.log) {
@@ -427,11 +427,10 @@ class CreateAccountOption: OptionGroup() {
     val phone: String? by option(help = "Phone number used for TAN 
transmission")
     val cashout_payto_uri: IbanPayto? by option(
         help = "Payto URI of a fiant account who receive cashout amount"
-    ).convert { IbanPayto(it) }
-    val internal_payto_uri: IbanPayto? by option(hidden = true).convert { 
IbanPayto(it) }
-    val payto_uri: IbanPayto? by option(
+    ).convert { Payto.parse(it).expectIban() }
+    val payto_uri: Payto? by option(
         help = "Payto URI of this account"
-    ).convert { IbanPayto(it) }
+    ).convert { Payto.parse(it) }
     val debit_threshold: TalerAmount? by option(
         help = "Max debit allowed for this account")
     .convert { TalerAmount(it) }
@@ -477,7 +476,7 @@ class CreateAccount : CliktCommand(
                         AccountCreationResult.LoginReuse ->
                             throw Exception("Account username reuse 
'${req.username}'")
                         AccountCreationResult.PayToReuse ->
-                            throw Exception("Bank internalPayToUri reuse 
'${internalPayto.payto}'")
+                            throw Exception("Bank internalPayToUri reuse 
'$internalPayto'")
                         AccountCreationResult.Success ->
                             logger.info("Account '${req.username}' created")
                     }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt
index 8d37ecd5..9009e1a5 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt
@@ -37,8 +37,8 @@ fun Routing.revenueApi(db: Database, ctx: BankConfig) {
         }
         get("/accounts/{USERNAME}/taler-revenue/history") {
             val params = HistoryParams.extract(context.request.queryParameters)
-            val bankAccount = call.bankInfo(db)
-            val items = db.transaction.revenueHistory(params, 
bankAccount.bankAccountId);
+            val bankAccount = call.bankInfo(db, ctx.payto)
+            val items = db.transaction.revenueHistory(params, 
bankAccount.bankAccountId, ctx.payto);
         
             if (items.isEmpty()) {
                 call.respond(HttpStatusCode.NoContent)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
index a9e60e57..822b1642 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -80,6 +80,11 @@ enum class Operation {
     withdrawal
 }
 
+enum class WireMethod {
+    IBAN,
+    X_TALER_BANK
+}
+
 @Serializable(with = Option.Serializer::class)
 sealed class Option<out T> {
     data object None : Option<Nothing>()
@@ -173,14 +178,14 @@ data class RegisterAccountRequest(
     val is_taler_exchange: Boolean = false,
     val contact_data: ChallengeContactData? = null,
     val cashout_payto_uri: IbanPayto? = null,
-    val payto_uri: IbanPayto? = null,
+    val payto_uri: Payto? = null,
     val debit_threshold: TalerAmount? = null,
     val tan_channel: TanChannel? = null,
 )
 
 @Serializable
 data class RegisterAccountResponse(
-    val internal_payto_uri: FullIbanPayto
+    val internal_payto_uri: String
 )
 
 /**
@@ -245,7 +250,7 @@ data class MonitorWithConversion(
  * from/to the database.
  */
 data class BankInfo(
-    val payto: FullIbanPayto,
+    val payto: String,
     val bankAccountId: Long,
     val isTalerExchange: Boolean,
 )
@@ -341,7 +346,7 @@ data class Balance(
 data class AccountMinimalData(
     val username: String,
     val name: String,
-    val payto_uri: FullIbanPayto,
+    val payto_uri: String,
     val balance: Balance,
     val debit_threshold: TalerAmount,
     val is_public: Boolean,
@@ -363,7 +368,7 @@ data class ListBankAccountsResponse(
 data class AccountData(
     val name: String,
     val balance: Balance,
-    val payto_uri: FullIbanPayto,
+    val payto_uri: String,
     val debit_threshold: TalerAmount,
     val contact_data: ChallengeContactData? = null,
     val cashout_payto_uri: String? = null,
@@ -374,7 +379,7 @@ data class AccountData(
 
 @Serializable
 data class TransactionCreateRequest(
-    val payto_uri: IbanPayto,
+    val payto_uri: Payto,
     val amount: TalerAmount?
 )
 
@@ -387,8 +392,8 @@ data class TransactionCreateResponse(
   or from GET /transactions */
 @Serializable
 data class BankAccountTransactionInfo(
-    val creditor_payto_uri: FullIbanPayto,
-    val debtor_payto_uri: FullIbanPayto,
+    val creditor_payto_uri: String,
+    val debtor_payto_uri: String,
     val amount: TalerAmount,
     val direction: TransactionDirection,
     val subject: String,
@@ -443,7 +448,7 @@ data class BankWithdrawalOperationStatus(
     val confirm_transfer_url: String? = null,
     val selected_reserve_pub: EddsaPublicKey? = null,
     val selected_exchange_account: String? = null,
-    val wire_types: MutableList<String> = mutableListOf("iban"),
+    val wire_types: List<String>,
     // TODO remove
     val aborted: Boolean,
     val selection_done: Boolean,
@@ -456,7 +461,7 @@ data class BankWithdrawalOperationStatus(
 @Serializable
 data class BankWithdrawalOperationPostRequest(
     val reserve_pub: EddsaPublicKey,
-    val selected_exchange: IbanPayto,
+    val selected_exchange: Payto,
 )
 
 /**
@@ -539,7 +544,7 @@ data class ConversionResponse(
 data class AddIncomingRequest(
     val amount: TalerAmount,
     val reserve_pub: EddsaPublicKey,
-    val debit_account: IbanPayto
+    val debit_account: Payto
 )
 
 /**
@@ -557,7 +562,7 @@ data class AddIncomingResponse(
 @Serializable
 data class IncomingHistory(
     val incoming_transactions: List<IncomingReserveTransaction>,
-    val credit_account: FullIbanPayto
+    val credit_account: String
 )
 
 /**
@@ -569,7 +574,7 @@ data class IncomingReserveTransaction(
     val row_id: Long, // DB row ID of the payment.
     val date: TalerProtocolTimestamp,
     val amount: TalerAmount,
-    val debit_account: FullIbanPayto,
+    val debit_account: String,
     val reserve_pub: EddsaPublicKey
 )
 
@@ -579,7 +584,7 @@ data class IncomingReserveTransaction(
 @Serializable
 data class OutgoingHistory(
     val outgoing_transactions: List<OutgoingTransaction>,
-    val debit_account: FullIbanPayto
+    val debit_account: String
 )
 
 /**
@@ -590,7 +595,7 @@ data class OutgoingTransaction(
     val row_id: Long, // DB row ID of the payment.
     val date: TalerProtocolTimestamp,
     val amount: TalerAmount,
-    val credit_account: FullIbanPayto,
+    val credit_account: String,
     val wtid: ShortHashCode,
     val exchange_base_url: String,
 )
@@ -598,7 +603,7 @@ data class OutgoingTransaction(
 @Serializable
 data class RevenueIncomingHistory(
     val incoming_transactions : List<RevenueIncomingBankTransaction>,
-    val credit_account: FullIbanPayto
+    val credit_account: String
 )
 
 @Serializable
@@ -606,7 +611,7 @@ data class RevenueIncomingBankTransaction(
     val row_id: Long,
     val date: TalerProtocolTimestamp,
     val amount: TalerAmount,
-    val debit_account: FullIbanPayto,
+    val debit_account: String,
     val subject: String
 )
 
@@ -619,7 +624,7 @@ data class TransferRequest(
     val amount: TalerAmount,
     val exchange_base_url: ExchangeUrl,
     val wtid: ShortHashCode,
-    val credit_account: IbanPayto
+    val credit_account: Payto
 )
 
 /**
@@ -645,7 +650,7 @@ data class PublicAccountsResponse(
 @Serializable
 data class PublicAccount(
     val username: String,
-    val payto_uri: FullIbanPayto,
+    val payto_uri: String,
     val balance: Balance,
     val is_taler_exchange: Boolean,
 )
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
index 0e62fdba..0a197422 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
@@ -82,11 +82,11 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) {
     }
     auth(db, TokenScope.readonly) {
         suspend fun <T> PipelineContext<Unit, ApplicationCall>.historyEndpoint(
-            reduce: (List<T>, FullIbanPayto) -> Any, 
-            dbLambda: suspend ExchangeDAO.(HistoryParams, Long) -> List<T>
+            reduce: (List<T>, String) -> Any, 
+            dbLambda: suspend ExchangeDAO.(HistoryParams, Long, BankPaytoCtx) 
-> List<T>
         ) {
             val params = HistoryParams.extract(context.request.queryParameters)
-            val bankAccount = call.bankInfo(db)
+            val bankAccount = call.bankInfo(db, ctx.payto)
             
             if (!bankAccount.isTalerExchange)
                 throw conflict(
@@ -94,7 +94,7 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) {
                     TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE
                 )
 
-            val items = db.exchange.dbLambda(params, 
bankAccount.bankAccountId);
+            val items = db.exchange.dbLambda(params, 
bankAccount.bankAccountId, ctx.payto);
         
             if (items.isEmpty()) {
                 call.respond(HttpStatusCode.NoContent)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
index 8f12d02b..e172ea78 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
@@ -42,7 +42,7 @@ class AccountDAO(private val db: Database) {
         email: String?,
         phone: String?,
         cashoutPayto: IbanPayto?,
-        internalPaytoUri: IbanPayto,
+        internalPayto: Payto,
         isPublic: Boolean,
         isTalerExchange: Boolean,
         maxDebt: TalerAmount,
@@ -71,9 +71,9 @@ class AccountDAO(private val db: Database) {
                 setString(1, name)
                 setString(2, email)
                 setString(3, phone)
-                setString(4, cashoutPayto?.full(name)?.full)
+                setString(4, cashoutPayto?.full(name))
                 setBoolean(5, checkPaytoIdempotent)
-                setString(6, internalPaytoUri.canonical)
+                setString(6, internalPayto.canonical)
                 setBoolean(7, isPublic)
                 setBoolean(8, isTalerExchange)
                 setString(9, tanChannel?.name)
@@ -90,19 +90,20 @@ class AccountDAO(private val db: Database) {
                     AccountCreationResult.LoginReuse
                 }
             } else {
-                conn.prepareStatement("""
-                    INSERT INTO iban_history(
-                        iban
-                        ,creation_time
-                    ) VALUES (?, ?)
-                """).run {
-                    setString(1, internalPaytoUri.iban.value)
-                    setLong(2, now)
-                    if (!executeUpdateViolation()) {
-                        conn.rollback()
-                        return@transaction AccountCreationResult.PayToReuse
+                if (internalPayto is IbanPayto)
+                    conn.prepareStatement("""
+                        INSERT INTO iban_history(
+                            iban
+                            ,creation_time
+                        ) VALUES (?, ?)
+                    """).run {
+                        setString(1, internalPayto.iban.value)
+                        setLong(2, now)
+                        if (!executeUpdateViolation()) {
+                            conn.rollback()
+                            return@transaction AccountCreationResult.PayToReuse
+                        }
                     }
-                }
 
                 val customerId = conn.prepareStatement("""
                     INSERT INTO customers (
@@ -122,7 +123,7 @@ class AccountDAO(private val db: Database) {
                     setString(3, name)
                     setString(4, email)
                     setString(5, phone)
-                    setString(6, cashoutPayto?.full(name)?.full)
+                    setString(6, cashoutPayto?.full(name))
                     setString(7, tanChannel?.name)
                     oneOrNull { it.getLong("customer_id") }!!
                 }
@@ -136,7 +137,7 @@ class AccountDAO(private val db: Database) {
                         ,max_debt
                     ) VALUES (?, ?, ?, ?, (?, ?)::taler_amount)
                 """).run {
-                    setString(1, internalPaytoUri.canonical)
+                    setString(1, internalPayto.canonical)
                     setLong(2, customerId)
                     setBoolean(3, isPublic)
                     setBoolean(4, isTalerExchange)
@@ -153,7 +154,7 @@ class AccountDAO(private val db: Database) {
                         SELECT out_balance_insufficient
                         FROM 
bank_transaction(?,'admin','bonus',(?,?)::taler_amount,?,true)
                     """).run {
-                        setString(1, internalPaytoUri.canonical)
+                        setString(1, internalPayto.canonical)
                         setLong(2, bonus.value)
                         setInt(3, bonus.frac)
                         setLong(4, now)
@@ -297,7 +298,7 @@ class AccountDAO(private val db: Database) {
         // Check reconfig rights
         if (checkName && name != curr.name) 
             return@transaction AccountPatchResult.NonAdminName
-        if (checkCashout && fullCashoutPayto?.full != curr.cashoutPayTo) 
+        if (checkCashout && fullCashoutPayto != curr.cashoutPayTo) 
             return@transaction AccountPatchResult.NonAdminCashout
         if (checkDebtLimit && debtLimit != curr.debtLimit)
             return@transaction AccountPatchResult.NonAdminDebtLimit
@@ -352,7 +353,7 @@ class AccountDAO(private val db: Database) {
             },
             "WHERE customer_id = ?",
             sequence {
-                cashoutPayto.some { yield(fullCashoutPayto?.full) }
+                cashoutPayto.some { yield(fullCashoutPayto) }
                 phone.some { yield(it) }
                 email.some { yield(it) }
                 tan_channel.some { yield(it?.name) }
@@ -418,7 +419,7 @@ class AccountDAO(private val db: Database) {
     }
 
     /** Get bank info of account [login] */
-    suspend fun bankInfo(login: String): BankInfo? = db.conn { conn ->
+    suspend fun bankInfo(login: String, ctx: BankPaytoCtx): BankInfo? = 
db.conn { conn ->
         val stmt = conn.prepareStatement("""
             SELECT
              bank_account_id
@@ -433,7 +434,7 @@ class AccountDAO(private val db: Database) {
         stmt.setString(1, login)
         stmt.oneOrNull {
             BankInfo(
-                payto = it.getFullPayto("internal_payto_uri", "name"),
+                payto = it.getBankPayto("internal_payto_uri", "name", ctx),
                 isTalerExchange = it.getBoolean("is_taler_exchange"),
                 bankAccountId = it.getLong("bank_account_id")
             )
@@ -441,7 +442,7 @@ class AccountDAO(private val db: Database) {
     }
 
     /** Get data of account [login] */
-    suspend fun get(login: String): AccountData? = db.conn { conn ->
+    suspend fun get(login: String, ctx: BankPaytoCtx): AccountData? = db.conn 
{ conn ->
         val stmt = conn.prepareStatement("""
             SELECT
                 name
@@ -472,7 +473,7 @@ class AccountDAO(private val db: Database) {
                 ),
                 tan_channel = it.getString("tan_channel")?.run { 
TanChannel.valueOf(this) },
                 cashout_payto_uri = it.getString("cashout_payto"),
-                payto_uri = it.getFullPayto("internal_payto_uri", "name"),
+                payto_uri = it.getBankPayto("internal_payto_uri", "name", ctx),
                 balance = Balance(
                     amount = it.getAmount("balance", db.bankCurrency),
                     credit_debit_indicator =
@@ -490,7 +491,7 @@ class AccountDAO(private val db: Database) {
     }
 
     /** Get a page of all public accounts */
-    suspend fun pagePublic(params: AccountParams): List<PublicAccount>
+    suspend fun pagePublic(params: AccountParams, ctx: BankPaytoCtx): 
List<PublicAccount>
         = db.page(
             params.page,
             "bank_account_id",
@@ -514,7 +515,7 @@ class AccountDAO(private val db: Database) {
         ) {
             PublicAccount(
                 username = it.getString("login"),
-                payto_uri = it.getFullPayto("internal_payto_uri", "name"),
+                payto_uri = it.getBankPayto("internal_payto_uri", "name", ctx),
                 balance = Balance(
                     amount = it.getAmount("balance", db.bankCurrency),
                     credit_debit_indicator = if (it.getBoolean("has_debt")) {
@@ -528,7 +529,7 @@ class AccountDAO(private val db: Database) {
         }
 
     /** Get a page of accounts */
-    suspend fun pageAdmin(params: AccountParams): List<AccountMinimalData>
+    suspend fun pageAdmin(params: AccountParams, ctx: BankPaytoCtx): 
List<AccountMinimalData>
         = db.page(
             params.page,
             "bank_account_id",
@@ -567,7 +568,7 @@ class AccountDAO(private val db: Database) {
                 debit_threshold = it.getAmount("max_debt", db.bankCurrency),
                 is_public = it.getBoolean("is_public"),
                 is_taler_exchange = it.getBoolean("is_taler_exchange"),
-                payto_uri = it.getFullPayto("internal_payto_uri", "name"),
+                payto_uri = it.getBankPayto("internal_payto_uri", "name", ctx),
             )
         }
 }
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
index 0788a77d..a8d926f9 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
@@ -31,7 +31,8 @@ class ExchangeDAO(private val db: Database) {
     /** Query [exchangeId] history of taler incoming transactions  */
     suspend fun incomingHistory(
         params: HistoryParams, 
-        exchangeId: Long
+        exchangeId: Long,
+        ctx: BankPaytoCtx
     ): List<IncomingReserveTransaction> 
         = db.poolHistory(params, exchangeId, 
NotificationWatcher::listenIncoming,  """
             SELECT
@@ -51,7 +52,7 @@ class ExchangeDAO(private val db: Database) {
                 row_id = it.getLong("bank_transaction_id"),
                 date = it.getTalerTimestamp("transaction_date"),
                 amount = it.getAmount("amount", db.bankCurrency),
-                debit_account = it.getFullPayto("debtor_payto_uri", 
"debtor_name"),
+                debit_account = it.getBankPayto("debtor_payto_uri", 
"debtor_name", ctx),
                 reserve_pub = EddsaPublicKey(it.getBytes("reserve_pub")),
             )
         }
@@ -59,7 +60,8 @@ class ExchangeDAO(private val db: Database) {
     /** Query [exchangeId] history of taler outgoing transactions  */
     suspend fun outgoingHistory(
         params: HistoryParams, 
-        exchangeId: Long
+        exchangeId: Long,
+        ctx: BankPaytoCtx
     ): List<OutgoingTransaction> 
         = db.poolHistory(params, exchangeId, 
NotificationWatcher::listenOutgoing,  """
             SELECT
@@ -80,7 +82,7 @@ class ExchangeDAO(private val db: Database) {
                 row_id = it.getLong("bank_transaction_id"),
                 date = it.getTalerTimestamp("transaction_date"),
                 amount = it.getAmount("amount", db.bankCurrency),
-                credit_account = it.getFullPayto("creditor_payto_uri", 
"creditor_name"),
+                credit_account = it.getBankPayto("creditor_payto_uri", 
"creditor_name", ctx),
                 wtid = ShortHashCode(it.getBytes("wtid")),
                 exchange_base_url = it.getString("exchange_base_url")
             )
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
index d36045d0..46692204 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
@@ -104,12 +104,12 @@ class TanDAO(private val db: Database) {
     }
 
     /** Result of TAN challenge solution */
-    sealed class TanSolveResult {
-        data class Success(val body: String, val op: Operation, val channel: 
TanChannel?, val info: String?): TanSolveResult()
-        data object NotFound: TanSolveResult()
-        data object NoRetry: TanSolveResult()
-        data object Expired: TanSolveResult()
-        data object BadCode: TanSolveResult()
+    sealed interface TanSolveResult {
+        data class Success(val body: String, val op: Operation, val channel: 
TanChannel?, val info: String?): TanSolveResult
+        data object NotFound: TanSolveResult
+        data object NoRetry: TanSolveResult
+        data object Expired: TanSolveResult
+        data object BadCode: TanSolveResult
     }
 
     /** Solve TAN challenge */
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
index 82d4a7dd..f3c8cd30 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
@@ -43,7 +43,7 @@ class TransactionDAO(private val db: Database) {
 
     /** Create a new transaction */
     suspend fun create(
-        creditAccountPayto: IbanPayto,
+        creditAccountPayto: Payto,
         debitAccountUsername: String,
         subject: String,
         amount: TalerAmount,
@@ -141,7 +141,7 @@ class TransactionDAO(private val db: Database) {
     }
     
     /** Get transaction [rowId] owned by [login] */
-    suspend fun get(rowId: Long, login: String): BankAccountTransactionInfo? = 
db.conn { conn ->
+    suspend fun get(rowId: Long, login: String, ctx: BankPaytoCtx): 
BankAccountTransactionInfo? = db.conn { conn ->
         val stmt = conn.prepareStatement("""
             SELECT 
               creditor_payto_uri
@@ -163,8 +163,8 @@ class TransactionDAO(private val db: Database) {
         stmt.setString(2, login)
         stmt.oneOrNull {
             BankAccountTransactionInfo(
-                creditor_payto_uri = it.getFullPayto("creditor_payto_uri", 
"creditor_name"),
-                debtor_payto_uri = it.getFullPayto("debtor_payto_uri", 
"debtor_name"),
+                creditor_payto_uri = it.getBankPayto("creditor_payto_uri", 
"creditor_name", ctx),
+                debtor_payto_uri = it.getBankPayto("debtor_payto_uri", 
"debtor_name", ctx),
                 amount = it.getAmount("amount", db.bankCurrency),
                 direction = 
TransactionDirection.valueOf(it.getString("direction")),
                 subject = it.getString("subject"),
@@ -177,7 +177,8 @@ class TransactionDAO(private val db: Database) {
     /** Pool [accountId] transactions history */
     suspend fun pollHistory(
         params: HistoryParams, 
-        accountId: Long
+        accountId: Long,
+        ctx: BankPaytoCtx
     ): List<BankAccountTransactionInfo> {
         return db.poolHistory(params, accountId, 
NotificationWatcher::listenBank,  """
             SELECT
@@ -196,8 +197,8 @@ class TransactionDAO(private val db: Database) {
             BankAccountTransactionInfo(
                 row_id = it.getLong("bank_transaction_id"),
                 date = it.getTalerTimestamp("transaction_date"),
-                creditor_payto_uri = it.getFullPayto("creditor_payto_uri", 
"creditor_name"),
-                debtor_payto_uri = it.getFullPayto("debtor_payto_uri", 
"debtor_name"),
+                creditor_payto_uri = it.getBankPayto("creditor_payto_uri", 
"creditor_name", ctx),
+                debtor_payto_uri = it.getBankPayto("debtor_payto_uri", 
"debtor_name", ctx),
                 amount = it.getAmount("amount", db.bankCurrency),
                 subject = it.getString("subject"),
                 direction = 
TransactionDirection.valueOf(it.getString("direction"))
@@ -208,7 +209,8 @@ class TransactionDAO(private val db: Database) {
     /** Query [accountId] history of incoming transactions to its account */
     suspend fun revenueHistory(
         params: HistoryParams, 
-        accountId: Long
+        accountId: Long,
+        ctx: BankPaytoCtx
     ): List<RevenueIncomingBankTransaction> 
         = db.poolHistory(params, accountId, 
NotificationWatcher::listenRevenue, """
             SELECT
@@ -225,7 +227,7 @@ class TransactionDAO(private val db: Database) {
                 row_id = it.getLong("bank_transaction_id"),
                 date = it.getTalerTimestamp("transaction_date"),
                 amount = it.getAmount("amount", db.bankCurrency),
-                debit_account = it.getFullPayto("debtor_payto_uri", 
"debtor_name"),
+                debit_account = it.getBankPayto("debtor_payto_uri", 
"debtor_name", ctx),
                 subject = it.getString("subject")
             )
         }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
index 1280e79f..cc952691 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
@@ -102,7 +102,7 @@ class WithdrawalDAO(private val db: Database) {
     /** Set details ([exchangePayto] & [reservePub]) for withdrawal operation 
[uuid] */
     suspend fun setDetails(
         uuid: UUID,
-        exchangePayto: IbanPayto,
+        exchangePayto: Payto,
         reservePub: EddsaPublicKey
     ): WithdrawalSelectionResult = db.serializable { conn ->
         val stmt = conn.prepareStatement("""
@@ -267,7 +267,7 @@ class WithdrawalDAO(private val db: Database) {
         }
 
     /** Pool public status of operation [uuid] */
-    suspend fun pollStatus(uuid: UUID, params: StatusParams): 
BankWithdrawalOperationStatus? =
+    suspend fun pollStatus(uuid: UUID, params: StatusParams, wire: 
WireMethod): BankWithdrawalOperationStatus? =
         poll(uuid, params, status = { it.status }) {
             db.conn { conn ->
                 val stmt = conn.prepareStatement("""
@@ -303,6 +303,12 @@ class WithdrawalDAO(private val db: Database) {
                         suggested_exchange = null,
                         selected_exchange_account = 
it.getString("selected_exchange_payto"),
                         selected_reserve_pub = 
it.getBytes("reserve_pub")?.run(::EddsaPublicKey),
+                        wire_types = listOf(
+                            when (wire) {
+                                WireMethod.IBAN -> "iban"
+                                WireMethod.X_TALER_BANK -> "x-taler-bank"
+                            } 
+                        )
                     )
                 }
             }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index b40223c6..5f52fda0 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -48,11 +48,8 @@ fun ApplicationCall.expectParameter(name: String) =
     )
 
 /** Retrieve the bank account info for the selected username*/
-suspend fun ApplicationCall.bankInfo(db: Database): BankInfo
-    = db.account.bankInfo(username) ?: throw unknownAccount(username)
-
-// Generates a new Payto-URI with IBAN scheme.
-fun genIbanPaytoUri(): String = "payto://iban/${getIban()}"
+suspend fun ApplicationCall.bankInfo(db: Database, ctx: BankPaytoCtx): BankInfo
+    = db.account.bankInfo(username, ctx) ?: throw unknownAccount(username)
 
 /**
  *  Builds the taler://withdraw-URI.  Such URI will serve the requests
@@ -125,7 +122,7 @@ suspend fun maybeCreateAdminAccount(db: Database, ctx: 
BankConfig, pw: String? =
         login = "admin",
         password = pwStr,
         name = "Bank administrator",
-        internalPaytoUri = IbanPayto(genIbanPaytoUri()),
+        internalPayto = IbanPayto.rand(),
         isPublic = false,
         isTalerExchange = false,
         maxDebt = ctx.defaultDebtLimit,
diff --git a/bank/src/test/kotlin/AmountTest.kt 
b/bank/src/test/kotlin/AmountTest.kt
index e23cdfda..417ef469 100644
--- a/bank/src/test/kotlin/AmountTest.kt
+++ b/bank/src/test/kotlin/AmountTest.kt
@@ -32,13 +32,13 @@ class AmountTest {
     // Test amount computation in database
     @Test
     fun computationTest() = bankSetup { db -> db.conn { conn ->
-        conn.execSQLUpdate("UPDATE libeufin_bank.bank_accounts SET balance.val 
= 100000 WHERE internal_payto_uri = '$customerPayto'")
+        conn.execSQLUpdate("UPDATE libeufin_bank.bank_accounts SET balance.val 
= 100000 WHERE internal_payto_uri = '${customerPayto.canonical}'")
         val stmt = conn.prepareStatement("""
             UPDATE libeufin_bank.bank_accounts 
                 SET balance = (?, ?)::taler_amount
                     ,has_debt = ?
                     ,max_debt = (?, ?)::taler_amount
-            WHERE internal_payto_uri = '$merchantPayto'
+            WHERE internal_payto_uri = '${merchantPayto.canonical}'
         """)
         suspend fun routine(balance: TalerAmount, due: TalerAmount, 
hasBalanceDebt: Boolean, maxDebt: TalerAmount): Boolean {
             stmt.setLong(1, balance.value)
diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt 
b/bank/src/test/kotlin/BankIntegrationApiTest.kt
index d2205f21..73d6a700 100644
--- a/bank/src/test/kotlin/BankIntegrationApiTest.kt
+++ b/bank/src/test/kotlin/BankIntegrationApiTest.kt
@@ -53,7 +53,8 @@ class BankIntegrationApiTest {
                 assert(!it.aborted)
                 assert(!it.transfer_done)
                 assertEquals(amount, it.amount)
-                // TODO check all status
+                assertEquals(listOf("iban"), it.wire_types)
+                assertEquals(amount, it.amount)
             }
         }
 
@@ -75,7 +76,7 @@ class BankIntegrationApiTest {
         val reserve_pub = randEddsaPublicKey()
         val req = obj {
             "reserve_pub" to reserve_pub
-            "selected_exchange" to exchangePayto
+            "selected_exchange" to exchangePayto.canonical
         }
 
         // Check bad UUID
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt 
b/bank/src/test/kotlin/CoreBankApiTest.kt
index 4eaddcda..1e087a05 100644
--- a/bank/src/test/kotlin/CoreBankApiTest.kt
+++ b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -187,26 +187,26 @@ class CoreBankAccountsApiTest {
             // Check payto conflict
             client.post("/accounts") {
                 json(req) {
-                    "payto_uri" to genIbanPaytoUri()
+                    "payto_uri" to IbanPayto.rand()
                 }
             }.assertConflict(TalerErrorCode.BANK_REGISTER_USERNAME_REUSE)
         }
 
         // Check given payto
-        val IbanPayto = IbanPayto(genIbanPaytoUri())
+        val payto = IbanPayto.rand()
         val req = obj {
             "username" to "foo"
             "password" to "password"
             "name" to "Jane"
             "is_public" to true
-            "payto_uri" to IbanPayto
+            "payto_uri" to payto
             "is_taler_exchange" to true
         }
         // Check Ok
         client.post("/accounts") {
             json(req)
         }.assertOkJson<RegisterAccountResponse> {
-            assertEquals(IbanPayto.withName("Jane").full, 
it.internal_payto_uri.full)
+            assertEquals(payto.full("Jane"), it.internal_payto_uri)
         }
         // Testing idempotency
         client.post("/accounts") {
@@ -303,13 +303,13 @@ class CoreBankAccountsApiTest {
                 "username" to "cashout_guess"
                 "password" to "cashout_guess-password"
                 "name" to "Mr Guess My Name"
-                "cashout_payto_uri" to IbanPayto
+                "cashout_payto_uri" to payto
             }
         }.assertOk()
         client.getA("/accounts/cashout_guess").assertOkJson<AccountData> {
-            assertEquals(IbanPayto.full("Mr Guess My Name").full, 
it.cashout_payto_uri)
+            assertEquals(payto.full("Mr Guess My Name"), it.cashout_payto_uri)
         }
-        val full = IbanPayto.full("Santa Claus").full
+        val full = payto.full("Santa Claus")
         client.post("/accounts") {
             json {
                 "username" to "cashout_keep"
@@ -486,7 +486,7 @@ class CoreBankAccountsApiTest {
         }
 
         // Successful attempt now
-        val cashout = IbanPayto(genIbanPaytoUri())
+        val cashout = IbanPayto.rand()
         val req = obj {
             "cashout_payto_uri" to cashout
             "name" to "Roger"
@@ -519,7 +519,7 @@ class CoreBankAccountsApiTest {
         // Check patch
         client.getA("/accounts/merchant").assertOkJson<AccountData> { obj ->
             assertEquals("Roger", obj.name)
-            assertEquals(cashout.full(obj.name).full, obj.cashout_payto_uri)
+            assertEquals(cashout.full(obj.name), obj.cashout_payto_uri)
             assertEquals("+99", obj.contact_data?.phone?.get())
             assertEquals("foo@example.com", obj.contact_data?.email?.get())
             assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold)
@@ -533,7 +533,7 @@ class CoreBankAccountsApiTest {
         }.assertNoContent()
         client.getA("/accounts/merchant").assertOkJson<AccountData> { obj ->
             assertEquals("Roger", obj.name)
-            assertEquals(cashout.full(obj.name).full, obj.cashout_payto_uri)
+            assertEquals(cashout.full(obj.name), obj.cashout_payto_uri)
             assertEquals("+99", obj.contact_data?.phone?.get())
             assertEquals("foo@example.com", obj.contact_data?.email?.get())
             assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold)
@@ -556,11 +556,12 @@ class CoreBankAccountsApiTest {
                 "name" to "Mr Cashout Cashout"
             }
         }.assertOk()
+        val canonical = Payto.parse(cashout.canonical).expectIban()
         for ((cashout, name, expect) in listOf(
-            Triple(cashout.canonical, null, cashout.full("Mr Cashout 
Cashout").full),
-            Triple(cashout.canonical, "New name", cashout.full("New 
name").full),
-            Triple(cashout.full("Full name").full, null, cashout.full("Full 
name").full),
-            Triple(cashout.full("Full second name").full, "Another name", 
cashout.full("Full second name").full)
+            Triple(cashout.canonical, null, canonical.full("Mr Cashout 
Cashout")),
+            Triple(cashout.canonical, "New name", canonical.full("New name")),
+            Triple(cashout.full("Full name"), null, cashout.full("Full name")),
+            Triple(cashout.full("Full second name"), "Another name", 
cashout.full("Full second name"))
         )) {
             client.patch("/accounts/cashout") {
                 pwAuth("admin")
@@ -597,7 +598,7 @@ class CoreBankAccountsApiTest {
             TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME
         )
         checkAdminOnly(
-            obj { "cashout_payto_uri" to genIbanPaytoUri() },
+            obj { "cashout_payto_uri" to IbanPayto.rand() },
             TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT
         )
         // Check idempotent
@@ -902,10 +903,11 @@ class CoreBankTransactionsApiTest {
             }
         }.assertConflict(TalerErrorCode.BANK_SAME_ACCOUNT)
         // Transaction to admin
-        val adminPayto = db.account.bankInfo("admin")!!.payto
+        val adminPayto = client.getA("/accounts/admin")
+            .assertOkJson<AccountData>().payto_uri
         client.postA("/accounts/merchant/transactions") {
             json(valid_req) {
-                "payto_uri" to "${adminPayto.payto}?message=payout"
+                "payto_uri" to "$adminPayto&message=payout"
             }
         }.assertConflict(TalerErrorCode.BANK_ADMIN_CREDITOR)
 
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 01cb0dd2..877e6042 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -168,7 +168,4 @@ class DatabaseTest {
             assertEquals(Triple(true, false, false), cTry(this, "new-code", 
expired))
         }
     }}
-}
-
-
-
+}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/PaytoTest.kt 
b/bank/src/test/kotlin/PaytoTest.kt
new file mode 100644
index 00000000..635d864f
--- /dev/null
+++ b/bank/src/test/kotlin/PaytoTest.kt
@@ -0,0 +1,75 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+import io.ktor.client.plugins.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.server.testing.*
+import java.util.*
+import kotlinx.coroutines.*
+import kotlinx.serialization.json.*
+import org.junit.Test
+import tech.libeufin.bank.*
+import tech.libeufin.common.*
+import kotlin.test.*
+
+class PaytoTest {
+    // x-taler-bank
+    @Test
+    fun xTalerBank() = bankSetup("test_x_taler_bank.conf") { _ ->
+        // Check Ok
+        client.post("/accounts") {
+            json {
+                "username" to "john"
+                "password" to "john-password"
+                "name" to "John"
+            }
+        }.assertOkJson<RegisterAccountResponse> {
+            
assertEquals("payto://x-taler-bank/bank.hostname.test/john?receiver-name=John", 
it.internal_payto_uri)
+        }
+
+        // Check payto_uri is ignored
+        client.post("/accounts") {
+            json {
+                "username" to "foo"
+                "password" to "foo-password"
+                "name" to "Jane"
+                "payto_uri" to IbanPayto.rand()
+            }
+        }.assertOkJson<RegisterAccountResponse> {
+            
assertEquals("payto://x-taler-bank/bank.hostname.test/foo?receiver-name=Jane", 
it.internal_payto_uri)
+        }
+
+        // Check payto canonicalisation 
+        client.postA("/accounts/john/transactions") {
+            json {
+                "payto_uri" to 
"payto://x-taler-bank/ignored/foo?message=payout&amount=KUDOS:0.3"
+            }
+        }.assertOkJson<TransactionCreateResponse> {
+            client.getA("/accounts/john/transactions/${it.row_id}")
+                .assertOkJson<BankAccountTransactionInfo> { tx ->
+                assertEquals("payout", tx.subject)
+                
assertEquals("payto://x-taler-bank/bank.hostname.test/foo?receiver-name=Jane", 
tx.creditor_payto_uri)
+                
assertEquals("payto://x-taler-bank/bank.hostname.test/john?receiver-name=John", 
tx.debtor_payto_uri)
+                assertEquals(TalerAmount("KUDOS:0.3"), tx.amount)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt 
b/bank/src/test/kotlin/WireGatewayApiTest.kt
index ed8bdbf9..ec20ec15 100644
--- a/bank/src/test/kotlin/WireGatewayApiTest.kt
+++ b/bank/src/test/kotlin/WireGatewayApiTest.kt
@@ -46,7 +46,7 @@ class WireGatewayApiTest {
             "amount" to "KUDOS:55"
             "exchange_base_url" to "http://exchange.example.com/";
             "wtid" to randShortHashCode()
-            "credit_account" to merchantPayto
+            "credit_account" to merchantPayto.canonical
         };
 
         authRoutine(HttpMethod.Post, 
"/accounts/merchant/taler-wire-gateway/transfer", valid_req)
@@ -207,7 +207,7 @@ class WireGatewayApiTest {
         val valid_req = obj {
             "amount" to "KUDOS:44"
             "reserve_pub" to randEddsaPublicKey()
-            "debit_account" to merchantPayto
+            "debit_account" to merchantPayto.canonical
         };
 
         authRoutine(HttpMethod.Post, 
"/accounts/merchant/taler-wire-gateway/admin/add-incoming", valid_req, 
requireAdmin = true)
diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt
index fa8b63a4..3175c74c 100644
--- a/bank/src/test/kotlin/helpers.kt
+++ b/bank/src/test/kotlin/helpers.kt
@@ -37,11 +37,11 @@ import tech.libeufin.common.*
 
 /* ----- Setup ----- */
 
-val merchantPayto = IbanPayto(genIbanPaytoUri())
-val exchangePayto = IbanPayto(genIbanPaytoUri())
-val customerPayto = IbanPayto(genIbanPaytoUri())
-val unknownPayto  = IbanPayto(genIbanPaytoUri())
-var tmpPayTo      = IbanPayto(genIbanPaytoUri())
+val merchantPayto = IbanPayto.rand()
+val exchangePayto = IbanPayto.rand()
+val customerPayto = IbanPayto.rand()
+val unknownPayto  = IbanPayto.rand()
+var tmpPayTo      = IbanPayto.rand()
 val paytos = mapOf(
     "merchant" to merchantPayto, 
     "exchange" to exchangePayto, 
@@ -49,7 +49,7 @@ val paytos = mapOf(
 )
 
 fun genTmpPayTo(): IbanPayto {
-    tmpPayTo = IbanPayto(genIbanPaytoUri())
+    tmpPayTo = IbanPayto.rand()
     return tmpPayTo
 }
 
@@ -86,7 +86,7 @@ fun bankSetup(
             login = "merchant",
             password = "merchant-password",
             name = "Merchant",
-            internalPaytoUri = merchantPayto,
+            internalPayto = merchantPayto,
             maxDebt = TalerAmount("KUDOS:10"),
             isTalerExchange = false,
             isPublic = false,
@@ -101,7 +101,7 @@ fun bankSetup(
             login = "exchange",
             password = "exchange-password",
             name = "Exchange",
-            internalPaytoUri = exchangePayto,
+            internalPayto = exchangePayto,
             maxDebt = TalerAmount("KUDOS:10"),
             isTalerExchange = true,
             isPublic = false,
@@ -116,7 +116,7 @@ fun bankSetup(
             login = "customer",
             password = "customer-password",
             name = "Customer",
-            internalPaytoUri = customerPayto,
+            internalPayto = customerPayto,
             maxDebt = TalerAmount("KUDOS:10"),
             isTalerExchange = false,
             isPublic = false,
diff --git a/common/src/main/kotlin/DB.kt b/common/src/main/kotlin/DB.kt
index 1b9f5c0a..2c298d69 100644
--- a/common/src/main/kotlin/DB.kt
+++ b/common/src/main/kotlin/DB.kt
@@ -339,9 +339,6 @@ fun ResultSet.getAmount(name: String, currency: String): 
TalerAmount{
     )
 }
 
-fun ResultSet.getFullPayto(payto: String, name: String): FullIbanPayto{
-    return FullIbanPayto(
-        IbanPayto(getString(payto)),
-        getString(name),
-    )
+fun ResultSet.getBankPayto(payto: String, name: String, ctx: BankPaytoCtx): 
String {
+    return Payto.parse(getString(payto)).bank(getString(name), ctx) 
 }
\ No newline at end of file
diff --git a/common/src/main/kotlin/TalerCommon.kt 
b/common/src/main/kotlin/TalerCommon.kt
index d7a923ea..b96e87a3 100644
--- a/common/src/main/kotlin/TalerCommon.kt
+++ b/common/src/main/kotlin/TalerCommon.kt
@@ -29,7 +29,7 @@ import java.net.URI
 sealed class CommonError(msg: String): Exception(msg) {
     class AmountFormat(msg: String): CommonError(msg)
     class AmountNumberTooBig(msg: String): CommonError(msg)
-    class IbanPayto(msg: String): CommonError(msg)
+    class Payto(msg: String): CommonError(msg)
 }
 
 @Serializable(with = TalerAmount.Serializer::class)
@@ -121,105 +121,176 @@ value class IBAN private constructor(val value: String) 
{
             }
             val str = builder.toString()
             val mod = str.toBigInteger().mod(97.toBigInteger()).toInt();
-            if (mod != 1) throw CommonError.IbanPayto("Iban malformed, modulo 
is $mod expected 1")
+            if (mod != 1) throw CommonError.Payto("Iban malformed, modulo is 
$mod expected 1")
             return IBAN(iban)
         }
+
+        fun rand(): IBAN {
+            val ccNoCheck = "131400" // DE00
+            val bban = (0..10).map {
+                (0..9).random()
+            }.joinToString("") // 10 digits account number
+            var checkDigits: String = 
"98".toBigInteger().minus("$bban$ccNoCheck".toBigInteger().mod("97".toBigInteger())).toString()
+            if (checkDigits.length == 1) {
+                checkDigits = "0${checkDigits}"
+            }
+            return IBAN("DE$checkDigits$bban")
+        }
     }
 }
 
-
-sealed class PaytoUri {
+@Serializable(with = Payto.Serializer::class)
+sealed class Payto {
+    abstract val parsed: URI
+    abstract val canonical: String
     abstract val amount: TalerAmount?
     abstract val message: String?
     abstract val receiverName: String?
-}
 
-// TODO x-taler-bank Payto
+    /** Transform a payto URI to its bank form, using [name] as the 
receiver-name and the bank [ctx] */
+    fun bank(name: String, ctx: BankPaytoCtx): String = when (this) {
+        is IbanPayto -> 
"payto://iban/${ctx.bic!!}/$iban?receiver-name=${name.encodeURLParameter()}"
+        is XTalerBankPayto -> 
"payto://x-taler-bank/${ctx.hostname!!}/$username?receiver-name=${name.encodeURLParameter()}"
+    }
 
-@Serializable(with = IbanPayto.Serializer::class)
-class IbanPayto: PaytoUri {
-    val parsed: URI
-    val canonical: String
-    val iban: IBAN
-    override val amount: TalerAmount?
-    override val message: String?
-    override val receiverName: String?
+    fun expectIban(): IbanPayto {
+        return when (this) {
+            is IbanPayto -> this
+            else -> throw CommonError.Payto("expected an IBAN payto URI got 
'${parsed.host}'")
+        }
+    }
 
-    // TODO maybe add a fster builder that performs less expensive checks when 
the payto is from the database ?
+    fun expectXTalerBank(): XTalerBankPayto {
+        return when (this) {
+            is XTalerBankPayto -> this
+            else -> throw CommonError.Payto("expected a x-taler-bank payto URI 
got '${parsed.host}'")
+        }
+    }
+
+    internal object Serializer : KSerializer<Payto> {
+        override val descriptor: SerialDescriptor =
+            PrimitiveSerialDescriptor("Payto", PrimitiveKind.STRING)
 
-    constructor(raw: String) {
-        try {
-            parsed = URI(raw)
-        } catch (e: Exception) {
-            throw CommonError.IbanPayto("expecteda valid URI")
+        override fun serialize(encoder: Encoder, value: Payto) {
+            encoder.encodeString(value.toString())
         }
-        
-        if (parsed.scheme != "payto") throw CommonError.IbanPayto("expect a 
payto URI")
-        if (parsed.host != "iban") throw CommonError.IbanPayto("expect a IBAN 
payto URI")
-
-        val splitPath = parsed.path.split("/").filter { it.isNotEmpty() }
-        val rawIban = when (splitPath.size) {
-            1 -> splitPath[0]
-            2 -> splitPath[1]
-            else -> throw CommonError.IbanPayto("too many path segments")
+
+        override fun deserialize(decoder: Decoder): Payto {
+            return Payto.parse(decoder.decodeString())
         }
-        iban = IBAN.parse(rawIban)
-        canonical = "payto://iban/$iban"
-    
-        val params = (parsed.query ?: "").parseUrlEncodedParameters();
-        amount = params["amount"]?.run { TalerAmount(this) }
-        message = params["message"]
-        receiverName = params["receiver-name"]
     }
 
-    /** Full IBAN payto with receiver-name parameter set to [name] */
-    fun withName(name: String): FullIbanPayto = FullIbanPayto(this, name)
-
-    /** Full IBAN payto with receiver-name parameter if present */
-    fun maybeFull(): FullIbanPayto? {
-        return withName(receiverName ?: return null)
+    companion object {
+        fun parse(raw: String): Payto {
+            val parsed = try {
+                URI(raw)
+            } catch (e: Exception) {
+                throw CommonError.Payto("expected a valid URI")
+            }
+            if (parsed.scheme != "payto") throw CommonError.Payto("expect a 
payto URI got '${parsed.scheme}'")
+
+            val params = (parsed.query ?: "").parseUrlEncodedParameters();
+            val amount = params["amount"]?.run { TalerAmount(this) }
+            val message = params["message"]
+            val receiverName = params["receiver-name"]
+
+            return when (parsed.host) {
+                "iban" -> {
+                    val splitPath = parsed.path.split("/", limit=3).filter { 
it.isNotEmpty() }
+                    val (bic, rawIban) = when (splitPath.size) {
+                        1 -> Pair(null, splitPath[0])
+                        2 -> Pair(splitPath[0], splitPath[1])
+                        else -> throw CommonError.Payto("too many path 
segments for an IBAN payto URI")
+                    }
+                    val iban = IBAN.parse(rawIban)
+                    IbanPayto(
+                        parsed, 
+                        "payto://iban/$iban",
+                        amount, 
+                        message,
+                        receiverName,
+                        bic,
+                        iban
+                    )
+                }
+                "x-taler-bank" -> {
+                    val splitPath = parsed.path.split("/", limit=3).filter { 
it.isNotEmpty() }
+                    if (splitPath.size != 2)
+                        throw CommonError.Payto("bad number of path segments 
for a x-taler-bank payto URI")
+                    val username = splitPath[1]
+                    XTalerBankPayto(
+                        parsed, 
+                        "payto://x-taler-bank/localhost/$username",
+                        amount, 
+                        message,
+                        receiverName,
+                        username
+                    )
+                }
+                else -> throw CommonError.Payto("unsupported payto URI kind 
'${parsed.host}'")
+            }
+        }
     }
+}
 
-    /** Full IBAN payto with receiver-name parameter set to [defaultName] if 
absent */
-    fun full(defaultName: String): FullIbanPayto = withName(receiverName ?: 
defaultName)
+@Serializable(with = IbanPayto.Serializer::class)
+class IbanPayto internal constructor(
+    override val parsed: URI,
+    override val canonical: String,
+    override val amount: TalerAmount?,
+    override val message: String?,
+    override val receiverName: String?,
+    val bic: String?,
+    val iban: IBAN
+): Payto() {
 
-    /** Full IBAN payto with receiver-name parameter if present, fail if 
absent */
-    fun requireFull(): FullIbanPayto {
-        return maybeFull() ?: throw Exception("Missing receiver-name")
-    }    
+    override fun toString(): String = parsed.toString()
 
-    override fun toString(): String = canonical
+    /** Transform an IBAN payto URI to its full form, using [defaultName] if 
receiver-name is missing */
+    fun full(defaultName: String): String {
+        val bic = if (this.bic != null) "$bic/" else ""
+        return "payto://iban/$bic$iban?receiver-name=${(receiverName ?: 
defaultName).encodeURLParameter()}"
+    }
 
     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())
+            encoder.encodeString(value.toString())
         }
 
         override fun deserialize(decoder: Decoder): IbanPayto {
-            return IbanPayto(decoder.decodeString())
+            return Payto.parse(decoder.decodeString()).expectIban()
         }
     }
-}
-
-@Serializable(with = FullIbanPayto.Serializer::class)
-class FullIbanPayto(val payto: IbanPayto, val receiverName: String) {
-    val full = payto.canonical + "?receiver-name=" + 
receiverName.encodeURLParameter()
-
-    override fun toString(): String = full
 
-    internal object Serializer : KSerializer<FullIbanPayto> {
-        override val descriptor: SerialDescriptor =
-            PrimitiveSerialDescriptor("IbanPayto", PrimitiveKind.STRING)
-
-        override fun serialize(encoder: Encoder, value: FullIbanPayto) {
-            encoder.encodeString(value.full)
+    companion object {
+        fun rand(): IbanPayto {
+            return 
Payto.parse("payto://iban/SANDBOXX/${IBAN.rand()}").expectIban()
         }
+    }
+}
+
+class XTalerBankPayto internal constructor(
+    override val parsed: URI,
+    override val canonical: String,
+    override val amount: TalerAmount?,
+    override val message: String?,
+    override val receiverName: String?,
+    val username: String
+): Payto() {
+    override fun toString(): String = parsed.toString()
 
-        override fun deserialize(decoder: Decoder): FullIbanPayto {
-            return IbanPayto(decoder.decodeString()).requireFull()
+    companion object {
+        fun forUsername(username: String): XTalerBankPayto {
+            return 
Payto.parse("payto://x-taler-bank/hostname/$username").expectXTalerBank()
         }
     }
-}
\ No newline at end of file
+}
+
+/** Context specific data nescessary to create a bank payto URI from a 
canonical payto URI */
+data class BankPaytoCtx(
+    val bic: String? = null,
+    val hostname: String? = null
+)
\ No newline at end of file
diff --git a/common/src/main/kotlin/iban.kt b/common/src/main/kotlin/iban.kt
index 499ad8e7..328e7f82 100644
--- a/common/src/main/kotlin/iban.kt
+++ b/common/src/main/kotlin/iban.kt
@@ -19,18 +19,6 @@
 
 package tech.libeufin.common
 
-fun getIban(): String {
-    val ccNoCheck = "131400" // DE00
-    val bban = (0..10).map {
-        (0..9).random()
-    }.joinToString("") // 10 digits account number
-    var checkDigits: String = 
"98".toBigInteger().minus("$bban$ccNoCheck".toBigInteger().mod("97".toBigInteger())).toString()
-    if (checkDigits.length == 1) {
-        checkDigits = "0${checkDigits}"
-    }
-    return "DE$checkDigits$bban"
-}
-
 // Taken from the ISO20022 XSD schema
 private val bicRegex = Regex("^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$")
 
diff --git a/common/src/test/kotlin/PaytoTest.kt 
b/common/src/test/kotlin/PaytoTest.kt
index 110a4530..33635f4c 100644
--- a/common/src/test/kotlin/PaytoTest.kt
+++ b/common/src/test/kotlin/PaytoTest.kt
@@ -24,30 +24,34 @@ import kotlin.test.*
 class PaytoTest {
     @Test
     fun wrongCases() {
-        assertFailsWith<CommonError.IbanPayto> { 
IbanPayto("http://iban/BIC123/IBAN123?receiver-name=The%20Name";) }
-        assertFailsWith<CommonError.IbanPayto> { 
IbanPayto("payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house") }
-        assertFailsWith<CommonError.IbanPayto> { 
IbanPayto("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo") }
+        assertFailsWith<CommonError.Payto> { 
Payto.parse("http://iban/BIC123/IBAN123?receiver-name=The%20Name";) }
+        assertFailsWith<CommonError.Payto> { 
Payto.parse("payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house") 
}
+        assertFailsWith<CommonError.Payto> { 
Payto.parse("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo") }
     }
 
     @Test
     fun parsePaytoTest() {
-        val withBic = 
IbanPayto("payto://iban/BIC123/CH9300762011623852957?receiver-name=The%20Name")
+        val withBic = 
Payto.parse("payto://iban/BIC123/CH9300762011623852957?receiver-name=The%20Name").expectIban()
         assertEquals(withBic.iban.value, "CH9300762011623852957")
         assertEquals(withBic.receiverName, "The Name")
-        val complete = 
IbanPayto("payto://iban/BIC123/CH9300762011623852957?sender-name=The%20Name&amount=EUR:1&message=donation")
+        val complete = 
Payto.parse("payto://iban/BIC123/CH9300762011623852957?sender-name=The%20Name&amount=EUR:1&message=donation").expectIban()
         assertEquals(withBic.iban.value, "CH9300762011623852957")
         assertEquals(withBic.receiverName, "The Name")
         assertEquals(complete.message, "donation")
         assertEquals(complete.amount.toString(), "EUR:1")
-        val withoutOptionals = IbanPayto("payto://iban/CH9300762011623852957")
+        val withoutOptionals = 
Payto.parse("payto://iban/CH9300762011623852957").expectIban()
         assertNull(withoutOptionals.message)
         assertNull(withoutOptionals.receiverName)
         assertNull(withoutOptionals.amount)
     }
 
     @Test
-    fun normalization() {
+    fun forms() {
+        val ctx = BankPaytoCtx(
+            bic = "TESTBIC"
+        )
         val canonical = "payto://iban/CH9300762011623852957"
+        val bank = 
"payto://iban/TESTBIC/CH9300762011623852957?receiver-name=Name"
         val inputs = listOf(
             "payto://iban/BIC/CH9300762011623852957?receiver-name=NotGiven",
             "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
@@ -57,24 +61,16 @@ class PaytoTest {
             "NotGiven", "Grothoff Hans", null
         )
         val full = listOf(
-            "payto://iban/CH9300762011623852957?receiver-name=NotGiven",
+            "payto://iban/BIC/CH9300762011623852957?receiver-name=NotGiven",
             "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
-            canonical
+            "payto://iban/CH9300762011623852957?receiver-name=Santa%20Claus",
         )
         for ((i, input) in inputs.withIndex()) {
-            val payto = IbanPayto(input)
+            val payto = Payto.parse(input).expectIban()
             assertEquals(canonical, payto.canonical)
-            assertEquals(full[i], payto.maybeFull()?.full ?: payto.canonical)
+            assertEquals(bank, payto.bank("Name", ctx))
+            assertEquals(full[i], payto.full("Santa Claus"))
             assertEquals(names[i], payto.receiverName)
         }
-        
-        assertEquals(
-            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
-            
IbanPayto("payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans").full("Santa
 Claus").full
-        )
-        assertEquals(
-            "payto://iban/CH9300762011623852957?receiver-name=Santa%20Claus",
-            IbanPayto("payto://iban/CH9300762011623852957").full("Santa 
Claus").full
-        )
     }
 }
\ No newline at end of file
diff --git a/contrib/bank.conf b/contrib/bank.conf
index cb470a49..bb8cdbfb 100644
--- a/contrib/bank.conf
+++ b/contrib/bank.conf
@@ -3,6 +3,15 @@
 # Internal currency of the libeufin-bank
 CURRENCY = KUDOS
 
+# Supported payment method, this can either be iban or x-taler-bank
+PAYMENT_METHOD = iban
+
+# Bank BIC used in generated iban payto URI
+IBAN_PAYTO_BIC = SANDBOXX
+
+# Bank hostname used in generated x-taler-bank payto URI
+# X_TALER_BANK_PAYTO_HOSTNAME=bank.$FOO.taler.net
+
 # Default debt limit for newly created accounts Default is CURRENCY:0
 # DEFAULT_DEBT_LIMIT = KUDOS:200
 
diff --git a/ebics/src/main/kotlin/Ebics.kt b/ebics/src/main/kotlin/Ebics.kt
index 558ceaee..406da44c 100644
--- a/ebics/src/main/kotlin/Ebics.kt
+++ b/ebics/src/main/kotlin/Ebics.kt
@@ -57,14 +57,14 @@ data class EbicsDateRange(
     val end: Instant
 )
 
-sealed class EbicsOrderParams
+sealed interface EbicsOrderParams
 data class EbicsStandardOrderParams(
     val dateRange: EbicsDateRange? = null
-) : EbicsOrderParams()
+) : EbicsOrderParams
 
 data class EbicsGenericOrderParams(
     val params: Map<String, String> = mapOf()
-) : EbicsOrderParams()
+) : EbicsOrderParams
 
 enum class EbicsInitState {
     SENT, NOT_SENT, UNKNOWN
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
index 82d493e8..2883fec1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -86,9 +86,10 @@ enum class DatabaseSubmissionState {
  * the database.
  */
 data class InitiatedPayment(
+    val id: Long,
     val amount: TalerAmount,
     val wireTransferSubject: String,
-    val creditPaytoUri: FullIbanPayto,
+    val creditPaytoUri: String,
     val initiationTime: Instant,
     val requestUid: String
 )
@@ -426,7 +427,7 @@ class Database(dbConfig: String): DbPool(dbConfig, 
"libeufin_nexus") {
      * @param currency in which currency should the payment be submitted to 
the bank.
      * @return [Map] of the initiated payment row ID and [InitiatedPayment]
      */
-    suspend fun initiatedPaymentsSubmittableGet(currency: String): Map<Long, 
InitiatedPayment> = conn { conn ->
+    suspend fun initiatedPaymentsSubmittableGet(currency: String): 
List<InitiatedPayment> = conn { conn ->
         val stmt = conn.prepareStatement("""
             SELECT
               initiated_outgoing_transaction_id
@@ -440,25 +441,21 @@ class Database(dbConfig: String): DbPool(dbConfig, 
"libeufin_nexus") {
              WHERE (submitted='unsubmitted' OR submitted='transient_failure')
                AND ((amount).val != 0 OR (amount).frac != 0);
         """)
-        val maybeMap = mutableMapOf<Long, InitiatedPayment>()
-        stmt.executeQuery().use {
-            if (!it.next()) return@use
-            do {
-                val rowId = it.getLong("initiated_outgoing_transaction_id")
-                val initiationTime = 
it.getLong("initiation_time").microsToJavaInstant()
-                if (initiationTime == null) { // nexus fault
-                    throw Exception("Found invalid timestamp at initiated 
payment with ID: $rowId")
-                }
-                maybeMap[rowId] = InitiatedPayment(
-                    amount = it.getAmount("amount", currency),
-                    creditPaytoUri = 
IbanPayto(it.getString("credit_payto_uri")).requireFull(),
-                    wireTransferSubject = 
it.getString("wire_transfer_subject"),
-                    initiationTime = initiationTime,
-                    requestUid = it.getString("request_uid")
-                )
-            } while (it.next())
+        stmt.all {
+            val rowId = it.getLong("initiated_outgoing_transaction_id")
+            val initiationTime = 
it.getLong("initiation_time").microsToJavaInstant()
+            if (initiationTime == null) { // nexus fault
+                throw Exception("Found invalid timestamp at initiated payment 
with ID: $rowId")
+            }
+            InitiatedPayment(
+                id = it.getLong("initiated_outgoing_transaction_id"),
+                amount = it.getAmount("amount", currency),
+                creditPaytoUri = it.getString("credit_payto_uri"),
+                wireTransferSubject = it.getString("wire_transfer_subject"),
+                initiationTime = initiationTime,
+                requestUid = it.getString("request_uid")
+            )
         }
-        return@conn maybeMap
     }
     /**
      * Initiate a payment in the database.  The "submit"
@@ -487,7 +484,7 @@ class Database(dbConfig: String): DbPool(dbConfig, 
"libeufin_nexus") {
         stmt.setLong(1, paymentData.amount.value)
         stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
-        stmt.setString(4, paymentData.creditPaytoUri.full)
+        stmt.setString(4, paymentData.creditPaytoUri.toString())
         val initiationTime = paymentData.initiationTime.toDbMicros() ?: run {
             throw Exception("Initiation time could not be converted to 
microseconds for the database.")
         }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
index ca5e98fe..ab236430 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -90,15 +90,27 @@ class NexusSubmitException(
  */
 private suspend fun submitInitiatedPayment(
     ctx: SubmissionContext,
-    initiatedPayment: InitiatedPayment
+    payment: InitiatedPayment
 ) { 
+    val creditAccount = try {
+        val payto = Payto.parse(payment.creditPaytoUri).expectIban()
+        IbanAccountMetadata(
+            iban = payto.iban.value,
+            bic = payto.bic,
+            name = payto.receiverName!!
+        )
+    } catch (e: Exception) {
+        throw e // TODO handle payto error
+    }
+    
+    
     val xml = createPain001(
-        requestUid = initiatedPayment.requestUid,
-        initiationTimestamp = initiatedPayment.initiationTime,
-        amount = initiatedPayment.amount,
-        creditAccount = initiatedPayment.creditPaytoUri,
+        requestUid = payment.requestUid,
+        initiationTimestamp = payment.initiationTime,
+        amount = payment.amount,
+        creditAccount = creditAccount,
         debitAccount = ctx.cfg.myIbanAccount,
-        wireTransferSubject = initiatedPayment.wireTransferSubject
+        wireTransferSubject = payment.wireTransferSubject
     )
     ctx.fileLogger.logSubmit(xml)
     try {
@@ -152,9 +164,9 @@ private fun submitBatch(
     logger.debug("Running submit at: ${Instant.now()}")
     runBlocking {
         db.initiatedPaymentsSubmittableGet(ctx.cfg.currency).forEach {
-            logger.debug("Submitting payment initiation with row ID: 
${it.key}")
+            logger.debug("Submitting payment initiation with row ID: ${it.id}")
             val submissionState = try {
-                submitInitiatedPayment(ctx, initiatedPayment = it.value)
+                submitInitiatedPayment(ctx, it)
                 DatabaseSubmissionState.success
             } catch (e: NexusSubmitException) {
                 logger.error(e.message)
@@ -179,7 +191,7 @@ private fun submitBatch(
                     NexusSubmissionStage.ebics -> 
DatabaseSubmissionState.permanent_failure
                 }
             }
-            db.initiatedPaymentSetSubmittedState(it.key, submissionState)
+            db.initiatedPaymentSetSubmittedState(it.id, submissionState)
         }
     }
 }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
index cbfbd50b..97ba52f6 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -75,7 +75,7 @@ fun createPain001(
     debitAccount: IbanAccountMetadata,
     amount: TalerAmount,
     wireTransferSubject: String,
-    creditAccount: FullIbanPayto
+    creditAccount: IbanAccountMetadata
 ): String {
     val namespace = Pain001Namespaces(
         fullNamespace = "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09",
@@ -102,7 +102,8 @@ fun createPain001(
                 el("ReqdExctnDt/Dt", 
DateTimeFormatter.ISO_DATE.format(zonedTimestamp))
                 el("Dbtr/Nm", debitAccount.name)
                 el("DbtrAcct/Id/IBAN", debitAccount.iban)
-                el("DbtrAgt/FinInstnId/BICFI", debitAccount.bic)
+                if (debitAccount.bic != null) 
+                    el("DbtrAgt/FinInstnId/BICFI", debitAccount.bic)
                 el("CdtTrfTxInf") {
                     el("PmtId") {
                         el("InstrId", "NOTPROVIDED")
@@ -112,8 +113,9 @@ fun createPain001(
                         attr("Ccy", amount.currency)
                         text(amountWithoutCurrency)
                     }
-                    el("Cdtr/Nm", creditAccount.receiverName)
-                    el("CdtrAcct/Id/IBAN", creditAccount.payto.iban.value)
+                    el("Cdtr/Nm", creditAccount.name)
+                    // TODO write credit account bic if we have it
+                    el("CdtrAcct/Id/IBAN", creditAccount.iban)
                     el("RmtInf/Ustrd", wireTransferSubject)
                 }
             }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index a5f8dccf..7c4aae70 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -43,7 +43,7 @@ val logger: Logger = LoggerFactory.getLogger("libeufin-nexus")
  */
 data class IbanAccountMetadata(
     val iban: String,
-    val bic: String,
+    val bic: String?,
     val name: String
 )
 
diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt
index 422450fc..2c4b7750 100644
--- a/nexus/src/test/kotlin/Common.kt
+++ b/nexus/src/test/kotlin/Common.kt
@@ -100,8 +100,9 @@ fun genInitPay(
     requestUid: String = "unique"
 ) =
     InitiatedPayment(
+        id = -1,
         amount = TalerAmount(44, 0, "KUDOS"),
-        creditPaytoUri = 
IbanPayto("payto://iban/CH9300762011623852957?receiver-name=Test").requireFull(),
+        creditPaytoUri = 
"payto://iban/CH9300762011623852957?receiver-name=Test",
         wireTransferSubject = subject,
         initiationTime = Instant.now(),
         requestUid = requestUid
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt 
b/nexus/src/test/kotlin/DatabaseTest.kt
index 6e2443e7..187ac0f1 100644
--- a/nexus/src/test/kotlin/DatabaseTest.kt
+++ b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -205,8 +205,9 @@ class PaymentInitiationsTest {
             assertEquals(beEmpty.size, 0)
         }
         val initPay = InitiatedPayment(
+            id = -1,
             amount = TalerAmount(44, 0, "KUDOS"),
-            creditPaytoUri = 
IbanPayto("payto://iban/CH9300762011623852957?receiver-name=Test").requireFull(),
+            creditPaytoUri = 
"payto://iban/CH9300762011623852957?receiver-name=Test",
             wireTransferSubject = "test",
             requestUid = "unique",
             initiationTime = Instant.now()
@@ -218,8 +219,8 @@ class PaymentInitiationsTest {
             val haveOne = db.initiatedPaymentsSubmittableGet("KUDOS")
             assertTrue("Size ${haveOne.size} instead of 1") {
                 haveOne.size == 1
-                        && haveOne.containsKey(1)
-                        && haveOne[1]?.requestUid == "unique"
+                        && haveOne.first().id == 1L
+                        && haveOne.first().requestUid == "unique"
             }
             assertTrue(db.initiatedPaymentSetSubmittedState(1, 
DatabaseSubmissionState.success))
             assertNotNull(db.initiatedPaymentGetFromUid("unique"))
@@ -286,10 +287,11 @@ class PaymentInitiationsTest {
 
             // Expecting all the payments BUT the #3 in the result.
             db.initiatedPaymentsSubmittableGet("KUDOS").apply {
+
                 assertEquals(3, this.size)
-                assertEquals("#1", this[1]?.wireTransferSubject)
-                assertEquals("#2", this[2]?.wireTransferSubject)
-                assertEquals("#4", this[4]?.wireTransferSubject)
+                assertEquals("#1", this[0].wireTransferSubject)
+                assertEquals("#2", this[1].wireTransferSubject)
+                assertEquals("#4", this[2].wireTransferSubject)
             }
         }
     }
diff --git a/testbench/src/main/kotlin/Main.kt 
b/testbench/src/main/kotlin/Main.kt
index 4b999072..f99b6312 100644
--- a/testbench/src/main/kotlin/Main.kt
+++ b/testbench/src/main/kotlin/Main.kt
@@ -159,8 +159,9 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                     put("tx", suspend {
                         step("Test submit one transaction")
                         nexusDb.initiatedPaymentCreate(InitiatedPayment(
+                            id = -1,
                             amount = TalerAmount("CFH:42"),
-                            creditPaytoUri = IbanPayto(payto).requireFull(),
+                            creditPaytoUri = payto,
                             wireTransferSubject = "single transaction test",
                             initiationTime = Instant.now(),
                             requestUid = Base32Crockford.encode(randBytes(16))
@@ -171,8 +172,9 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                         step("Test submit many transaction")
                         repeat(4) {
                             nexusDb.initiatedPaymentCreate(InitiatedPayment(
+                                id = -1,
                                 amount = TalerAmount("CFH:${100L+it}"),
-                                creditPaytoUri = 
IbanPayto(payto).requireFull(),
+                                creditPaytoUri = payto,
                                 wireTransferSubject = "multi transaction test 
$it",
                                 initiationTime = Instant.now(),
                                 requestUid = 
Base32Crockford.encode(randBytes(16))
@@ -185,8 +187,9 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                         step("Submit new transaction")
                         // TODO interactive payment editor
                         nexusDb.initiatedPaymentCreate(InitiatedPayment(
+                            id = -1,
                             amount = TalerAmount("CFH:1.1"),
-                            creditPaytoUri = IbanPayto(payto).requireFull(),
+                            creditPaytoUri = payto,
                             wireTransferSubject = "single transaction test",
                             initiationTime = Instant.now(),
                             requestUid = Base32Crockford.encode(randBytes(16))
diff --git a/testbench/src/test/kotlin/IntegrationTest.kt 
b/testbench/src/test/kotlin/IntegrationTest.kt
index a66df71c..7795acb7 100644
--- a/testbench/src/test/kotlin/IntegrationTest.kt
+++ b/testbench/src/test/kotlin/IntegrationTest.kt
@@ -138,8 +138,8 @@ class IntegrationTest {
         }
 
         setup { db ->
-            val userPayTo = IbanPayto(genIbanPaytoUri())
-            val fiatPayTo = IbanPayto(genIbanPaytoUri())
+            val userPayTo = IbanPayto.rand()
+            val fiatPayTo = IbanPayto.rand()
     
             // Load conversion setup manually as the server would refuse to 
start without an exchange account
             val sqlProcedures = 
Path("../database-versioning/libeufin-conversion-setup.sql")
@@ -151,7 +151,7 @@ class IntegrationTest {
             val reservePub = randBytes(32)
             val payment = IncomingPayment(
                 amount = TalerAmount("EUR:10"),
-                debitPaytoUri = userPayTo.canonical,
+                debitPaytoUri = userPayTo.toString(),
                 wireTransferSubject = "Error test 
${Base32Crockford.encode(reservePub)}",
                 executionTime = Instant.now(),
                 bankId = "error"
@@ -210,7 +210,7 @@ class IntegrationTest {
             // Check success
             ingestIncomingPayment(db, IncomingPayment(
                 amount = TalerAmount("EUR:10"),
-                debitPaytoUri = userPayTo.canonical,
+                debitPaytoUri = userPayTo.toString(),
                 wireTransferSubject = "Success 
${Base32Crockford.encode(randBytes(32))}",
                 executionTime = Instant.now(),
                 bankId = "success"
@@ -240,8 +240,8 @@ class IntegrationTest {
         }
         
         setup { db -> 
-            val userPayTo = IbanPayto(genIbanPaytoUri())
-            val fiatPayTo = IbanPayto(genIbanPaytoUri())
+            val userPayTo = IbanPayto.rand()
+            val fiatPayTo = IbanPayto.rand()
 
             // Create user
             client.post("http://0.0.0.0:8080/accounts";) {
@@ -284,7 +284,7 @@ class IntegrationTest {
                 ingestIncomingPayment(db, 
                     IncomingPayment(
                         amount = amount,
-                        debitPaytoUri = userPayTo.canonical,
+                        debitPaytoUri = userPayTo.toString(),
                         wireTransferSubject = subject,
                         executionTime = Instant.now(),
                         bankId = Base32Crockford.encode(reservePub)

-- 
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]