WalletBackendRequest.swift (10039B)
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, Hashable { 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 ProductSold: Codable, Identifiable, Hashable { 143 let productId: String? 144 let productName: String? 145 // let productGroupId: Int? 146 let description: String 147 let descriptionI18n: I18nDict? 148 let quantity: Int? 149 let unitQuantity: String? // float with max 6 fractional digits 150 let unit: String? // MerchantUnit? 151 let price: Amount? // deprecated - use prices 152 let prices: [Amount]? 153 let pricesAreNet: Bool? 154 let image: String? // product image 128x128 encoded 155 let taxes: [Tax]? 156 let deliveryDate: Timestamp? 157 let productMoneyPot: Int? 158 159 var id: String { productId ?? "unknown" } 160 161 var locDescription: String { 162 self.descriptionI18n?.localized ?? self.description 163 } 164 165 enum CodingKeys: String, CodingKey { 166 case productId = "product_id" 167 case productName = "product_name" 168 // case productGroupId = "product_group_id" 169 case description 170 case descriptionI18n = "description_i18n" 171 case quantity 172 case unitQuantity = "unit_quantity" 173 // case unitAllowFraction = "unit_allow_fraction" 174 case unit 175 case price 176 // case unitPrice = "unit_price" 177 case prices 178 case pricesAreNet = "prices_are_net" 179 case image 180 case taxes 181 case deliveryDate = "delivery_date" 182 case productMoneyPot = "product_money_pot" 183 } 184 } 185 186 187 /// Brief information about an order. 188 struct OrderShortInfo: Codable { 189 var orderId: String 190 var merchant: MerchantInfo 191 var summary: String 192 var summary_i18n: I18nDict? 193 // var products: [Product]? // MerchantContractTerms has [ProductSold] 194 var fulfillmentUrl: String? 195 var fulfillmentMessage: String? 196 var fulfillmentMessage_i18n: I18nDict? 197 var contractTermsHash: String? 198 199 var locSummary: String { 200 self.summary_i18n?.localized ?? self.summary 201 } 202 } 203 // MARK: - 204 /// A request to force update an exchange. 205 struct WalletBackendForceUpdateRequest: WalletBackendFormattedRequest { 206 var operation: String { "addRequest" } 207 func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) } 208 209 var exchangeBaseUrl: String 210 struct Args: Encodable { 211 var exchangeBaseUrl: String 212 } 213 214 struct Response: Decodable {} 215 } 216 217 /// A request to deposit funds. 218 struct WalletBackendCreateDepositGroupRequest: WalletBackendFormattedRequest { 219 var operation: String { "createDepositGroup" } 220 func args() -> Args { Args(depositPayToUri: depositePayToUri, amount: amount) } 221 222 var depositePayToUri: String 223 var amount: Amount 224 225 struct Args: Encodable { 226 var depositPayToUri: String 227 var amount: Amount 228 } 229 230 struct Response: Decodable { 231 var depositGroupId: String 232 } 233 } 234 235 /// A request to get information about a payment request. 236 struct WalletBackendPreparePayRequest: WalletBackendFormattedRequest { 237 var operation: String { "preparePay" } 238 func args() -> Args { Args(talerPayUri: talerPayUri) } 239 var talerPayUri: String 240 241 struct Args: Encodable { 242 var talerPayUri: String 243 } 244 245 struct Response: Decodable {} 246 } 247 248 // MARK: - 249 struct IntegrationTestArgs: Codable { 250 var exchangeBaseUrl: String 251 var bankBaseUrl: String 252 var merchantBaseUrl: String 253 var merchantApiKey: String 254 var amountToWithdraw: String 255 var amountToSpend: String 256 } 257 258 /// A request to run a basic integration test. 259 struct WalletBackendRunIntegrationTestRequest: WalletBackendFormattedRequest { 260 var operation: String { "runIntegrationTest" } 261 func args() -> Args { integrationTestArgs } 262 var integrationTestArgs: IntegrationTestArgs 263 264 typealias Args = IntegrationTestArgs 265 266 struct Response: Decodable {} 267 } 268 269 struct TestPayArgs: Codable { 270 var merchantBaseUrl: String 271 var merchantApiKey: String 272 var amount: String 273 var summary: String 274 } 275 276 /// A request to make a test payment. 277 struct WalletBackendTestPayRequest: WalletBackendFormattedRequest { 278 var operation: String { "testPay" } 279 func args() -> Args { testPayArgs } 280 281 var testPayArgs: TestPayArgs 282 typealias Args = TestPayArgs 283 284 struct Response: Decodable {} 285 } 286 287 struct Coin: Codable { 288 var denom_pub: String 289 var denom_pub_hash: String 290 var denom_value: String 291 var coin_pub: String 292 var exchange_base_url: String 293 var remaining_value: String 294 var refresh_parent_coin_pub: String 295 var withdrawal_reserve_pub: String 296 var coin_suspended: Bool 297 } 298 299 /// A request to dump all coins to JSON. 300 struct WalletBackendDumpCoinsRequest: WalletBackendFormattedRequest { 301 var operation: String { "dumpCoins" } 302 func args() -> Args { Args() } 303 struct Args: Encodable { } 304 305 struct Response: Decodable { 306 var coins: [Coin] 307 } 308 309 } 310 311 /// A request to suspend or unsuspend a coin. 312 struct WalletBackendSuspendCoinRequest: WalletBackendFormattedRequest { 313 struct Response: Decodable {} 314 var operation: String { "setCoinSuspended" } 315 func args() -> Args { Args(coinPub: coinPub, suspended: suspended) } 316 317 var coinPub: String 318 var suspended: Bool 319 320 struct Args: Encodable { 321 var coinPub: String 322 var suspended: Bool 323 } 324 } 325 326