taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }