taler-ios

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

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 }