Here is how I deal with pending transactions in my app
import StoreKit
import AmplitudeSwift
import Optimizely
class PurchaseManager: ObservableObject {
// A printed property to carry out there merchandise
@Printed var merchandise: [Product] = []
// A printed property to trace the standing of transactions
@Printed var transactionState: String = "Idle"
var loadingIndicator: ThreeBubblesLoadingView!
// A set of product identifiers
non-public let productIdentifiers: Set = [
PaymentHandler.sharedInstance.YEARLY_PRODUCT_ID,
PaymentHandler.sharedInstance.YEARLY_PRODUCT_ID_50_OFF,
PaymentHandler.sharedInstance.MONTHLY_PRODUCT_ID,
PaymentHandler.sharedInstance.YEARLY_PRODUCT_ID_40_OFF,
PaymentHandler.sharedInstance.YEARLY_PRODUCT_ID_FREE_TRIAL,
PaymentHandler.sharedInstance.YEARLY_PRODUCT_ID_50,
PaymentHandler.sharedInstance.MONTHLY_PRODUCT_ID_13
]
// Shared occasion for use all through the app
static let shared = PurchaseManager()
non-public init() {}
// MARK: - Fetch Merchandise from App Retailer
func fetchProducts() async {
do {
let merchandise = attempt await Product.merchandise(for: productIdentifiers)
self.merchandise = merchandise
} catch {
print("Didn't fetch merchandise: (error.localizedDescription)")
}
}
// MARK: - Deal with Buy
func purchaseProduct(product: Product, supply: String, vc: UIViewController) async -> Bool {
do {
DispatchQueue.most important.async {
self.loadingIndicator = ThreeBubblesLoadingView()
self.loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(self.loadingIndicator)
NSLayoutConstraint.activate([
self.loadingIndicator.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor),
self.loadingIndicator.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor)
])
}
// Begin the acquisition
let consequence = attempt await product.buy()
// Deal with the results of the acquisition
change consequence {
case .success(let verificationResult):
change verificationResult {
case .verified(let transaction):
self.transactionState = "Buy Profitable"
await transaction.end()
DispatchQueue.most important.async {
Amplitude.sharedInstance.monitor(
eventType: "payment_completed",
eventProperties: [
"PlanId": transaction.productID,
"UserId": WUser.sharedInstance.userId,
"Source": source,
"VariationKey": WUser.sharedInstance.variationKey
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
return await PaymentHandler.sharedInstance.buy(
vc: vc,
productId: transaction.productID,
product: transaction.productID,
transaction: transaction
)
case .unverified(let transaction, let error):
self.transactionState = "Buy Unverified: (error.localizedDescription)"
await transaction.end()
DispatchQueue.most important.async {
showMessageWithTitle("Error!", "There was an error processing your buy", .error)
Amplitude.sharedInstance.monitor(
eventType: "payment_failed",
eventProperties: [
"PlanId": transaction.productID,
"UserId": WUser.sharedInstance.userId,
"Source": source,
"Error": error.localizedDescription,
"ErrorType": "UnverifiedTransaction",
"ErrorObject": String(describing: error)
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
return false
}
case .userCancelled:
self.transactionState = "Consumer cancelled the acquisition."
DispatchQueue.most important.async {
Amplitude.sharedInstance.monitor(
eventType: "payment_cancelled",
eventProperties: [
"PlanId": product.id,
"UserId": WUser.sharedInstance.userId,
"Source": source
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
return false
case .pending:
self.transactionState = "Buy is pending."
DispatchQueue.most important.async {
Amplitude.sharedInstance.monitor(
eventType: "payment_pending",
eventProperties: [
"PlanId": product.id,
"UserId": WUser.sharedInstance.userId,
"Source": source
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
return false
@unknown default:
self.transactionState = "Unknown buy consequence."
DispatchQueue.most important.async {
showMessageWithTitle("Error!", "There was an error processing your buy", .error)
Amplitude.sharedInstance.monitor(
eventType: "payment_failed",
eventProperties: [
"PlanId": product.id,
"UserId": WUser.sharedInstance.userId,
"Source": source,
"Error": "unknown"
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
return false
}
} catch {
self.transactionState = "Buy failed: (error.localizedDescription)"
DispatchQueue.most important.async {
showMessageWithTitle("Error!", "There was an error processing your buy", .error)
Amplitude.sharedInstance.monitor(
eventType: "payment_failed",
eventProperties: [
"PlanId": product.id,
"UserId": WUser.sharedInstance.userId,
"Source": source,
"Error": error.localizedDescription,
"ErrorType": "CatchError",
"ErrorObject": String(describing: error)
]
)
self.loadingIndicator.removeFromSuperview()
}
return false
}
}
// MARK: - Pay attention for Transaction Updates
func listenForTransactionUpdates() {
Job {
for await lead to Transaction.updates {
change consequence {
case .verified(let transaction):
self.transactionState = "Transaction verified: (transaction.productID)"
await transaction.end()
DispatchQueue.most important.async {
Amplitude.sharedInstance.monitor(
eventType: "payment_completed",
eventProperties: [
"PlanId": transaction.productID,
"UserId": WUser.sharedInstance.userId,
"TransactionType": "Pending"
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
if (PaymentHandler.sharedInstance.vc != nil) {
await PaymentHandler.sharedInstance.buy(
vc: PaymentHandler.sharedInstance.vc!,
productId: transaction.productID,
product: transaction.productID,
transaction: transaction
)
}
case .unverified(let transaction, let error):
self.transactionState = "Unverified transaction: (error.localizedDescription)"
DispatchQueue.most important.async {
Amplitude.sharedInstance.monitor(
eventType: "payment_failed",
eventProperties: [
"PlanId": transaction.productID,
"UserId": WUser.sharedInstance.userId,
"Error": error.localizedDescription,
"ErrorType": "UnverifiedPendingTransaction",
"ErrorObject": String(describing: error)
]
)
if (self.loadingIndicator != nil) {
self.loadingIndicator.removeFromSuperview()
}
}
await transaction.end()
}
}
}
}
}
Once I make a purchase order, I name the perform purchaseProduct.
Sadly, the pending transaction shouldn’t be being processed. Can somebody please assist? About 5 transactions went via as pending however wasn’t processed by Apple. The fee was not captured. Is that this code improper?
Within the AppDelegate, I’ve the next:
PurchaseManager.shared.listenForTransactionUpdates()
The fee shouldn’t be displaying up in app retailer join and I am not getting an apple server notification about it.