taler-ios

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

commit dbe41fb94ec92b4cbfa694e26b45ccc4e5f6aa78
parent d2e04e2538f193f6bb6f386516438ffbebdc670f
Author: Marc Stibane <marc@taler.net>
Date:   Fri, 14 Aug 2026 09:24:55 +0200

AI: Tax receipts

Diffstat:
MTalerWallet1/Model/Model+Payment.swift | 40++++++++++++++++++++++++++++++++++++----
1 file changed, 36 insertions(+), 4 deletions(-)

diff --git a/TalerWallet1/Model/Model+Payment.swift b/TalerWallet1/Model/Model+Payment.swift @@ -123,7 +123,7 @@ struct ContractOutput: Codable, Hashable { } } -struct ContractOutputTaxReceipt: Codable { +struct ContractOutputTaxReceipt: Codable, Hashable { let type: String // "tax-receipt" // Array of base URLs of donation authorities that can be @@ -131,7 +131,7 @@ struct ContractOutputTaxReceipt: Codable { let donauUrls: [String] // Total amount that will be on the tax receipt. - let amount: Amount + let amount: Amount? enum CodingKeys: String, CodingKey { case type, amount @@ -139,13 +139,45 @@ struct ContractOutputTaxReceipt: Codable { } } +/// wallet-core sends a union discriminated on "type" - a tax-receipt output has neither +/// "key_index" nor "token_family_slug", so decoding it as ContractOutput throws and takes +/// the whole getChoicesForPayment response with it, leaving nothing for the user to confirm. +enum ContractOutputAny: Codable, Hashable { + case token(ContractOutput) + case taxReceipt(ContractOutputTaxReceipt) + case unknown(String) // a type added after this was written + + private enum TypeKey: String, CodingKey { + case type + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TypeKey.self) + switch try container.decode(String.self, forKey: .type) { + case "token": self = .token(try ContractOutput(from: decoder)) + case "tax-receipt": self = .taxReceipt(try ContractOutputTaxReceipt(from: decoder)) + case let other: self = .unknown(other) + } + } + + func encode(to encoder: Encoder) throws { + switch self { + case .token(let output): try output.encode(to: encoder) + case .taxReceipt(let output): try output.encode(to: encoder) + case .unknown(let type): + var container = encoder.container(keyedBy: TypeKey.self) + try container.encode(type, forKey: .type) + } + } +} + struct ContractChoice: Codable, Hashable { let amount: Amount // Total amount payable let maxFee: Amount // Maximum deposit fee covered by the merchant let description: String? // let descriptionI18n: I18nDict? // " localized " let inputs: [ContractInput] - let outputs: [ContractOutput] + let outputs: [ContractOutputAny] enum CodingKeys: String, CodingKey { case amount, description @@ -185,7 +217,7 @@ struct MerchantContractTerms: Codable { let publicReorderURL: String? // URL meant to share the shopping cart let fulfillmentURL: String? // Fulfillment URL to view the product or delivery status let fulfillmentMessage: String? // Plain text fulfillment message in the merchant's default language - let fulfillmentMessageI18n: String? // Plain text fulfillment message in the merchant's default language + let fulfillmentMessageI18n: I18nDict?// " localized ", keyed by BCP-47 tag let products: [Product]? // Products that are sold in this contract let timestamp: Timestamp // Time when the contract was generated by the merchant let refundDeadline: Timestamp? // Deadline for refunds