commit dbe41fb94ec92b4cbfa694e26b45ccc4e5f6aa78
parent d2e04e2538f193f6bb6f386516438ffbebdc670f
Author: Marc Stibane <marc@taler.net>
Date: Fri, 14 Aug 2026 09:24:55 +0200
AI: Tax receipts
Diffstat:
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