Model+Deposit.swift (12523B)
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 talerBankHostnames: [String] 110 } 111 112 struct DepositWireTypesResponse: Codable { 113 /// can be used to pre-filter payment target types to offer the user as an input option 114 // let wireTypes: [PaytoType] 115 let wireTypeDetails: [WireTypeDetails] 116 } 117 118 /// A request to get wire types that can be used for a deposit operation. 119 fileprivate struct DepositWireTypes: WalletBackendFormattedRequest { 120 typealias Response = DepositWireTypesResponse 121 func operation() -> String { "getDepositWireTypes" } 122 func args() -> Args { Args(currency: currency, scopeInfo: scopeInfo) } 123 124 var currency: String? 125 var scopeInfo: ScopeInfo? 126 struct Args: Encodable { 127 // one of these must be set - don't set both to nil 128 var currency: String? 129 var scopeInfo: ScopeInfo? 130 } 131 } 132 extension WalletModel { 133 /// Get wire types that can be used for a deposit operation 134 nonisolated func depositWireTypes(_ currency: String?, scopeInfo: ScopeInfo? = nil, viewHandles: Bool = false) 135 async throws -> [WireTypeDetails] { 136 let request = DepositWireTypes(currency: currency, scopeInfo: scopeInfo) 137 let response = try await sendRequest(request, viewHandles: viewHandles) 138 return response.wireTypeDetails 139 } 140 } 141 // MARK: - max Deposit amount 142 /// Check if initiating a deposit is possible, check fees 143 fileprivate struct AmountResponse: Codable { 144 let effectiveAmount: Amount? 145 let rawAmount: Amount 146 } 147 fileprivate struct GetMaxDepositAmount: WalletBackendFormattedRequest { 148 typealias Response = AmountResponse 149 func operation() -> String { "getMaxDepositAmount" } 150 func args() -> Args { Args(currency: scope.currency, restrictScope: scope) } 151 152 var scope: ScopeInfo 153 struct Args: Encodable { 154 var currency: String 155 var restrictScope: ScopeInfo 156 // var depositPaytoUri: String 157 } 158 } 159 extension WalletModel { 160 nonisolated func getMaxDepositAmount(_ scope: ScopeInfo, viewHandles: Bool = false) 161 async throws -> Amount { 162 let request = GetMaxDepositAmount(scope: scope) 163 let response = try await sendRequest(request, viewHandles: viewHandles) 164 return response.rawAmount 165 } 166 } // getMaxDepositAmount 167 // MARK: - Deposit 168 struct DepositFees: Codable { 169 let coin: Amount 170 let wire: Amount 171 let refresh: Amount 172 } 173 struct CheckDepositResponse: Codable { 174 let totalDepositCost: Amount 175 let effectiveDepositAmount: Amount 176 let fees: DepositFees 177 let kycSoftLimit: Amount? 178 let kycHardLimit: Amount? 179 let kycExchanges: [String]? // Base URL of exchanges that would likely require soft KYC 180 } 181 /// A request to get an exchange's deposit contract terms. 182 fileprivate struct CheckDeposit: WalletBackendFormattedRequest { 183 typealias Response = CheckDepositResponse 184 func operation() -> String { "checkDeposit" } 185 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 186 amount: amount, 187 clientCancellationId: "cancel") } 188 var depositPaytoUri: String 189 var amount: Amount 190 struct Args: Encodable { 191 var depositPaytoUri: String 192 var amount: Amount 193 var clientCancellationId: String? 194 } 195 } 196 extension WalletModel { 197 /// check fees for deposit. No Networking 198 nonisolated func checkDeposit4711(_ depositPaytoUri: String, amount: Amount, viewHandles: Bool = false) 199 async throws -> CheckDepositResponse { 200 let request = CheckDeposit(depositPaytoUri: depositPaytoUri, amount: amount) 201 let response = try await sendRequest(request, viewHandles: viewHandles) 202 return response 203 } 204 } 205 // MARK: - 206 struct DepositGroupResult: Decodable { 207 var transactionId: String 208 var txState: TransactionState? 209 } 210 /// A request to deposit some coins. 211 fileprivate struct CreateDepositGroup: WalletBackendFormattedRequest { 212 typealias Response = DepositGroupResult 213 func operation() -> String { "createDepositGroup" } 214 func args() -> Args { Args(depositPaytoUri: depositPaytoUri, 215 restrictScope: scope, 216 amount: amount) } 217 var depositPaytoUri: String 218 var scope: ScopeInfo 219 var amount: Amount 220 struct Args: Encodable { 221 var depositPaytoUri: String 222 var restrictScope: ScopeInfo 223 var amount: Amount 224 } 225 } 226 extension WalletModel { 227 /// deposit coins. Networking involved 228 nonisolated func createDepositGroup(_ depositPaytoUri: String, scope: ScopeInfo, amount: Amount, viewHandles: Bool = false) 229 async throws -> DepositGroupResult { 230 let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, scope: scope, amount: amount) 231 let response = try await sendRequest(request, viewHandles: viewHandles) 232 return response 233 } 234 } 235 // MARK: - 236 struct BankAccountsInfo: Decodable, Hashable { 237 var bankAccountId: String 238 var paytoUri: String 239 var kycCompleted: Bool 240 var label: String? 241 var payToWorkAround: String { 242 let payto = PayTo(paytoUri) 243 244 if let cyclos = payto.cyclos { 245 if cyclos.count > 1 { 246 let receiver = payto.receiver?.replacingOccurrences(of: SPACE, with: "+") ?? DEMO 247 return String("payto://cyclos/\(cyclos)?receiver-name=\(receiver)") 248 } 249 } 250 return paytoUri 251 } 252 } 253 struct BankAccounts: Decodable, Hashable { 254 var accounts: [BankAccountsInfo] 255 } 256 /// A request to list known bank accounts. 257 fileprivate struct ListBankAccounts: WalletBackendFormattedRequest { 258 typealias Response = BankAccounts 259 func operation() -> String { "listBankAccounts" } 260 func args() -> Args { Args(currency: currency) } 261 var currency: String? 262 struct Args: Encodable { 263 var currency: String? 264 } 265 } 266 extension WalletModel { 267 /// ask for known accounts. No networking 268 nonisolated func listBankAccounts(_ currency: String? = nil, viewHandles: Bool = false) 269 async throws -> [BankAccountsInfo] { 270 let request = ListBankAccounts(currency: currency) 271 let response = try await sendRequest(request, viewHandles: viewHandles) 272 return response.accounts 273 } 274 } 275 276 /// A request to get one bank account. 277 fileprivate struct GetBankAccountById: WalletBackendFormattedRequest { 278 typealias Response = BankAccountsInfo 279 func operation() -> String { "getBankAccountById" } 280 func args() -> Args { Args(bankAccountId: bankAccountId) } 281 var bankAccountId: String 282 struct Args: Encodable { 283 var bankAccountId: String 284 } 285 } 286 extension WalletModel { 287 /// ask for a specific account. No networking 288 nonisolated func getBankAccountById(_ bankAccountId: String, viewHandles: Bool = false) 289 async throws -> BankAccountsInfo { 290 let request = GetBankAccountById(bankAccountId: bankAccountId) 291 let response = try await sendRequest(request, viewHandles: viewHandles) 292 return response 293 } 294 } 295 296 struct AddBankAccountResponse: Decodable, Hashable { 297 var bankAccountId: String // Identifier of the added bank account 298 } 299 /// A request to add a known bank account. 300 fileprivate struct AddBankAccount: WalletBackendFormattedRequest { 301 typealias Response = AddBankAccountResponse 302 func operation() -> String { "addBankAccount" } 303 func args() -> Args { Args(paytoUri: uri, 304 label: label, 305 replaceBankAccountId: replace) } 306 var uri: String 307 var label: String 308 var replace: String? 309 struct Args: Encodable { 310 var paytoUri: String // bank account that should be added 311 var label: String // Human-readable label 312 var replaceBankAccountId: String? // account that this new account should replace 313 } 314 } 315 extension WalletModel { 316 /// add (or update) a known account. No networking 317 nonisolated func addBankAccount(_ uri: String, 318 label: String, 319 replace: String? = nil, 320 viewHandles: Bool = false) 321 async throws -> String { 322 let request = AddBankAccount(uri: uri, label: label, replace: replace) 323 let response = try await sendRequest(request, viewHandles: viewHandles) 324 return response.bankAccountId 325 } 326 } 327 328 /// A request to forget a known bank account. 329 fileprivate struct ForgetBankAccount: WalletBackendFormattedRequest { 330 struct Response: Decodable {} // no result - getting no error back means success 331 func operation() -> String { "forgetBankAccount" } 332 func args() -> Args { Args(bankAccountId: accountId) } 333 var accountId: String 334 struct Args: Encodable { 335 var bankAccountId: String // bank account that should be forgotten 336 } 337 } 338 extension WalletModel { 339 /// add a known account. No networking 340 nonisolated func forgetBankAccount(_ accountId: String, viewHandles: Bool = false) 341 async throws { 342 let request = ForgetBankAccount(accountId: accountId) 343 _ = try await sendRequest(request, viewHandles: viewHandles) 344 } 345 }