WalletBackendRequest.swift (8821B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Jonathan Buchanan 7 * @author Marc Stibane 8 */ 9 import Foundation 10 import AnyCodable 11 import taler_swift 12 13 /// A request sent to the wallet backend. 14 struct WalletBackendRequest: Encodable { 15 /// The operation name of the request. 16 var operation: String 17 18 /// The body of the request as JSON. 19 var args: AnyEncodable 20 } 21 22 protocol WalletBackendFormattedRequest { 23 associatedtype Args: Encodable 24 associatedtype Response: Decodable 25 26 var operation: String { get } 27 func args() -> Args 28 } 29 // MARK: - 30 /// The scope of a currency: either global or a dedicated exchange 31 struct ScopeInfo: Codable, Hashable { 32 enum ScopeInfoType: String, Codable { 33 case global 34 case exchange 35 case auditor 36 case exchangeLegacyKeys = "exchange-legacy-keys" 37 case unknown // => type unknown, currency name taken from amount 38 39 /// A scope type this app does not know must not make the whole balance list undecodable. 40 /// NOTE: `unknown` is app-invented and wallet-core's codecForScopeInfo rejects it, 41 /// so a degraded ScopeInfo must never be sent back as an argument. 42 init(from decoder: Decoder) throws { 43 let raw = try decoder.singleValueContainer().decode(String.self) 44 self = ScopeInfoType(rawValue: raw) ?? .unknown 45 } 46 } 47 var type: ScopeInfoType 48 var currency: String // 3-char ISO 4217 code for global currency. Regional MUST be >= 4 letters 49 var noFees: Bool? // only for "global". Regional have this field per Exchange 50 var url: String? // only for "exchange", "auditor" and "exchange-legacy-keys" 51 var masterPub: String? // only for "exchange-legacy-keys": the superseded key 52 53 public static func zero() -> ScopeInfo { 54 ScopeInfo(type: .unknown, currency: UNKNOWN) 55 } 56 public static func zero(_ currency: String) -> ScopeInfo { 57 ScopeInfo(type: .unknown, currency: currency) 58 } 59 public static func < (lhs: ScopeInfo, rhs: ScopeInfo) -> Bool { 60 if lhs.type == .global { 61 if rhs.type == .global { // both global ==> alphabetic currency 62 return lhs.currency < rhs.currency 63 } 64 return true // global comes first 65 } 66 if rhs.type == .global { 67 return false // global comes first 68 } 69 if lhs.currency == rhs.currency { 70 if let lhsBaseURL = lhs.url { 71 if let rhsBaseURL = rhs.url { 72 return lhsBaseURL < rhsBaseURL 73 } 74 return true 75 } 76 if rhs.url != nil { 77 return false 78 } 79 // fall thru 80 } 81 return lhs.currency < rhs.currency 82 } 83 public static func == (lhs: ScopeInfo, rhs: ScopeInfo) -> Bool { 84 if let lhsBaseURL = lhs.url { 85 if let rhsBaseURL = rhs.url { 86 if lhsBaseURL != rhsBaseURL { return false } // different exchanges 87 // else fall thru and check type & currency 88 } else { return false } // left but not right 89 } else if rhs.url != nil { 90 return false // right but not left 91 } 92 return lhs.type == rhs.type && 93 lhs.currency == rhs.currency && 94 lhs.masterPub == rhs.masterPub // two legacy-key scopes of one exchange differ 95 } 96 public func hash(into hasher: inout Hasher) { 97 hasher.combine(type) 98 if let url { 99 hasher.combine(url) 100 } 101 hasher.combine(currency) 102 hasher.combine(masterPub) 103 } 104 } 105 // MARK: - 106 /// A billing or mailing location. 107 struct Location: Codable, Equatable, Hashable { 108 var country: String? 109 var country_subdivision: String? 110 var district: String? 111 var town: String? 112 var town_location: String? 113 var post_code: String? 114 var street: String? 115 var building_name: String? 116 var building_number: String? 117 var address_lines: [String]? 118 } 119 120 /// Information identifying a merchant. 121 struct MerchantInfo: Identifiable, Codable, Hashable, Sendable { 122 var name: String 123 var email: String? 124 var website: String? 125 var logo: String? // ImageDataUrl 126 var address: Location? 127 var jurisdiction: Location? 128 129 var id: String { 130 return name + (email ?? EMPTYSTRING) 131 } 132 } 133 134 /// A tax made on a payment. 135 struct Tax: Codable { 136 var name: String 137 var tax: Amount 138 } 139 140 /// A product being purchased from a merchant. 141 /// https://docs.taler.net/core/api-merchant.html#the-contract-terms 142 struct Product: Codable, Identifiable { 143 var product_id: String? 144 var description: String 145 // var description_i18n: ? 146 var quantity: Int? 147 var unit: String? 148 var price: Amount? 149 var image: String? // product image 128x128 encoded 150 var taxes: [Tax]? 151 var delivery_date: Timestamp? 152 153 var id: String { product_id ?? "unknown" } 154 } 155 156 /// Brief information about an order. 157 struct OrderShortInfo: Codable { 158 var orderId: String 159 var merchant: MerchantInfo 160 var summary: String 161 var summary_i18n: I18nDict? 162 var products: [Product]? 163 var fulfillmentUrl: String? 164 var fulfillmentMessage: String? 165 var fulfillmentMessage_i18n: I18nDict? 166 var contractTermsHash: String? 167 } 168 // MARK: - 169 /// A request to force update an exchange. 170 struct WalletBackendForceUpdateRequest: WalletBackendFormattedRequest { 171 var operation: String { "addRequest" } 172 func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) } 173 174 var exchangeBaseUrl: String 175 struct Args: Encodable { 176 var exchangeBaseUrl: String 177 } 178 179 struct Response: Decodable {} 180 } 181 182 /// A request to deposit funds. 183 struct WalletBackendCreateDepositGroupRequest: WalletBackendFormattedRequest { 184 var operation: String { "createDepositGroup" } 185 func args() -> Args { Args(depositPayToUri: depositePayToUri, amount: amount) } 186 187 var depositePayToUri: String 188 var amount: Amount 189 190 struct Args: Encodable { 191 var depositPayToUri: String 192 var amount: Amount 193 } 194 195 struct Response: Decodable { 196 var depositGroupId: String 197 } 198 } 199 200 /// A request to get information about a payment request. 201 struct WalletBackendPreparePayRequest: WalletBackendFormattedRequest { 202 var operation: String { "preparePay" } 203 func args() -> Args { Args(talerPayUri: talerPayUri) } 204 var talerPayUri: String 205 206 struct Args: Encodable { 207 var talerPayUri: String 208 } 209 210 struct Response: Decodable {} 211 } 212 213 // MARK: - 214 struct IntegrationTestArgs: Codable { 215 var exchangeBaseUrl: String 216 var bankBaseUrl: String 217 var merchantBaseUrl: String 218 var merchantApiKey: String 219 var amountToWithdraw: String 220 var amountToSpend: String 221 } 222 223 /// A request to run a basic integration test. 224 struct WalletBackendRunIntegrationTestRequest: WalletBackendFormattedRequest { 225 var operation: String { "runIntegrationTest" } 226 func args() -> Args { integrationTestArgs } 227 var integrationTestArgs: IntegrationTestArgs 228 229 typealias Args = IntegrationTestArgs 230 231 struct Response: Decodable {} 232 } 233 234 struct TestPayArgs: Codable { 235 var merchantBaseUrl: String 236 var merchantApiKey: String 237 var amount: String 238 var summary: String 239 } 240 241 /// A request to make a test payment. 242 struct WalletBackendTestPayRequest: WalletBackendFormattedRequest { 243 var operation: String { "testPay" } 244 func args() -> Args { testPayArgs } 245 246 var testPayArgs: TestPayArgs 247 typealias Args = TestPayArgs 248 249 struct Response: Decodable {} 250 } 251 252 struct Coin: Codable { 253 var denom_pub: String 254 var denom_pub_hash: String 255 var denom_value: String 256 var coin_pub: String 257 var exchange_base_url: String 258 var remaining_value: String 259 var refresh_parent_coin_pub: String 260 var withdrawal_reserve_pub: String 261 var coin_suspended: Bool 262 } 263 264 /// A request to dump all coins to JSON. 265 struct WalletBackendDumpCoinsRequest: WalletBackendFormattedRequest { 266 var operation: String { "dumpCoins" } 267 func args() -> Args { Args() } 268 struct Args: Encodable { } 269 270 struct Response: Decodable { 271 var coins: [Coin] 272 } 273 274 } 275 276 /// A request to suspend or unsuspend a coin. 277 struct WalletBackendSuspendCoinRequest: WalletBackendFormattedRequest { 278 struct Response: Decodable {} 279 var operation: String { "setCoinSuspended" } 280 func args() -> Args { Args(coinPub: coinPub, suspended: suspended) } 281 282 var coinPub: String 283 var suspended: Bool 284 285 struct Args: Encodable { 286 var coinPub: String 287 var suspended: Bool 288 } 289 } 290 291