Model+Withdraw.swift (12120B)
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 SymLog 11 12 enum AccountRestrictionType: String, Codable { 13 case deny 14 case regex 15 } 16 17 typealias HintDict = [String:String] 18 19 struct AccountRestriction: Codable, Hashable { 20 var type: AccountRestrictionType 21 var payto_regex: String? 22 var human_hint: String? 23 var human_hint_i18n: HintDict? 24 } 25 extension AccountRestriction: Identifiable { 26 var id: AccountRestriction {self} 27 } 28 29 struct ExchangeAccountDetails: Decodable { 30 var status: String // "OK" or "error" - then conversionError 31 var paytoUri: String 32 var transferAmount: Amount? // only if "OK" 33 var bankLabel: String? // only if wallet-core knows it 34 var currencySpecification: CurrencySpecification? // only if wallet-core knows it 35 var creditRestrictions: [AccountRestriction]? // only if restrictions apply 36 // var conversionError: TalerErrorDetail? // only if error 37 var scope: ScopeInfo? // for Deposit 38 } 39 // MARK: - 40 enum WithdrawalOperationStatus: String, Codable { 41 case pending 42 case selected 43 case aborted 44 case confirmed 45 } 46 /// The result from getWithdrawalDetailsForUri 47 struct WithdrawUriInfoResponse: Decodable { 48 var operationId: String 49 var status: WithdrawalOperationStatus // pending, selected, aborted, confirmed 50 var confirmTransferUrl: String? 51 var currency: String // use this if amount=nil 52 var amount: Amount? // if nil then either ask User (editableAmount=true), or it's cash2ecash (editableAmount=false) 53 var editableAmount: Bool // if true then ask User 54 var maxAmount: Amount? // limit how much the user may withdraw 55 var wireFee: Amount? 56 var defaultExchangeBaseUrl: String? // if nil then use possibleExchanges 57 var editableExchange: Bool // TODO: what for? 58 var possibleExchanges: [Exchange] // TODO: query these for fees? 59 } 60 /// A request to get an exchange's withdrawal details. 61 fileprivate struct GetWithdrawalDetailsForURI: WalletBackendFormattedRequest { 62 typealias Response = WithdrawUriInfoResponse 63 func operation() -> String { "getWithdrawalDetailsForUri" } 64 func args() -> Args { Args(talerWithdrawUri: talerUri) } 65 66 var talerUri: String 67 struct Args: Encodable { 68 var talerWithdrawUri: String 69 } 70 } 71 // MARK: - 72 /// The result from prepareWithdrawExchange 73 struct WithdrawExchangeResponse: Decodable { 74 var exchangeBaseUrl: String 75 var amount: Amount? 76 } 77 /// A request to get an exchange's withdrawal details. 78 fileprivate struct PrepareWithdrawExchange: WalletBackendFormattedRequest { 79 typealias Response = WithdrawExchangeResponse 80 func operation() -> String { "prepareWithdrawExchange" } 81 func args() -> Args { Args(talerUri: talerUri) } 82 83 var talerUri: String 84 struct Args: Encodable { 85 var talerUri: String 86 } 87 } 88 // MARK: - 89 /// The result from getWithdrawalDetailsForAmount 90 struct WithdrawalDetailsForAmount: Decodable { 91 var exchangeBaseUrl: String 92 var amountRaw: Amount // Amount that the user will transfer to the exchange 93 var amountEffective: Amount // Amount that will be added to the user's wallet balance 94 var numCoins: Int? // Number of coins this amountEffective will create 95 var withdrawalAccountsList: [ExchangeAccountDetails]? 96 var ageRestrictionOptions: [Int]? // Array of ages 97 var scopeInfo: ScopeInfo 98 } 99 /// A request to get an exchange's withdrawal details. 100 fileprivate struct GetWithdrawalDetailsForAmount: WalletBackendFormattedRequest { 101 typealias Response = WithdrawalDetailsForAmount 102 func operation() -> String { "getWithdrawalDetailsForAmount" } 103 func args() -> Args { Args(amount: amount, 104 exchangeBaseUrl: baseUrl, 105 restrictScope: scope, 106 clientCancellationId: "cancel") } 107 var amount: Amount 108 var baseUrl: String? 109 var scope: ScopeInfo? 110 struct Args: Encodable { 111 var amount: Amount 112 var exchangeBaseUrl: String? // needed for b-i-withdrawals 113 var restrictScope: ScopeInfo? // only if exchangeBaseUrl is nil 114 var clientCancellationId: String? 115 } 116 } 117 // MARK: - 118 enum ExchangeTosStatus: String, Codable { 119 case missingTos = "missing-tos" 120 case pending 121 case proposed 122 case accepted 123 } 124 struct ExchangeTermsOfService: Decodable { 125 var currentEtag: String 126 var acceptedEtag: String? 127 var tosStatus: ExchangeTosStatus 128 var tosAvailableLanguages: [String] 129 var contentType: String 130 var contentLanguage: String? 131 var content: String 132 } 133 /// A request to query an exchange's terms of service. 134 fileprivate struct GetExchangeTermsOfService: WalletBackendFormattedRequest { 135 typealias Response = ExchangeTermsOfService 136 func operation() -> String { "getExchangeTos" } 137 func args() -> Args { Args(exchangeBaseUrl: baseUrl, 138 acceptedFormat: acceptedFormat, 139 acceptLanguage: acceptLanguage) } 140 var baseUrl: String 141 var acceptedFormat: [String]? 142 var acceptLanguage: String? 143 struct Args: Encodable { 144 var exchangeBaseUrl: String 145 var acceptedFormat: [String]? 146 var acceptLanguage: String? 147 } 148 } 149 /// A request to mark an exchange's terms of service as accepted. 150 fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest { 151 struct Response: Decodable {} // no result - getting no error back means success 152 func operation() -> String { "setExchangeTosAccepted" } 153 func args() -> Args { Args(exchangeBaseUrl: baseUrl) } 154 155 var baseUrl: String 156 157 struct Args: Encodable { 158 var exchangeBaseUrl: String 159 } 160 } 161 // MARK: - 162 struct AcceptWithdrawalResponse: Decodable { 163 var transactionId: String 164 var confirmTransferUrl: String? 165 // var reservePub: String 166 } 167 /// A request to accept a bank-integrated withdrawl. 168 fileprivate struct AcceptBankIntegratedWithdrawal: WalletBackendFormattedRequest { 169 typealias Response = AcceptWithdrawalResponse 170 func operation() -> String { "acceptBankIntegratedWithdrawal" } 171 func args() -> Args { Args(talerWithdrawUri: talerUri, exchangeBaseUrl: baseUrl, amount: amount, restrictAge: restrictAge) } 172 173 var talerUri: String 174 var baseUrl: String 175 var amount: Amount? 176 var restrictAge: Int? 177 178 struct Args: Encodable { 179 var talerWithdrawUri: String 180 var exchangeBaseUrl: String 181 var amount: Amount? 182 var restrictAge: Int? 183 } 184 } 185 // MARK: - 186 struct AcceptManualWithdrawalResult: Decodable { 187 var reservePub: String 188 var exchangePaytoUris: [String] 189 var withdrawalAccountsList: [ExchangeAccountDetails] 190 var transactionId: String 191 } 192 /// A request to accept a manual withdrawl. 193 fileprivate struct AcceptManualWithdrawal: WalletBackendFormattedRequest { 194 typealias Response = AcceptManualWithdrawalResult 195 func operation() -> String { "acceptManualWithdrawal" } 196 func args() -> Args { Args(amount: amount, exchangeBaseUrl: baseUrl, restrictAge: restrictAge) } 197 198 var amount: Amount 199 var baseUrl: String 200 var restrictAge: Int? 201 202 struct Args: Encodable { 203 var amount: Amount 204 var exchangeBaseUrl: String 205 var restrictAge: Int? 206 } 207 } 208 // MARK: - 209 struct QrCodeSpec: Decodable, Hashable { 210 var type: String 211 var qrContent: String 212 } 213 /// A request to accept a manual withdrawl. 214 fileprivate struct GetQrCodesForPayto: WalletBackendFormattedRequest { 215 func operation() -> String { "getQrCodesForPayto" } 216 func args() -> Args { Args(paytoUri: paytoUri) } 217 218 var paytoUri: String 219 220 struct Response: Decodable, Sendable { // list of QrCodeSpecs 221 var codes: [QrCodeSpec] 222 } 223 224 struct Args: Encodable { 225 var paytoUri: String 226 } 227 } 228 // MARK: - 229 extension WalletModel { 230 /// load withdraw-exchange details. Networking involved 231 nonisolated func prepareWithdrawExchange(_ talerUri: String, 232 viewHandles: Bool = false) 233 async throws -> WithdrawExchangeResponse { 234 let request = PrepareWithdrawExchange(talerUri: talerUri) 235 let response = try await sendRequest(request, viewHandles: viewHandles) 236 return response 237 } 238 239 /// load withdrawal details. Networking involved 240 nonisolated func getWithdrawalDetailsForUri(_ talerUri: String, 241 viewHandles: Bool = false) 242 async throws -> WithdrawUriInfoResponse { 243 let request = GetWithdrawalDetailsForURI(talerUri: talerUri) 244 let response = try await sendRequest(request, viewHandles: viewHandles) 245 return response 246 } 247 248 nonisolated func getWithdrawalDetailsForAmount(_ amount: Amount, 249 baseUrl: String?, 250 scope: ScopeInfo?, 251 viewHandles: Bool = false) 252 async throws -> WithdrawalDetailsForAmount { 253 let request = GetWithdrawalDetailsForAmount(amount: amount, baseUrl: baseUrl, scope: scope) 254 let response = try await sendRequest(request, viewHandles: viewHandles) 255 return response 256 } 257 258 nonisolated func loadExchangeTermsOfService(_ baseUrl: String, 259 acceptedFormat: [String], 260 acceptLanguage: String, 261 viewHandles: Bool = false) 262 async throws -> ExchangeTermsOfService { 263 let request = GetExchangeTermsOfService(baseUrl: baseUrl, 264 acceptedFormat: acceptedFormat, 265 acceptLanguage: acceptLanguage) 266 let response = try await sendRequest(request, viewHandles: viewHandles) 267 return response 268 } 269 270 nonisolated func setExchangeTOSAccepted(_ baseUrl: String, 271 viewHandles: Bool = false) 272 async throws -> Decodable { 273 let request = SetExchangeTOSAccepted(baseUrl: baseUrl) 274 let response = try await sendRequest(request, viewHandles: viewHandles) 275 return response 276 } 277 278 nonisolated func acceptBankIntWithdrawal(_ baseUrl: String, 279 withdrawURL: String, 280 amount: Amount?, 281 restrictAge: Int?, 282 viewHandles: Bool = false) 283 async throws -> AcceptWithdrawalResponse? { 284 let request = AcceptBankIntegratedWithdrawal(talerUri: withdrawURL, baseUrl: baseUrl, 285 amount: amount, restrictAge: restrictAge) 286 let response = try await sendRequest(request, viewHandles: viewHandles) 287 return response 288 } 289 290 nonisolated func acceptManualWithdrawal(_ amount: Amount, 291 baseUrl: String, 292 restrictAge: Int?, 293 viewHandles: Bool = false) 294 async throws -> AcceptManualWithdrawalResult? { 295 let request = AcceptManualWithdrawal(amount: amount, baseUrl: baseUrl, restrictAge: restrictAge) 296 let response = try await sendRequest(request, viewHandles: viewHandles) 297 return response 298 } 299 300 nonisolated func getQrCodesForPayto(_ paytoUri: String, 301 viewHandles: Bool = false) 302 async throws -> [QrCodeSpec]? { 303 let request = GetQrCodesForPayto(paytoUri: paytoUri) 304 let response = try await sendRequest(request, viewHandles: viewHandles) 305 return response.codes 306 } 307 }