taler-ios

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

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 }