taler-ios

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

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 }