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 }