Model+Deposit.swift (10121B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import Foundation 9 import taler_swift 10 import AnyCodable 11 //import SymLog 12 13 struct AccountChange: Codable { // Notification 14 enum TransitionType: String, Codable { 15 case change = "bank-account-change" 16 } 17 var type: TransitionType 18 var bankAccountId: String 19 } 20 21 // MARK: - IBAN 22 struct ValidateIbanResult: Codable { 23 let valid: Bool 24 } 25 /// A request to validate an IBAN. 26 fileprivate struct ValidateIban: WalletBackendFormattedRequest { 27 typealias Response = ValidateIbanResult 28 func operation() -> String { "validateIban" } 29 func args() -> Args { Args(iban: iban) } 30 31 var iban: String 32 struct Args: Encodable { 33 var iban: String 34 } 35 } 36 extension WalletModel { 37 /// validate IBAN. No Networking 38 nonisolated func validateIban(_ iban: String, viewHandles: Bool = false) 39 async throws -> Bool { 40 let request = ValidateIban(iban: iban) 41 let response = try await sendRequest(request, viewHandles: viewHandles) 42 return response.valid 43 } 44 } 45 // MARK: - Deposit 46 enum PaytoType: String, Codable { 47 case unknown 48 case iban 49 case bitcoin 50 case xTalerBank = "x-taler-bank" 51 } 52 53 struct WireTypeDetails: Codable { 54 let paymentTargetType: PaytoType 55 let talerBankHostnames: [String] 56 } 57 58 struct DepositWireTypesResponse: Codable { 59 /// can be used to pre-filter payment target types to offer the user as an input option 60 // let wireTypes: [PaytoType] 61 let wireTypeDetails: [WireTypeDetails] 62 } 63 64 /// A request to get wire types that can be used for a deposit operation. 65 fileprivate struct DepositWireTypes: WalletBackendFormattedRequest { 66 typealias Response = DepositWireTypesResponse 67 func operation() -> String { "getDepositWireTypes" } 68 func args() -> Args { Args(currency: currency, scopeInfo: scopeInfo) } 69 70 var currency: String? 71 var scopeInfo: ScopeInfo? 72 struct Args: Encodable { 73 // one of these must be set - don't set both to nil 74 var currency: String? 75 var scopeInfo: ScopeInfo? 76 } 77 } 78 extension WalletModel { 79 /// Get wire types that can be used for a deposit operation 80 nonisolated func depositWireTypes(_ currency: String?, scopeInfo: ScopeInfo? = nil, viewHandles: Bool = false) 81 async throws -> [WireTypeDetails] { 82 let request = DepositWireTypes(currency: currency, scopeInfo: scopeInfo) 83 let response = try await sendRequest(request, viewHandles: viewHandles) 84 return response.wireTypeDetails 85 } 86 } 87 // MARK: - max Deposit amount 88 /// Check if initiating a deposit is possible, check fees 89 fileprivate struct AmountResponse: Codable { 90 let effectiveAmount: Amount? 91 let rawAmount: Amount 92 } 93 fileprivate struct GetMaxDepositAmount: WalletBackendFormattedRequest { 94 typealias Response = AmountResponse 95 func operation() -> String { "getMaxDepositAmount" } 96 func args() -> Args { Args(currency: scope.currency, restrictScope: scope) } 97 98 var scope: ScopeInfo 99 struct Args: Encodable { 100 var currency: String 101 var restrictScope: ScopeInfo 102 // var depositPaytoUri: String 103 } 104 } 105 extension WalletModel { 106 nonisolated func getMaxDepositAmount(_ scope: ScopeInfo, viewHandles: Bool = false) 107 async throws -> Amount { 108 let request = GetMaxDepositAmount(scope: scope) 109 let response = try await sendRequest(request, viewHandles: viewHandles) 110 return response.rawAmount 111 } 112 } // getMaxDepositAmount 113 // MARK: - Deposit 114 struct DepositFees: Codable { 115 let coin: Amount 116 let wire: Amount 117 let refresh: Amount 118 } 119 struct CheckDepositResponse: Codable { 120 let totalDepositCost: Amount 121 let effectiveDepositAmount: Amount 122 let fees: DepositFees 123 let kycSoftLimit: Amount? 124 let kycHardLimit: Amount? 125 let kycExchanges: [String]? // Base URL of exchanges that would likely require soft KYC 126 } 127 /// A request to get an exchange's deposit contract terms. 128 fileprivate struct CheckDeposit: WalletBackendFormattedRequest { 129 typealias Response = CheckDepositResponse 130 func operation() -> String { "checkDeposit" } 131 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 132 amount: amount, 133 clientCancellationId: "cancel") } 134 var depositPaytoUri: String 135 var amount: Amount 136 struct Args: Encodable { 137 var depositPaytoUri: String 138 var amount: Amount 139 var clientCancellationId: String? 140 } 141 } 142 extension WalletModel { 143 /// check fees for deposit. No Networking 144 nonisolated func checkDeposit4711(_ depositPaytoUri: String, amount: Amount, viewHandles: Bool = false) 145 async throws -> CheckDepositResponse { 146 let request = CheckDeposit(depositPaytoUri: depositPaytoUri, amount: amount) 147 let response = try await sendRequest(request, viewHandles: viewHandles) 148 return response 149 } 150 } 151 // MARK: - 152 struct DepositGroupResult: Decodable { 153 var transactionId: String 154 var txState: TransactionState? 155 } 156 /// A request to deposit some coins. 157 fileprivate struct CreateDepositGroup: WalletBackendFormattedRequest { 158 typealias Response = DepositGroupResult 159 func operation() -> String { "createDepositGroup" } 160 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 161 restrictScope: scope, 162 amount: amount) } 163 var depositPaytoUri: String 164 var scope: ScopeInfo 165 var amount: Amount 166 struct Args: Encodable { 167 var depositPaytoUri: String 168 var restrictScope: ScopeInfo 169 var amount: Amount 170 } 171 } 172 extension WalletModel { 173 /// deposit coins. Networking involved 174 nonisolated func createDepositGroup(_ depositPaytoUri: String, scope: ScopeInfo, amount: Amount, viewHandles: Bool = false) 175 async throws -> DepositGroupResult { 176 let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, scope: scope, amount: amount) 177 let response = try await sendRequest(request, viewHandles: viewHandles) 178 return response 179 } 180 } 181 // MARK: - 182 struct BankAccountsInfo: Decodable, Hashable { 183 var bankAccountId: String 184 var paytoUri: String 185 var kycCompleted: Bool 186 var label: String? 187 } 188 struct BankAccounts: Decodable, Hashable { 189 var accounts: [BankAccountsInfo] 190 } 191 /// A request to list known bank accounts. 192 fileprivate struct ListBankAccounts: WalletBackendFormattedRequest { 193 typealias Response = BankAccounts 194 func operation() -> String { "listBankAccounts" } 195 func args() -> Args { Args(currency: currency) } 196 var currency: String? 197 struct Args: Encodable { 198 var currency: String? 199 } 200 } 201 extension WalletModel { 202 /// ask for known accounts. No networking 203 nonisolated func listBankAccounts(_ currency: String? = nil, viewHandles: Bool = false) 204 async throws -> [BankAccountsInfo] { 205 let request = ListBankAccounts(currency: currency) 206 let response = try await sendRequest(request, viewHandles: viewHandles) 207 return response.accounts 208 } 209 } 210 211 /// A request to get one bank account. 212 fileprivate struct GetBankAccountById: WalletBackendFormattedRequest { 213 typealias Response = BankAccountsInfo 214 func operation() -> String { "getBankAccountById" } 215 func args() -> Args { Args(bankAccountId: bankAccountId) } 216 var bankAccountId: String 217 struct Args: Encodable { 218 var bankAccountId: String 219 } 220 } 221 extension WalletModel { 222 /// ask for a specific account. No networking 223 nonisolated func getBankAccountById(_ bankAccountId: String, viewHandles: Bool = false) 224 async throws -> BankAccountsInfo { 225 let request = GetBankAccountById(bankAccountId: bankAccountId) 226 let response = try await sendRequest(request, viewHandles: viewHandles) 227 return response 228 } 229 } 230 231 struct AddBankAccountResponse: Decodable, Hashable { 232 var bankAccountId: String // Identifier of the added bank account 233 } 234 /// A request to add a known bank account. 235 fileprivate struct AddBankAccount: WalletBackendFormattedRequest { 236 typealias Response = AddBankAccountResponse 237 func operation() -> String { "addBankAccount" } 238 func args() -> Args { Args(paytoUri: uri, 239 label: label, 240 replaceBankAccountId: replace) } 241 var uri: String 242 var label: String 243 var replace: String? 244 struct Args: Encodable { 245 var paytoUri: String // bank account that should be added 246 var label: String // Human-readable label 247 var replaceBankAccountId: String? // account that this new account should replace 248 } 249 } 250 extension WalletModel { 251 /// add (or update) a known account. No networking 252 nonisolated func addBankAccount(_ uri: String, 253 label: String, 254 replace: String? = nil, 255 viewHandles: Bool = false) 256 async throws -> String { 257 let request = AddBankAccount(uri: uri, label: label, replace: replace) 258 let response = try await sendRequest(request, viewHandles: viewHandles) 259 return response.bankAccountId 260 } 261 } 262 263 /// A request to forget a known bank account. 264 fileprivate struct ForgetBankAccount: WalletBackendFormattedRequest { 265 struct Response: Decodable {} // no result - getting no error back means success 266 func operation() -> String { "forgetBankAccount" } 267 func args() -> Args { Args(bankAccountId: accountId) } 268 var accountId: String 269 struct Args: Encodable { 270 var bankAccountId: String // bank account that should be forgotten 271 } 272 } 273 extension WalletModel { 274 /// add a known account. No networking 275 nonisolated func forgetBankAccount(_ accountId: String, viewHandles: Bool = false) 276 async throws { 277 let request = ForgetBankAccount(accountId: accountId) 278 _ = try await sendRequest(request, viewHandles: viewHandles) 279 } 280 }