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