I have implemented IAP in my app which seem to work fine in the simulator, however they don’t appear in my preview canvas or when i run it on a real device. I request products by calling this when the screen appears
.onAppear(perform: {IAPManager.shared.getProductsV5()})
then use a foreach to display the requested products
ForEach((0 ..< self.products.items.count), id: \.self) { column in
Button(action: {
let _ = IAPManager.shared.purchaseV5(product: self.products.items[column]); self.soundEffectsM.playSound(sound: "ShellGrab", type: "wav")
}) {
Text(self.products.items[column].localizedDescription) }
And if it helps here is my purchase manager file, i’m sorry if its too much code but i want to give all the info i can
final class ProductsDB: ObservableObject, Identifiable {
static let shared = ProductsDB()
var items: [SKProduct] = [] {
willSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
}
class IAPManager: NSObject, ObservableObject {
// extra rounds
@Published var rounds = Extra()
var purchaseExtraRounds = false
let purchasePublisher = PassthroughSubject<(String, Bool), Never>()
static let shared = IAPManager()
var totalRestoredPurchases: Int = 0
private override init() {
super.init()
SKPaymentQueue.default().add(self)
}
func buyV5(product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
func startObserving() {
SKPaymentQueue.default().add(self)
}
func stopObserving() {
SKPaymentQueue.default().remove(self)
}
func getProductsV5() {
let productIDs = Set(returnProductIDs())
let request = SKProductsRequest(productIdentifiers: Set(productIDs))
request.delegate = self
request.start()
}
func returnProductIDs() -> [String] {
return ["ProductOne", "ProductTwo", "ProductThree"]
}
func formatPrice(for product: SKProduct) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
return formatter.string(from: product.price)
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
if totalRestoredPurchases != 0 {
purchasePublisher.send(("IAP: Purchases successfuly restored!", true))
} else {
purchasePublisher.send(("IAP: No purchases to restore!", true))
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
if let error = error as? SKError {
if error.code != .paymentCancelled {
purchasePublisher.send(("IAP Restore Error: " + error.localizedDescription, false))
} else {
purchasePublisher.send(("IAP Error: " + error.localizedDescription, false))
}
}
}
func restorePurchasesV5() {
totalRestoredPurchases = 0
SKPaymentQueue.default().restoreCompletedTransactions()
}
func purchaseV5(product: SKProduct) -> Bool {
if !IAPManager.shared.canMakePayments() {
return false
} else {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
return true
}
}
extension IAPManager: SKProductsRequestDelegate, SKRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let badProducts = response.invalidProductIdentifiers
let goodProducts = response.products
if !goodProducts.isEmpty {
ProductsDB.shared.items = response.products
print("Good", ProductsDB.shared.items)
}
print("BadProducts", badProducts)
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("didFailWithError", error)
purchasePublisher.send(("Purchase request failed", true))
}
func requestDidFinish(_ request: SKRequest) {
print("Request did finish")
}
}
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
purchasePublisher.send(("Purchase successful", true))
handlePurchase(transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
print("Purchase successful")
case .restored:
totalRestoredPurchases += 1
purchasePublisher.send(("Purchases restored", true))
SKPaymentQueue.default().finishTransaction(transaction)
print("Restore purchases successful")
case .failed:
if let error = transaction.error as? SKError {
purchasePublisher.send(("Payment error \(error.code) ", false))
print("Purchase failed \(error.code)")
}
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred:
print("Purchase permission required")
purchasePublisher.send(("Payment deferred", false))
SKPaymentQueue.default().finishTransaction(transaction)
case .purchasing:
print("Purchase in progress")
purchasePublisher.send(("Payment in progress", false))
default:
break
}
}
}
private func handlePurchase(_ id: String) {
}
I tried to come up with another way of displaying the IAP by creating buttons to each directly call a specific index in the returnProductIDs function but i get a crash with the error index out of range.
Thanks for any help.