Cannot handle unfinished consumable in Transaction.currentEntitlements when testing Ask to Buy in Xcode

I am trying to test Ask to Buy for consumable product.

When the transaction was approved, I can get verified transaction in Transaction.updates, meaning the process stops at a breakpoint in Transaction.updates. However, when I call Transaction.currentEntitlements after that, I cannot handle transaction as consumable.

Am I missing anything?

Here is my code.

func newTransactionListenerTask() -> Task<Void, Never> {

    Task(priority: .background) {
        for await verificationResult in Transaction.updates {

            guard
                case .verified(let transaction) = verificationResult,
                transaction.revocationDate == nil
            else { return }
        
            // Breakpoint stops here
            await refreshPurchasedProducts()

            await transaction.finish()
        }
   }
}

func refreshPurchasedProducts() async {
    for await verificationResult in Transaction.currentEntitlements {

        guard case .verified(let transaction) = verificationResult else { return }

        switch transaction.productType {
        case .consumable:
            // This code is not called
            updateConsumablePurchaseCount(productID: transaction.productID)
        default:
            break
        }
    }
}

Accepted Reply

Thank you for the reply.

I still don't understand. In the code above, I try to deliver consumable product before I call transaction.finish(). I'm testing for Ask to Buy and I'm not calling transaction.finish() before the transaction approval. (When the purchase result is .pending I don't call transaction.finish() as shown in the code below)

    func purchase(_ info: ConsumableProductInfo) async throws {
        let result: Product.PurchaseResult
        switch info {
        case .coffee:
            guard let coffeeProduct else { return }
            result = try await coffeeProduct.purchase()
        }
        switch result {
        case let .success(verificationResult):
            switch verificationResult {
            case let .verified(transaction):

                await refreshPurchasedProducts()

                // Deliver consumable products because it couldn't be handled in `refreshPurchasedProducts()`
                if case .consumable = transaction.productType {
                    updateConsumablePurchaseCount(productID: transaction.productID)
                }

                await transaction.finish()

            case let .unverified(_, verificationError):
                throw verificationError
            }
        case .pending, .userCancelled:
            break
        @unknown default:
            break
        }
    }

I found a blog post that is similar to this problem: https://iosexample.com/implementing-and-testing-in-app-purchases-with-storekit2-in-xcode-13-swift-5-5-and-ios-15/ It says that "In tests I've done transactions for consumables do not remain in the receipt, even if you omit to call finish()."

So I wonder if this is a bug or problem in Xcode testing.

  • I accidentally marked this as "solved" while it is not

Add a Comment

Replies

CurrentEntitlements only returns persistent transactions such as non-consumables, non-renewing subscriptions and auto-renewing subscriptions.

Looks like you are getting the successful buy and transaction, verifying it and marking it as finished. But note that the intention is you have delivered the purchased goods before you marked it as finished. As consumables are removed from purchase history once delivered. This is a good practices to follow and ensures you only deliver the consumables once per purchase.

  • I still don't understand. In the code above, I try to deliver consumable product before I call transaction.finish(). I'm testing for Ask to Buy and I'm not calling transaction.finish() before the transaction approval. I found a blog post similar to this problem: https://iosexample.com/implementing-and-testing-in-app-purchases-with-storekit2-in-xcode-13-swift-5-5-and-ios-15/ It says "In tests I've done transactions for consumables do not remain in the receipt, even if you omit to call finish()."

Add a Comment

Thank you for the reply.

I still don't understand. In the code above, I try to deliver consumable product before I call transaction.finish(). I'm testing for Ask to Buy and I'm not calling transaction.finish() before the transaction approval. (When the purchase result is .pending I don't call transaction.finish() as shown in the code below)

    func purchase(_ info: ConsumableProductInfo) async throws {
        let result: Product.PurchaseResult
        switch info {
        case .coffee:
            guard let coffeeProduct else { return }
            result = try await coffeeProduct.purchase()
        }
        switch result {
        case let .success(verificationResult):
            switch verificationResult {
            case let .verified(transaction):

                await refreshPurchasedProducts()

                // Deliver consumable products because it couldn't be handled in `refreshPurchasedProducts()`
                if case .consumable = transaction.productType {
                    updateConsumablePurchaseCount(productID: transaction.productID)
                }

                await transaction.finish()

            case let .unverified(_, verificationError):
                throw verificationError
            }
        case .pending, .userCancelled:
            break
        @unknown default:
            break
        }
    }

I found a blog post that is similar to this problem: https://iosexample.com/implementing-and-testing-in-app-purchases-with-storekit2-in-xcode-13-swift-5-5-and-ios-15/ It says that "In tests I've done transactions for consumables do not remain in the receipt, even if you omit to call finish()."

So I wonder if this is a bug or problem in Xcode testing.

  • I accidentally marked this as "solved" while it is not

Add a Comment

Resolved in https://developer.apple.com/forums/thread/723025