taler-ios

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

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 }