[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.
- [taler-taler-ios] branch master updated (df4fe35 -> 4f2b20c), gnunet, 2023/02/22
- [taler-taler-ios] 01/07: Error handling, Amount.diff, gnunet, 2023/02/22
- [taler-taler-ios] 04/07: Constants, cleanup, gnunet, 2023/02/22
- [taler-taler-ios] 03/07: added ext+taler and web+taler to the list of recognized URL schemes, gnunet, 2023/02/22
- [taler-taler-ios] 06/07: Info & SceneConfigurations, v0.9.2, bundleID, gnunet, 2023/02/22
- [taler-taler-ios] 02/07: ported remaining sync wallet-core funcs to try await, gnunet, 2023/02/22
- [taler-taler-ios] 05/07: Transaction definition and JSON decoding - Bug 7678,
gnunet <=
- [taler-taler-ios] 07/07: Transaction list and details, gnunet, 2023/02/22