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