taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

Accounts.kt (9157B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2022 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet.accounts
     18 
     19 import android.net.Uri
     20 import kotlinx.serialization.ExperimentalSerializationApi
     21 import kotlinx.serialization.SerialName
     22 import kotlinx.serialization.Serializable
     23 import kotlinx.serialization.json.JsonClassDiscriminator
     24 import net.taler.common.Bech32
     25 import net.taler.wallet.backend.TalerErrorInfo
     26 import androidx.core.net.toUri
     27 
     28 @Serializable
     29 data class KnownBankAccountInfo(
     30     val bankAccountId: String,
     31     val paytoUri: String,
     32 
     33     /**
     34      * Did we previously complete a KYC process for this bank account?
     35      */
     36     val kycCompleted: Boolean,
     37 
     38     /**
     39      * Currencies supported by the bank, if known.
     40      */
     41     val currencies: List<String>? = null,
     42 
     43     val label: String? = null,
     44 )
     45 
     46 @Serializable
     47 sealed class ListBankAccountsResult {
     48     @Serializable
     49     data object None: ListBankAccountsResult()
     50 
     51     @Serializable
     52     data class Success(
     53         val accounts: List<KnownBankAccountInfo>,
     54         val currency: String?,
     55     ): ListBankAccountsResult()
     56 
     57     @Serializable
     58     data class Error(val error: TalerErrorInfo): ListBankAccountsResult()
     59 }
     60 
     61 @Serializable
     62 data class ListBankAccountsResponse(
     63     val accounts: List<KnownBankAccountInfo>,
     64 )
     65 
     66 @Serializable
     67 data class AddBankAccountResponse(
     68     val bankAccountId: String,
     69 )
     70 
     71 @Serializable
     72 @OptIn(ExperimentalSerializationApi::class)
     73 @JsonClassDiscriminator("targetType")
     74 sealed class PaytoUri(
     75     val isKnown: Boolean,
     76     val targetType: String,
     77 ) {
     78     abstract val targetPath: String
     79     abstract val params: Map<String, String>
     80     abstract val receiverName: String?
     81 
     82     companion object {
     83         fun parse(paytoUri: String): PaytoUri? {
     84             val uri = paytoUri.toUri()
     85             if (uri.scheme != "payto") return null
     86             if (uri.pathSegments.isEmpty()) return null
     87             return when (uri.authority?.lowercase()) {
     88                 "iban" -> PaytoUriIban.fromString(uri)
     89                 "x-taler-bank" -> PaytoUriTalerBank.fromString(uri)
     90                 "cyclos" -> PaytoUriCyclos.fromString(uri)
     91                 "bitcoin" -> PaytoUriBitcoin.fromString(uri)
     92                 else -> null
     93             }
     94         }
     95     }
     96 }
     97 
     98 @Serializable
     99 @SerialName("iban")
    100 data class PaytoUriIban(
    101     val iban: String,
    102     val bic: String? = "SANDBOXX",
    103     override val targetPath: String,
    104     override val params: Map<String, String>,
    105     override val receiverName: String?,
    106     val receiverPostalCode: String?,
    107     val receiverTown: String?,
    108 ) : PaytoUri(
    109     isKnown = true,
    110     targetType = "iban",
    111 ) {
    112     val paytoUri: String
    113         get() = Uri.Builder()
    114             .scheme("payto")
    115             .authority(targetType)
    116             .apply { if (bic != null) appendPath(bic) }
    117             .appendPath(iban)
    118             .apply {
    119                 receiverName?.let { appendQueryParameter("receiver-name", it) }
    120                 receiverPostalCode?.let { appendQueryParameter("receiver-postal-code", it) }
    121                 receiverTown?.let { appendQueryParameter("receiver-town", it) }
    122                 params.forEach { (key, value) ->
    123                     if (value.isNotEmpty() && build().getQueryParameter(key) == null) {
    124                         appendQueryParameter(key, value)
    125                     }
    126                 }
    127             }.build().toString()
    128 
    129     companion object {
    130         fun fromString(uri: Uri): PaytoUriIban? {
    131             return PaytoUriIban(
    132                 iban = uri.lastPathSegment ?: return null,
    133                 bic = if (uri.pathSegments.size > 1) {
    134                     uri.pathSegments.first() ?: return null
    135                 } else null,
    136                 params = uri.queryParametersMap,
    137                 receiverName = uri.getQueryParameter("receiver-name"),
    138                 receiverPostalCode = uri.getQueryParameter("receiver-postal-code"),
    139                 receiverTown = uri.getQueryParameter("receiver-town"),
    140                 targetPath = "",
    141             )
    142         }
    143     }
    144 }
    145 
    146 @Serializable
    147 @SerialName("x-taler-bank")
    148 data class PaytoUriTalerBank(
    149     val host: String,
    150     val account: String,
    151     override val targetPath: String,
    152     override val params: Map<String, String>,
    153     override val receiverName: String?,
    154 ) : PaytoUri(
    155     isKnown = true,
    156     targetType = "x-taler-bank",
    157 ) {
    158     val paytoUri: String
    159         get() = Uri.Builder()
    160             .scheme("payto")
    161             .authority(targetType)
    162             .appendPath(host)
    163             .appendPath(account)
    164             .apply {
    165                 receiverName?.let { appendQueryParameter("receiver-name", it) }
    166                 params.forEach { (key, value) ->
    167                     if (value.isNotEmpty()) {
    168                         appendQueryParameter(key, value)
    169                     }
    170                 }
    171             }
    172             .build().toString()
    173 
    174     companion object {
    175         fun fromString(uri: Uri): PaytoUriTalerBank? {
    176             return PaytoUriTalerBank(
    177                 host = uri.pathSegments.getOrNull(0) ?: return null,
    178                 account = uri.pathSegments.getOrNull(1) ?: return null,
    179                 params = uri.queryParametersMap,
    180                 receiverName = uri.getQueryParameter("receiver-name"),
    181                 targetPath = "",
    182             )
    183         }
    184     }
    185 }
    186 
    187 @Serializable
    188 @SerialName("cyclos")
    189 data class PaytoUriCyclos(
    190     val host: String,
    191     val account: String,
    192     override val targetPath: String,
    193     override val params: Map<String, String>,
    194     override val receiverName: String,
    195 ) : PaytoUri(
    196     isKnown = true,
    197     targetType = "cyclos",
    198 ) {
    199     val paytoUri: String
    200         get() = Uri.Builder()
    201             .scheme("payto")
    202             .authority(targetType)
    203             .appendPath(host)
    204             .appendPath(account)
    205             .apply {
    206                 appendQueryParameter("receiver-name", receiverName)
    207                 params.forEach { (key, value) ->
    208                     if (value.isNotEmpty()) {
    209                         appendQueryParameter(key, value)
    210                     }
    211                 }
    212             }
    213             .build().toString()
    214 
    215     companion object {
    216         fun fromString(uri: Uri): PaytoUriCyclos? {
    217             return PaytoUriCyclos(
    218                 account = uri.lastPathSegment ?: return null,
    219                 receiverName = uri.getQueryParameter("receiver-name") ?: return null,
    220                 host = uri.pathSegments
    221                     .subList(0, uri.pathSegments.lastIndex)
    222                     .joinToString("/"),
    223                 params = uri.queryParametersMap,
    224                 targetPath = "",
    225             )
    226         }
    227     }
    228 }
    229 
    230 @Serializable
    231 @SerialName("bitcoin")
    232 data class PaytoUriBitcoin(
    233     @SerialName("segwitAddrs")
    234     val segwitAddresses: List<String>,
    235     override val targetPath: String,
    236     override val params: Map<String, String> = emptyMap(),
    237     override val receiverName: String?,
    238 ) : PaytoUri(
    239     isKnown = true,
    240     targetType = "bitcoin",
    241 ) {
    242     val paytoUri: String
    243         get() = Uri.Builder()
    244             .scheme("payto")
    245             .authority(targetType)
    246             .apply {
    247                 segwitAddresses.forEach { address ->
    248                     appendPath(address)
    249                 }
    250             }
    251             .apply {
    252                 receiverName?.let { appendQueryParameter("receiver-name", it) }
    253                 params.forEach { (key, value) ->
    254                     if (value.isNotEmpty()) {
    255                         appendQueryParameter(key, value)
    256                     }
    257                 }
    258             }
    259             .build().toString()
    260 
    261     companion object {
    262         fun fromString(uri: Uri): PaytoUriBitcoin? {
    263             val msg = uri.getQueryParameter("message").orEmpty()
    264             val reg = "\\b([A-Z0-9]{52})\\b".toRegex().find(msg)
    265             val reserve = reg?.value
    266                 ?: uri.getQueryParameter("subject")
    267                 ?: return null
    268             val segwitAddresses = Bech32.generateFakeSegwitAddress(
    269                 reservePub = reserve,
    270                 addr = uri.pathSegments.firstOrNull()
    271                     ?: return null,
    272             )
    273 
    274             return PaytoUriBitcoin(
    275                 segwitAddresses = segwitAddresses,
    276                 params = uri.queryParametersMap,
    277                 receiverName = uri.getQueryParameter("receiver-name"),
    278                 targetPath = "",
    279             )
    280         }
    281     }
    282 }
    283 
    284 val Uri.queryParametersMap: Map<String, String>
    285     get() = queryParameterNames.mapNotNull { name ->
    286         getQueryParameter(name)?.let { name to it }
    287     }.toMap()