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