BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

How to listen for transaction updates in my Storekit setup

Forums > SwiftUI

I've got a basic Storekit setup for my app, allowing users to send a tip if they're enjoying the app.

What I'd like help with:

Resolving the warning: Making a purchase without listening for transaction updates risks missing successful purchases. Create a Task to iterate Transaction.updates at launch.

Code

Ok, so here's the code I use in my AboutView.swift (simplified):

import SwiftUI
import StoreKit

struct AboutScreen: View {
    // Binding for our sheet
    @Binding var isPresented: Bool

    // States for Storekit
    @State var products: [Product] = []

    // Our IAPs
    let iapProductIDs = Set([
        "MYAPP.iaps.smallTip",
        "MYAPP.iaps.mediumTip",
        "MYAPP.iaps.largeTip",
    ])

    var body: some View {
        NavigationView {
            VStack {
                // Only display if we have products
                if products.count != 0 {
                    ForEach(products) { product in
                        Button {
                            Task.init {
                                try await purchaseProduct(product)
                            }
                        } label: {
                            HStack {
                                Text(product.displayName)
                                Spacer()
                                Text(product.displayPrice)
                            }
                        }
                    }
                }
                // The rest of the view
                // ...
            }
            .navigationTitle("About")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        isPresented = false
                    } label: {
                        Text("Close")
                    }
                }
            }
        }
        .task {
            await fetchProducts()
        }
    }

    // Fetch our products and save them to `products`
    func fetchProducts() async {
        do {
            self.products = try await Product.products(for: iapProductIDs)
        } catch {
            print("STOREKIT: Unable to fetch products")
        }
    }

    // Buy a product 
    func purchaseProduct(_ product: Product) async throws -> StoreKit.Transaction {
        let result = try await product.purchase()

        switch result {
            case .pending:
                throw PurchaseError.pending

            case .success(let verification):
                switch verification {
                    case .verified(let transaction):
                        print("STOREKIT: Successfully purchased \(product.displayName)")
                        await transaction.finish()
                        return transaction

                    case .unverified:
                        throw PurchaseError.failed
                }
            case .userCancelled:
                throw PurchaseError.cancelled

            @unknown default:
                assertionFailure("Unexpected result")
                throw PurchaseError.failed
        }
    }

    enum PurchaseError: Error {
        case pending, failed, cancelled
    }
}

The purchase runs successfully in the simulator, but throws the following warning:

Making a purchase without listening for transaction updates risks missing successful purchases. Create a Task to iterate Transaction.updates at launch.

I've been looking online for a few hours and I can't reallly understand how to resolve this without using an entirely new storekit setup. The tutorials I've looked at all approach this differently, which is confusing me a lot.

   

@Obelix I'm already using a ForEach for the buttons (see top code block). I was asking how I could use individual buttons instead, and how to correctly format targetting a specific product's purchase within the button.

Edit - updated my original post for extra clarity around the second question.

Edit - I've removed part of my original post to keep it focussed on a single problem instead of 2.

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.