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