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