taler-ios

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

Model+Balances.swift (7325B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-26 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  */
      8 import Foundation
      9 import taler_swift
     10 
     11 // MARK: -
     12 
     13 enum BalanceFlag: String, Codable {
     14     case incomingAml = "incoming-aml"
     15     case incomingConfirmation = "incoming-confirmation"
     16     case incomingKyc = "incoming-kyc"
     17     case outgoingKyc = "outgoing-kyc"
     18 }
     19 
     20 /// A currency balance
     21 struct Balance: Identifiable, Decodable, Hashable, Sendable {
     22     var id: String {
     23         if let url = scopeInfo.url {
     24             if let masterPub = scopeInfo.masterPub {
     25                 // exchange-legacy-keys: same url as the exchange's current scope,
     26                 // must stay distinct or the two rows collide in SwiftUI lists
     27                 return url + "#" + masterPub
     28             }
     29             return url
     30         }
     31         return scopeInfo.currency   // only ISO 4217 currencies!
     32     }
     33 
     34     var scopeInfo: ScopeInfo
     35     var available: Amount
     36     var pendingIncoming: Amount
     37     var pendingOutgoing: Amount
     38     var flags: [BalanceFlag]
     39     var shoppingUrls: [String]?
     40     var disablePeerPayments: Bool?
     41     var disableDirectDeposits: Bool?                // TODO: en/disable actions based on this
     42 
     43     public static func == (lhs: Balance, rhs: Balance) -> Bool {
     44         lhs.scopeInfo == rhs.scopeInfo
     45         && lhs.available == rhs.available
     46         && lhs.pendingIncoming == rhs.pendingIncoming
     47         && lhs.pendingOutgoing == rhs.pendingOutgoing
     48         && lhs.flags == rhs.flags
     49         && lhs.disablePeerPayments == rhs.disablePeerPayments
     50         && lhs.disableDirectDeposits == rhs.disableDirectDeposits
     51     }
     52 
     53     public func hash(into hasher: inout Hasher) {
     54         hasher.combine(scopeInfo)
     55         hasher.combine(available)
     56         hasher.combine(pendingIncoming)
     57         hasher.combine(pendingOutgoing)
     58         hasher.combine(flags)
     59         hasher.combine(disablePeerPayments)
     60         hasher.combine(disableDirectDeposits)
     61     }
     62 }
     63 extension Balance {
     64     static func firstwithDeposit(_ balances: [Balance]) -> Balance? {
     65         balances.first { balance in
     66             balance.disableDirectDeposits != true
     67         }
     68     }
     69     static func firstWithP2P(_ balances: [Balance]) -> Balance? {
     70         balances.first { balance in
     71             balance.disablePeerPayments != true
     72         }
     73     }
     74 }
     75 // MARK: -
     76 struct BalancesResponse: Decodable, Hashable, Sendable {
     77     let haveProdBalance: Bool                           // if false, show DefaultExchange hint
     78     let balances: [Balance]
     79 }
     80 
     81 /// A request to get the balances held in the wallet.
     82 fileprivate struct Balances: WalletBackendFormattedRequest {
     83     var operation: String { "getBalances" }
     84     func args() -> Args { Args() }
     85 
     86     struct Args: Encodable {}                           // no arguments needed
     87 
     88     typealias Response = BalancesResponse
     89 }
     90 // MARK: -
     91 struct TalerToken: Identifiable, Codable, Hashable, Sendable {
     92     // Hash of token family info.
     93     var tokenFamilyHash: String
     94     // Hash of token issue public key.
     95     var tokenIssuePubHash: String
     96     // URL of the merchant issuing the token.
     97     var merchantBaseUrl: String
     98     // Name of the merchant instance issuing the token.
     99     var merchantInfo: MerchantInfo?
    100     // Human-readable name for the token family.
    101     var name: String
    102     // Human-readable description for the token family.
    103     var description: String
    104     // Optional map from IETF BCP 47 language tags to localized descriptions.
    105     var descriptionI18n: I18nDict?
    106     // Start time of the token's validity period.
    107     var validityStart: Timestamp
    108     // End time of the token's validity period.
    109     var validityEnd: Timestamp
    110     // Number of tokens available to use.
    111     var tokensAvailable: Int?                   // Only for Discount
    112 
    113     var id: String {
    114         return tokenFamilyHash
    115     }
    116 }
    117 // MARK: -
    118 struct DiscountsResponse: Decodable, Hashable, Sendable {
    119     let discounts: [TalerToken]
    120 }
    121 
    122 /// A request to get the discounts held in the wallet.
    123 fileprivate struct ListDiscounts: WalletBackendFormattedRequest {
    124     var operation: String { "listDiscounts" }
    125     func args() -> Args { Args() }
    126 
    127     struct Args: Encodable {}                           // no arguments needed
    128 
    129     typealias Response = DiscountsResponse
    130 }
    131 /// A request to delete a discount by ID.
    132 struct DeleteDiscount: WalletBackendFormattedRequest {
    133     struct Response: Decodable {}   // no result - getting no error back means success
    134     var operation: String { "deleteDiscount" }
    135     func args() -> Args { Args(tokenFamilyHash: tokenFamilyHash) }
    136 
    137     var tokenFamilyHash: String
    138     struct Args: Encodable {
    139         var tokenFamilyHash: String
    140     }
    141 }
    142 // MARK: -
    143 struct SubscriptionsResponse: Decodable, Hashable, Sendable {
    144     let subscriptions: [TalerToken]
    145 }
    146 
    147 /// A request to get the subscriptions held in the wallet.
    148 fileprivate struct ListSubscriptions: WalletBackendFormattedRequest {
    149     var operation: String { "listSubscriptions" }
    150     func args() -> Args { Args() }
    151 
    152     struct Args: Encodable {}                           // no arguments needed
    153 
    154     typealias Response = SubscriptionsResponse
    155 }
    156 /// A request to delete a subscription by ID.
    157 struct DeleteSubscription: WalletBackendFormattedRequest {
    158     struct Response: Decodable {}   // no result - getting no error back means success
    159     var operation: String { "deleteSubscription" }
    160     func args() -> Args { Args(tokenFamilyHash: tokenFamilyHash) }
    161 
    162     var tokenFamilyHash: String
    163     struct Args: Encodable {
    164         var tokenFamilyHash: String
    165     }
    166 }
    167 // MARK: -
    168 extension WalletModel {
    169     /// fetch Balances from Wallet-Core. No networking involved
    170     nonisolated func getBalances(_ stack: CallStack, viewHandles: Bool = false) async throws -> BalancesResponse {
    171         let request = Balances()
    172         let response = try await sendRequest(request, viewHandles: viewHandles)
    173         return response
    174     }
    175     /// fetch Discounts from Wallet-Core. No networking involved
    176     nonisolated func listDiscounts(_ stack: CallStack, viewHandles: Bool = false) async throws -> DiscountsResponse {
    177         let request = ListDiscounts()
    178         let response = try await sendRequest(request, viewHandles: viewHandles)
    179         return response
    180     }
    181     /// delete a discount. No networking involved
    182     nonisolated func deleteDiscount(_ tokenID: String, viewHandles: Bool = false) async throws {
    183         let request = DeleteDiscount(tokenFamilyHash: tokenID)
    184         logger.notice("deleteDiscount: \(tokenID, privacy: .private(mask: .hash))")
    185         let _ = try await sendRequest(request, viewHandles: viewHandles)
    186     }
    187     /// fetch Subscriptions from Wallet-Core. No networking involved
    188     nonisolated func listSubscriptions(_ stack: CallStack, viewHandles: Bool = false) async throws -> SubscriptionsResponse {
    189         let request = ListSubscriptions()
    190         let response = try await sendRequest(request, viewHandles: viewHandles)
    191         return response
    192     }
    193     /// delete a subscription. No networking involved
    194     nonisolated func deleteSubscription(_ tokenID: String, viewHandles: Bool = false) async throws {
    195         let request = DeleteSubscription(tokenFamilyHash: tokenID)
    196         logger.notice("deleteSubscription: \(tokenID, privacy: .private(mask: .hash))")
    197         let _ = try await sendRequest(request, viewHandles: viewHandles)
    198     }
    199 }