7.1 C
New York
Saturday, March 15, 2025

ios – How I deal with pending transactions


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.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles