diff options
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt')
-rw-r--r-- | wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt | 235 |
1 files changed, 158 insertions, 77 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt index 90b8570..e308b2a 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt @@ -19,7 +19,7 @@ package net.taler.wallet.withdraw import android.net.Uri import android.util.Log import androidx.annotation.UiThread -import androidx.lifecycle.LiveData +import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -31,23 +31,32 @@ import net.taler.common.toEvent import net.taler.wallet.TAG import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.balances.ScopeInfo import net.taler.wallet.exchanges.ExchangeFees import net.taler.wallet.exchanges.ExchangeItem +import net.taler.wallet.transactions.WithdrawalExchangeAccountDetails import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails sealed class WithdrawStatus { data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus() - data class NeedsExchange(val exchangeSelection: Event<ExchangeSelection>) : WithdrawStatus() + + data class NeedsExchange( + val talerWithdrawUri: String, + val amount: Amount, + val possibleExchanges: List<ExchangeItem>, + ) : WithdrawStatus() data class TosReviewRequired( val talerWithdrawUri: String? = null, val exchangeBaseUrl: String, val amountRaw: Amount, val amountEffective: Amount, + val withdrawalAccountList: List<WithdrawalExchangeAccountDetails>, val ageRestrictionOptions: List<Int>? = null, val tosText: String, val tosEtag: String, val showImmediately: Event<Boolean>, + val possibleExchanges: List<ExchangeItem> = emptyList(), ) : WithdrawStatus() data class ReceivedDetails( @@ -55,36 +64,60 @@ sealed class WithdrawStatus { val exchangeBaseUrl: String, val amountRaw: Amount, val amountEffective: Amount, + val withdrawalAccountList: List<WithdrawalExchangeAccountDetails>, val ageRestrictionOptions: List<Int>? = null, + val possibleExchanges: List<ExchangeItem> = emptyList(), ) : WithdrawStatus() - object Withdrawing : WithdrawStatus() + data object Withdrawing : WithdrawStatus() + data class Success(val currency: String, val transactionId: String) : WithdrawStatus() - sealed class ManualTransferRequired : WithdrawStatus() { - abstract val uri: Uri - abstract val transactionId: String? - } - data class ManualTransferRequiredIBAN( + class ManualTransferRequired( + val transactionId: String?, + val transactionAmountRaw: Amount, + val transactionAmountEffective: Amount, val exchangeBaseUrl: String, - override val uri: Uri, - val iban: String, - val subject: String, - val amountRaw: Amount, - override val transactionId: String?, - ) : ManualTransferRequired() + val withdrawalTransfers: List<TransferData>, + ) : WithdrawStatus() - data class ManualTransferRequiredBitcoin( - val exchangeBaseUrl: String, - override val uri: Uri, + data class Error(val message: String?) : WithdrawStatus() +} + +sealed class TransferData { + abstract val subject: String + abstract val amountRaw: Amount + abstract val amountEffective: Amount + abstract val withdrawalAccount: WithdrawalExchangeAccountDetails + + val currency get() = withdrawalAccount.transferAmount?.currency + + data class Taler( + override val subject: String, + override val amountRaw: Amount, + override val amountEffective: Amount, + override val withdrawalAccount: WithdrawalExchangeAccountDetails, + val receiverName: String? = null, val account: String, - val segwitAddrs: List<String>, - val subject: String, - val amountRaw: Amount, - override val transactionId: String?, - ) : ManualTransferRequired() + ): TransferData() - data class Error(val message: String?) : WithdrawStatus() + data class IBAN( + override val subject: String, + override val amountRaw: Amount, + override val amountEffective: Amount, + override val withdrawalAccount: WithdrawalExchangeAccountDetails, + val receiverName: String? = null, + val iban: String, + ): TransferData() + + data class Bitcoin( + override val subject: String, + override val amountRaw: Amount, + override val amountEffective: Amount, + override val withdrawalAccount: WithdrawalExchangeAccountDetails, + val account: String, + val segwitAddresses: List<String>, + ): TransferData() } sealed class WithdrawTestStatus { @@ -101,11 +134,19 @@ data class WithdrawalDetailsForUri( ) @Serializable -data class WithdrawalDetails( +data class WithdrawExchangeResponse( + val exchangeBaseUrl: String, + val amount: Amount? = null, +) + +@Serializable +data class ManualWithdrawalDetails( val tosAccepted: Boolean, val amountRaw: Amount, val amountEffective: Amount, + val withdrawalAccountsList: List<WithdrawalExchangeAccountDetails>, val ageRestrictionOptions: List<Int>? = null, + val scopeInfo: ScopeInfo, ) @Serializable @@ -115,12 +156,9 @@ data class AcceptWithdrawalResponse( @Serializable data class AcceptManualWithdrawalResponse( - val exchangePaytoUris: List<String>, -) - -data class ExchangeSelection( - val amount: Amount, - val talerWithdrawUri: String, + val reservePub: String, + val withdrawalAccountsList: List<WithdrawalExchangeAccountDetails>, + val transactionId: String, ) class WithdrawManager( @@ -131,8 +169,6 @@ class WithdrawManager( val withdrawStatus = MutableLiveData<WithdrawStatus>() val testWithdrawalStatus = MutableLiveData<WithdrawTestStatus>() - private val _exchangeSelection = MutableLiveData<Event<ExchangeSelection>>() - val exchangeSelection: LiveData<Event<ExchangeSelection>> = _exchangeSelection var exchangeFees: ExchangeFees? = null private set @@ -145,11 +181,6 @@ class WithdrawManager( } } - @UiThread - fun selectExchange(selection: ExchangeSelection) { - _exchangeSelection.value = selection.toEvent() - } - fun getWithdrawalDetails(uri: String) = scope.launch { withdrawStatus.value = WithdrawStatus.Loading(uri) api.request("getWithdrawalDetailsForUri", WithdrawalDetailsForUri.serializer()) { @@ -158,13 +189,17 @@ class WithdrawManager( handleError("getWithdrawalDetailsForUri", error) }.onSuccess { details -> if (details.defaultExchangeBaseUrl == null) { - val exchangeSelection = ExchangeSelection(details.amount, uri) - withdrawStatus.value = WithdrawStatus.NeedsExchange(exchangeSelection.toEvent()) + withdrawStatus.value = WithdrawStatus.NeedsExchange( + talerWithdrawUri = uri, + amount = details.amount, + possibleExchanges = details.possibleExchanges, + ) } else getWithdrawalDetails( exchangeBaseUrl = details.defaultExchangeBaseUrl, amount = details.amount, showTosImmediately = false, uri = uri, + possibleExchanges = details.possibleExchanges, ) } } @@ -174,9 +209,10 @@ class WithdrawManager( amount: Amount, showTosImmediately: Boolean = false, uri: String? = null, + possibleExchanges: List<ExchangeItem> = emptyList(), ) = scope.launch { withdrawStatus.value = WithdrawStatus.Loading(uri) - api.request("getWithdrawalDetailsForAmount", WithdrawalDetails.serializer()) { + api.request("getWithdrawalDetailsForAmount", ManualWithdrawalDetails.serializer()) { put("exchangeBaseUrl", exchangeBaseUrl) put("amount", amount.toJSONString()) }.onError { error -> @@ -188,17 +224,34 @@ class WithdrawManager( exchangeBaseUrl = exchangeBaseUrl, amountRaw = details.amountRaw, amountEffective = details.amountEffective, + withdrawalAccountList = details.withdrawalAccountsList, ageRestrictionOptions = details.ageRestrictionOptions, + possibleExchanges = possibleExchanges, ) - } else getExchangeTos(exchangeBaseUrl, details, showTosImmediately, uri) + } else getExchangeTos(exchangeBaseUrl, details, showTosImmediately, uri, possibleExchanges) + } + } + + @WorkerThread + suspend fun prepareManualWithdrawal(uri: String): WithdrawExchangeResponse? { + withdrawStatus.postValue(WithdrawStatus.Loading(uri)) + var response: WithdrawExchangeResponse? = null + api.request("prepareWithdrawExchange", WithdrawExchangeResponse.serializer()) { + put("talerUri", uri) + }.onError { + handleError("prepareWithdrawExchange", it) + }.onSuccess { + response = it } + return response } private fun getExchangeTos( exchangeBaseUrl: String, - details: WithdrawalDetails, + details: ManualWithdrawalDetails, showImmediately: Boolean, uri: String?, + possibleExchanges: List<ExchangeItem>, ) = scope.launch { api.request("getExchangeTos", TosResponse.serializer()) { put("exchangeBaseUrl", exchangeBaseUrl) @@ -210,10 +263,12 @@ class WithdrawManager( exchangeBaseUrl = exchangeBaseUrl, amountRaw = details.amountRaw, amountEffective = details.amountEffective, + withdrawalAccountList = details.withdrawalAccountsList, ageRestrictionOptions = details.ageRestrictionOptions, tosText = it.content, tosEtag = it.currentEtag, showImmediately = showImmediately.toEvent(), + possibleExchanges = possibleExchanges, ) } } @@ -234,7 +289,9 @@ class WithdrawManager( exchangeBaseUrl = s.exchangeBaseUrl, amountRaw = s.amountRaw, amountEffective = s.amountEffective, + withdrawalAccountList = s.withdrawalAccountList, ageRestrictionOptions = s.ageRestrictionOptions, + possibleExchanges = s.possibleExchanges, ) } } @@ -275,18 +332,15 @@ class WithdrawManager( handleError("acceptManualWithdrawal", it) }.onSuccess { response -> withdrawStatus.value = createManualTransferRequired( - amount = status.amountRaw, - exchangeBaseUrl = status.exchangeBaseUrl, - // TODO what if there's more than one or no URI? - uriStr = response.exchangePaytoUris[0], + status = status, + response = response, ) } } - @UiThread private fun handleError(operation: String, error: TalerErrorInfo) { Log.e(TAG, "Error $operation $error") - withdrawStatus.value = WithdrawStatus.Error(error.userFacingMsg) + withdrawStatus.postValue(WithdrawStatus.Error(error.userFacingMsg)) } /** @@ -301,33 +355,60 @@ class WithdrawManager( } fun createManualTransferRequired( - amount: Amount, + transactionId: String, exchangeBaseUrl: String, - uriStr: String, - transactionId: String? = null, -): WithdrawStatus.ManualTransferRequired { - val uri = Uri.parse(uriStr.replace("receiver-name=", "receiver_name=")) - if ("bitcoin".equals(uri.authority, true)) { - val msg = uri.getQueryParameter("message").orEmpty() - val reg = "\\b([A-Z0-9]{52})\\b".toRegex().find(msg) - val reserve = reg?.value ?: uri.getQueryParameter("subject")!! - val segwitAddrs = Bech32.generateFakeSegwitAddress(reserve, uri.pathSegments.first()) - return WithdrawStatus.ManualTransferRequiredBitcoin( - exchangeBaseUrl = exchangeBaseUrl, - uri = uri, - account = uri.lastPathSegment!!, - segwitAddrs = segwitAddrs, - subject = reserve, - amountRaw = amount, - transactionId = transactionId, - ) - } - return WithdrawStatus.ManualTransferRequiredIBAN( - exchangeBaseUrl = exchangeBaseUrl, - uri = uri, - iban = uri.lastPathSegment!!, - subject = uri.getQueryParameter("message") ?: "Error: No message in URI", - amountRaw = amount, - transactionId = transactionId, - ) -} + amountRaw: Amount, + amountEffective: Amount, + withdrawalAccountList: List<WithdrawalExchangeAccountDetails>, +) = WithdrawStatus.ManualTransferRequired( + transactionId = transactionId, + transactionAmountRaw = amountRaw, + transactionAmountEffective = amountEffective, + exchangeBaseUrl = exchangeBaseUrl, + withdrawalTransfers = withdrawalAccountList.mapNotNull { + val uri = Uri.parse(it.paytoUri.replace("receiver-name=", "receiver_name=")) + if ("bitcoin".equals(uri.authority, true)) { + val msg = uri.getQueryParameter("message").orEmpty() + val reg = "\\b([A-Z0-9]{52})\\b".toRegex().find(msg) + val reserve = reg?.value ?: uri.getQueryParameter("subject")!! + val segwitAddresses = Bech32.generateFakeSegwitAddress(reserve, uri.pathSegments.first()) + TransferData.Bitcoin( + account = uri.lastPathSegment!!, + segwitAddresses = segwitAddresses, + subject = reserve, + amountRaw = amountRaw, + amountEffective = amountEffective, + withdrawalAccount = it.copy(paytoUri = uri.toString()) + ) + } else if (uri.authority.equals("x-taler-bank", true)) { + TransferData.Taler( + account = uri.lastPathSegment!!, + receiverName = uri.getQueryParameter("receiver_name"), + subject = uri.getQueryParameter("message") ?: "Error: No message in URI", + amountRaw = amountRaw, + amountEffective = amountEffective, + withdrawalAccount = it.copy(paytoUri = uri.toString()), + ) + } else if (uri.authority.equals("iban", true)) { + TransferData.IBAN( + iban = uri.lastPathSegment!!, + receiverName = uri.getQueryParameter("receiver_name"), + subject = uri.getQueryParameter("message") ?: "Error: No message in URI", + amountRaw = amountRaw, + amountEffective = amountEffective, + withdrawalAccount = it.copy(paytoUri = uri.toString()), + ) + } else null + }, +) + +fun createManualTransferRequired( + status: ReceivedDetails, + response: AcceptManualWithdrawalResponse, +): WithdrawStatus.ManualTransferRequired = createManualTransferRequired( + transactionId = response.transactionId, + exchangeBaseUrl = status.exchangeBaseUrl, + amountRaw = status.amountRaw, + amountEffective = status.amountEffective, + withdrawalAccountList = response.withdrawalAccountsList, +)
\ No newline at end of file |