gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated: parse withdraw transaction deta


From: gnunet
Subject: [taler-taler-ios] branch master updated: parse withdraw transaction details
Date: Tue, 23 Aug 2022 21:40:14 +0200

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

jonathan-buchanan pushed a commit to branch master
in repository taler-ios.

The following commit(s) were added to refs/heads/master by this push:
     new 0d3486f  parse withdraw transaction details
0d3486f is described below

commit 0d3486f224f600aac7942ef14999b2779deb4638
Author: Jonathan Buchanan <jonathan.russ.buchanan@gmail.com>
AuthorDate: Tue Aug 23 15:39:31 2022 -0400

    parse withdraw transaction details
---
 README.md                                          |   2 +-
 Taler.xcodeproj/project.pbxproj                    |   4 +
 Taler/Model/BalancesModel.swift                    |  21 ++-
 ...BalancesModel.swift => TransactionsModel.swift} |  29 ++-
 Taler/Views/BalancesView.swift                     |  66 +++++--
 Taler/WalletBackend.swift                          | 200 ++++++++++++++++++++-
 taler-swift/Sources/taler-swift/Time.swift         |  10 +-
 7 files changed, 279 insertions(+), 53 deletions(-)

diff --git a/README.md b/README.md
index e9bfa87..bd2bf6b 100644
--- a/README.md
+++ b/README.md
@@ -11,4 +11,4 @@ Before building anything, you should initialize and update 
the submodules by run
     
     $ ./bootstrap
     
-To build the app, open `Taler.xcodeproj` with Xcode. The minimum version of 
iOS supported is 13.0. This iOS version is compatible on all iPhone models at 
least as new as the iPhone 6S.
+To build the app, open `Taler.xcodeproj` with Xcode. The minimum version of 
iOS supported is 14.0. This iOS version is compatible on all iPhone models at 
least as new as the iPhone 6S.
diff --git a/Taler.xcodeproj/project.pbxproj b/Taler.xcodeproj/project.pbxproj
index 6b6eac4..99b68e5 100644
--- a/Taler.xcodeproj/project.pbxproj
+++ b/Taler.xcodeproj/project.pbxproj
@@ -9,6 +9,7 @@
 /* Begin PBXBuildFile section */
                AB1F87C82887C94700AB82A0 /* TalerApp.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = AB1F87C72887C94700AB82A0 /* TalerApp.swift */; };
                AB1F87CA2887D2F400AB82A0 /* ContentView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB1F87C92887D2F400AB82A0 /* ContentView.swift 
*/; };
+               AB32199128B18859008AAC75 /* TransactionsModel.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = AB32199028B18859008AAC75 /* 
TransactionsModel.swift */; };
                AB4C534A28AC21C9003004F7 /* BalancesView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB4C534928AC21C9003004F7 /* BalancesView.swift 
*/; };
                AB4C534C28AC25FC003004F7 /* BalancesModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB4C534B28AC25FC003004F7 /* BalancesModel.swift 
*/; };
                AB69F9FA28AAED53005CCC2E /* WithdrawModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB69F9F928AAED53005CCC2E /* WithdrawModel.swift 
*/; };
@@ -60,6 +61,7 @@
 /* Begin PBXFileReference section */
                AB1F87C72887C94700AB82A0 /* TalerApp.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalerApp.swift; 
sourceTree = "<group>"; };
                AB1F87C92887D2F400AB82A0 /* ContentView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
ContentView.swift; sourceTree = "<group>"; };
+               AB32199028B18859008AAC75 /* TransactionsModel.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
TransactionsModel.swift; sourceTree = "<group>"; };
                AB4C534928AC21C9003004F7 /* BalancesView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BalancesView.swift; sourceTree = "<group>"; };
                AB4C534B28AC25FC003004F7 /* BalancesModel.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BalancesModel.swift; sourceTree = "<group>"; };
                AB69F9F928AAED53005CCC2E /* WithdrawModel.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
WithdrawModel.swift; sourceTree = "<group>"; };
@@ -129,6 +131,7 @@
                        isa = PBXGroup;
                        children = (
                                ABB33066289C658900668B42 /* 
BackendManager.swift */,
+                               AB32199028B18859008AAC75 /* 
TransactionsModel.swift */,
                                ABB33064289C5BBB00668B42 /* 
ExchangeManager.swift */,
                                ABC4AC3E28A473070047A56F /* 
PendingManager.swift */,
                                AB69F9F928AAED53005CCC2E /* WithdrawModel.swift 
*/,
@@ -390,6 +393,7 @@
                                ABB33065289C5BBB00668B42 /* 
ExchangeManager.swift in Sources */,
                                AB4C534A28AC21C9003004F7 /* BalancesView.swift 
in Sources */,
                                D1D65B9826992E4600C1012A /* WalletBackend.swift 
in Sources */,
+                               AB32199128B18859008AAC75 /* 
TransactionsModel.swift in Sources */,
                                ABB762AD2891059600E88634 /* SettingsView.swift 
in Sources */,
                                ABC4AC3B28A4619C0047A56F /* PendingView.swift 
in Sources */,
                                ABC4AC3F28A473070047A56F /* 
PendingManager.swift in Sources */,
diff --git a/Taler/Model/BalancesModel.swift b/Taler/Model/BalancesModel.swift
index d4b2164..29a6656 100644
--- a/Taler/Model/BalancesModel.swift
+++ b/Taler/Model/BalancesModel.swift
@@ -17,33 +17,32 @@
 import Foundation
 
 class BalancesModel: ObservableObject {
-    enum State {
-        case begin
-        case loading
-        case loaded([Balance])
-    }
-    
     var backend: WalletBackend
-    @Published var state: State
+    
+    @Published var loading: Bool = false
+    @Published var balances: [Balance]?
     
     init(backend: WalletBackend) {
         self.backend = backend
-        self.state = .begin
     }
     
     func getBalances() {
-        self.state = .loading
+        self.loading = true
         let req = WalletBackendGetBalancesRequest()
         backend.sendFormattedRequest(request: req) { response, err in
             // TODO: Use Combine instead
             DispatchQueue.main.async {
+                self.loading = false
                 if let res = response {
-                    self.state = .loaded(res.balances)
+                    self.balances = res.balances
                 } else {
                     // TODO: Handle error.
-                    self.state = .begin
                 }
             }
         }
     }
+    
+    func getTransactionsModel() -> TransactionsModel {
+        return TransactionsModel(backend: self.backend, currency: nil)
+    }
 }
diff --git a/Taler/Model/BalancesModel.swift 
b/Taler/Model/TransactionsModel.swift
similarity index 64%
copy from Taler/Model/BalancesModel.swift
copy to Taler/Model/TransactionsModel.swift
index d4b2164..753d5cd 100644
--- a/Taler/Model/BalancesModel.swift
+++ b/Taler/Model/TransactionsModel.swift
@@ -16,32 +16,31 @@
 
 import Foundation
 
-class BalancesModel: ObservableObject {
-    enum State {
-        case begin
-        case loading
-        case loaded([Balance])
-    }
-    
+class TransactionsModel: ObservableObject {
     var backend: WalletBackend
-    @Published var state: State
+    var currency: String?
+    
+    @Published var loading: Bool = false
+    @Published var transactions: [Transaction]?
     
-    init(backend: WalletBackend) {
+    init(backend: WalletBackend, currency: String?) {
         self.backend = backend
-        self.state = .begin
+        self.currency = currency
     }
     
-    func getBalances() {
-        self.state = .loading
-        let req = WalletBackendGetBalancesRequest()
+    func loadTransactions(searchString: String? = nil) {
+        self.loading = true
+        let req = WalletBackendGetTransactionsRequest(currency: self.currency,
+                                                      search: searchString)
         backend.sendFormattedRequest(request: req) { response, err in
             // TODO: Use Combine instead
             DispatchQueue.main.async {
+                self.loading = false
                 if let res = response {
-                    self.state = .loaded(res.balances)
+                    print("x")
+                    self.transactions = res.transactions
                 } else {
                     // TODO: Handle error.
-                    self.state = .begin
                 }
             }
         }
diff --git a/Taler/Views/BalancesView.swift b/Taler/Views/BalancesView.swift
index 39bf282..a8eab64 100644
--- a/Taler/Views/BalancesView.swift
+++ b/Taler/Views/BalancesView.swift
@@ -16,6 +16,47 @@
 
 import SwiftUI
 
+struct TransactionsView: View {
+    @ObservedObject var model: TransactionsModel
+    
+    var body: some View {
+        VStack {
+            if model.transactions == nil {
+                ProgressView()
+                    .onAppear {
+                        model.loadTransactions()
+                    }
+            } else if model.loading {
+                ProgressView()
+            } else {
+                List(model.transactions!, id: \.self) { tx in
+                    VStack {
+                        Text("Transaction: \(tx.transactionId)")
+                    }
+                }
+                Text("Loaded")
+                /*VStack {
+                    Text("Balances")
+                    NavigationLink {
+                        TransactionsView(model: 
self.balancesModel.getTransactionsModel())
+                    } label: {
+                        Text("Transactions")
+                    }
+
+                }
+                    .padding(16)
+                    .navigationTitle("Balances")
+                    .navigationBarItems(
+                        leading: Button(action: self.showSidebar, label: {
+                            Image(systemName: "line.3.horizontal")
+                        })
+                    )*/
+            }
+        }
+            .navigationTitle("Transactions")
+    }
+}
+
 struct BalancesView: View {
     @ObservedObject var balancesModel: BalancesModel
     @EnvironmentObject var backend: BackendManager
@@ -23,19 +64,18 @@ struct BalancesView: View {
     
     var body: some View {
         NavigationView {
-            switch balancesModel.state {
-            case .begin:
-                EmptyView()
-                    .onAppear(perform: {
-                        balancesModel.getBalances()
-                    })
+            if balancesModel.balances == nil {
+                ProgressView()
                     .navigationTitle("Balances")
                     .navigationBarItems(
                         leading: Button(action: self.showSidebar, label: {
                             Image(systemName: "line.3.horizontal")
                         })
                     )
-            case .loading:
+                    .onAppear {
+                        balancesModel.getBalances()
+                    }
+            } else if balancesModel.loading {
                 ProgressView()
                     .navigationTitle("Balances")
                     .navigationBarItems(
@@ -43,9 +83,15 @@ struct BalancesView: View {
                             Image(systemName: "line.3.horizontal")
                         })
                     )
-            case .loaded(let balances):
+            } else {
                 VStack {
                     Text("Balances")
+                    NavigationLink {
+                        TransactionsView(model: 
self.balancesModel.getTransactionsModel())
+                    } label: {
+                        Text("Transactions")
+                    }
+
                 }
                     .padding(16)
                     .navigationTitle("Balances")
@@ -57,8 +103,4 @@ struct BalancesView: View {
             }
         }
     }
-    
-    /*init(showSidebar: @escaping () -> Void) {
-        self.showSidebar = showSidebar
-    }*/
 }
diff --git a/Taler/WalletBackend.swift b/Taler/WalletBackend.swift
index d00a8ae..e6df597 100644
--- a/Taler/WalletBackend.swift
+++ b/Taler/WalletBackend.swift
@@ -202,22 +202,203 @@ enum TransactionType: Codable {
     }
 }
 
-/// An error associated with a transaction.
-struct TransactionError: Codable {
-    var ec: Int
-    var hint: String?
-    //var details: Any?
+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
+}
+
+/// Details for a bank-integrated withdrawal.
+struct BankIntegratedWithdrawalDetails: Codable {
+    /// 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.
+    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)
+            }
+            let bankDetails = BankIntegratedWithdrawalDetails(confirmed: 
confirmed, bankConfirmationUrl: bankConfirmationUrl)
+            self.withdrawalDetails = .bankIntegrated(bankDetails)
+        } else {
+            throw TransactionDecodingError.invalidStringValue
+        }
+    }
+}
+
+/// A payment transaction.
+struct TransactionPayment: Codable {
+    /// Additional information about the payment.
+    // TODO
+    
+    /// An identifier for the payment.
+    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
+}
+
+/// A refund transaction.
+struct TransactionRefund: Codable {
+    /// Identifier for the refund.
+    var refundedTransactionId: String
+    
+    /// Additional information about the refund
+    // TODO
+    
+    /// 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
+}
+
+/// A tip transaction.
+struct TransactionTip: Codable {
+    /// The current status of the tip.
+    // TODO
+    
+    /// 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 {
+    /// 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: Codable {
+struct Transaction: Decodable, Hashable {
+    enum TransactionDetail {
+        case withdrawal(TransactionWithdrawal)
+    }
+    
     var transactionId: String
-    var type: TransactionType
     var timestamp: Timestamp
     var pending: Bool
-    var error: TransactionError?
+    var error: AnyCodable?
     var amountRaw: Amount
     var amountEffective: Amount
+    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
+        }
+    }
+    
+    static func == (lhs: Transaction, rhs: Transaction) -> Bool {
+        return lhs.transactionId == rhs.transactionId
+    }
+    
+    func hash(into hasher: inout Hasher) {
+        transactionId.hash(into: &hasher)
+    }
 }
 
 /// A request to get the transactions in the wallet's history.
@@ -231,7 +412,7 @@ struct WalletBackendGetTransactionsRequest: 
WalletBackendFormattedRequest {
     }
     
     struct Response: Decodable {
-        
+        var transactions: [Transaction]
     }
     
     func operation() -> String {
@@ -901,6 +1082,7 @@ class WalletBackend: IonoMessageHandler {
     }
     
     func handleMessage(message: String) {
+        print(message)
         do {
             guard let messageData = message.data(using: .utf8) else { throw 
WalletBackendError.deserializationError }
             let data = try JSONSerialization.jsonObject(with: messageData, 
options: .allowFragments) as? [String : Any]
diff --git a/taler-swift/Sources/taler-swift/Time.swift 
b/taler-swift/Sources/taler-swift/Time.swift
index cca3bd3..b1d712e 100644
--- a/taler-swift/Sources/taler-swift/Time.swift
+++ b/taler-swift/Sources/taler-swift/Time.swift
@@ -30,15 +30,15 @@ public enum Timestamp: Codable, Equatable {
     case never
     
     enum CodingKeys: String, CodingKey {
-        case t_ms = "t_ms"
+        case t_s = "t_s"
     }
     
     public init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         do {
-            self = Timestamp.milliseconds(try container.decode(UInt64.self, 
forKey: .t_ms))
+            self = Timestamp.milliseconds(try container.decode(UInt64.self, 
forKey: .t_s) * 1000)
         } catch {
-            let stringValue = try container.decode(String.self, forKey: .t_ms)
+            let stringValue = try container.decode(String.self, forKey: .t_s)
             if stringValue == "never" {
                 self = Timestamp.never
             } else {
@@ -55,9 +55,9 @@ public enum Timestamp: Codable, Equatable {
         var value = encoder.container(keyedBy: CodingKeys.self)
         switch self {
         case .milliseconds(let t_ms):
-            try value.encode(t_ms, forKey: .t_ms)
+            try value.encode(t_ms / 1000, forKey: .t_s)
         case .never:
-            try value.encode("never", forKey: .t_ms)
+            try value.encode("never", forKey: .t_s)
         }
     }
 }

-- 
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]