diff options
Diffstat (limited to 'src/commonMain')
3 files changed, 287 insertions, 1 deletions
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt index ebf445e..4143389 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Time.kt @@ -80,6 +80,6 @@ data class Duration( val ms: Long ) { companion object { - const val FOREVER: Long = -1 + const val FOREVER: Long = -1 // TODO or UINT64_MAX? } } diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/reserve/ReserveManager.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/reserve/ReserveManager.kt new file mode 100644 index 0000000..c8e7bd4 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/reserve/ReserveManager.kt @@ -0,0 +1,126 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.kotlin.reserve + +import io.ktor.client.HttpClient +import net.taler.wallet.kotlin.Amount +import net.taler.wallet.kotlin.Base32Crockford +import net.taler.wallet.kotlin.Db +import net.taler.wallet.kotlin.DbFactory +import net.taler.wallet.kotlin.Timestamp +import net.taler.wallet.kotlin.crypto.Crypto +import net.taler.wallet.kotlin.crypto.CryptoFactory +import net.taler.wallet.kotlin.exchange.Exchange +import net.taler.wallet.kotlin.getDefaultHttpClient +import net.taler.wallet.kotlin.reserve.ReserveRecordStatus.REGISTERING_BANK +import net.taler.wallet.kotlin.reserve.ReserveRecordStatus.UNCONFIRMED + +internal class ReserveManager( + private val crypto: Crypto = CryptoFactory.getCrypto(), + private val httpClient: HttpClient = getDefaultHttpClient(), + private val db: Db = DbFactory().openDb() +) { + + /** + * Request to create a reserve. + */ + data class CreateReserveRequest( + /** + * The initial amount for the reserve. + */ + val amount: Amount, + + /** + * Exchange URL where the bank should create the reserve. + */ + val exchangeBaseUrl: String, + + /** + * Payto URI that identifies the exchange's account that the funds for this reserve go into. + */ + val exchangePaytoUri: String, + + /** + * Wire details (as a payto URI) for the bank account that sent the funds to the exchange. + */ + val senderPaytoUri: String? = null, + + /** + * URL to fetch the withdraw status from the bank. + */ + val bankWithdrawStatusUrl: String? = null + ) + + /** + * Response for the create reserve request to the wallet. + */ + data class CreateReserveResponse( + /** + * Exchange URL where the bank should create the reserve. + * The URL is normalized in the response. + */ + val exchangeBaseUrl: String, + + /** + * Reserve public key of the newly created reserve. + */ + val reservePub: String + ) + + /** + * Create a reserve, but do not flag it as confirmed yet. + */ + fun create(request: CreateReserveRequest): CreateReserveResponse { + val keyPair = crypto.createEddsaKeyPair() + val now = Timestamp.now() + val exchangeBaseUrl = Exchange.normalizeUrl(request.exchangeBaseUrl) + val bankInfo = request.bankWithdrawStatusUrl?.let { bankWithdrawStatusUrl -> + ReserveBankInfo( + statusUrl = bankWithdrawStatusUrl, + amount = request.amount, + bankWithdrawalGroupId = Base32Crockford.encode(crypto.getRandomBytes(32)), + withdrawalStarted = false + ) + } + val reserve = Reserve( + reservePub = keyPair.encodedPublicKey, + reservePriv = keyPair.encodedPrivateKey, + exchangeBaseUrl = exchangeBaseUrl, + currency = request.amount.currency, + timestampCreated = now, + senderPaytoUri = request.senderPaytoUri, + exchangePaytoUri = request.exchangePaytoUri, + bankInfo = bankInfo, + reserveStatus = if (request.bankWithdrawStatusUrl == null) UNCONFIRMED else REGISTERING_BANK, + retryInfo = RetryInfo.getInitial() + ) + // TODO store reserve history in DB + // TODO store senderPaytoUri in DB + // TODO store currency record in DB + // TODO store withdraw URI in DB +// db.put(reserve) + return CreateReserveResponse( + exchangeBaseUrl = exchangeBaseUrl, + reservePub = keyPair.encodedPublicKey + ) + } + + fun processBankStatus(reservePub: String) { + TODO("Not yet implemented") + } + +} diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/reserve/ReserveRecord.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/reserve/ReserveRecord.kt new file mode 100644 index 0000000..b618719 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/reserve/ReserveRecord.kt @@ -0,0 +1,160 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.kotlin.reserve + +import net.taler.wallet.kotlin.Amount +import net.taler.wallet.kotlin.Timestamp +import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo + +/** + * A reserve record as stored in the wallet's database. + */ +data class Reserve( + /** + * The reserve public key. + */ + val reservePub: String, + + /** + * The reserve private key. + */ + val reservePriv: String, + + /** + * The exchange base URL. + */ + val exchangeBaseUrl: String, + + /** + * Currency of the reserve. + */ + val currency: String, + + /** + * Time when the reserve was created. + */ + val timestampCreated: Timestamp, + + /** + * Time when the information about this reserve was posted to the bank. + * + * Only applies if bankWithdrawStatusUrl is not null. + * + * Set to 0 if that hasn't happened yet. + */ + val timestampReserveInfoPosted: Timestamp? = null, + + /** + * Time when the reserve was confirmed, either manually by the user or by the bank. + * + * Set to undefined if not confirmed yet. + */ + val timestampConfirmed: Timestamp? = null, + + /** + * Wire information (as payto URI) for the bank account that transferred funds for this reserve. + */ + val senderPaytoUri: String?, + + /** + * Wire information (as payto URI) for the exchange, + * specifically the account that was transferred to when creating the reserve. + */ + val exchangePaytoUri: String, + + /** + * Extra state for when this is a withdrawal involving a Taler-integrated bank. + */ + val bankInfo: ReserveBankInfo?, + + val reserveStatus: ReserveRecordStatus, + + /** + * Time of the last successful status query. + */ + val lastSuccessfulStatusQuery: Timestamp? = null, + + /** + * Retry info. This field is present even if no retry is scheduled, + * because we need it to be present for the index on the object store + * to work. + */ + val retryInfo: RetryInfo +) + +enum class ReserveRecordStatus(val value: String) { + /** + * Waiting for manual confirmation. + */ + UNCONFIRMED("unconfirmed"), + + /** + * Reserve must be registered with the bank. + */ + REGISTERING_BANK("registering-bank"), + + /** + * We've registered reserve's information with the bank + * and are now waiting for the user to confirm the withdraw + * with the bank (typically 2nd factor auth). + */ + WAIT_CONFIRM_BANK("wait-confirm-bank"), + + /** + * Querying reserve status with the exchange. + */ + QUERYING_STATUS("querying-status"), + + /** + * Status is queried, the wallet must now select coins + * and start withdrawing. + */ + WITHDRAWING("withdrawing"), + + /** + * The corresponding withdraw record has been created. + * No further processing is done, unless explicitly requested + * by the user. + */ + DORMANT("dormant") +} + +data class ReserveBankInfo( + val statusUrl: String, + val confirmUrl: String? = null, + val amount: Amount, + val bankWithdrawalGroupId: String, + val withdrawalStarted: Boolean, + val denomSel: DenominationSelectionInfo? = null // TODO do we really need this? nullable for now +) + +data class RetryInfo( + val firstTry: Timestamp, + val nextRetry: Timestamp, + val retryCounter: Int, + val active: Boolean +) { + // TODO implement retry policies and set proper nextRetry + companion object { + fun getInitial() = RetryInfo( + firstTry = Timestamp.now(), + nextRetry = Timestamp(0), + retryCounter = 0, + active = true + ) + } +} |