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 }