Model+Deposit.swift (13190B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 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 var 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 // or BBAN for HUF (and CHF when taler://dev-experiment/fake-chf-bban was called) 49 case bitcoin 50 case cyclos 51 case xTalerBank = "x-taler-bank" 52 } 53 54 struct IbanAccountFieldToPaytoResponse: Codable { 55 let ok: Bool 56 let type: String? // either "iban" or "bban", only if ok==true 57 let paytoUri: String? // only if ok==true 58 } 59 60 /// A request to convert an IBAN/BBAN to PayTo. 61 fileprivate struct IbanAccountFieldToPayto: WalletBackendFormattedRequest { 62 typealias Response = IbanAccountFieldToPaytoResponse 63 var operation: String { "convertIbanAccountFieldToPayto" } 64 func args() -> Args { Args(value: value, currency: currency) } 65 66 var value: String 67 var currency: String 68 struct Args: Encodable { 69 var value: String 70 var currency: String 71 } 72 } 73 74 struct IbanPaytoToAccountFieldResponse: Codable { 75 let type: String 76 let value: String 77 } 78 79 /// A request to convert PayTo to IBAN/BBAN. 80 fileprivate struct IbanPaytoToAccountField: WalletBackendFormattedRequest { 81 typealias Response = IbanPaytoToAccountFieldResponse 82 var operation: String { "convertIbanPaytoToAccountField" } 83 func args() -> Args { Args(paytoUri: paytoUri) } 84 85 var paytoUri: String 86 struct Args: Encodable { 87 var paytoUri: String 88 } 89 } 90 91 extension WalletModel { 92 /// convert an IBAN/BBAN to PayTo 93 nonisolated func convertIbanAccountFieldToPayto(_ value: String, currency: String, viewHandles: Bool = false) 94 async throws -> IbanAccountFieldToPaytoResponse { 95 let request = IbanAccountFieldToPayto(value: value, currency: currency) 96 let response = try await sendRequest(request, viewHandles: viewHandles) 97 return response 98 } 99 nonisolated func convertIbanPaytoToAccountField(_ paytoUri: String, viewHandles: Bool = false) 100 async throws -> IbanPaytoToAccountFieldResponse { 101 let request = IbanPaytoToAccountField(paytoUri: paytoUri) 102 let response = try await sendRequest(request, viewHandles: viewHandles) 103 return response 104 } 105 } 106 // MARK: - Deposit 107 struct WireTypeDetails: Codable { 108 let paymentTargetType: PaytoType 109 let preferredEntryType: String? // iban / bban TODO: use this 110 let talerBankHostnames: [String]? 111 } 112 113 struct DepositWireTypesResponse: Codable { 114 /// can be used to pre-filter payment target types to offer the user as an input option 115 // let wireTypes: [PaytoType] 116 let wireTypeDetails: [WireTypeDetails] 117 } 118 119 /// A request to get wire types that can be used for a deposit operation. 120 fileprivate struct DepositWireTypes: WalletBackendFormattedRequest { 121 typealias Response = DepositWireTypesResponse 122 var operation: String { "getDepositWireTypes" } 123 func args() -> Args { Args(currency: currency, scopeInfo: scopeInfo) } 124 125 var currency: String? 126 var scopeInfo: ScopeInfo? 127 struct Args: Encodable { 128 // one of these must be set - don't set both to nil 129 var currency: String? 130 var scopeInfo: ScopeInfo? 131 } 132 } 133 extension WalletModel { 134 /// Get wire types that can be used for a deposit operation 135 nonisolated func depositWireTypes(_ currency: String?, scopeInfo: ScopeInfo? = nil, viewHandles: Bool = false) 136 async throws -> [WireTypeDetails] { 137 let request = DepositWireTypes(currency: currency, scopeInfo: scopeInfo) 138 let response = try await sendRequest(request, viewHandles: viewHandles) 139 return response.wireTypeDetails 140 } 141 } 142 // MARK: - max Deposit amount 143 /// Check if initiating a deposit is possible, check fees 144 fileprivate struct AmountResponse: Codable { 145 let effectiveAmount: Amount? 146 let rawAmount: Amount 147 } 148 fileprivate struct GetMaxDepositAmount: WalletBackendFormattedRequest { 149 typealias Response = AmountResponse 150 var operation: String { "getMaxDepositAmount" } 151 func args() -> Args { Args(currency: scope.currency, restrictScope: scope) } 152 153 var scope: ScopeInfo 154 struct Args: Encodable { 155 var currency: String 156 var restrictScope: ScopeInfo 157 // var depositPaytoUri: String 158 } 159 } 160 extension WalletModel { 161 nonisolated func getMaxDepositAmount(_ scope: ScopeInfo, viewHandles: Bool = false) 162 async throws -> Amount { 163 let request = GetMaxDepositAmount(scope: scope) 164 let response = try await sendRequest(request, viewHandles: viewHandles) 165 return response.rawAmount 166 } 167 } // getMaxDepositAmount 168 // MARK: - Deposit 169 struct DepositFees: Codable { 170 let coin: Amount 171 let wire: Amount 172 let refresh: Amount 173 } 174 struct CheckDepositResponse: Codable { 175 let totalDepositCost: Amount 176 let effectiveDepositAmount: Amount 177 let fees: DepositFees 178 let kycSoftLimit: Amount? 179 let kycHardLimit: Amount? 180 let kycExchanges: [String]? // Base URL of exchanges that would likely require soft KYC 181 } 182 /// A request to get an exchange's deposit contract terms. 183 fileprivate struct CheckDeposit: WalletBackendFormattedRequest { 184 typealias Response = CheckDepositResponse 185 var operation: String { "checkDeposit" } 186 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 187 amount: amount, 188 progressToken: depositPaytoUri) } 189 var depositPaytoUri: String 190 var amount: Amount 191 struct Args: Encodable { 192 var depositPaytoUri: String 193 var amount: Amount 194 var progressToken: String? 195 } 196 } 197 extension WalletModel { 198 /// check fees for deposit. No Networking 199 nonisolated func checkDeposit4711(_ depositPaytoUri: String, amount: Amount, viewHandles: Bool = false) 200 async throws -> CheckDepositResponse { 201 let request = CheckDeposit(depositPaytoUri: depositPaytoUri, amount: amount) 202 let controller = Controller.shared 203 controller.progressOperation = request.operation 204 controller.progressToken = depositPaytoUri 205 let response = try await sendRequest(request, viewHandles: viewHandles) 206 return response 207 } 208 } 209 // MARK: - 210 struct DepositGroupResult: Decodable { 211 var transactionId: String 212 var txState: TransactionState? 213 } 214 /// A request to deposit some coins. 215 fileprivate struct CreateDepositGroup: WalletBackendFormattedRequest { 216 typealias Response = DepositGroupResult 217 var operation: String { "createDepositGroup" } 218 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 219 restrictScope: scope, 220 amount: amount, 221 progressToken: depositPaytoUri) } 222 var depositPaytoUri: String 223 var scope: ScopeInfo 224 var amount: Amount 225 struct Args: Encodable { 226 var depositPaytoUri: String 227 var restrictScope: ScopeInfo 228 var amount: Amount 229 var progressToken: String 230 } 231 } 232 extension WalletModel { 233 /// deposit coins. Networking involved 234 nonisolated func createDepositGroup(_ depositPaytoUri: String, scope: ScopeInfo, amount: Amount, viewHandles: Bool = false) 235 async throws -> DepositGroupResult { 236 let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, scope: scope, amount: amount) 237 let controller = Controller.shared 238 controller.progressOperation = request.operation 239 controller.progressToken = depositPaytoUri 240 let response = try await sendRequest(request, viewHandles: viewHandles) 241 return response 242 } 243 } 244 // MARK: - 245 struct BankAccountsInfo: Decodable, Hashable { 246 var bankAccountId: String 247 var paytoUri: String 248 var kycCompleted: Bool 249 var currencies: [String]? // Currencies supported by the bank, if known 250 var label: String? 251 var payToWorkAround: String { 252 let payto = PayTo(paytoUri) 253 254 if let cyclos = payto.cyclos { 255 if cyclos.count > 1 { 256 // TODO: wallet-core should ensure that receivers are valid and don't have whitespace 257 // https://bugs.gnunet.org/view.php?id=11726 258 let receiver = payto.receiver?.replacingOccurrences(of: SPACE, with: "+") ?? DEMO 259 return String("payto://cyclos/\(cyclos)?receiver-name=\(receiver)") 260 } 261 } 262 return paytoUri 263 } 264 } 265 struct BankAccounts: Decodable, Hashable { 266 var accounts: [BankAccountsInfo] 267 } 268 /// A request to list known bank accounts. 269 fileprivate struct ListBankAccounts: WalletBackendFormattedRequest { 270 typealias Response = BankAccounts 271 var operation: String { "listBankAccounts" } 272 func args() -> Args { Args(currency: currency) } 273 var currency: String? 274 struct Args: Encodable { 275 var currency: String? 276 } 277 } 278 extension WalletModel { 279 /// ask for known accounts. No networking 280 nonisolated func listBankAccounts(_ currency: String? = nil, viewHandles: Bool = false) 281 async throws -> [BankAccountsInfo] { 282 let request = ListBankAccounts(currency: currency) 283 let response = try await sendRequest(request, viewHandles: viewHandles) 284 return response.accounts 285 } 286 } 287 288 /// A request to get one bank account. 289 fileprivate struct GetBankAccountById: WalletBackendFormattedRequest { 290 typealias Response = BankAccountsInfo 291 var operation: String { "getBankAccountById" } 292 func args() -> Args { Args(bankAccountId: bankAccountId) } 293 var bankAccountId: String 294 struct Args: Encodable { 295 var bankAccountId: String 296 } 297 } 298 extension WalletModel { 299 /// ask for a specific account. No networking 300 nonisolated func getBankAccountById(_ bankAccountId: String, viewHandles: Bool = false) 301 async throws -> BankAccountsInfo { 302 let request = GetBankAccountById(bankAccountId: bankAccountId) 303 let response = try await sendRequest(request, viewHandles: viewHandles) 304 return response 305 } 306 } 307 308 struct AddBankAccountResponse: Decodable, Hashable { 309 var bankAccountId: String // Identifier of the added bank account 310 } 311 /// A request to add a known bank account. 312 fileprivate struct AddBankAccount: WalletBackendFormattedRequest { 313 typealias Response = AddBankAccountResponse 314 var operation: String { "addBankAccount" } 315 func args() -> Args { Args(paytoUri: uri, 316 label: label, 317 replaceBankAccountId: replace) } 318 var uri: String 319 var label: String 320 var replace: String? 321 struct Args: Encodable { 322 var paytoUri: String // bank account that should be added 323 var label: String // Human-readable label 324 var replaceBankAccountId: String? // account that this new account should replace 325 } 326 } 327 extension WalletModel { 328 /// add (or update) a known account. No networking 329 nonisolated func addBankAccount(_ uri: String, 330 label: String, 331 replace: String? = nil, 332 viewHandles: Bool = false) 333 async throws -> String { 334 let request = AddBankAccount(uri: uri, label: label, replace: replace) 335 let response = try await sendRequest(request, viewHandles: viewHandles) 336 return response.bankAccountId 337 } 338 } 339 340 /// A request to forget a known bank account. 341 fileprivate struct ForgetBankAccount: WalletBackendFormattedRequest { 342 struct Response: Decodable {} // no result - getting no error back means success 343 var operation: String { "forgetBankAccount" } 344 func args() -> Args { Args(bankAccountId: accountId) } 345 var accountId: String 346 struct Args: Encodable { 347 var bankAccountId: String // bank account that should be forgotten 348 } 349 } 350 extension WalletModel { 351 /// add a known account. No networking 352 nonisolated func forgetBankAccount(_ accountId: String, viewHandles: Bool = false) 353 async throws { 354 let request = ForgetBankAccount(accountId: accountId) 355 _ = try await sendRequest(request, viewHandles: viewHandles) 356 } 357 }