skip to Main Content

I am making an app in SwiftUI using an In-app purchase. In this app, the user should be able to buy points as many times as he wants, so I have used consumable products. But when I’ve tried to buy them once again I got the information "This In-App purchase has already been bought. It will be restored for free". I’ve already searched for way how to do it but none of the ideas worked for me.

Here is my StoreManager class:

import Foundation
import StoreKit
import SwiftUI

class StoreManager : NSObject, ObservableObject, SKProductsRequestDelegate {
    
    @EnvironmentObject var authViewModel: AuthViewModel
    
    @Published var transactionState: SKPaymentTransactionState?
    @Published var myProducts = [SKProduct]()
    var request: SKProductsRequest!
    
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        print("Did receive response")
        
        if !response.products.isEmpty {
            for fetchedProduct in response.products {
                    DispatchQueue.main.async {
                        self.myProducts.append(fetchedProduct)
                }
            }
            for invalidIdentifier in response.invalidProductIdentifiers {
                print("Invalid identifiers found: (invalidIdentifier)")
            }
        }else{
            print("it's empty")
        }
    }
    
    func getProducts(productIDs: [String]) {
        let request = SKProductsRequest(productIdentifiers: Set(productIDs))
        request.delegate = self
        request.start()
    }
    
    func request(_ request: SKRequest, didFailWithError error: Error) {
        print("Request did fail: (error)")
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchasing:
                transactionState = .purchasing
                break
            case .purchased:
                print("purchased")
                queue.finishTransaction(transaction)
                transactionState = .purchased
                break
            case .restored:
                print("restored")
                transactionState = .restored
                queue.finishTransaction(transaction)
                break
            case .failed, .deferred:
                    queue.finishTransaction(transaction)
                    transactionState = .failed
                break
                    default:
                    queue.finishTransaction(transaction)
                break
            }
        }
    }
    
    func purchaseProduct(product: SKProduct) {
        
        if SKPaymentQueue.canMakePayments() {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)

        } else {
            print("User can't make payment.")
        }
    }
    
    func restoreProducts() {
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
}

And I am simply using getProducts with onAppear, and purchase product on button’s action.

Please help me or if an answer to a similar question already exists send me a link to that thread.

2

Answers


  1. This is because there are two types of in-app purchases. Consumable and
    Non – Consumable. Consumable in-purchases can be made multiple times and Non -consumable in-app purchases can only be made once.

    https://developer.apple.com/in-app-purchase/

    Solution

    Check in App Store Connect in-app purchases section to see which type of in-app purchase you created. If it was Non – Consumable, delete the existing in-app purchase and create a new one with is consumable.

    Resources

    Implement In App Purchase (IAP) in iOS applications

    Adding In-App Purchases to a SwiftUI App

    Login or Signup to reply.
  2. Your StoreManager class needs to conform to SKPaymentTransactionObserver – You have implemented the relevant delegate method from this protocol, but you haven’t declared conformance or, most importantly, added your StoreManager instance as a transaction queue observer.

    Since you aren’t observing the transaction, you will never call finishTransaction – When you attempt to purchase the consumable a second time, Store Kit sees that there is an incomplete purchase of that item and so you get the message that you have already purchased it.

    As well as actually registering as a transaction queue observer, it is important that your app creates an instance of StoreManager as soon as it launches – in didFinishLaunching or an equivalent location.

    This is so that any incomplete purchases from a previous run can be presented to your transaction queue observer.

    You should only ever have one instance of StoreManager and its lifetime should be the lifetime of your app.

    You need something like:

    class StoreManager : NSObject, ObservableObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
        init() {
             SKPaymentQueue.default().add(self)
        }
    

    Also, seeing @EnvironmentObject var authViewModel: AuthViewModel leads me to think that you may have a separation-of-concerns issue in your design.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search