gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-ios] 05/07: Transaction definition and JSON decoding - Bug


From: gnunet
Subject: [taler-taler-ios] 05/07: Transaction definition and JSON decoding - Bug 7678
Date: Wed, 22 Feb 2023 16:16:32 +0100

This is an automated email from the git hooks/post-receive script.

marc-stibane pushed a commit to branch master
in repository taler-ios.

commit cc281301979d4b0ed3732522c6b95057c9e89c89
Author: Marc Stibane <marc@taler.net>
AuthorDate: Mon Feb 20 15:13:43 2023 +0100

    Transaction definition and JSON decoding - Bug 7678
---
 TalerWallet1/Backend/Transaction.swift             | 384 ++++++++-------------
 .../Views/Transactions/TransactionDetail.swift     |  29 +-
 .../Views/Transactions/TransactionRow.swift        |  22 +-
 .../Views/Transactions/TransactionsListView.swift  |   7 +-
 4 files changed, 174 insertions(+), 268 deletions(-)

diff --git a/TalerWallet1/Backend/Transaction.swift 
b/TalerWallet1/Backend/Transaction.swift
index 86a0ae9..aa2211d 100644
--- a/TalerWallet1/Backend/Transaction.swift
+++ b/TalerWallet1/Backend/Transaction.swift
@@ -22,293 +22,193 @@ enum TransactionTypeError: Error {
     case unknownTypeError
 }
 
-/// Different types of transactions.
-enum TransactionType: Codable {
-    case withdrawal
-    case payment
-    case refund
-    case tip
-    case refresh
-    
-    init(from decoder: Decoder) throws {
-        let value = try decoder.singleValueContainer()
-        let str = try value.decode(String.self)
-        let codingNames = [
-            "TransactionWithdrawal" : TransactionType.withdrawal,
-            "TransactionPayment" : TransactionType.payment,
-            "TransactionRefund" : TransactionType.refund,
-            "TransactionTip" : TransactionType.tip,
-            "TransactionRefresh" : TransactionType.refresh
-        ]
-        if let type = codingNames[str] {
-            self = type
-        } else {
-            throw TransactionTypeError.unknownTypeError
-        }
-    }
-    
-    func encode(to encoder: Encoder) throws {
-        var value = encoder.singleValueContainer()
-        switch self {
-            case .withdrawal:
-                try value.encode("TransactionWithdrawal")
-            case .payment:
-                try value.encode("TransactionPayment")
-            case .refund:
-                try value.encode("TransactionRefund")
-            case .tip:
-                try value.encode("TransactionTip")
-            case .refresh:
-                try value.encode("TransactionRefresh")
-        }
-    }
-}
-
 enum TransactionDecodingError: Error {
     case invalidStringValue
 }
 
-/// Details for a manual withdrawal.
-struct ManualWithdrawalDetails: Codable {
-    /// The payto URIs that the exchange supports.
-    var exchangePaytoUris: [String]
-    
-    /// The public key of the newly created reserve.
-    var reservePub: String
+struct TransactionCommon: Decodable {
+    var type: String
+    var amountEffective: Amount
+    var amountRaw: Amount
+    var transactionId: String
+    var timestamp: Timestamp
+    var extendedStatus: String      // TODO: enum with some fixed values?
+    var pending: Bool
+    var frozen: Bool
 }
 
-/// Details for a bank-integrated withdrawal.
-struct BankIntegratedWithdrawalDetails: Codable {
-    /// Whether the bank has confirmed the withdrawal.
-    var confirmed: Bool
+struct WithdrawalDetails: Decodable {
+    enum WithdrawalType: String, Decodable {
+        case manual = "manual-transfer"
+        case bankIntegrated = "taler-bank-integration-api"
+        case peerPullCredit = "peer-pull-credit"
+        case peerPushCredit = "peer-push-credit"
+    }
+    var type: WithdrawalType
+    /// The public key of the reserve.
+    var reservePub: String
 
-    /// The public key of the newly created reserve.
-    var reservePub: String?
+  /// Details for manual withdrawals:
+    /// The payto URIs that the exchange supports.
+    var exchangePaytoUris: [String]?
 
+  /// Details for bank-integrated withdrawals:
+    /// Whether the bank has confirmed the withdrawal.
+    var confirmed: Bool?
     /// URL for user-initiated confirmation
     var bankConfirmationUrl: String?
 }
 
-/// A withdrawal transaction.
-struct TransactionWithdrawal: Decodable {
-    enum WithdrawalDetails {
-        case manual(ManualWithdrawalDetails)
-        case bankIntegrated(BankIntegratedWithdrawalDetails)
-    }
-    
-    /// The exchange that was withdrawn from.
+struct WithdrawalTransactionDetails: Decodable {
     var exchangeBaseUrl: String
-    
-    /// The amount of the withdrawal, including fees.
-    var amountRaw: Amount
-    
-    /// The amount that will be added to the withdrawer's account.
-    var amountEffective: Amount
-    
-    /// The details of the withdrawal.
     var withdrawalDetails: WithdrawalDetails
-    
-    init(from decoder: Decoder) throws {
-        enum CodingKeys: String, CodingKey {
-            case exchangeBaseUrl
-            case amountRaw
-            case amountEffective
-            case withdrawalDetails
-            case type
-            case exchangePaytoUris
-            case reservePub
-            case confirmed
-            case bankConfirmationUrl
-        }
-        
-        let value = try decoder.container(keyedBy: CodingKeys.self)
-        self.exchangeBaseUrl = try value.decode(String.self, forKey: 
.exchangeBaseUrl)
-        self.amountRaw = try value.decode(Amount.self, forKey: .amountRaw)
-        self.amountEffective = try value.decode(Amount.self, forKey: 
.amountEffective)
-        
-        let detail = try value.nestedContainer(keyedBy: CodingKeys.self, 
forKey: .withdrawalDetails)
-        let detailType = try detail.decode(String.self, forKey: .type)
-        if detailType == "manual-transfer" {
-            let paytoUris = try detail.decode([String].self, forKey: 
.exchangePaytoUris)
-            let reservePub = try detail.decode(String.self, forKey: 
.reservePub)
-            let manual = ManualWithdrawalDetails(exchangePaytoUris: paytoUris, 
reservePub: reservePub)
-            self.withdrawalDetails = .manual(manual)
-        } else if detailType == "taler-bank-integration-api" {
-            let confirmed = try detail.decode(Bool.self, forKey: .confirmed)
-            var bankConfirmationUrl: String? = nil
-            if detail.contains(.bankConfirmationUrl) {
-                bankConfirmationUrl = try detail.decode(String.self, forKey: 
.bankConfirmationUrl)
-            }
-            var reservePub : String? = nil
-            if detail.contains(.reservePub) {
-                reservePub = try detail.decode(String.self, forKey: 
.reservePub)
-            }
-            let bankDetails = BankIntegratedWithdrawalDetails(confirmed: 
confirmed, reservePub: reservePub,
-                                                              
bankConfirmationUrl: bankConfirmationUrl)
-            self.withdrawalDetails = .bankIntegrated(bankDetails)
-        } else {
-            throw TransactionDecodingError.invalidStringValue
-        }
-    }
 }
-#if DEBUG
-extension TransactionWithdrawal {       // for PreViews
-    init(url: String) {
-        self.exchangeBaseUrl = url
-        self.amountRaw = try! Amount(fromString: "Taler:5")
-        self.amountEffective = try! Amount(fromString: "Taler:4.8")
-        let bankDetails = BankIntegratedWithdrawalDetails(confirmed: true, 
reservePub: nil,
-                                                          bankConfirmationUrl: 
nil)
-        self.withdrawalDetails = .bankIntegrated(bankDetails)
-    }
+
+struct WithdrawalTransaction {
+    var common: TransactionCommon
+    var details: WithdrawalTransactionDetails
 }
-#endif
 
-/// A payment transaction.
-struct TransactionPayment: Codable {
-    /// Additional information about the payment.
-    // TODO
-    
-    /// An identifier for the payment.
+struct PaymentTransactionDetails: Decodable {
     var proposalId: String
-    
-    /// The current status of the payment.
-    // TODO
-    
-    /// The amount that must be paid.
-    var amountRaw: Amount
-    
-    /// The amount that was paid.
-    var amountEffective: Amount
+    var status: String          // "paid"
+    var totalRefundRaw: Amount
+    var totalRefundEffective: Amount
+    var refundPending: Amount?
+//    var refunds: []
+    var info: OrderShortInfo
 }
 
-/// A refund transaction.
-struct TransactionRefund: Codable {
-    /// Identifier for the refund.
+struct PaymentTransaction {
+    var common: TransactionCommon
+    var details: PaymentTransactionDetails
+}
+
+struct RefundTransactionDetails: Decodable {
     var refundedTransactionId: String
-    
-    /// Additional information about the refund
-    // TODO
-    
+    var refundPending: Amount?
     /// The amount that couldn't be applied because refund permissions expired.
-    var amountInvalid: Amount
-    
-    /// The amount refunded by the merchant.
-    var amountRaw: Amount
-    
-    /// The amount paid to the wallet after fees.
-    var amountEffective: Amount
+    var amountInvalid: Amount?
+    var info: OrderShortInfo
 }
 
-/// A tip transaction.
-struct TransactionTip: Codable {
-    /// The current status of the tip.
-    // TODO
-    
+struct RefundTransaction {
+    var common: TransactionCommon
+    var details: RefundTransactionDetails
+}
+
+struct TipTransactionDetails: Decodable {
     /// The exchange that the tip will be withdrawn from
     var exchangeBaseUrl: String
-    
-    /// More information about the merchant sending the tip.
-    // TODO
-    
-    /// The raw amount of the tip without fees.
-    var amountRaw: Amount
-    
-    /// The amount added to the recipient's wallet.
-    var amountEffective: Amount
 }
 
-/// A refresh transaction.
-struct TransactionRefresh: Codable {
+struct TipTransaction {
+    var common: TransactionCommon
+    var details: TipTransactionDetails
+}
+
+struct RefreshTransactionDetails: Decodable {
     /// The exchange that the coins are refreshed with.
     var exchangeBaseUrl: String
-    
-    /// The raw amount to refresh.
-    var amountRaw: Amount
-    
-    /// The amount to be paid as fees for the refresh.
-    var amountEffective: Amount
 }
 
-/// A wallet transaction.
-struct Transaction: Decodable, Hashable {
-//    private let symLog = SymLogC(0)
-
-    var type: String
-    var amountRaw: Amount
-    var amountEffective: Amount
-    var transactionId: String
-    var timestamp: Timestamp
-    var extendedStatus: String
-    var pending: Bool
-    var frozen: Bool
+struct RefreshTransaction {
+    var common: TransactionCommon
+    var details: RefreshTransactionDetails
+}
 
-    var error: AnyCodable?
-    var exchangeBaseUrl: String?
+enum Transaction: Decodable, Hashable, Identifiable {
+    case withdrawal (WithdrawalTransaction)
+    case payment (PaymentTransaction)
+    case refund (RefundTransaction)
+    case tip (TipTransaction)
+    case refresh (RefreshTransaction)
 
+    init(from decoder: Decoder) throws {
+        let common = try TransactionCommon.init(from: decoder)
+
+        switch (common.type) {
+            case WITHDRAWAL:
+                let details = try WithdrawalTransactionDetails.init(from: 
decoder)
+                self = .withdrawal(WithdrawalTransaction(common: common, 
details: details))
+            case PAYMENT:
+                let details = try PaymentTransactionDetails.init(from: decoder)
+                self = .payment(PaymentTransaction(common: common, details: 
details))
+            case REFUND:
+                let details = try RefundTransactionDetails.init(from: decoder)
+                self = .refund(RefundTransaction(common: common, details: 
details))
+            case TIP:
+                let details = try TipTransactionDetails.init(from: decoder)
+                self = .tip(TipTransaction(common: common, details: details))
+            case REFRESH:
+                let details = try RefreshTransactionDetails.init(from: decoder)
+                self = .refresh(RefreshTransaction(common: common, details: 
details))
+            default:
+                let context = DecodingError.Context(
+                    codingPath: decoder.codingPath,
+                    debugDescription: "Invalid transaction type")
+                throw DecodingError.typeMismatch(Transaction.self, context)
+        }
+    }
 
+    var id: String { common().transactionId }
 
-//    enum TransactionDetail {
-//        case withdrawal(TransactionWithdrawal)
-//    }
-    
-//    var detail: TransactionDetail
-    
-//    init(from decoder: Decoder) throws {
-//        enum CodingKeys: String, CodingKey {
-//            case transactionId
-//            case timestamp
-//            case pending
-//            case error
-//            case amountRaw
-//            case amountEffective
-//            case type
-//        }
-//
-//        let value = try decoder.container(keyedBy: CodingKeys.self)
-//        self.transactionId = try value.decode(String.self, forKey: 
.transactionId)
-//        self.timestamp = try value.decode(Timestamp.self, forKey: .timestamp)
-//        self.pending = try value.decode(Bool.self, forKey: .pending)
-//        if value.contains(.error) {
-//            self.error = try value.decode(AnyCodable.self, forKey: .error)
-//        }
-//        self.amountRaw = try value.decode(Amount.self, forKey: .amountRaw)
-//        self.amountEffective = try value.decode(Amount.self, forKey: 
.amountEffective)
-//
-//        let type = try value.decode(String.self, forKey: .type)
-//        if type == "withdrawal" {
-//            let withdrawDetail = try TransactionWithdrawal.init(from: 
decoder)
-//            self.detail = .withdrawal(withdrawDetail)
-//        } else {
-//            throw TransactionDecodingError.invalidStringValue
-//        }
-//        symLog.log("\(self)")
-//    }
-    
     static func == (lhs: Transaction, rhs: Transaction) -> Bool {
-        return lhs.transactionId == rhs.transactionId
+        return lhs.id == rhs.id
     }
-    
+
     func hash(into hasher: inout Hasher) {
-        transactionId.hash(into: &hasher)
+        id.hash(into: &hasher)
+    }
+
+    func common() -> TransactionCommon {
+        switch self {
+            case .withdrawal(let withdrawalTransaction):
+                return withdrawalTransaction.common
+            case .payment(let paymentTransaction):
+                return paymentTransaction.common
+            case .refund(let refundTransaction):
+                return refundTransaction.common
+            case .tip(let tipTransaction):
+                return tipTransaction.common
+            case .refresh(let refreshTransaction):
+                return refreshTransaction.common
+        }
+    }
+
+    func detailsToShow() -> Dictionary<String, String> {
+        var result: [String:String] = [:]
+        switch self {
+            case .withdrawal(let withdrawalTransaction):
+                result[EXCHANGEBASEURL] = 
withdrawalTransaction.details.exchangeBaseUrl
+            case .payment(let paymentTransaction):
+                result["status"] = paymentTransaction.details.status
+            case .refund(let refundTransaction):
+                result["summary"] = refundTransaction.details.info.summary
+            case .tip(let tipTransaction):
+                result[EXCHANGEBASEURL] = 
tipTransaction.details.exchangeBaseUrl
+            case .refresh(let refreshTransaction):
+                result[EXCHANGEBASEURL] = 
refreshTransaction.details.exchangeBaseUrl
+        }
+        return result
     }
 }
 
+
 #if DEBUG
 extension Transaction {             // for PreViews
     init(id: String, time: Timestamp) {
-        self.type = "withdrawal"
-        self.amountRaw = try! Amount(fromString: "Taler:5")
-        self.amountEffective = try! Amount(fromString: "Taler:4.8")
-        self.transactionId = id
-        self.timestamp = time
-        self.extendedStatus = "done"
-        self.pending = false
-        self.frozen = false
-        self.error = nil
-        self.exchangeBaseUrl = "Exchange.Demo.Taler.net"
-//        let withdrawDetail = TransactionWithdrawal(url: 
"Exchange.Demo.Taler.net")
-//        self.detail = .withdrawal(withdrawDetail)
+        let common = TransactionCommon(type: WITHDRAWAL,
+                            amountEffective: try! Amount(fromString: 
"Taler:4.8"),
+                                  amountRaw: try! Amount(fromString: 
"Taler:5"),
+                              transactionId: id, timestamp: time,
+                             extendedStatus: "done", pending: false, frozen: 
false)
+        let withdrawalDetails = WithdrawalDetails(type: 
WithdrawalDetails.WithdrawalType.bankIntegrated,
+                                            reservePub: "Public Key of the 
Exchange",
+                                             confirmed: true)
+        let details = WithdrawalTransactionDetails(exchangeBaseUrl: 
"Exchange.Demo.Taler.net",
+                                                   withdrawalDetails: 
withdrawalDetails)
+        self = .withdrawal(WithdrawalTransaction(common: common, details: 
details))
     }
 }
 #endif
diff --git a/TalerWallet1/Views/Transactions/TransactionDetail.swift 
b/TalerWallet1/Views/Transactions/TransactionDetail.swift
index d092c79..3d5fe56 100644
--- a/TalerWallet1/Views/Transactions/TransactionDetail.swift
+++ b/TalerWallet1/Views/Transactions/TransactionDetail.swift
@@ -20,10 +20,11 @@ struct TransactionDetail: View {
     var transaction : Transaction
 
     var body: some View {
-        let raw = transaction.amountRaw
-        let effective = transaction.amountEffective
+        let common = transaction.common()
+        let raw = common.amountRaw
+        let effective = common.amountEffective
         let fee = try! Amount.diff(raw, effective)      // TODO: different 
currencies
-        let dateString = TalerDater.dateString(from: transaction.timestamp)
+        let dateString = TalerDater.dateString(from: common.timestamp)
 
         VStack() {
             Spacer()
@@ -40,20 +41,20 @@ struct TransactionDetail: View {
             AmountView(title: "Obtained coins:",
                        value: effective.readableDescription, color: 
Color("Incoming"))
                 .padding(.bottom)
-            if let baseURL = transaction.exchangeBaseUrl {
-                VStack {
-                    Text("From exchange:")
-                        .font(.title3)
-                    Text("\(baseURL.trimURL())")
-                        .font(.title)
-                        .fontWeight(.medium)
-                }
-                .frame(maxWidth: .infinity, alignment: .center)
-            }
+//            if let baseURL = transaction.exchangeBaseUrl {
+//                VStack {
+//                    Text("From exchange:")
+//                        .font(.title3)
+//                    Text("\(baseURL.trimURL())")
+//                        .font(.title)
+//                        .fontWeight(.medium)
+//                }
+//                .frame(maxWidth: .infinity, alignment: .center)
+//            }
             Spacer()
             Button(role: .destructive, action: {
                 // TODO: delete from wallet-core
-                print("Should delete \(transaction.transactionId)")
+                print("Should delete \(common.transactionId)")
             }, label: {
                 HStack {
                     Text("Delete from list" + "        ")
diff --git a/TalerWallet1/Views/Transactions/TransactionRow.swift 
b/TalerWallet1/Views/Transactions/TransactionRow.swift
index 63df274..c76012a 100644
--- a/TalerWallet1/Views/Transactions/TransactionRow.swift
+++ b/TalerWallet1/Views/Transactions/TransactionRow.swift
@@ -36,13 +36,16 @@ struct TransactionRow: View {
     var transaction : Transaction
 
     var body: some View {
-        let amount = transaction.amountEffective
-        let withdraw: Bool = transaction.type == "withdrawal"
-        let payment: Bool = transaction.type == "payment"
-        let refund: Bool = transaction.type == "refund"
+        let common = transaction.common()
+        let details = transaction.detailsToShow()
+        let keys = details.keys
+        let amount = common.amountEffective
+        let withdraw: Bool = common.type == WITHDRAWAL
+        let payment: Bool = common.type == PAYMENT
+        let refund: Bool = common.type == REFUND
         let incoming = withdraw || refund
-        let counterparty = transaction.exchangeBaseUrl
-        let dateString = TalerDater.dateString(from: transaction.timestamp, 
relative: true)
+//        let counterparty = transaction.exchangeBaseUrl
+        let dateString = TalerDater.dateString(from: common.timestamp, 
relative: true)
 
         HStack {
             Image(systemName: incoming ? "text.badge.plus" : 
"text.badge.minus")
@@ -50,8 +53,8 @@ struct TransactionRow: View {
                 .padding(.trailing)
                 .font(.largeTitle)
 
-            if withdraw {
-                if let baseURL = counterparty {
+            if keys.contains(EXCHANGEBASEURL) {
+                if let baseURL = details[EXCHANGEBASEURL] {
                     TransactionRowCenter(centerTop: baseURL.trimURL(), 
centerBottom: dateString)
                 }
             } else if payment {
@@ -73,7 +76,8 @@ struct TransactionRow: View {
 
 #if DEBUG
 struct TransactionRow_Previews: PreviewProvider {
-    static var transaction = Transaction(id:"some transActionID", time: 
Timestamp(from: 1_666_000_000_000))
+    static var transaction = Transaction(id: "some transActionID",
+                                       time: Timestamp(from: 
1_666_000_000_000))
     static var previews: some View {
         TransactionRow(transaction: transaction)
     }
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index 9026a97..ef365e1 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -53,7 +53,8 @@ extension TransactionsListView {
 
         var body: some View {
             let transactions = viewModel.transactions!
-            List(transactions, id: \.transactionId) { transaction in
+            List(transactions) { transaction in
+                let common = transaction.common()
                 NavigationLink {
                     TransactionDetail(transaction: transaction)
                 } label: {
@@ -61,7 +62,7 @@ extension TransactionsListView {
                 }
                     .swipeActions(edge: .leading, allowsFullSwipe: true) {
                         Button {
-                            symLog?.log("bookmarked 
\(transaction.transactionId)")
+                            symLog?.log("bookmarked \(common.transactionId)")
                             // TODO: Bookmark
                         } label: {
                             Label("Bookmark", systemImage: "bookmark")
@@ -69,7 +70,7 @@ extension TransactionsListView {
                     }
                     .swipeActions(edge: .trailing, allowsFullSwipe: true) {
                         Button(role: .destructive) {
-                            symLog?.log("deleted \(transaction.transactionId)")
+                            symLog?.log("deleted \(common.transactionId)")
                             // TODO: delete from Model. SwiftUI deletes this 
row from view already :-)
                         } label: {
                             Label("Delete", systemImage: "trash")

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]