taler-android

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

PaymentResponses.kt (11675B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2020 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.payment
     18 
     19 import androidx.annotation.StringRes
     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.Amount
     25 import net.taler.common.ContractTerms
     26 import net.taler.wallet.R
     27 import net.taler.wallet.backend.TalerErrorInfo
     28 import net.taler.wallet.donau.DonauInfo
     29 import net.taler.wallet.payment.InsufficientBalanceHint.AgeRestricted
     30 import net.taler.wallet.payment.InsufficientBalanceHint.ExchangeMissingGlobalFees
     31 import net.taler.wallet.payment.InsufficientBalanceHint.FeesNotCovered
     32 import net.taler.wallet.payment.InsufficientBalanceHint.MerchantAcceptInsufficient
     33 import net.taler.wallet.payment.InsufficientBalanceHint.MerchantDepositInsufficient
     34 import net.taler.wallet.payment.InsufficientBalanceHint.Unknown
     35 import net.taler.wallet.payment.InsufficientBalanceHint.WalletBalanceAvailableInsufficient
     36 import net.taler.wallet.payment.InsufficientBalanceHint.WalletBalanceMaterialInsufficient
     37 
     38 @OptIn(ExperimentalSerializationApi::class)
     39 @Serializable
     40 @JsonClassDiscriminator("status")
     41 sealed class PreparePayResponse {
     42 
     43     @Serializable
     44     @SerialName("payment-possible")
     45     data class PaymentPossibleResponse(
     46         val transactionId: String,
     47         val contractTerms: ContractTerms,
     48     ) : PreparePayResponse() {
     49         fun toPayStatusPrepared() = PayStatus.Prepared(
     50             contractTerms = contractTerms,
     51             transactionId = transactionId,
     52         )
     53     }
     54 
     55     @Serializable
     56     @SerialName("insufficient-balance")
     57     data class InsufficientBalanceResponse(
     58         val transactionId: String,
     59         val amountRaw: Amount,
     60         val contractTerms: ContractTerms,
     61         val balanceDetails: PaymentInsufficientBalanceDetails,
     62     ) : PreparePayResponse()
     63 
     64     @Serializable
     65     @SerialName("already-confirmed")
     66     data class AlreadyConfirmedResponse(
     67         val transactionId: String,
     68         /**
     69          * Did the payment succeed?
     70          */
     71         val paid: Boolean,
     72         val amountRaw: Amount,
     73         val amountEffective: Amount? = null,
     74         val contractTerms: ContractTerms,
     75     ) : PreparePayResponse()
     76 
     77     @Serializable
     78     @SerialName("choice-selection")
     79     data class ChoiceSelection(
     80         val transactionId: String,
     81         val contractTerms: ContractTerms,
     82     ) : PreparePayResponse() {
     83         fun toPayStatusPrepared() = PayStatus.Prepared(
     84             contractTerms = contractTerms,
     85             transactionId = transactionId,
     86         )
     87     }
     88 }
     89 
     90 @Serializable
     91 data class PreparePayV2Response (
     92     val transactionId: String,
     93 )
     94 
     95 @Serializable
     96 data class GetChoicesForPaymentResponse(
     97     val choices: List<ChoiceSelectionDetail>,
     98     val contractTerms: ContractTerms,
     99     val defaultChoiceIndex: Int? = null,
    100     val automaticExecution: Boolean? = null,
    101     val automaticExecutableIndex: Int? = null,
    102 ) {
    103     @Serializable
    104     @OptIn(ExperimentalSerializationApi::class)
    105     @JsonClassDiscriminator("status")
    106     sealed class ChoiceSelectionDetail {
    107         abstract val amountRaw: Amount
    108         abstract val tokenDetails: PaymentTokenAvailabilityDetails?
    109         abstract val description: String?
    110         abstract val descriptionI18n: Map<String, String>?
    111 
    112         @Serializable
    113         @SerialName("payment-possible")
    114         data class PaymentPossible(
    115             override val amountRaw: Amount,
    116             val amountEffective: Amount,
    117             override val tokenDetails: PaymentTokenAvailabilityDetails? = null,
    118             override val description: String? = null,
    119             override val descriptionI18n: Map<String, String>? = null,
    120         ) : ChoiceSelectionDetail()
    121 
    122         @Serializable
    123         @SerialName("insufficient-balance")
    124         data class InsufficientBalance(
    125             override val amountRaw: Amount,
    126             val balanceDetails: PaymentInsufficientBalanceDetails? = null,
    127             override val tokenDetails: PaymentTokenAvailabilityDetails? = null,
    128             override val description: String? = null,
    129             override val descriptionI18n: Map<String, String>? = null,
    130         ) : ChoiceSelectionDetail()
    131     }
    132 }
    133 
    134 @Serializable
    135 enum class InsufficientBalanceHint {
    136     Unknown,
    137 
    138     /**
    139      *  Merchant doesn't accept money from exchange(s) that the wallet supports.
    140      */
    141     @SerialName("merchant-accept-insufficient")
    142     MerchantAcceptInsufficient,
    143 
    144     /**
    145      * Merchant accepts funds from a matching exchange, but the funds can't be
    146      * deposited with the wire method.
    147      */
    148     @SerialName("merchant-deposit-insufficient")
    149     MerchantDepositInsufficient,
    150 
    151     /**
    152      * While in principle the balance is sufficient,
    153      * the age restriction on coins causes the spendable
    154      * balance to be insufficient.
    155      */
    156     @SerialName("age-restricted")
    157     AgeRestricted,
    158 
    159     /**
    160      * Wallet has enough available funds,
    161      * but the material funds are insufficient. Usually because there is a
    162      * pending refresh operation.
    163      */
    164     @SerialName("wallet-balance-material-insufficient")
    165     WalletBalanceMaterialInsufficient,
    166 
    167     /**
    168      * The wallet simply doesn't have enough available funds.
    169      * This is the "obvious" case of insufficient balance.
    170      */
    171     @SerialName("wallet-balance-available-insufficient")
    172     WalletBalanceAvailableInsufficient,
    173 
    174     /**
    175      * Exchange is missing the global fee configuration, thus fees are unknown
    176      * and funds from this exchange can't be used for p2p payments.
    177      */
    178     @SerialName("exchange-missing-global-fees")
    179     ExchangeMissingGlobalFees,
    180 
    181     /**
    182      * Even though the balance looks sufficient for the instructed amount,
    183      * the fees can be covered by neither the merchant nor the remaining wallet
    184      * balance.
    185      */
    186     @SerialName("fees-not-covered")
    187     FeesNotCovered;
    188 }
    189 
    190 @StringRes
    191 fun InsufficientBalanceHint.stringResId(): Int? = when(this) {
    192     Unknown -> null
    193     MerchantAcceptInsufficient -> R.string.payment_balance_insufficient_hint_merchant_accept_insufficient
    194     MerchantDepositInsufficient -> R.string.payment_balance_insufficient_hint_merchant_deposit_insufficient
    195     AgeRestricted -> R.string.payment_balance_insufficient_hint_age_restricted
    196     WalletBalanceMaterialInsufficient -> R.string.payment_balance_insufficient_hint_wallet_balance_material_insufficient
    197     WalletBalanceAvailableInsufficient -> null // "normal case"
    198     ExchangeMissingGlobalFees -> R.string.payment_balance_insufficient_hint_exchange_missing_global_fees
    199     FeesNotCovered -> R.string.payment_balance_insufficient_hint_fees_not_covered
    200 }
    201 
    202 @Serializable
    203 data class PaymentInsufficientBalanceDetails(
    204     /**
    205      * Amount requested by the merchant.
    206      */
    207     val amountRequested: Amount,
    208 
    209     /**
    210      * Wire method for the requested payment, only applicable
    211      * for merchant payments.
    212      */
    213     val wireMethod: String? = null,
    214 
    215     /**
    216      * Hint as to why the balance is insufficient.
    217      *
    218      * If this hint is not provided, the balance hints of
    219      * the individual exchanges should be shown, as the overall
    220      * reason might be a combination of the reasons for different exchanges.
    221      */
    222     val causeHint: InsufficientBalanceHint? = null,
    223 
    224     /**
    225      * Balance of type "available" (see balance.ts for definition).
    226      */
    227     val balanceAvailable: Amount,
    228 
    229     /**
    230      * Balance of type "material" (see balance.ts for definition).
    231      */
    232     val balanceMaterial: Amount,
    233 
    234     /**
    235      * Balance of type "age-acceptable" (see balance.ts for definition).
    236      */
    237     val balanceAgeAcceptable: Amount,
    238 
    239     /**
    240      * Balance of type "merchant-acceptable" (see balance.ts for definition).
    241      */
    242     val balanceReceiverAcceptable: Amount,
    243 
    244     /**
    245      * Balance of type "merchant-depositable" (see balance.ts for definition).
    246      */
    247     val balanceReceiverDepositable: Amount,
    248 
    249     val balanceExchangeDepositable: Amount,
    250 
    251     /**
    252      * Maximum effective amount that the wallet can spend,
    253      * when all fees are paid by the wallet.
    254      */
    255     val maxEffectiveSpendAmount: Amount,
    256 
    257     val perExchange: Map<String, PerExchange>,
    258 ) {
    259 
    260     @Serializable
    261     data class PerExchange(
    262         val balanceAvailable: Amount,
    263         val balanceMaterial: Amount,
    264         val balanceExchangeDepositable: Amount,
    265         val balanceAgeAcceptable: Amount,
    266         val balanceReceiverAcceptable: Amount,
    267         val balanceReceiverDepositable: Amount,
    268         val maxEffectiveSpendAmount: Amount,
    269 
    270         /**
    271          * Exchange doesn't have global fees configured for the relevant year,
    272          * p2p payments aren't possible.
    273          *
    274          * @deprecated (2025-02-18) use causeHint instead
    275          */
    276         val missingGlobalFees: Boolean,
    277 
    278         /**
    279          * Hint that UIs should show to explain the insufficient
    280          * balance.
    281          */
    282         val causeHint: InsufficientBalanceHint? = null,
    283     )
    284 }
    285 
    286 @Serializable
    287 data class PaymentTokenAvailabilityDetails(
    288     /**
    289      * Number of tokens requested by the merchant.
    290      */
    291     val tokensRequested: Int,
    292 
    293     /**
    294      * Number of tokens for which the merchant is unexpected.
    295      *
    296      * Can be used to pay (i.e. with forced selection),
    297      * but a warning should be displayed to the user.
    298      */
    299     val tokensAvailable: Int,
    300 
    301     /**
    302      * Number of tokens for which the merchant is untrusted.
    303      *
    304      * Cannot be used to pay, so an error should be displayed.
    305      */
    306     val tokensUnexpected: Int,
    307 
    308     /**
    309      * Number of tokens with a malformed domain.
    310      *
    311      * Cannot be used to pay, so an error should be displayed.
    312      */
    313     val tokensUntrusted: Int,
    314 
    315     // {[slug: String]: PerTokenFamily}
    316     val perTokenFamily: Map<String, PerTokenFamily>,
    317 ) {
    318     @Serializable
    319     data class PerTokenFamily(
    320         val causeHint: TokenAvailabilityHint? = null,
    321         val requested: Int,
    322         val available: Int,
    323         val unexpected: Int,
    324         val untrusted: Int,
    325     )
    326 }
    327 
    328 @Serializable
    329 enum class TokenAvailabilityHint {
    330     Unknown,
    331 
    332     @SerialName("wallet-tokens-available-insufficient")
    333     WalletTokensAvailableInsufficient,
    334 
    335     @SerialName("merchant-unexpected")
    336     MerchantUnexpected,
    337 
    338     @SerialName("merchant-untrusted")
    339     MerchantUntrusted,
    340 }
    341 
    342 sealed class DonauStatus {
    343     data object Unavailable: DonauStatus()
    344     data object Available: DonauStatus()
    345     data class Unset(val donauUrls: List<String>): DonauStatus()
    346     data class Mismatch(
    347         val donauInfo: DonauInfo,
    348         val donauUrls: List<String>
    349     ): DonauStatus()
    350 }
    351 
    352 @Serializable
    353 sealed class ConfirmPayResult {
    354     @Serializable
    355     @SerialName("done")
    356     data class Done(
    357         val transactionId: String,
    358         val contractTerms: ContractTerms,
    359     ) : ConfirmPayResult()
    360 
    361     @Serializable
    362     @SerialName("pending")
    363     data class Pending(
    364         val transactionId: String,
    365         val lastError: TalerErrorInfo? = null,
    366     ) : ConfirmPayResult()
    367 }