Model+Deposit.swift (13039B)
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 clientCancellationId: "cancel") } 189 var depositPaytoUri: String 190 var amount: Amount 191 struct Args: Encodable { 192 var depositPaytoUri: String 193 var amount: Amount 194 var clientCancellationId: 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 response = try await sendRequest(request, viewHandles: viewHandles) 203 return response 204 } 205 } 206 // MARK: - 207 struct DepositGroupResult: Decodable { 208 var transactionId: String 209 var txState: TransactionState? 210 } 211 /// A request to deposit some coins. 212 fileprivate struct CreateDepositGroup: WalletBackendFormattedRequest { 213 typealias Response = DepositGroupResult 214 var operation: String { "createDepositGroup" } 215 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 216 restrictScope: scope, 217 amount: amount, 218 progressToken: depositPaytoUri) } 219 var depositPaytoUri: String 220 var scope: ScopeInfo 221 var amount: Amount 222 struct Args: Encodable { 223 var depositPaytoUri: String 224 var restrictScope: ScopeInfo 225 var amount: Amount 226 var progressToken: String 227 } 228 } 229 extension WalletModel { 230 /// deposit coins. Networking involved 231 nonisolated func createDepositGroup(_ depositPaytoUri: String, scope: ScopeInfo, amount: Amount, viewHandles: Bool = false) 232 async throws -> DepositGroupResult { 233 let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, scope: scope, amount: amount) 234 let controller = Controller.shared 235 controller.progressOperation = request.operation 236 controller.progressToken = depositPaytoUri 237 let response = try await sendRequest(request, viewHandles: viewHandles) 238 return response 239 } 240 } 241 // MARK: - 242 struct BankAccountsInfo: Decodable, Hashable { 243 var bankAccountId: String 244 var paytoUri: String 245 var kycCompleted: Bool 246 var currencies: [String]? // Currencies supported by the bank, if known 247 var label: String? 248 var payToWorkAround: String { 249 let payto = PayTo(paytoUri) 250 251 if let cyclos = payto.cyclos { 252 if cyclos.count > 1 { 253 // TODO: wallet-core should ensure that receivers are valid and don't have whitespace 254 // https://bugs.gnunet.org/view.php?id=11726 255 let receiver = payto.receiver?.replacingOccurrences(of: SPACE, with: "+") ?? DEMO 256 return String("payto://cyclos/\(cyclos)?receiver-name=\(receiver)") 257 } 258 } 259 return paytoUri 260 } 261 } 262 struct BankAccounts: Decodable, Hashable { 263 var accounts: [BankAccountsInfo] 264 } 265 /// A request to list known bank accounts. 266 fileprivate struct ListBankAccounts: WalletBackendFormattedRequest { 267 typealias Response = BankAccounts 268 var operation: String { "listBankAccounts" } 269 func args() -> Args { Args(currency: currency) } 270 var currency: String? 271 struct Args: Encodable { 272 var currency: String? 273 } 274 } 275 extension WalletModel { 276 /// ask for known accounts. No networking 277 nonisolated func listBankAccounts(_ currency: String? = nil, viewHandles: Bool = false) 278 async throws -> [BankAccountsInfo] { 279 let request = ListBankAccounts(currency: currency) 280 let response = try await sendRequest(request, viewHandles: viewHandles) 281 return response.accounts 282 } 283 } 284 285 /// A request to get one bank account. 286 fileprivate struct GetBankAccountById: WalletBackendFormattedRequest { 287 typealias Response = BankAccountsInfo 288 var operation: String { "getBankAccountById" } 289 func args() -> Args { Args(bankAccountId: bankAccountId) } 290 var bankAccountId: String 291 struct Args: Encodable { 292 var bankAccountId: String 293 } 294 } 295 extension WalletModel { 296 /// ask for a specific account. No networking 297 nonisolated func getBankAccountById(_ bankAccountId: String, viewHandles: Bool = false) 298 async throws -> BankAccountsInfo { 299 let request = GetBankAccountById(bankAccountId: bankAccountId) 300 let response = try await sendRequest(request, viewHandles: viewHandles) 301 return response 302 } 303 } 304 305 struct AddBankAccountResponse: Decodable, Hashable { 306 var bankAccountId: String // Identifier of the added bank account 307 } 308 /// A request to add a known bank account. 309 fileprivate struct AddBankAccount: WalletBackendFormattedRequest { 310 typealias Response = AddBankAccountResponse 311 var operation: String { "addBankAccount" } 312 func args() -> Args { Args(paytoUri: uri, 313 label: label, 314 replaceBankAccountId: replace) } 315 var uri: String 316 var label: String 317 var replace: String? 318 struct Args: Encodable { 319 var paytoUri: String // bank account that should be added 320 var label: String // Human-readable label 321 var replaceBankAccountId: String? // account that this new account should replace 322 } 323 } 324 extension WalletModel { 325 /// add (or update) a known account. No networking 326 nonisolated func addBankAccount(_ uri: String, 327 label: String, 328 replace: String? = nil, 329 viewHandles: Bool = false) 330 async throws -> String { 331 let request = AddBankAccount(uri: uri, label: label, replace: replace) 332 let response = try await sendRequest(request, viewHandles: viewHandles) 333 return response.bankAccountId 334 } 335 } 336 337 /// A request to forget a known bank account. 338 fileprivate struct ForgetBankAccount: WalletBackendFormattedRequest { 339 struct Response: Decodable {} // no result - getting no error back means success 340 var operation: String { "forgetBankAccount" } 341 func args() -> Args { Args(bankAccountId: accountId) } 342 var accountId: String 343 struct Args: Encodable { 344 var bankAccountId: String // bank account that should be forgotten 345 } 346 } 347 extension WalletModel { 348 /// add a known account. No networking 349 nonisolated func forgetBankAccount(_ accountId: String, viewHandles: Bool = false) 350 async throws { 351 let request = ForgetBankAccount(accountId: accountId) 352 _ = try await sendRequest(request, viewHandles: viewHandles) 353 } 354 }