Below is Apple's stock Request Refund Logic from the Food Truck Building a SwiftUI Multiplatform App that I am trying to modify to catch refund request errors. Apple's doesn't seem to think that it is important to show how to catch the errors associated with their sample logic making it difficult for people like me that are still learning how to handle errors.
I am seeing the errors below in result where the comment says catch refund errors here. The one case statement dismisses control if the refund was successful.
This is what I am seeing with my various print statements:
refund error Binding<Optional>(transaction: SwiftUI.Transaction(plist: []), location: SwiftUI.StoredLocation<Swift.Optional<Swift.Error>>, _value: nil) failure(StoreKit.Transaction.RefundRequestError.duplicateRequest) Transaction.RefundError RefundRequestError
So my question is how to get a localizedDescription of the error (in this case duplicateRequest) into the alert?
Part of the problem seems to be that the errors are presented after the successful case branches off without using a do / catch block. I have tried using error to store and use in the alert and gotError but I don't know the result type.
Thanks for your assistance!
struct RefundView: View {
@State private var recentTransactions: [StoreKit.Transaction] = []
@State private var selectedTransactionID: UInt64?
@State private var refundSheetIsPresented = false
@State private var error: Error?
@State private var refundError: Bool = false
@State var gotError: String = ""
@Environment(\.dismiss) private var dismiss
var body: some View {
List(recentTransactions, selection: $selectedTransactionID) { transaction in
TransactionRowView(transaction: transaction)
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 0) {
Text("Select a purchase to refund")
.font(.subheadline)
.foregroundColor(.secondary)
.padding()
Button {
refundSheetIsPresented = true
} label: {
Text("Request a refund")
.bold()
.padding(.vertical, 5)
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.padding([.horizontal, .bottom])
.disabled(selectedTransactionID == nil)
}
}
.task { @MainActor in
for await transaction in StoreKit.Transaction.all {
recentTransactions.append(transaction.unsafePayloadValue)
}
}
.refundRequestSheet(
for: selectedTransactionID ?? 0,
isPresented: $refundSheetIsPresented
) { result in
if case .success(.success) = result {
dismiss()
}
// ******* catch refund errors here *******
self.error = error
refundError.toggle()
// $gotError = result
print("refund error \($error)")
print(result)
print("Transaction.RefundError \(Transaction.RefundRequestError)")
}
.navigationTitle("Refund Purchase")
.alert("Refund error: \(gotError)", isPresented: $refundError, actions: {})
}
}
The result
in this case is Result<Transaction.RefundRequestStatus, Transaction.RefundRequestError>
and your gotError
variable is a String
, so it's not equivalent. You also don't need the prefix $
. This symbol is for read-write binding.
if case .success(.success) = result {
dismiss()
}
self.error = error
refundError.toggle()
By writing like above, you're saying that in any case: success or failure, the refundError
property will always toggle, and that leads to:
Part of the problem seems to be that the errors are presented after the successful case
You can fix it by breaking result
into two cases, as @lorem said in his comment. Then change refundError = true
only, since it's a @State object and was linked with .alert(isPresented:)
. It will automatically return to false
when the alert is dismissed.
swift result {
case .success(let state):
if state == .success {
dismiss()
}
case .failure(let storeKitError):
self.error = storeKitError
self.refundError = true
}
Finally, you don't have to use another @State var gotError: String
here because you already stored error
. So, I recommend using computed property to get the error localized description.
private var gotError: String {
error?.localizedDescription ?? ""
}
...
.alert("Refund error: \(gotError)", isPresented: $refundError, actions: {})