taler-ios

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

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