I keep getting this invalid product identifiers. Is my code fine? what could be the problem? Ive written the code to print out my in app purchases but it keeps going to this if statement:
if response.invalidProductIdentifiers.count != 0 {
print(response)
print(response.invalidProductIdentifiers.description)
}
In my in-app purchase page on Itunes connect I have the exact ID for the product copied into xcode yet it still does not work.
My full code:
//global variables
var productsArray: Array<SKProduct!> = []
var productIdentifiers = Set<NSObject>()
func requestProductInfo() {
if SKPaymentQueue.canMakePayments() {
//let productIdentifiers = NSSet(array: productIDs)
let productRequest = SKProductsRequest(productIdentifiers: self.productIdentifiers as! Set<String>)
productRequest.delegate = self
productRequest.start()
}
else {
print("Cannot perform In App Purchases.")
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
if response.invalidProductIdentifiers.count != 0 {
print("here")
print(response)
print(response.invalidProductIdentifiers.description)
print("here2")
}
if response.products.count != 0 {
for product in response.products {
productsArray.append(product)
print(productsArray)
}
}
else {
print("There are no products.")
}
}
Your code is good, I guess you have problem with app bundle ID or Setting provisional profiles.
You must have to set same BundleId and latest provisional profile(Certificates) with enabled of "In-App purchase".
Related
I'm trying to do fetch ProductIDs. But response always be null.
I want to show what I do.
I Create App IDs in developer Account.BundleID equal to AppID
I've installed the project App Connect. Selected my Project BundleID
Created ProductID's
Switch on the In App Purchase in Xcode
My Code:
Created ProductID's:
`enum IAPProduct: String {
case Consumble = "com.xxxx.xxx.Consumable"
case nonConsumble = "com.xxxx.xxx.TekSeferAl"
}`
My fetch code:
class IAPService: NSObject {
static let shared = IAPService()
private override init() {}
//MARK:- Properties
//MARK:- Private
func getProducts() {
SKPaymentQueue.canMakePayments()
let products: Set = [IAPProduct.Consumble.rawValue,
IAPProduct.nonConsumble.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.cancel()
request.delegate = self
request.start()
}
}
Delegate:
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
response.invalidProductIdentifiers.forEach { product in
print("Invalid: \(product)")
}
response.products.forEach { product in
print("Valid: \(product)")
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error for request: \(error.localizedDescription)")
}
}
}
Output
Where am I making a mistake?
The problem is that it seems your products are in the state of "Missing Metadata" in App Store Connect - they need to be in "Ready to Submit" for testing.
You're probably either:
Missing a photo (you can use a blank image for testing)
Haven't signed the the Paid Applications Agreement with Apple
Here's a good blog post that covers setting up products in more detail: Configuring In-app Products is Hard
I have followed a walkthrough to include a non-consumable in-app purchase. Unfortunately, nothing is happening at all after the user clicks the button for the purchase. I am not getting any prompt to log in to iTunes or to accept the payment. Is there something I am missing here?
I have tried multiple walkthroughs and they all seem to have the similar code, I have followed the steps through the apple website, however I am unsure as to whether I have to do a full app submission before I can test the in-app purchases through a sandbox.
IAPService.swift
import Foundation
import StoreKit
import UIKit
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [IAPProduct.nonConsumable.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase(product: IAPProduct) {
guard let productToPurchase = products.filter({
$0.productIdentifier == product.rawValue }).first else { return }
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
func restorePurchases() {
print("restore purchases")
paymentQueue.restoreCompletedTransactions()
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive
response: SKProductsResponse) {
products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions
transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(),
transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing: break
default: queue.finishTransaction(transaction)
}
}
}
}
extension SKPaymentTransactionState {
func status() -> String {
switch self {
case .deferred: return "deferred"
case .failed: return "failed"
case .purchased: return "purchased"
case .purchasing: return "purchasing"
case .restored: return "restored"
}
}
}
In my products file
IAP.Products.swift
import Foundation
enum IAPProduct: String {
case nonConsumable = "Quizly"
}
In my mainVC
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
IAPService.shared.getProducts()
print("IAP == \(IAPService.shared.products)") // Why is this an empty array?
}
#objc func pressToGetPremium(_ sender : UIButton) {
IAPService.shared.purchase(product: .nonConsumable)
print("IAP ===== \(IAPService.shared.products)")
}
When the user clicks the button I was hoping that a pop up would come up first making the user have to sign in to their apple account and then another pop up would ask if they wanted to accept the non-consumable product ($1.99) etc. But I am not getting a pop up at all.
I am receiving this back from didReceive products......
response SKProductsResponse 0x00000002811cba10
baseNSObject#0 NSObject
isa Class 0x2811cba10 0x00000002811cba10
_internal SKProductsResponseInternal * 0x28139c0e0 0x000000028139c0e0
NSObject NSObject
_invalidIdentifiers __NSSingleObjectArrayI * 1 element 0x00000002811cb930
[0] __NSCFString * "Quizly" 0x000000028139c620
NSMutableString NSMutableString
_products __NSArray0 * 0 elements 0x00000002811c0050
NSArray NSArray
NSObject NSObject
isa Class __NSArray0 0x000001a25a605811
As per our comment discussion - The error produced from the didReceive products method indicates your bundleIdentifiers are not matching the ones on the appStore.
I am implementing the non renewable purchase in my app. I am still using in sandbox mode. After I subscribe for the product, when I again try to subscribe the same product, it gives me an alert saying ‘This In-App purchase has already been bought. It will be restored for free.’. I don’t know how I should enable user to subscribe again.
How can I handle multiple user on same device? If one user has paid for the subscription and another user log in into same device to my application he/she should not get the alert as above.
Code :
import StoreKit
class className: SKProductsRequestDelegate
{
var productIDs: Array<String?> = []
var productsArray: Array<SKProduct?> = []
override func viewDidLoad(){
// product IDs for in-app purchase products
productIDs.append(“monthly_subscription_id”) // Monthly
productIDs.append(“yearly_subscription_id”) // Year
requestProductInfo()
SKPaymentQueue.default().add(self)
}
func requestProductInfo() {
if SKPaymentQueue.canMakePayments() {
let productIdentifiers = NSSet(array: productIDs)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> as! Set<String>)
productRequest.delegate = self
productRequest.start()
}
else {
print("Cannot perform In App Purchases.")
}
}
// MARK: SKProductsRequestDelegate method implementation
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.count != 0 {
for product in response.products {
productsArray.append(product )
}
}
else {
print("There are no products.")
}
if response.invalidProductIdentifiers.count != 0 {
print(response.invalidProductIdentifiers.description)
}
}
// MARK: Buy Subscription button action
#IBAction func btn_purchase_Action(_ sender: Any){
let payment = SKPayment(product: self.productsArray[productIndex]!)
SKPaymentQueue.default().add(payment)
self.transactionInProgress = true
}
}
// MARK: - SKPaymentTransactionObserver
extension className: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction){
print("complete...")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction){
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restore... \(productIdentifier)")
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction){
print("fail...")
if let transactionError = transaction.error as? NSError {
if transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(transaction.error?.localizedDescription)")
}
}
SKPaymentQueue.default().finishTransaction(transaction)
}
}
I could see popup saying in-app purchase is successful, but "updatedTransaction" function is not called when i successfully finish in-app purchase process.
First time in-app purchase is completed but when i try to purchase the same product again it shows the alert that product is already purchased and could restore for free.
From your code it looks like your transaction observer is a view controller.
If the view controller is dismissed before the payment transaction has been processed then you won't get a chance to complete the transaction.
Your payment queue observer should be an object that is instantiated as soon as your app launches and remains in memory for the lifetime of your app.
Creating the payment queue observer in didFinishLaunching and holding a reference to it in your app delegate is one approach that you can use.
I am trying to implement a removing ads in app purchase into my application. However, whenever I test it out, it keeps telling me it has not found the product ID. I have triple checked the product ID, and it is correct. I can't seem to figure out the problem. This is what prints to the console:
Product not found: com.myCoolAwesomeApp.mjay.noAds
var product: SKProduct?
var productID = "com.myCoolAwesomeApp.mjay.noAds"
func getPurchaseInfo() {
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers: NSSet(objects: self.productID) as! Set<String>)
request.delegate = self
request.start()
} else {
productDescription.text = "Please enable In App Purchases in your settings."
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count) == 0 {
productDescription.text = "Product not found."
} else {
product = products[0]
productDescription.text = product!.localizedDescription
buyButton.isEnabled = true
}
let invalids = response.invalidProductIdentifiers
for product in invalids {
print("Product not found: \(product)")
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
SKPaymentQueue.default().finishTransaction(transaction)
productDescription.text = "Purchase Successful"
buyButton.isEnabled = false
let save = UserDefaults.standard
save.set(true, forKey: "Purchase")
save.synchronize()
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
productDescription.text = "Purchase Failed. Try again later."
default:
break
}
}
}
}
I run into the exact similar problem. Here are some steps that needs to be adressed/checked after reading this technical note from Apple:
Create a unique App ID
Enable in-app purchasing for this App ID
Generate and install a new provisioning profile on your device
Update the bundle ID and code signing profile in Xcode
Check that your project’s .plist Bundle ID match your App ID
Complete your contract, tax, and banking Information. You need a VALID contract
As mentioned by the official doc there is NO need to submit binaries neither submit screenshot of in-app purchase. I have seen a lot of missleading info about this on various blogs.
After checking you have addressed each point in this list, delete your app and reinstall it.
I was basically missing two things on my side: setting up correctly my contract for ios Paid app and i didn't install the new provisioning profile on my device. May be that was your issue?
It is important to check are In-App Purchases enabled to block UI properly, RayWenderlich blog says:
Apple requires that you handle this situation gracefully; not doing so will likely result in an app rejection.
When you disable In-App Purchase, the Restrictions SKPaymentQueue.canMakePayments() should return false but it always returns true no matter what. I tried with 2 different projects, including, this one from RayWenderlich.
I tested this only with iOS 9.
How to recognise that In-App Purchases disabled with parental restrictions?
Update.
Somebody requested to share my code. I don't think it is necessary, the code is obvious and doesn't have errors. I can reproduce this problems with the Ray's project too.
// This function is called in from viewDidLoad()
// And after SKProduct is updated.
func addTextFromProduct(p: SKProduct) {
if let title = p.localizedTitle as String? {
self.navigationBar.topItem?.title = title
}
if let description = p.localizedDescription as String? {
if dailyLimit {
self.informationLabel.text? = "\(waitingTime)\(description)"
} else {
self.informationLabel.text? = "\(description)"
}
if SKPaymentQueue.canMakePayments() {
unblockButtons()
}
} else {
self.informationLabel.text? = "\(waitingTime)\(description)\n\nIn-App Purchase is unavailable at this moment."
blockButtons()
}
if SKPaymentQueue.canMakePayments() {
self.priceFormatter.locale = p.priceLocale
let localPrice: String! = self.priceFormatter.stringFromNumber(p.price)
let label = "\(localPrice)"
self.buyButton.setTitle(label, forState: UIControlState.Normal)
} else {
blockButtons()
buyButton.setTitle("Not Available", forState: UIControlState.Disabled)
}
}
You need to check in your viewDidLoad method if the user has enabled IAP.
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
// Your Products
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
else {
print("IAP not allowed")
//Do something like show an alert, the user has not allowed in app purchases
}