taler-ios

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

Model+Payment.swift (16690B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  */
      8 import Foundation
      9 import taler_swift
     10 import AnyCodable
     11 //import SymLog
     12 
     13 // MARK: - ContractTerms
     14 
     15 //  export interface TalerProtocolDuration {
     16 //      readonly d_us: number | "forever";
     17 //  }
     18 
     19 struct TokenIssuePublicKey: Codable {
     20     let cipher: String          // "RSA", "CS"
     21 
     22     // RSA public key
     23     let rsaPub: String?         // RSA public key converted to Crockford Base32
     24 
     25     // CS public key
     26     let csPub: String?          // 32-byte value representing a point on Curve25519
     27 
     28     // Start time of this key's signatures validity period
     29     let signatureValidityStart: Timestamp
     30 
     31     // End time of this key's signatures validity period
     32     let signatureValidityEnd: Timestamp
     33 
     34     enum CodingKeys: String, CodingKey {
     35         case cipher
     36         case rsaPub = "rsa_pub"
     37         case csPub = "cs_pub"
     38         case signatureValidityStart = "signature_validity_start"
     39         case signatureValidityEnd = "signature_validity_end"
     40     }
     41 }
     42 
     43 struct ContractTokenDetails: Codable {
     44     let clazz: String                           // "subscription", "discount"
     45 
     46     // Array of domain names where this subscription can be safely used
     47     // (e.g. the issuer warrants that these sites will re-issue tokens of this type
     48     // if the respective contract says so).  May contain "*" for any domain or subdomain.
     49     let trustedDomains: [String]?               // only for subscription
     50 
     51     // Array of domain names where this discount token is intended to be used.
     52     // May contain "*" for any domain or subdomain.  Users should be warned about sites
     53     // proposing to consume discount tokens of this type that are not in this list that
     54     // the merchant is accepting a coupon from a competitor and thus may be attaching
     55     // different semantics (like get 20% discount for my competitors 30% discount token).
     56     let expectedDomains: [String]?              // only for discount
     57 
     58     enum CodingKeys: String, CodingKey {
     59         case clazz = "class"
     60         case trustedDomains = "trusted_domains"
     61         case expectedDomains = "expected_domains"
     62     }
     63 }
     64 
     65 struct ContractTokenFamily: Codable {
     66     // Human-readable name of the token family.
     67     let name: String
     68 
     69     // Human-readable description of the semantics of this token family (for display).
     70     let description: String
     71 
     72     // Map from IETF BCP 47 language tags to localized descriptions.
     73     let descriptionI18n: [String: String]?
     74 
     75     // Public keys used to validate tokens issued by this token family.
     76     let keys: [TokenIssuePublicKey]
     77 
     78     // Kind-specific information of the token
     79     let details: ContractTokenDetails
     80 
     81     // Must a wallet understand this token type to
     82     // process contracts that use or issue it?
     83     let critical: Bool
     84 
     85     enum CodingKeys: String, CodingKey {
     86         case name, description
     87         case descriptionI18n = "description_i18n"
     88         case keys, details, critical
     89     }
     90 }
     91 
     92 struct ContractInput: Codable {
     93     let type: String                            // "token"
     94 
     95     // Slug of the token family in the token_families map on the order
     96     let tokenFamilySlug: String?
     97 
     98     // Number of tokens of this type required.
     99     // Defaults to one if the field is not provided.
    100     let count: Int?
    101 
    102     enum CodingKeys: String, CodingKey {
    103         case type, count
    104         case tokenFamilySlug = "token_family_slug"
    105     }
    106 }
    107 
    108 struct ContractOutput: Codable {
    109     let type: String                            // "token"
    110 
    111     // Slug of the token family in the token_families map on the order
    112     let tokenFamilySlug: String?
    113 
    114     // Number of tokens of this type required.
    115     // Defaults to one if the field is not provided.
    116     let count: Int?
    117 
    118     // Index of the public key for this output token
    119     // in the ContractTokenFamily keys array.
    120     let keyIndex: Int
    121 
    122     enum CodingKeys: String, CodingKey {
    123         case type, count
    124         case tokenFamilySlug = "token_family_slug"
    125         case keyIndex = "key_index"
    126     }
    127 }
    128 
    129 struct ContractOutputTaxReceipt: Codable {
    130     let type: String                            // "tax-receipt"
    131 
    132     // Array of base URLs of donation authorities that can be
    133     // used to issue the tax receipts. The client must select one.
    134     let donauUrls: [String]
    135 
    136     // Total amount that will be on the tax receipt.
    137     let amount: Amount
    138 
    139     enum CodingKeys: String, CodingKey {
    140         case type, amount
    141         case donauUrls = "donau_urls"
    142     }
    143 }
    144 
    145 struct ContractChoice: Codable {
    146     let amount: Amount                          // Total amount payable
    147     let maxFee: Amount                          // Maximum deposit fee covered by the merchant
    148     let description: String?                    //
    149     let descriptionI18n: [String: String]?      //      "      localized     "
    150     let inputs: [ContractInput]
    151     let outputs: [ContractOutput]
    152 
    153     enum CodingKeys: String, CodingKey {
    154         case amount, description
    155         case maxFee = "max_fee"
    156         case descriptionI18n = "description_i18n"
    157         case inputs, outputs
    158     }
    159 }
    160 
    161 struct MerchantContractTerms: Codable {
    162     let version: Int?
    163 
    164     // ContractTermsV0
    165     let amount: Amount?                 // Total amount payable
    166     let maxFee: Amount?                 // Maximum deposit fee covered by the merchant
    167 
    168     // ContractTermsCommon
    169     let summary: String                 // Human-readable short summary of the contract
    170     let summaryI18n: [String: String]?  //      "      localized     "
    171     let orderID: String                 // uniquely identify the purchase within one merchant instance
    172     let publicReorderURL: String?       // URL meant to share the shopping cart
    173     let fulfillmentURL: String?         // Fulfillment URL to view the product or delivery status
    174     let fulfillmentMessage: String?     // Plain text fulfillment message in the merchant's default language
    175     let fulfillmentMessageI18n: String? // Plain text fulfillment message in the merchant's default language
    176     let products: [Product]?            // Products that are sold in this contract
    177     let timestamp: Timestamp            // Time when the contract was generated by the merchant
    178     let refundDeadline: Timestamp?      // Deadline for refunds
    179     let payDeadline: Timestamp          // Deadline to pay for the contract
    180     let wireTransferDeadline: Timestamp?// Deadline for the wire transfer
    181     let merchantPub: String             // Public key of the merchant
    182     let merchantBaseURL: String         // Base URL of the merchant's backend
    183     let merchant: Merchant
    184     let hWire: String                   // Hash of the merchant's wire details
    185     let wireMethod: String              // merchant wants to use
    186     let exchanges: [ExchangeForPay]
    187     let deliveryLocation: Location?     // Delivery location for (all!) products
    188     let deliveryDate: Timestamp?        // indicating when the order should be delivered
    189     let nonce: String                   // used to ensure freshness
    190     let autoRefund: Duration?
    191     let extra: Extra?                   // Extra data, interpreted by the merchant only
    192     let minimumAge: Int?
    193 
    194 // deprecated   let wireFeeAmortization: Int?       // Share of the wire fee that must be settled with one payment
    195 //    let maxWireFee: Amount?             // Maximum wire fee that the merchant agrees to pay for
    196 //    let auditors: [Auditor]?
    197 
    198     // ContractTermsV1
    199     let choices: [ContractChoice]?
    200     // Map of storing metadata and issue keys of
    201     // token families referenced in this contract.
    202     // @since protocol **vSUBSCRIBE**
    203     let tokenFamilies: [String: ContractTokenFamily]?
    204 
    205     enum CodingKeys: String, CodingKey {
    206         case version
    207         case amount
    208         case maxFee = "max_fee"
    209 
    210         case summary
    211         case summaryI18n = "summary_i18n"
    212         case orderID = "order_id"
    213         case publicReorderURL = "public_reorder_url"
    214         case fulfillmentURL = "fulfillment_url"
    215         case fulfillmentMessage = "fulfillment_message"
    216         case fulfillmentMessageI18n = "fulfillment_message_i18n"
    217         case products
    218         case timestamp
    219         case refundDeadline = "refund_deadline"
    220         case payDeadline = "pay_deadline"
    221         case wireTransferDeadline = "wire_transfer_deadline"
    222         case merchantPub = "merchant_pub"
    223         case merchantBaseURL = "merchant_base_url"
    224         case merchant
    225         case hWire = "h_wire"
    226         case wireMethod = "wire_method"
    227         case exchanges
    228         case deliveryLocation = "delivery_location"
    229         case deliveryDate = "delivery_date"
    230         case nonce
    231         case autoRefund = "auto_refund"
    232         case extra
    233         case minimumAge = "minimum_age"
    234 
    235 //        case wireFeeAmortization = "wire_fee_amortization"
    236 //        case maxWireFee = "max_wire_fee"
    237 //        case auditors
    238         case choices
    239         case tokenFamilies = "token_families"
    240     }
    241 }
    242 // MARK: - Auditor
    243 struct Auditor: Codable {
    244     let name: String
    245     let auditorPub: String
    246     let url: String
    247 
    248     enum CodingKeys: String, CodingKey {
    249         case name
    250         case auditorPub = "auditor_pub"
    251         case url
    252     }
    253 }
    254 // MARK: - Exchange
    255 struct ExchangeForPay: Codable {
    256     let url: String
    257     let masterPub: String
    258 
    259     enum CodingKeys: String, CodingKey {
    260         case url
    261         case masterPub = "master_pub"
    262     }
    263 }
    264 // MARK: - Extra
    265 struct Extra: Codable {
    266     let articleName: String?
    267 
    268     enum CodingKeys: String, CodingKey {
    269         case articleName = "article_name"
    270     }
    271 }
    272 // MARK: -
    273 enum PreparePayResultType: String, Codable {
    274     case paymentPossible = "payment-possible"
    275     case alreadyConfirmed = "already-confirmed"
    276     case insufficientBalance = "insufficient-balance"
    277     case choiceSelection = "choice-selection"
    278 }
    279 
    280 struct PayMerchantInsufficientBalanceDetails: Codable {
    281     let amountRequested: Amount
    282     let balanceAvailable: Amount
    283     let balanceMaterial: Amount
    284     let balanceAgeAcceptable: Amount
    285     let balanceReceiverAcceptable: Amount
    286     let balanceReceiverDepositable: Amount
    287     let perExchange: [String:ExchangeFeeGapEstimate]
    288     let causeHint: InsufficientBalanceHint
    289 }
    290 
    291 struct ExchangeFeeGapEstimate: Codable {
    292     let balanceAvailable: Amount
    293     let balanceMaterial: Amount
    294     let balanceExchangeDepositable: Amount
    295     let balanceAgeAcceptable: Amount
    296     let balanceReceiverAcceptable: Amount
    297     let balanceReceiverDepositable: Amount
    298     let maxEffectiveSpendAmount: Amount
    299 }
    300 
    301 struct PerScopeDetails: Codable {
    302     let scopeInfo: ScopeInfo
    303 }
    304 /// The result from PreparePayForUri
    305 struct PreparePayResult: Codable {
    306     let status: PreparePayResultType                            // InsufficientBalance, AlreadyConfirmed, PaymentPossible, ChoiceSelection
    307     let transactionId: String
    308     let contractTerms: MerchantContractTerms
    309     let contractTermsHash: String?                              // not for InsufficientBalance
    310     let scopes: [ScopeInfo]?                                    // not for ChoiceSelection
    311 //  let detailsPerScope: [ScopeInfo : PreparePayDetails]
    312     let amountRaw: Amount                                       // TODO: not for ChoiceSelection
    313 
    314     let amountEffective: Amount?                                // only if status != insufficientBalance
    315     let paid: Bool?                                             // only if status == alreadyConfirmed
    316     let talerUri: String?
    317     let balanceDetails: PayMerchantInsufficientBalanceDetails?  // only if status == insufficientBalance
    318 }
    319 /// A request to get an exchange's payment contract terms.
    320 fileprivate struct PreparePayForUri: WalletBackendFormattedRequest {
    321     typealias Response = PreparePayResult
    322     func operation() -> String { "preparePayForUri" }
    323     func args() -> Args { Args(talerPayUri: talerPayUri) }
    324 
    325     var talerPayUri: String
    326     struct Args: Encodable {
    327         var talerPayUri: String
    328     }
    329 }
    330 
    331 struct TemplateParams: Codable {
    332     let amount: Amount?                     // Total amount payable
    333     let summary: String?                    // Human-readable short summary of the contract
    334 }
    335 /// A request to get an exchange's payment contract terms.
    336 fileprivate struct PreparePayForTemplateRequest: WalletBackendFormattedRequest {
    337     typealias Response = PreparePayResult
    338     func operation() -> String { "preparePayForTemplate" }
    339     func args() -> Args { Args(talerPayTemplateUri: talerPayTemplateUri, templateParams: templateParams) }
    340 
    341     var talerPayTemplateUri: String
    342     var templateParams: TemplateParams
    343     struct Args: Encodable {
    344         var talerPayTemplateUri: String
    345         var templateParams: TemplateParams
    346     }
    347 }
    348 // MARK: -
    349 struct TemplateContractDetails: Codable {
    350     let summary: String?                // Human-readable short summary of the contract. Editable if nil
    351     let currency: String?               // specify currency when amount is nil - unspecified if nil
    352     let amount: Amount?                 // Total amount payable. Fixed if this field exists, editable if nil
    353     let scopeInfo: ScopeInfo?
    354     let minimumAge: Int
    355     let payDuration: RelativeTime
    356 
    357     enum CodingKeys: String, CodingKey {
    358         case summary, currency, amount
    359         case scopeInfo
    360         case minimumAge = "minimum_age"
    361         case payDuration = "pay_duration"
    362     }
    363 }
    364 struct TemplateContractDetailsDefaults: Codable {
    365     let summary: String?                // Default 'Human-readable summary' when editable: empty if nil
    366     let currency: String?               // Default currency when unspecified: any if nil (e.g. donations)
    367     let amount: Amount?                 // Default amount when editable: unspecified if nil
    368 }
    369 struct TalerMerchantTemplateDetails: Codable {
    370     let templateContract: TemplateContractDetails
    371     let editableDefaults: TemplateContractDetailsDefaults?
    372 //    let requiredCurrency: String?
    373     enum CodingKeys: String, CodingKey {
    374         case templateContract = "template_contract"
    375         case editableDefaults = "editable_defaults"
    376 //        case requiredCurrency = "required_currency"
    377     }
    378 }
    379 
    380 /// The result from checkPayForTemplate
    381 struct WalletTemplateDetails: Codable {
    382     let templateDetails: TalerMerchantTemplateDetails
    383     let supportedCurrencies: [String]
    384 }
    385 /// A request to get an exchange's payment contract terms.
    386 fileprivate struct CheckPayForTemplate: WalletBackendFormattedRequest {
    387     typealias Response = WalletTemplateDetails
    388     func operation() -> String { "checkPayForTemplate" }
    389     func args() -> Args { Args(talerPayTemplateUri: talerPayTemplateUri) }
    390 
    391     var talerPayTemplateUri: String
    392     struct Args: Encodable {
    393         var talerPayTemplateUri: String
    394     }
    395 }
    396 // MARK: -
    397 /// The result from confirmPayForUri
    398 struct ConfirmPayResult: Decodable {
    399     var type: String
    400     var contractTerms: MerchantContractTerms
    401     var transactionId: String
    402 }
    403 /// A request to get an exchange's payment details.
    404 fileprivate struct ConfirmPayForUri: WalletBackendFormattedRequest {
    405     typealias Response = ConfirmPayResult
    406     func operation() -> String { "confirmPay" }
    407     func args() -> Args { Args(transactionId: transactionId) }
    408 
    409     var transactionId: String
    410     struct Args: Encodable {
    411         var transactionId: String
    412     }
    413 }
    414 // MARK: -
    415 extension WalletModel {
    416     /// load payment details. Networking involved
    417     nonisolated func checkPayForTemplate(_ talerPayTemplateUri: String, viewHandles: Bool = false)
    418       async throws -> WalletTemplateDetails {
    419         let request = CheckPayForTemplate(talerPayTemplateUri: talerPayTemplateUri)
    420         let response = try await sendRequest(request, viewHandles: viewHandles)
    421         return response
    422     }
    423 
    424     nonisolated func preparePayForTemplate(_ talerPayTemplateUri: String, amount: Amount?, summary: String?, viewHandles: Bool = false)
    425       async throws -> PreparePayResult {
    426         let templateParams = TemplateParams(amount: amount, summary: summary)
    427         let request = PreparePayForTemplateRequest(talerPayTemplateUri: talerPayTemplateUri, templateParams: templateParams)
    428         let response = try await sendRequest(request, viewHandles: viewHandles)
    429         return response
    430     }
    431 
    432     nonisolated func preparePayForUri(_ talerPayUri: String, viewHandles: Bool = false)
    433       async throws -> PreparePayResult {
    434         let request = PreparePayForUri(talerPayUri: talerPayUri)
    435         let response = try await sendRequest(request, viewHandles: viewHandles)
    436         return response
    437     }
    438 
    439     nonisolated func confirmPay(_ transactionId: String, viewHandles: Bool = false)
    440       async throws -> ConfirmPayResult {
    441         let request = ConfirmPayForUri(transactionId: transactionId)
    442         let response = try await sendRequest(request, viewHandles: viewHandles)
    443         return response
    444     }
    445 }